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 Component from '@ember/component';
import { observer } from '@ember/object';
import { next } from '@ember/runloop';
export default Component.extend({
actions: {
@ -19,16 +19,26 @@ export default Component.extend({
}
},
enforceMinimalOptionsAmount: observer('options', 'isMakeAPoll', function() {
if (this.get('options.length') < 2) {
let options = this.options;
for (let missingOptions = 2 - this.get('options.length'); missingOptions > 0; missingOptions--) {
options.pushObject(
this.store.createFragment('option')
);
}
enforceMinimalOptionsAmount() {
let options = this.options;
while (options.length < 2) {
options.pushObject(
this.store.createFragment('option')
);
}
}).on('init'),
},
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 { readOnly } from '@ember/object/computed';
import EmberObject, { computed, observer } from '@ember/object';
import EmberObject, { computed } from '@ember/object';
import Controller from '@ember/controller';
import { getOwner } from '@ember/application';
import { getProperties } from '@ember/object';
const formStepObject = EmberObject.extend({
active: computed('routing.currentRouteName', function() {
const currentRouteName = this.get('routing.currentRouteName');
return currentRouteName === this.route;
const FormStep = EmberObject.extend({
router: service(),
disabled: computed('requiredState', 'visited', function() {
let { visited, requiredState } = this;
return !visited || requiredState === false;
}),
disabled: true,
hidden: false,
label: null,
route: null,
routing: service('-routing'),
updateDisabledState: observer('active', function() {
if (this.active) {
this.set('disabled', false);
}
}).on('init'),
visited: false,
init() {
this._super(...arguments);
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({
router: service(),
formSteps: computed('model', function() {
const owner = getOwner(this);
return [
formStepObject.create(owner.ownerInjection(), {
label: 'create.formStep.type',
route: 'create.index'
}),
formStepObject.create(owner.ownerInjection(), {
label: 'create.formStep.meta',
route: 'create.meta'
}),
formStepObject.extend({
label: computed('pollType', function() {
const pollType = this.pollType;
if (pollType === 'FindADate') {
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'
})
];
let owner = getOwner(this);
return FORM_STEPS.map((definition, index) => {
let computedProperties = Object.keys(definition).filter((key) => typeof definition[key] === 'function');
let values = Object.keys(definition).filter((key) => typeof definition[key] !== 'function');
let extendDefinition = getProperties(definition, ...computedProperties)
let createDefinition = Object.assign({ model: this.model }, getProperties(definition, ...values));
if (index === 0) {
createDefinition.visited = true;
}
return FormStep.extend(extendDefinition).create(owner.ownerInjection(), createDefinition);
});
})
});

View file

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

View file

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