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:
parent
67caaad803
commit
b421d19601
4 changed files with 112 additions and 74 deletions
|
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}}
|
||||
|
|
Loading…
Reference in a new issue