Repository: baianat/vee-validate
Pull request #1751 (2018-12-13 03:37:23)

fix: make detach() scope parameter optional in validatorDecorator
### 🔎 Overview
According to docs *scope* parameter in detach() method is optional, but in code it's required actually. 
If we want to remove field without scope, we must do this (which is not obvious): 
```js
validator.detach('field', null);
```

This pull-request makes *scope* parameter optional, so we also can do this:
```js
validator.detach('field');
```

## ✔ Related Issues
Possible related issue #782

codecov[bot]: # [Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1751?src=pr&el=h1) Report
> Merging [#1751](https://codecov.io/gh/baianat/vee-validate/pull/1751?src=pr&el=desc) into [master](https://codecov.io/gh/baianat/vee-validate/commit/b2c4e8385bae97b013a4055f5a71d642c3f85a9c?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/baianat/vee-validate/pull/1751/graphs/tree.svg?width=650&token=XFzlvHKFP5&height=150&src=pr)](https://codecov.io/gh/baianat/vee-validate/pull/1751?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master    #1751   +/-   ##
=======================================
  Coverage   97.68%   97.68%           
=======================================
  Files          57       57           
  Lines        2119     2119           
  Branches      658      658           
=======================================
  Hits         2070     2070           
  Misses         43       43           
  Partials        6        6
```


| [Impacted Files](https://codecov.io/gh/baianat/vee-validate/pull/1751?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/core/validatorDecorator.js](https://codecov.io/gh/baianat/vee-validate/pull/1751/diff?src=pr&el=tree#diff-c3JjL2NvcmUvdmFsaWRhdG9yRGVjb3JhdG9yLmpz) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1751?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1751?src=pr&el=footer). Last update [b2c4e83...9ed424f](https://codecov.io/gh/baianat/vee-validate/pull/1751?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 5decdee2..2faea38d 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -8,12 +8,14 @@ const sidebars = {
     '',
     'getting-started',
     'syntax',
+    'displaying-errors',
     'rules',
     'custom-rules',
     'inferred-rules',
     'messages',
     'localization',
     'flags',
+    'classes-and-styling',
     'events',
     'conditional-and-looping-inputs',
     'ui-integrations'
diff --git a/docs/guide/classes-and-styling.md b/docs/guide/classes-and-styling.md
new file mode 100644
index 00000000..4da839d7
--- /dev/null
+++ b/docs/guide/classes-and-styling.md
@@ -0,0 +1,115 @@
+# Styling and CSS Classes
+
+Styling certain aspects of your UI based on validation state is a common use-case for friendly UIs, vee-validate offers some UI utilities to make this easier for you.
+
+## Auto CSS Classes
+
+VeeValidate can apply classes on your native HTML input fields based on the validation state, for example you may want to apply a `valid` class on valid fields and `invalid` on fields that did not pass validation, this behavior is turned off by default.
+
+By setting the `classes` option to `true` in the configuration VeeValidate handles this for you and applies a bunch of other useful classes based on the [flags object](./flags.md) for that field.
+
+```js
+import Vue from 'vue';
+import VeeValidate from 'vee-validate';
+
+Vue.use(VeeValidate, {
+  classes: true
+});
+```
+
+### Customizing CSS classes
+
+By default the classes applied on the inputs match the flags names, you can override the class name for each flag by setting the `classNames` object when configuring vee-validate. This is very useful if you are using a CSS framework that has predefined styles for input states.
+
+This code snippet overrides the `valid` and `invalid` class names to have a bootstrap style of class names.
+
+```js
+import Vue from 'vue';
+import VeeValidate from 'vee-validate';
+
+Vue.use(VeeValidate, {
+  classes: true,
+  classNames: {
+    valid: 'is-valid',
+    invalid: 'is-invalid'
+  }
+});
+```
+You can [view the example live](https://codesandbox.io/s/l77o5m19lz)
+
+Additionaly you can override the class names for `dirty`, `pristine`, `touched`, `untouched` flags.
+
+## Styling with the HTML5 validation API
+
+VeeValidate plays nicely with the [Constrained Validation API](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation) or the HTML5 validation which is supported by most browsers, currently you can use `:valid` and `:invalid` psudo selectors to apply visual styles to your elements based on their validation state which is handled by vee-validate automatically.
+
+::: tip
+  This behavior is __disabled__ by default to prevent accidental styling, you can turn this feature on by setting the `validity` option to true when installing vee-validate.
+:::
+
+```js
+import Vue from 'vue';
+import VeeValidate from 'vee-validate';
+
+Vue.use(VeeValidate, {
+  validity: true
+});
+```
+
+```css
+input:valid {
+  border-color: green;
+}
+
+input:invalid {
+  border-color: red;
+}
+```
+
+You can view [this example live](https://codesandbox.io/s/9zl091p17r)
+
+::: tip
+  One caveat with this feature is that the field can only be one of two states, either invalid or valid meaning you would face some situations when the applied styles are not suitable, for example a red border around a field that the user did not intereact with yet may result in a poor UX.
+:::
+
+## Styling with the aria-* attributes
+
+VeeValidate also applies a couple of aria attributes on your HTML inputs:
+
+- aria-required: when the field is required it will be set to 'true' otherwise 'false'.
+- aria-invalid: when the field is invalid it will be set to 'true' otherwise 'false'.
+
+You could use these attributes for CSS selectors to apply specific styling to fields applied with those aria attributes.
+
+This feature is __enabled__ by default and can be disabled by setting the `aria` option to true in the plugin configuration.
+
+```js
+import Vue from 'vue';
+import VeeValidate from 'vee-validate';
+
+Vue.use(VeeValidate, {
+  aria: true
+});
+```
+
+```css
+input[aria-invalid="true"] {
+  border-color: red;
+}
+
+input[aria-invalid="false"] {
+  border-color: green;
+}
+```
+
+::: tip
+  Like the validity feature, this also have the caveat of having only binary states for the inputs which may result in a poor UX experience, you may use this approach coupled with css classes to further constrain the styling applied on your inputs.
+:::
+
+A [live example can be found here](https://codesandbox.io/s/n35q1rnq00)
+
+## Styling custom components
+
+The features mentioned above only work for native HTML5 inputs. As for components, you should always design your components around using props/state to control their visual representation.
+
+You may use a similar API with [Validation Components](/guide/components.md), specifically the [ValidationProvider component](/guide/components/validation-provider.md) that exposes useful props on the slot-scope to allow you to control your component's visuals.
diff --git a/docs/guide/displaying-errors.md b/docs/guide/displaying-errors.md
new file mode 100644
index 00000000..fb1d78e6
--- /dev/null
+++ b/docs/guide/displaying-errors.md
@@ -0,0 +1,65 @@
+# Displaying Errors
+
+After generating error messages, they are stored in an ErrorBag instance which simplifies displaying errors in your UI.
+
+By default the error bag instance will be injected in your component's computed properties under the `errors` name, which can be customized to avoid conflicts with other libraries/components.
+
+## Displaying single error message
+
+Typically you would want to display one error at a time for your fields, you can do this using `errors.first('fieldName')` method.
+
+```html
+<input type="text" name="fieldName" v-validate="'required'">
+<span>{{ errors.first('fieldName') }}</span>
+```
+
+::: tip
+  VeeValidate __only generates one message per field by default__ as it uses a fast-exit strategy when running the validation pipeline. When the first failing rule is detected it will have its message generated and stored in the error bag instance, other rules results are then ignored. To disable this behavior you may want to configure the `fastExit` option in [VeeValidate's config](/configuration.md) or use the [`continutes` modifier](/api/directive.md#continues).
+:::
+
+## Displaying Multiple error messages
+
+Another use-case is that you might want to dispaly all the errors for an input, typically to allow the user to fix multiple input errors at once. The `errors.collect('fieldName')` method collects all error messages for a specific field into an array.
+
+```html
+<input type="text" name="fieldName" v-validate.continues="'required|alpha|min:5'">
+<ul>
+  <li v-for="error in errors.collect('fieldName')">{{ error }}</li>
+</ul>
+```
+
+## Displaying all errors
+
+Sometimes you need to display all fields errors on top of a form, especially for very large forms. You can use either:
+
+### Flat list of errors
+
+You can use `errors.all()` to collect all fields errors into a single flat array.
+
+```html
+<input type="text" name="first" v-validate.continues="'required|alpha|min:5'">
+
+<input type="text" name="second" v-validate.continues="'required|alpha|min:5'">
+
+<ul>
+  <li v-for="error in errors.all()">{{ error }}</li>
+</ul>
+```
+
+### Grouped by field name
+
+Use `errors.collect()` without providing a field name to collect all errors into an object which keys are field names and the values are arrays of error messages for each field. In other words it groups error messages by field name.
+
+```html
+<input type="text" name="first" v-validate.continues="'required|alpha|min:5'">
+
+<input type="text" name="second" v-validate.continues="'required|alpha|min:5'">
+
+<ul>
+  <li v-for="group in errors.collect()">
+    <ul>
+      <li v-for="error in group">{{ error }}</li>
+    <ul>
+  </li>
+</ul>
+```
diff --git a/docs/guide/rules.md b/docs/guide/rules.md
index af4278e4..d48da098 100644
--- a/docs/guide/rules.md
+++ b/docs/guide/rules.md
@@ -154,11 +154,11 @@ The field under validation must have the same value as the confirmation field.
 
 - `target:` The ref of the password field.
 
-<input v-validate="'required'" :class="{'input': true, 'is-danger': errors.has('password_field') }" name="password" type="password" placeholder="Enter The Password" ref="password">
-<span v-show="errors.has('password_field')" class="help is-danger">{{ errors.first('password_field') }}</span>&nbsp;
+<input v-validate="'required'" :class="{'input': true, 'is-danger': errors.has('password') }" name="password" type="password" placeholder="Enter The Password" ref="password">
+<span v-show="errors.has('password')" class="help is-danger">{{ errors.first('password') }}</span>
 
-<input v-validate="'required|confirmed:password'" name="pw_confirm" :class="{'input': true, 'is-danger': errors.has('pw_confirm_field') }" type="password" placeholder="Confirm the password">
-<span v-show="errors.has('pw_confirm_field')" class="help is-danger">{{ errors.first('pw_confirm_field') }}</span>
+<input v-validate="'required|confirmed:password'" name="pw_confirm" :class="{'input': true, 'is-danger': errors.has('pw_confirm') }" type="password" placeholder="Confirm the password">
+<span v-show="errors.has('pw_confirm')" class="help is-danger">{{ errors.first('pw_confirm') }}</span>
 
 ::: tip
   Target based rules like `after`, `before`, and `confirmed` can target custom components as well as native inputs, but the target field must have a `ref` attribute set and the confirmed parameter must be the same ref value.
diff --git a/src/components/provider.js b/src/components/provider.js
index 8f77575d..92fb1125 100644
--- a/src/components/provider.js
+++ b/src/components/provider.js
@@ -111,13 +111,6 @@ function createValuesLookup (ctx) {
     }
 
     acc[depName] = providers[depName].value;
-    const watcherName = `$__${depName}`;
-    if (!isCallable(ctx[watcherName])) {
-      ctx[watcherName] = providers[depName].$watch('value', () => {
-        ctx.validate(ctx.value).then(ctx.applyResult);
-        ctx[watcherName]();
-      });
-    }
 
     return acc;
   }, {});
@@ -279,9 +272,18 @@ export const ValidationProvider = {
     },
     fieldDeps () {
       const rules = normalizeRules(this.rules);
+      let providers = this.$_veeObserver.refs;
 
       return Object.keys(rules).filter(RuleContainer.isTargetRule).map(rule => {
-        return rules[rule][0];
+        const depName = rules[rule][0];
+        const watcherName = `$__${depName}`;
+        if (!isCallable(this[watcherName])) {
+          this[watcherName] = providers[depName].$watch('value', () => {
+            this.validate().then(this.applyResult);
+          });
+        }
+
+        return depName;
       });
     },
     normalizedEvents () {
diff --git a/src/core/validatorDecorator.js b/src/core/validatorDecorator.js
index 56844ccc..25eb3e07 100644
--- a/src/core/validatorDecorator.js
+++ b/src/core/validatorDecorator.js
@@ -74,8 +74,8 @@ export default class ScopedValidator {
     return this._base.remove(ruleName);
   }
 
-  detach (...args) {
-    return this._base.detach(...args, this.id);
+  detach (name, scope) {
+    return this._base.detach(name, scope, this.id);
   }
 
   extend (...args) {
diff --git a/src/rules/alpha_helper.js b/src/rules/alpha_helper.js
index 2b3bc136..a4d991e8 100644
--- a/src/rules/alpha_helper.js
+++ b/src/rules/alpha_helper.js
@@ -18,6 +18,7 @@ export const alpha = {
   ru: /^[А-ЯЁ]*$/i,
   sk: /^[A-ZÁÄČĎÉÍĹĽŇÓŔŠŤÚÝŽ]*$/i,
   sr: /^[A-ZČĆŽŠĐ]*$/i,
+  sv: /^[A-ZÅÄÖ]*$/i,
   tr: /^[A-ZÇĞİıÖŞÜ]*$/i,
   uk: /^[А-ЩЬЮЯЄІЇҐ]*$/i,
   ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]*$/
@@ -38,6 +39,7 @@ export const alphaSpaces = {
   ru: /^[А-ЯЁ\s]*$/i,
   sk: /^[A-ZÁÄČĎÉÍĹĽŇÓŔŠŤÚÝŽ\s]*$/i,
   sr: /^[A-ZČĆŽŠĐ\s]*$/i,
+  sv: /^[A-ZÅÄÖ\s]*$/i,
   tr: /^[A-ZÇĞİıÖŞÜ\s]*$/i,
   uk: /^[А-ЩЬЮЯЄІЇҐ\s]*$/i,
   ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ\s]*$/
@@ -58,6 +60,7 @@ export const alphanumeric = {
   ru: /^[0-9А-ЯЁ]*$/i,
   sk: /^[0-9A-ZÁÄČĎÉÍĹĽŇÓŔŠŤÚÝŽ]*$/i,
   sr: /^[0-9A-ZČĆŽŠĐ]*$/i,
+  sv: /^[0-9A-ZÅÄÖ]*$/i,
   tr: /^[0-9A-ZÇĞİıÖŞÜ]*$/i,
   uk: /^[0-9А-ЩЬЮЯЄІЇҐ]*$/i,
   ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]*$/
@@ -78,6 +81,7 @@ export const alphaDash = {
   ru: /^[0-9А-ЯЁ_-]*$/i,
   sk: /^[0-9A-ZÁÄČĎÉÍĹĽŇÓŔŠŤÚÝŽ_-]*$/i,
   sr: /^[0-9A-ZČĆŽŠĐ_-]*$/i,
+  sv: /^[0-9A-ZÅÄÖ_-]*$/i,
   tr: /^[0-9A-ZÇĞİıÖŞÜ_-]*$/i,
   uk: /^[0-9А-ЩЬЮЯЄІЇҐ_-]*$/i,
   ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ_-]*$/
diff --git a/types/vee-validate.d.ts b/types/vee-validate.d.ts
index c4e1ec8f..0bf2a756 100644
--- a/types/vee-validate.d.ts
+++ b/types/vee-validate.d.ts
@@ -200,3 +200,11 @@ export const version: string;
 export const install: Vue.PluginFunction<Configuration>
 
 export const directive: Vue.DirectiveOptions;
+
+export const Rules: {
+    [key: string]: {
+        validate (value: any, args: Object | any[], data?: any): boolean | Promise<boolean>;
+        options?: object;
+        paramNames?: string[];
+    }
+}


Test patch:
diff --git a/tests/unit/validatorDecorator.js b/tests/unit/validatorDecorator.js
index 64ff2eaf..adebbce1 100644
--- a/tests/unit/validatorDecorator.js
+++ b/tests/unit/validatorDecorator.js
@@ -66,6 +66,22 @@ test('decorates flag()', () => {
   expect(base.flag).toHaveBeenCalledWith('name', { valid: false }, 0);
 });
 
+test('decorates detach()', () => {
+  const base = new Validator();
+  const decorator = new Decorator(base, { _uid: 0 });
+
+  decorator.attach({ name: 'email1' });
+  decorator.attach({ name: 'email2' });
+  decorator.attach({ name: 'email3', scope: 'scope' });
+
+  decorator.detach('email1');
+  decorator.detach('email2', null);
+  decorator.detach('email3', 'scope');
+
+  expect(base.fields.items).toHaveLength(0);
+  expect(decorator.fields.items).toHaveLength(0);
+});
+
 test('calls extend()', () => {
   const vm = { _uid: 0 };
   const base = new Validator();