From 84fe806d1ec34870c07011c38e83021f638b39ee Mon Sep 17 00:00:00 2001 From: German Bisurgi <13135260+germanbisurgi@users.noreply.github.com> Date: Tue, 20 Oct 2020 07:56:07 +0200 Subject: [PATCH] Feature/stepper (#841) * stepper editor * stepper enable disable logic * added stepper test * fixed code format * use right dist script in tests * fix display error message stepper * added button "type" attribute Co-authored-by: German Bisurgi Co-authored-by: Marc Mautz --- src/editors/index.js | 2 + src/editors/stepper.js | 24 ++++++++++ src/resolvers.js | 9 +++- src/theme.js | 38 +++++++++++++++ src/themes/bootstrap4.js | 55 +++++++++++++++++++++- tests/codeceptjs/editors/stepper_test.js | 27 +++++++++++ tests/pages/number.html | 30 ++++++++++-- tests/pages/stepper.html | 59 ++++++++++++++++++++++++ 8 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 src/editors/stepper.js create mode 100644 tests/codeceptjs/editors/stepper_test.js create mode 100644 tests/pages/stepper.html diff --git a/src/editors/index.js b/src/editors/index.js index 74307ba1d..ca52d4661 100644 --- a/src/editors/index.js +++ b/src/editors/index.js @@ -33,6 +33,7 @@ import { SelectizeEditor as selectize } from './selectize.js' import { SignatureEditor as signature } from './signature.js' import { SimplemdeEditor as simplemde } from './simplemde.js' import { StarratingEditor as starrating } from './starrating.js' +import { StepperEditor as stepper } from './stepper.js' import { StringEditor as string } from './string.js' import { TableEditor as table } from './table.js' import { UploadEditor as upload } from './upload.js' @@ -71,6 +72,7 @@ export const editors = { signature, simplemde, starrating, + stepper, string, table, upload, diff --git a/src/editors/stepper.js b/src/editors/stepper.js new file mode 100644 index 000000000..8ef2023df --- /dev/null +++ b/src/editors/stepper.js @@ -0,0 +1,24 @@ +import { IntegerEditor } from './integer.js' + +export class StepperEditor extends IntegerEditor { + build () { + super.build() + this.input.setAttribute('type', 'number') + const stepperButtons = this.theme.getStepperButtons(this.input) + this.control.appendChild(stepperButtons) + this.stepperDown = this.control.querySelector('.stepper-down') + this.stepperUp = this.control.querySelector('.stepper-up') + } + + enable () { + super.enable() + this.stepperDown.removeAttribute('disabled') + this.stepperUp.removeAttribute('disabled') + } + + disable () { + super.disable() + this.stepperDown.setAttribute('disabled', true) + this.stepperUp.setAttribute('disabled', true) + } +} diff --git a/src/resolvers.js b/src/resolvers.js index b10145bc5..17ed3e6b1 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -91,6 +91,13 @@ const describeBy = schema => { } } +/* Use the stepper editor for schemas with type `number` or `integer` and format `stepper` */ +const stepper = schema => { + if ((schema.type === 'integer' || schema.type === 'number') && schema.format === 'stepper') { + return 'stepper' + } +} + /* Enable custom editor type */ const button = schema => schema.format === 'button' && 'button' @@ -115,4 +122,4 @@ const ip = schema => schema.type === 'string' && ['ip', 'ipv4', 'ipv6', 'hostnam const colorPicker = schema => schema.type === 'string' && schema.format === 'color' && 'colorpicker' /* Export resolvers in order of discovery, first to last */ -export const resolvers = [colorPicker, ip, ace, xhtml, markdown, jodit, autoComplete, uuid, info, button, describeBy, starratings, date, oneOf, arraysOfStrings, enumeratedProperties, enumSource, table, upload, base64, any, boolean, signature, primitive, object, defaultResolver] +export const resolvers = [colorPicker, ip, ace, xhtml, markdown, jodit, autoComplete, uuid, info, button, stepper, describeBy, starratings, date, oneOf, arraysOfStrings, enumeratedProperties, enumSource, table, upload, base64, any, boolean, signature, primitive, object, defaultResolver] diff --git a/src/theme.js b/src/theme.js index c14d896e8..b093de552 100644 --- a/src/theme.js +++ b/src/theme.js @@ -1,3 +1,5 @@ +import { trigger } from './utilities' + const matchKey = [ 'matches', 'webkitMatchesSelector', @@ -231,6 +233,42 @@ export class AbstractTheme { return el } + getStepperButtons (input) { + const div = document.createElement('div') + + const minusBtn = document.createElement('button') + minusBtn.setAttribute('type', 'button') + minusBtn.classList.add('stepper-down') + + const plusBtn = document.createElement('button') + plusBtn.setAttribute('type', 'button') + plusBtn.classList.add('stepper-up') + + const readonly = input.getAttribute('readonly') + + if (readonly) { + minusBtn.setAttribute('disabled', true) + plusBtn.setAttribute('disabled', true) + } + + minusBtn.textContent = '-' + plusBtn.textContent = '+' + + minusBtn.addEventListener('click', () => { + input.stepDown() + trigger(input, 'change') + }) + + plusBtn.addEventListener('click', () => { + input.stepUp() + trigger(input, 'change') + }) + + div.appendChild(minusBtn) + div.appendChild(plusBtn) + return div + } + getRangeOutput (input, startvalue) { const output = document.createElement('output') output.value = startvalue || 0 diff --git a/src/themes/bootstrap4.js b/src/themes/bootstrap4.js index 237658aee..d1c5637a5 100644 --- a/src/themes/bootstrap4.js +++ b/src/themes/bootstrap4.js @@ -1,5 +1,6 @@ import { AbstractTheme } from '../theme.js' import rules from './bootstrap4.css.js' +import { trigger } from '../utilities' /* Theme config options that allows changing various aspects of the output */ const options = { @@ -85,6 +86,56 @@ export class bootstrap4Theme extends AbstractTheme { return el } + getStepperButtons (input) { + const inputGroup = document.createElement('div') + const prepend = document.createElement('div') + const append = document.createElement('div') + + const minusBtn = document.createElement('button') + minusBtn.setAttribute('type', 'button') + + const plusBtn = document.createElement('button') + plusBtn.setAttribute('type', 'button') + + inputGroup.appendChild(prepend) + inputGroup.appendChild(input) + inputGroup.appendChild(append) + prepend.appendChild(minusBtn) + append.appendChild(plusBtn) + + inputGroup.classList.add('input-group') + prepend.classList.add('input-group-prepend') + append.classList.add('input-group-append') + minusBtn.classList.add('btn') + minusBtn.classList.add('btn-secondary') + minusBtn.classList.add('stepper-down') + plusBtn.classList.add('btn') + plusBtn.classList.add('btn-secondary') + plusBtn.classList.add('stepper-up') + + const readonly = input.getAttribute('readonly') + + if (readonly) { + minusBtn.setAttribute('disabled', true) + plusBtn.setAttribute('disabled', true) + } + + minusBtn.textContent = '-' + plusBtn.textContent = '+' + + minusBtn.addEventListener('click', () => { + input.stepDown() + trigger(input, 'change') + }) + + plusBtn.addEventListener('click', () => { + input.stepUp() + trigger(input, 'change') + }) + + return inputGroup + } + getFormInputField (type) { const el = super.getFormInputField(type) if (type !== 'checkbox' && type !== 'radio' && type !== 'file') { @@ -402,10 +453,10 @@ export class bootstrap4Theme extends AbstractTheme { input.errmsg = document.createElement('p') input.errmsg.classList.add('invalid-feedback') input.controlgroup.appendChild(input.errmsg) - } else { - input.errmsg.style.display = '' + input.errmsg.style.display = 'block' } + input.errmsg.style.display = 'block' input.errmsg.textContent = text } diff --git a/tests/codeceptjs/editors/stepper_test.js b/tests/codeceptjs/editors/stepper_test.js new file mode 100644 index 000000000..ed88ddebd --- /dev/null +++ b/tests/codeceptjs/editors/stepper_test.js @@ -0,0 +1,27 @@ +/* global Feature Scenario Event */ + +var assert = require('assert') + +Feature('stepper') + +Scenario('should validate value @stepper', async (I) => { + I.amOnPage('stepper.html') + I.click('.get-value') + I.see('Property must be set.', '[data-schemapath="root.stepper"] div') + assert.equal(await I.grabValueFrom('.value'), '{}') +}) + +Scenario('should be constrained to maximun and minimun values when stepped @stepper', async (I) => { + I.amOnPage('stepper.html') + I.click('.stepper-up') + I.click('.stepper-up') + I.click('.stepper-up') + I.click('.get-value') + I.click('.get-value') + assert.equal(await I.grabValueFrom('.value'), '{"stepper":6}') + I.click('.stepper-down') + I.click('.stepper-down') + I.click('.stepper-down') + I.click('.get-value') + assert.equal(await I.grabValueFrom('.value'), '{"stepper":5}') +}) diff --git a/tests/pages/number.html b/tests/pages/number.html index 8a7497091..35e9aef3d 100644 --- a/tests/pages/number.html +++ b/tests/pages/number.html @@ -3,6 +3,9 @@ Number + + + @@ -10,10 +13,10 @@ -
+
+ + + + +
+ + + +
+
+ + + + + +