Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backtracking on form #1173

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 83 additions & 12 deletions addon/components/paper-autocomplete/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,90 @@ import template from './template';

import { tagName, layout } from '@ember-decorators/component';
import { action, computed } from '@ember/object';
import { tracked } from '@glimmer/tracking'
import calculatePosition from 'ember-paper/utils/calculate-ac-position';

import ValidationMixin from 'ember-paper/mixins/validation-mixin';

import { invokeAction } from 'ember-invoke-action';
import { assert } from '@ember/debug';

import { indexOfOption } from 'ember-power-select/utils/group-utils';

import { buildComputedValidationMessages, notifyValidityChange } from 'ember-paper/utils/validation';
import requiredValidator from 'ember-paper/validators/required';
import minValidator from 'ember-paper/validators/min';
import maxValidator from 'ember-paper/validators/max';
import minlengthValidator from 'ember-paper/validators/minlength';
import maxlengthValidator from 'ember-paper/validators/maxlength';

const validations = [
requiredValidator,
minValidator,
maxValidator,
minlengthValidator,
maxlengthValidator
];

@tagName('')
@layout(template)
class PaperAutocomplete extends Component.extend(ValidationMixin) {
class PaperAutocomplete extends Component {

@tracked
isTouched = false;

@computed('isTouched')
get formHasBeenValidated () {
return this.isTouched
}

set formHasBeenValidated (value) {
this.isTouched = value
}

validations = validations;

@tracked
errorMessages

@tracked
customValidations = []

@tracked
errors = []

@computed(
'onSearchTextChange',
'onSelectionChange',
'searchText',
'selected',
'errors.[]',
'customValidations.[]',
'errorMessages',
requiredValidator.param,
minValidator.param,
maxValidator.param,
minlengthValidator.param,
maxlengthValidator.param
)
get validationErrorMessages () {
const validationProperty = this.onSearchTextChange ? 'searchText' : 'selected';

return buildComputedValidationMessages.call(this, validationProperty)
}

@computed.bool('validationErrorMessages.length')
hasErrorMessages

@computed.reads('hasErrorMessages')
isInvalid

@computed.not('isInvalid')
isValid

init() {
this._initComponent();
super.init(...arguments);

invokeAction(this, 'onRegister', this.get('elementId'), this.get('isValid'), this.get('isTouched'), this.get('isInvalidAndTouched'));
}

// Init autocomplete component
Expand All @@ -31,6 +99,18 @@ class PaperAutocomplete extends Component.extend(ValidationMixin) {
assert('<PaperAutocomplete> requires at least one of the `@onSelectionChange` or `@onSearchTextChange` functions to be provided.', hasTextChange || hasSelectionChange);
}

destroy () {
const eltId = this.get('elementId')

super.destroy(...arguments);

invokeAction(this, 'onUnregister', eltId);
}

notifyValidityChange() {
notifyValidityChange.call(this);
}

@action
_onChange() {
if (this.onSelectionChange) {
Expand All @@ -40,15 +120,6 @@ class PaperAutocomplete extends Component.extend(ValidationMixin) {

calculatePosition = calculatePosition;

@computed('onSearchTextChange', 'onSelectionChange')
get validationProperty() {
if (this.onSearchTextChange) {
return 'searchText';
} else {
return 'selected';
}
}

didReceiveAttrs() {
super.didReceiveAttrs(...arguments);
this.notifyValidityChange();
Expand Down
186 changes: 143 additions & 43 deletions addon/components/paper-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,157 @@
import { not, and } from '@ember/object/computed';

import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from '../templates/components/paper-form';
import ParentMixin from 'ember-paper/mixins/parent-mixin';
import { tagName, layout } from '@ember-decorators/component';
import { run } from '@ember/runloop';
import { get, set, action } from '@ember/object';
import template from '../templates/components/paper-form';
import { A } from '@ember/array';
import { invokeAction } from 'ember-invoke-action';
import { tracked } from '@glimmer/tracking';

/**
* @class PaperForm
* @extends Ember.Component
* @uses ParentMixin
*/
export default Component.extend(ParentMixin, {
layout,
tagName: 'form',

inputComponent: 'paper-input',
submitButtonComponent: 'paper-button',
selectComponent: 'paper-select',
autocompleteComponent: 'paper-autocomplete',

isValid: not('isInvalid'),
isInvalid: computed('[email protected]', function() {
return this.get('childComponents').isAny('isInvalid');
}),

isTouched: computed('[email protected]', function() {
return this.get('childComponents').isAny('isTouched');
}),

isInvalidAndTouched: and('isInvalid', 'isTouched'),

submit() {
this.send('onSubmit');
return false;
},

actions: {
onValidityChange() {
if (this.get('lastIsValid') !== this.get('isValid') || this.get('lastIsTouched') !== this.get('isTouched')) {
invokeAction(this, 'onValidityChange', this.get('isValid'), this.get('isTouched'), this.get('isInvalidAndTouched'));
this.set('lastIsValid', this.get('isValid'));
this.set('lastIsTouched', this.get('isTouched'));
@tagName('form')
@layout(template)
export default class PaperForm extends Component {
inputComponent = 'paper-input';
submitButtonComponent = 'paper-button';
selectComponent = 'paper-select';
autocompleteComponent = 'paper-autocomplete';

finishFirstRender = false

@tracked
formHasBeenValidated = false

isValid = true
isTouched = false

@not('isValid')
isInvalid

@and('isInvalid', 'isTouched')
isInvalidAndTouched

childComponents = A()

updateValidity ({ childId, isValid, isTouched, isInvalidAndTouched }) {
const child = get(this, 'childComponents').findBy('childId', childId);

if (child) {
const lastIsValid = child.isValid;
const lastIsTouched = child.isTouched;

if (
lastIsValid !== isValid
|| lastIsTouched !== isTouched
) {
set(child, 'isValid', isValid);
set(child, 'isTouched', isTouched);
set(child, 'isInvalidAndTouched', isInvalidAndTouched);

this.notifyPropertyChange('childComponents')
}
}

this.triggerValidityChange()
}

triggerValidityChange () {
const lastIsValid = get(this, 'isValid');
const lastIsTouched = get(this, 'isTouched');

if (
get(this, 'finishFirstRender')
&& (
lastIsValid !== this.getIsValid()
|| lastIsTouched !== this.getIsTouched()
)
) {
this.setNewValidity()
}
}

getIsValid () {
return get(this, 'childComponents').isEvery('isValid')
}

getIsTouched () {
return get(this, 'childComponents').isAny('isTouched')
}

setNewValidity () {
run.next(() => {
if (!get(this, 'isDestroying')) {
this.set('isValid', this.getIsValid());
this.set('isTouched', this.getIsTouched());

invokeAction(this, 'onValidityChange', get(this, 'isValid'), get(this, 'isTouched'), get(this, 'isInvalidAndTouched'));
}
},
onSubmit() {
if (this.get('isInvalid')) {
this.get('childComponents').setEach('isTouched', true);
invokeAction(this, 'onInvalid');
} else {
invokeAction(this, 'onSubmit');
this.get('childComponents').setEach('isTouched', false);
})
}

@action
onChildValidityChange ({ elementId: childId, isValid, isTouched, isInvalidAndTouched }) {
if (!this.isDestroying) {
this.updateValidity({
childId,
isValid,
isTouched,
isInvalidAndTouched
})
}
}

didRender () {
if (!get(this, 'finishFirstRender')) {
this.set('finishFirstRender', true)

this.setNewValidity()
}
}

submit (event) {
event.preventDefault()

this.onInternalSubmit(...arguments)
}

reset () {
this.set('formHasBeenValidated', false);
}

@action
onInternalSubmit () {
if (get(this, 'isInvalid')) {
this.set('formHasBeenValidated', true);

invokeAction(this, 'onInvalid');
} else {
invokeAction(this, 'onSubmit');

this.set('formHasBeenValidated', false);
}
}

@action
onRegister (childId) {
get(this, 'childComponents').pushObject({ childId });
}

@action
onUnregister (childId) {
if (!this.isDestroying) {
const child = get(this, 'childComponents').findBy('childId', childId)

if (child) {
get(this, 'childComponents').removeObject(child);

this.triggerValidityChange()
}
}
}
});
}
5 changes: 3 additions & 2 deletions addon/components/paper-icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ let PaperIconComponent = Component.extend(ColorMixin, {
reverseSpin: false,

iconClass: computed('icon', 'positionalIcon', function() {
let icon = this.getWithDefault('positionalIcon', this.get('icon'));
return icon;
let icon = this.get('positionalIcon');

return icon === undefined ? this.get('icon') : icon;
}),

'aria-hidden': false,
Expand Down
Loading