Skip to content
This repository has been archived by the owner on Feb 28, 2023. It is now read-only.

Commit

Permalink
[WIP] Tout ce qui est relatif au formulaire (#695)
Browse files Browse the repository at this point in the history
* * ajouter une fonctionnalité de validation au niveau du formulaire

* * typo

* * renaming
* small refactor

* * removed unused method

* * small fixes

* * change form state to be the internal state of the form
* added form validation class to represent the return of each validator callback of the form
* updated tests and sandbox

* * missing doc

* *  allow to display error message only when form field is touched

* * fix errorMessage algo to return errorMessage if messageAfterTouched = false
* added use case to sandbox

* * renaming
* tests

* * moved touch closer to validate

* * typo

* * removed unsused line

* * renaming

* * renaming

* * use form plugin

* * use to more strict toBe method
* reformat condition and use of variable to hold the value

* MODUL-707 - * use non-null assertion operator

* MODUL-710 - * access formfield by name
* new constructor for the form with form field group

* * renaming

* MODUL-710 - * renaming and typing

* * merge fix

* merge fix

* * formating

* ENA2-4264 - Add multiple validators

* ENA2-4264 - Allow toast to contain html

* ENA2-4264 - Fix plugin

* * missing doc

* ENA2-4264 - Move params from constructor to validating class and allow single function validating class

* Rebase and fixes

* * touch field to mark as touched + validate them
* bring back focus if only one field has error
* display summary error if it have errors & only one field have an error

* MODUL 716 - style scopé pour formulaire (#696)

* MODUL-716 - * ajout form.scss
* utilisation de classe formulaire au lieu des classe utilitaire

* MODUL-716 - * updated snapshot

* * use of v-html for validation message

* rename prop hasRequiredFields by requiredMarker

* MODUL-719 - * form field directive

* MODUL-719 - * remove unused setter

* remove unused setter

* MODUL-719 - * removed unused delclaration

* MAJ du style du champ requis

* Ajouter une transition accordeon pour l'affichage du message d'erreur

* Maj  champ requis

* MODUL-719 - * removed directive pluggin in favor of vue.directive
* added touch test
* small fixes

* MODUL-719 - * updated tests
* updated algo when form field is use on input

* MODUL-719 - * add comment for hack-ish code

* MODUL-719 - * refactor

* * removed weird import

* Delete form-field.ts

* * added key
* renaming
* use push in favor of concat

* MODUL-719 - * await on forceUpdate

* MODUL-719 - * more generic algo

* * renamed css class
* updated snapshots
* throw error if field not found in form

* * look like the weird import was necessary

* * please travis
  • Loading branch information
guignol1981 authored and chuckmah committed Feb 1, 2019
1 parent d323851 commit d9b1e76
Show file tree
Hide file tree
Showing 28 changed files with 765 additions and 177 deletions.
4 changes: 2 additions & 2 deletions src/components/accordion/accordion-transition.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Vue, { PluginObject, VNode, VNodeData, VueConstructor } from 'vue';

import { ACCORDION_TRANSITION_NAME } from '../component-names';


interface MAccordionTransitionProps {
heightDelta?: number;
transition?: boolean;
Expand Down Expand Up @@ -35,7 +35,7 @@ export const MAccordionTransition: VueConstructor<Vue> = Vue.extend({
el.style.removeProperty('height');
},
beforeLeave(el: HTMLElement): void {
el.style.height = el.scrollHeight + 'px';
el.style.height = parseInt((window.getComputedStyle(el).height as string), 10) + 'px';
if (props.transition === false && el.classList.contains(CLASS_HAS_TRANSITION)) {
el.classList.remove(CLASS_HAS_TRANSITION);
}
Expand Down
11 changes: 8 additions & 3 deletions src/components/accordion/accordion.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@
ref="accordionHeader">
<div class="m-accordion__header__content">
<slot name="header"></slot>
<m-i18n k="m-accordion:open" class="m-accordion__hidden" v-if="!propOpen"></m-i18n>
<m-i18n k="m-accordion:close" class="m-accordion__hidden" v-if="propOpen"></m-i18n>
<m-i18n k="m-accordion:open"
class="m-accordion__hidden"
v-if="!propOpen"></m-i18n>
<m-i18n k="m-accordion:close"
class="m-accordion__hidden"
v-if="propOpen"></m-i18n>
</div>
<m-plus class="m-accordion__header-icon"
:class="{ 'm--is-left': iconPosition !== 'right' }"
Expand All @@ -33,7 +37,8 @@
:id="propId + '-controls'"
:aria-labelledby="propId"
v-if="propOpen">
<div class="m-accordion__body" :class="{ 'm--has-padding': paddingBody && padding }">
<div class="m-accordion__body"
:class="{ 'm--has-padding': paddingBody && padding }">
<slot></slot>
</div>
</div>
Expand Down
15 changes: 10 additions & 5 deletions src/components/form/__snapshots__/form.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MForm When the form has no required fields, then it should not show a required label 1`] = `<form id="uuid"> </form>`;
exports[`MForm When the form has no required fields, then it should not show a required label 1`] = `
<form id="uuid" class="m-form">
<m-accordion-transition></m-accordion-transition>
</form>
`;

exports[`MForm When the form has required fields, then it should show a required label 1`] = `
<form id="uuid">
<p class="m-u--require"><span class="m-u--asterisk">*</span> <span class="m-u--typo--precision">m-form:required</span></p>
<form id="uuid" class="m-form">
<m-accordion-transition></m-accordion-transition>
<p class="required"><span class="required__marker">*</span> <span class="required__label">m-form:required</span></p>
</form>
`;

exports[`MForm should render correctly 1`] = `
<form id="uuid">
<p class="m-u--require"><span class="m-u--asterisk">*</span> <span class="m-u--typo--precision">m-form:required</span></p>
<form id="uuid" class="m-form">
<m-accordion-transition></m-accordion-transition>
</form>
`;
87 changes: 87 additions & 0 deletions src/components/form/form-field.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { createLocalVue, mount, Wrapper } from '@vue/test-utils';
import Vue, { VueConstructor } from 'vue';
import { resetModulPlugins } from '../../../tests/helpers/component';
import { FORM_FIELD_NAME } from '../../directives/directive-names';
import { Form } from '../../utils/form/form';
import { FormFieldValidation } from '../../utils/form/form-field-validation/form-field-validation';
import { FormField } from '../../utils/form/form-field/form-field';
import { ModulVue } from '../../utils/vue/vue';
import TextfieldPlugin from '../textfield/textfield';
import { FormFieldDirective } from './form-field';

let mockFormField: any = {};

jest.mock('../../utils/form/form-field/form-field', () => {
return {
FormField: jest.fn().mockImplementation(() => {
return mockFormField;
})
};
});

describe('form-field', () => {
let element: Wrapper<Vue>;
let localVue: VueConstructor<ModulVue>;

beforeEach(() => {
resetModulPlugins();
localVue = createLocalVue();
localVue.directive(FORM_FIELD_NAME, FormFieldDirective);
localVue.use(TextfieldPlugin);
});

describe(`The form validate its fields`, () => {
mockFormField = {
shouldFocus: false,
isTouched: false,
hasError: true,
touched: false,
touch: jest.fn()
};

let formField: FormField<any>;
let form: Form;

beforeEach(() => {
formField = new FormField(() => undefined, [(value: any) => {
return new FormFieldValidation(true, [''], ['']);
}]);

form = new Form({
'a-field': formField
});

element = mount(
{
template: `<input v-m-form-field="form.get('a-field')" ref="field"></input>`,
data(): any {
return {
form: form
};
}
},
{ localVue: localVue }
);
});

it(`the element should have the focus if first invalid`, async () => {
const spy: any = jest.spyOn(element.find({ ref: 'field' }).element, 'focus');
form.focusFirstFieldWithError();
expect(mockFormField.shouldFocus).toBe(true);

await element.vm.$forceUpdate();

expect(mockFormField.shouldFocus).toBe(false);
expect(spy).toHaveBeenCalled();
});

it(`it should touch the form field on blur`, () => {
const spy: any = jest.spyOn(mockFormField, 'touch');

element.find({ ref: 'field' }).element.focus();
element.find({ ref: 'field' }).element.blur();

expect(spy).toBeCalled();
});
});
});
44 changes: 44 additions & 0 deletions src/components/form/form-field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { FormField } from 'src/utils/form/form-field/form-field';
import { DirectiveOptions, VNode, VNodeDirective } from 'vue';

let touchFormField: any;

export const FormFieldDirective: DirectiveOptions = {
inserted(
el: HTMLElement,
binding: VNodeDirective,
vnode: VNode
): void {
const formField: FormField<any> = binding.value;
touchFormField = () => formField.touch();

el.addEventListener('blur', touchFormField, true);
},
update(
el: HTMLElement,
binding: VNodeDirective,
vnode: VNode
): void {
const formField: FormField<any> = binding.value;

if (formField.shouldFocus) {
const selector: string = 'input, textarea, [contenteditable=true]';
let container: HTMLDivElement = document.createElement('div');
container.appendChild(el);

const elements: NodeListOf<HTMLInputElement> = container.querySelectorAll(selector);

if (elements.length > 0) {
elements[0].focus();
}

formField.shouldFocus = false;
}
},
unbind(
el: HTMLElement,
binding: VNodeDirective
): void {
el.removeEventListener('blur', touchFormField, true);
}
};
30 changes: 17 additions & 13 deletions src/components/form/form.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
<form :id="form.id"
class="m-form"
@submit.prevent="submit"
@reset.prevent="reset">
<m-message class="m-u--margin-bottom--l"
v-if="hasErrors"
:state="messageStateEror"
ref="summary">
<ul class="m-u--no-margin">
<li v-for="error in errors">{{ error }}</li>
</ul>
</m-message>

<p class="m-u--require"
v-if="hasRequiredFields">
<span class="m-u--asterisk">*</span>
<span class="m-u--typo--precision">{{ 'm-form:required' | f-m-i18n }}</span>
<m-accordion-transition>
<m-message class="m-form__message-summary"
v-if="hasErrors"
:state="messageStateError"
ref="summary">
<ul>
<li v-for="(error, index) in errors"
:key="index"
v-html="error"></li>
</ul>
</m-message>
</m-accordion-transition>
<p class="required"
v-if="requiredMarker">
<span class="required__marker">*</span>
<span class="required__label">{{ 'm-form:required' | f-m-i18n }}</span>
</p>

<slot></slot>
Expand Down
39 changes: 26 additions & 13 deletions src/components/form/form.sandbox.html
Original file line number Diff line number Diff line change
@@ -1,39 +1,52 @@
<div>
<m-form :form="form"
:has-required-fields="true"
:required-marker="true"
@submit="submit">
<m-textfield v-model.trim="titleField.value"
<m-textfield v-model.trim="form.get('titleField').value"
label="Title"
:max-width="inputMaxWidthSmall"
:required-marker="true"
:error="titleField.hasError"
:error-message="titleField.errorMessage"
:error-message="form.get('titleField').errorMessage"
:character-count="true"
:max-length="maxTitleLength"
:character-count-threshold="thresholdTitle"
ref="title"></m-textfield>
v-m-form-field="form.get('titleField')">
</m-textfield>

<m-textarea v-model.trim="descriptionField.value"
<m-textarea v-model.trim="form.get('descriptionField').value"
label="Description"
:required-marker="true"
:max-width="inputMaxWidthLarge"
:error="descriptionField.hasError"
:error-message="descriptionField.errorMessage"
:error-message="form.get('descriptionField').errorMessage"
:character-count="true"
:max-length="maxDescriptionLength"
:character-count-threshold="thresholdDescription"
ref="description">
v-m-form-field="form.get('descriptionField')">
</m-textarea>

<m-textarea v-model.trim="form.get('passwordField').value"
label="Password"
:max-width="inputMaxWidthLarge"
:error-message="form.get('passwordField').errorMessage"
v-m-form-field="form.get('passwordField')">
</m-textarea>

<m-textarea v-model.trim="form.get('confirmPasswordField').value"
label="Confirm password"
:max-width="inputMaxWidthLarge"
:error-message="form.get('confirmPasswordField').errorMessage"
v-m-form-field="form.get('confirmPasswordField')">
</m-textarea>

<m-textarea v-model.trim="locationField.value"
<m-textarea v-model.trim="form.get('locationField').value"
label="Location"
:max-width="inputMaxWidthLarge"
:required-marker="true"
:error="locationField.hasError"
:error-message="locationField.errorMessage"
:error-message="form.get('locationField').errorMessage"
:character-count="true"
:max-length="maxLocationLength"
:character-count-threshold="thresholdLocation"
ref="location">
v-m-form-field="form.get('locationField')">
</m-textarea>
</m-form>
<m-button type="submit"
Expand Down
Loading

0 comments on commit d9b1e76

Please sign in to comment.