refactor step management and do not allow going forward with invalid state (#263)

Also removes an observer that causes a "You modified 'disabled' twice in a single render" and executes the logic in the next run loop to prevent that error. That's not ideal but it's not time for a major refactoring of that part.
This commit is contained in:
Jeldrik Hanschke 2019-10-29 08:42:00 +01:00 committed by GitHub
parent 67caaad803
commit b421d19601
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 74 deletions

View file

@ -1,6 +1,6 @@
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Component from '@ember/component'; import Component from '@ember/component';
import { observer } from '@ember/object'; import { next } from '@ember/runloop';
export default Component.extend({ export default Component.extend({
actions: { actions: {
@ -19,16 +19,26 @@ export default Component.extend({
} }
}, },
enforceMinimalOptionsAmount: observer('options', 'isMakeAPoll', function() { enforceMinimalOptionsAmount() {
if (this.get('options.length') < 2) { let options = this.options;
let options = this.options;
for (let missingOptions = 2 - this.get('options.length'); missingOptions > 0; missingOptions--) { while (options.length < 2) {
options.pushObject( options.pushObject(
this.store.createFragment('option') this.store.createFragment('option')
); );
}
} }
}).on('init'), },
store: service('store'), store: service('store'),
init() {
this._super(...arguments);
// need to delay pushing fragments into options array to prevent
// > You modified "disabled" twice on <(unknown):ember330> in a single render.
// error.
next(() => {
this.enforceMinimalOptionsAmount();
});
}
}); });

View file

@ -1,67 +1,94 @@
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { readOnly } from '@ember/object/computed'; import EmberObject, { computed } from '@ember/object';
import EmberObject, { computed, observer } from '@ember/object';
import Controller from '@ember/controller'; import Controller from '@ember/controller';
import { getOwner } from '@ember/application'; import { getOwner } from '@ember/application';
import { getProperties } from '@ember/object';
const formStepObject = EmberObject.extend({ const FormStep = EmberObject.extend({
active: computed('routing.currentRouteName', function() { router: service(),
const currentRouteName = this.get('routing.currentRouteName');
return currentRouteName === this.route; disabled: computed('requiredState', 'visited', function() {
let { visited, requiredState } = this;
return !visited || requiredState === false;
}), }),
disabled: true,
hidden: false, hidden: false,
label: null, label: null,
route: null, route: null,
routing: service('-routing'), visited: false,
updateDisabledState: observer('active', function() {
if (this.active) { init() {
this.set('disabled', false); this._super(...arguments);
}
}).on('init'), let setVisited = () => {
if (this.router.currentRouteName === this.route) {
this.set('visited', true);
}
};
this.router.on('routeDidChange', setVisited);
}
}); });
const FORM_STEPS = [
{
label: 'create.formStep.type',
route: 'create.index',
},
{
label: 'create.formStep.meta',
requiredState: computed('model.pollType', function() {
return this.model.pollType;
}),
route: 'create.meta',
},
{
label: computed('model.pollType', function() {
let { pollType } = this.model;
return pollType === 'FindADate' ? 'create.formStep.options.days' : 'create.formStep.options.text';
}),
requiredState: computed('model.title', function() {
let { title } = this.model;
return typeof title === 'string' && title.length >= 2;
}),
route: 'create.options',
},
{
hidden: computed('model.pollType', function() {
let { pollType } = this.model;
return pollType !== 'FindADate';
}),
label: 'create.formStep.options-datetime',
requiredState: computed('model.options.length', function() {
return this.model.options.length >= 1;
}),
route: 'create.options-datetime'
},
{
label: 'create.formStep.settings',
requiredState: computed('model.options.length', function() {
return this.model.options.length >= 1;
}),
route: 'create.settings',
},
];
export default Controller.extend({ export default Controller.extend({
router: service(),
formSteps: computed('model', function() { formSteps: computed('model', function() {
const owner = getOwner(this); let owner = getOwner(this);
return [
formStepObject.create(owner.ownerInjection(), { return FORM_STEPS.map((definition, index) => {
label: 'create.formStep.type', let computedProperties = Object.keys(definition).filter((key) => typeof definition[key] === 'function');
route: 'create.index' let values = Object.keys(definition).filter((key) => typeof definition[key] !== 'function');
}),
formStepObject.create(owner.ownerInjection(), { let extendDefinition = getProperties(definition, ...computedProperties)
label: 'create.formStep.meta', let createDefinition = Object.assign({ model: this.model }, getProperties(definition, ...values));
route: 'create.meta'
}), if (index === 0) {
formStepObject.extend({ createDefinition.visited = true;
label: computed('pollType', function() { }
const pollType = this.pollType;
if (pollType === 'FindADate') { return FormStep.extend(extendDefinition).create(owner.ownerInjection(), createDefinition);
return 'create.formStep.options.days'; });
} else {
return 'create.formStep.options.text';
}
}),
pollType: readOnly('model.pollType')
}).create(owner.ownerInjection(), {
model: this.model,
route: 'create.options'
}),
formStepObject.extend({
hidden: computed('pollType', function() {
const pollType = this.pollType;
return pollType !== 'FindADate';
}),
pollType: readOnly('model.pollType')
}).create(owner.ownerInjection(), {
label: 'create.formStep.options-datetime',
model: this.model,
route: 'create.options-datetime'
}),
formStepObject.create(owner.ownerInjection(), {
label: 'create.formStep.settings',
route: 'create.settings'
})
];
}) })
}); });

View file

@ -10,10 +10,11 @@
> >
<div class="input-group"> <div class="input-group">
<el.control <el.control
{{! first control should be autofocused }}
@autofocus={{unless index true false}} @autofocus={{unless index true false}}
@id={{el.id}}
@value={{el.value}}
@onChange={{action (mut el.value)}} @onChange={{action (mut el.value)}}
@value={{el.value}}
id={{el.id}}
/> />
<div class="input-group-append"> <div class="input-group-append">
<BsButton <BsButton

View file

@ -1,18 +1,18 @@
{{title (t "create.title")}} {{title (t "create.title")}}
{{#bs-button-group justified=true classNames="cr-steps-top-nav form-steps"}} <BsButtonGroup @justified={{true}} class="cr-steps-top-nav form-steps">
{{#each formSteps as |formStep|}} {{#each formSteps as |formStep|}}
{{#unless formStep.hidden}} {{#unless formStep.hidden}}
{{#bs-button <BsButton
onClick=(transition-to formStep.route) @onClick={{transition-to formStep.route}}
type=(if formStep.active "primary is-active" "default") @type={{if (eq router.currentRouteName formStep.route) "primary is-active" "default"}}
disabled=formStep.disabled disabled={{formStep.disabled}}
classNames="cr-steps-top-nav__button" class="cr-steps-top-nav__button"
}} >
{{t formStep.label}} {{t formStep.label}}
{{/bs-button}} </BsButton>
{{/unless}} {{/unless}}
{{/each}} {{/each}}
{{/bs-button-group}} </BsButtonGroup>
{{outlet}} {{outlet}}