Skip to content

Commit

Permalink
Merge pull request alexlafroscia#91 from alexlafroscia/remove-validat…
Browse files Browse the repository at this point in the history
…ion-hooks

Remove validation hooks
  • Loading branch information
alexlafroscia authored Mar 28, 2018
2 parents 597b9ef + b5e67ba commit bb7f981
Show file tree
Hide file tree
Showing 22 changed files with 805 additions and 1,265 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ env:
matrix:
fast_finish: true
allow_failures:
- env: EMBER_TRY_SCENARIO=ember-beta
- env: EMBER_TRY_SCENARIO=ember-canary

script:
- yarn lint:js
# Usually, it's ok to finish the test scenario without reverting
# to the addon's original dependency state, skipping "cleanup".
- node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup
Expand Down
120 changes: 14 additions & 106 deletions addon/components/step-manager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Component from '@ember/component';
import { set, get } from '@ember/object';
import { isEmpty } from '@ember/utils';
import RSVP from 'rsvp';
import hbs from 'htmlbars-inline-precompile';
import StateMachine from 'ember-steps/-private/state-machine';
import { MissingPropertyError } from 'ember-steps/-private/errors';
Expand All @@ -12,11 +11,10 @@ const layout = hbs`
register-step=(action 'register-step-component')
currentStep=transitions.currentStep
)
transition-to=(action 'transition-to-step')
transition-to-next=(action 'transition-to-next-step')
transition-to-previous=(action 'transition-to-previous-step')
transition-to=(action 'transition-to')
transition-to-next=(action 'transition-to-next')
transition-to-previous=(action 'transition-to-previous')
currentStep=transitions.currentStep
loading=loading
steps=(if _hasRendered transitions.stepArray)
)}}
`;
Expand Down Expand Up @@ -47,7 +45,6 @@ const layout = hbs`
* @yield {Action} w.transition-to-next Render the next step
* @yield {Action} w.transition-to-previous Render the previous step
* @yield {string} w.currentStep The name of the current step
* @yield {boolean} w.loading Whether an asynchronous validation is currently being performed
* @yield {Array<String>} w.steps All of the step names that are currently defined, in order
* @public
*/
Expand Down Expand Up @@ -107,24 +104,19 @@ export default Component.extend({
/**
* Used internally to transition to a specific named step
*
* @method do-transition
* @method doTransition
* @param {string} to the name of the step to transition to
* @param {string} from the name of the step being transitioned
* @param {*} value the value to pass to the transition actions
* @private
*/
'do-transition'(to, from, value, direction) {
doTransition(to) {
// Update the `currentStep` if it's mutable
if (!isEmpty(get(this, 'currentStep'))) {
set(this, 'currentStep', to);
}

// Activate the next step
get(this, 'transitions').activate(to);

if (this['did-transition']) {
this['did-transition']({ value, from, to, direction });
}
},

/**
Expand All @@ -150,57 +142,6 @@ export default Component.extend({
*/
currentStep: null,

/**
* This property indicates if the step-manager is performing an asynchronous
* operation.
*
* It can be used to display a loader for example.
*
* @property {Boolean} loading
* @public
*/
loading: false,

/**
* If provided, this action will be called with a single POJO as the
* argument, containing:
*
* - `value` -> The value passed to the transition action, or `undefined`
* - `from` -> The name of the step being transitioned from
* - `to` -> The name of the step being transitioned to
* - `direction` -> The direction of the transition when using transition-to-next or transition-to-previous
*
* The action is called before the next step is activated.
*
* The action can return a Promise or a boolean.
*
* By returning a Promise, you can wait the end of an asynchronous process
* before the transition. The transition will be prevented if the Promise rejects.
*
* By returning `false` from this action, you can prevent the transition
* from taking place.
*
* @argument {Action} will-transition
* @public
*/
'will-transition': null,

/**
* If provided, this action will be called with a single POJO as the
* argument, containing:
*
* - `value` -> The value passed to the transition action, or `undefined`
* - `from` -> The name of the step being transitioned from
* - `to` -> The name of the step being transitioned to
* - `direction` -> The direction of the transition when using transition-to-next or transition-to-previous
*
* The action is called after the next step is activated.
*
* @argument {Action} did-transition
* @public
*/
'did-transition': null,

didUpdateAttrs() {
this._super(...arguments);

Expand Down Expand Up @@ -246,9 +187,6 @@ export default Component.extend({
/**
* Transition to a named step
*
* If you have provided a `will-transition` action, it will call the action
* before transitioning to the step.
*
* If the `currentStep` property was provided as a mutable value, like:
*
* ```js
Expand All @@ -259,37 +197,13 @@ export default Component.extend({
*
* Then the external property will be updated to the new step name.
*
* @action transition-to-step
* @action transition-to
* @param {string} to the name of the step to transition to
* @param {*} value the value to pass to the transition actions
* @public
*/
'transition-to-step'(to, value, direction) {
const from = get(this, 'transitions.currentStep');
const validator = this['will-transition'];

// Prevent multiple transitions
if (get(this, 'loading')) {
return;
}

if (validator && typeof validator === 'function') {
set(this, 'loading', true);

RSVP.resolve(validator({ value, from, to, direction }))
.then(result => {
if (result !== false && !get(this, 'isDestroyed')) {
this['do-transition'](to, from, value, direction);
}
})
.finally(() => {
if (!get(this, 'isDestroyed')) {
set(this, 'loading', false);
}
});
} else {
this['do-transition'](to, from, value, direction);
}
'transition-to'(to) {
this.doTransition(to);
},

/**
Expand All @@ -299,16 +213,13 @@ export default Component.extend({
* one, as defined by the order of their insertion into the DOM (AKA, the
* order in the template).
*
* The last step will transition back to the first one.
*
* @action transition-to-next-step
* @param {*} value the value to pass to the transition actions
* @action transition-to-next
* @public
*/
'transition-to-next-step'(value) {
'transition-to-next'() {
const to = get(this, 'transitions').pickNext();

this.send('transition-to-step', to, value, 'next');
this.doTransition(to);
},

/**
Expand All @@ -317,16 +228,13 @@ export default Component.extend({
* When called, this action will go back to the previous step according to
* the step which was visited before entering the currentStep
*
* The first step will not transition to anything.
*
* @action transition-to-previous-step
* @param {*} value the value to pass to the transition actions
* @action transition-to-previous
* @public
*/
'transition-to-previous-step'(value) {
'transition-to-previous'() {
const to = get(this, 'transitions').pickPrevious();

this.send('transition-to-step', to, value, 'previous');
this.doTransition(to);
}
}
});
14 changes: 14 additions & 0 deletions addon/helpers/validate-transition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { helper } from '@ember/component/helper';
import { Promise } from 'rsvp';

export function validateTransition([transition], { with: validator }) {
return function() {
return new Promise(resolve => {
validator(resolve);
}).then(() => {
transition();
});
};
}

export default helper(validateTransition);
4 changes: 4 additions & 0 deletions app/helpers/validate-transition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
default,
validateTransition
} from 'ember-steps/helpers/validate-transition';
4 changes: 4 additions & 0 deletions ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ module.exports = function(defaults) {
const app = new EmberAddon(defaults, {
snippetSearchPaths: ['tests/dummy/app'],

'ember-composable-helpers': {
only: ['pipe']
},

cssModules: {
plugins: [require('postcss-nested')]
}
Expand Down
24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,26 @@
"devDependencies": {
"@ember/test-helpers": "^0.7.18",
"broccoli-asset-rev": "^2.5.0",
"ember-ajax": "^3.1.0",
"ember-block-slots": "^1.1.11",
"ember-cli": "~3.0.0",
"ember-cli-addon-docs": "^0.3.0",
"ember-cli-addon-docs-yuidoc": "^0.1.0",
"ember-cli-dependency-checker": "^2.0.1",
"ember-cli-dependency-checker": "^2.1.0",
"ember-cli-deploy": "^1.0.2",
"ember-cli-deploy-build": "^1.1.1",
"ember-cli-deploy-git": "^1.3.2",
"ember-cli-deploy-git-ci": "^1.0.1",
"ember-cli-document-title": "^0.3.3",
"ember-cli-github-pages": "0.1.2",
"ember-cli-eslint": "^4.2.3",
"ember-cli-inject-live-reload": "^1.7.0",
"ember-cli-qunit": "^4.3.2",
"ember-cli-shims": "^1.2.0",
"ember-cli-sri": "^2.1.0",
"ember-cli-test-loader": "^2.1.0",
"ember-cli-testdouble": "^0.1.2",
"ember-cli-uglify": "^2.0.0",
"ember-css-modules": "^0.7.2",
"ember-composable-helpers": "^2.1.0",
"ember-concurrency": "^0.8.16",
"ember-css-modules": "^0.7.10",
"ember-disable-prototype-extensions": "^1.1.3",
"ember-export-application-global": "^2.0.0",
"ember-hook": "^1.4.2",
Expand All @@ -54,21 +54,21 @@
"ember-resolver": "^4.3.0",
"ember-source": "~3.0.0",
"ember-source-channel-url": "^1.0.1",
"ember-truth-helpers": "^1.3.0",
"ember-truth-helpers": "^2.0.0",
"ember-try": "^0.2.23",
"eslint": "^4.5.0",
"eslint-config-prettier": "^2.3.0",
"eslint": "^4.19.1",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-ember": "^5.0.0",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-prettier": "^2.2.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-prettier": "^2.6.0",
"loader.js": "^4.6.0",
"postcss-nested": "^2.1.0",
"prettier": "^1.5.3",
"prettier": "^1.11.1",
"qunit-dom": "^0.5.0"
},
"dependencies": {
"debug": "^2.6.8",
"ember-cli-babel": "^6.7.2",
"ember-cli-babel": "^6.12.0",
"ember-cli-htmlbars": "^2.0.3",
"ember-cli-htmlbars-inline-precompile": "^1.0.1"
},
Expand Down
5 changes: 5 additions & 0 deletions tests/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
env: {
embertest: true
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// BEGIN-SNIPPET validating-steps-basic-usage.js
import Component from '@ember/component';

export default Component.extend({
password: '',

actions: {
checkPassword(resolve) {
const password = this.get('password');

if (password === 'password') {
resolve();
}
}
}
});
// END-SNIPPET
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{{#docs-demo as |demo|}}
{{#demo.example name='validating-steps-basic-usage.hbs'}}
{{#step-manager as |w|}}
{{#w.step}}
{{input
placeholder='Enter password'
value=password
}}

{{#control-group}}
<button
onClick={{validate-transition w.transition-to-next with=(action 'checkPassword')}}
>
Check Password
</button>
{{/control-group}}
{{/w.step}}

{{#w.step}}
Great job! You got the password right
{{/w.step}}
{{/step-manager}}
{{/demo.example}}

{{demo.snippet name='validating-steps-basic-usage.hbs'}}
{{demo.snippet name='validating-steps-basic-usage.js' label='component.js'}}
{{/docs-demo}}

26 changes: 26 additions & 0 deletions tests/dummy/app/docs/features/validating-steps/template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Validating Steps

You might want to run some validations to ensure that your UI is actually ready to move onto the next step. `ember-steps` provides a `validate-transition` helper to aid with accomplishing this.

Basic usage of the helper looks like this:

{{docs/features/validating-steps/basic-usage}}

The general idea is that

- Instead of invoking the `transition` action directly, you can wrap it with a `validate-transition` helper and provide a validator function
- The validator function received a callback to invoke if the validation is successful
- When the validator calls the callback, the transition is performed

This allows the `validate-transition` helper to play nicely with any kind of asynchronous behavior you might need to perform. If you need to bind a "loading state" to the validator currently being run, I recommend using an [`ember-concurrency`][ember-concurrency] task as the validator function. In the below example, we disable the `button` while the task is in-flight.

{{docs/features/validating-steps/with-ember-concurrency}}

You probably noticed that, in the last two examples, there is no good way to go "back" to the previous step. What would going back mean? We want to both perform a transition _and_ reset the state of the component. We want to know when the transition has taken place.

Thankfully, there are already great tools for composing actions together. I recommend the `pipe` operator from [`ember-composable-helpers`][ember-composable-helpers]:

{{docs/features/validating-steps/with-did-transition}}

[ember-concurrency]: http://ember-concurrency.com/docs/introduction/
[ember-composable-helpers]: https://github.com/DockYard/ember-composable-helpers
Loading

0 comments on commit bb7f981

Please sign in to comment.