Skip to content
Browse files

Added detailed documentation, changed the interface of the Plugin.

  • Loading branch information...
1 parent c9accdc commit a0f00d0350bec8a0f9dbc3fe5c07ca5ae2eb1a4e @inf3rno committed Jan 3, 2013
Showing with 287 additions and 120 deletions.
  1. +35 −105 README.md
  2. +15 −0 doc/Aggregator.md
  3. +28 −0 doc/AsyncModel.md
  4. +3 −0 doc/DependencyResolver.md
  5. +40 −0 doc/Messenger.md
  6. +88 −0 doc/Plugin.md
  7. +3 −0 doc/Runner.md
  8. +3 −0 doc/SyncModel.md
  9. +55 −0 doc/Validator.md
  10. +1 −1 package.json
  11. +16 −14 src/validation.js
View
140 README.md
@@ -1,124 +1,54 @@
-**Backbone validation 1.0.0.** (the current source version and implemented features are not necessarily the same)
+# bb-validation
-This is a plugin for Backbone validation, which works both in browsers and node.js applications. The main classes in this package are: **Model**, **Validator**, **Messenger** and **Aggregator**.
+This plugin is for helping the validation of Backbone models in Backbone applications. For better understanding of the validation configuration you have to check the [Plugin](doc/Plugin.md) and the [Validator](doc/Validator.md) classes in the documentation and the registration.js in the example directory. If you want to create custom views for the test results, you have to check the [Aggregator](doc/Aggregator.md) and [Messenger](doc/Messenger.md) classes in the documentation and the form.js in the example directory.
-**1.) Installation, custom test libraries**
+## Features
-Before anything else you have to define your custom tester functions. I created a ***basic test collection***. You can install it the following way with **require.js** :
+ * Support of client side (browser) and server side (node.js) applications.
+ * Validation of model with synchronous and asynchronous tests.
+ * Definition of custom plugin with test inheritance.
+ * Custom error messages which can be multi language if you use an i18n plugin.
+ * Easy to modify to make it work with your specific needs.
+ * And much, much more
- require(["jquery", "underscore", "backbone", "domReady!", "../src/validation", "../src/basicTests"], function ($, _, Backbone, domReady, validation, basics) {
- validation.Validator.customize(basics);
- ...
- });
+## Documentation
-Or you can extend the **Validator** class if you want to create a custom branch of tests. In that case you can create a custom **Model** class which uses your custom **Validator** class.
+The main classes in this package are:
- var MyValidator = validation.Validator.extend({}).customize(myTests);
- var MyModel = validation.Model.extend({
- Validator: MyValidator
- });
+ * [Plugin](doc/Plugin.md) - This is the class of the module or loader plugin object. A further role is helping the installation of custom test libraries.
+ * [Aggregator](doc/Aggregator.md) - Aggregates the validation result. For example you can create a button which is disabled by invalid form content.
+ * [Messenger](doc/Messenger.md) - Sends validation messages to the client.
+ * [AsyncModel/Model](doc/AsyncModel.md) - Validates the Model asynchronously. By this solution the errors are not preventing the change of the model.
+ * [SyncModel](doc/SyncModel.md) - Validates the Model synchronosly. You can use the default validation interface of the model with synchronous tests only, and validation errors will hinder the change of the model attributes.
+ * [Validator](doc/Validator.md) - The Validator is responsible for running the tests by Model instantiation and by every attribute change.
+ * [Runner](doc/Runner.md) - By each attribute there is a configured Runner instance which runs the tests asynchronously in series, and updates the Validator object with the result.
+ * [DependencyResolver](doc/DependencyResolver.md) - The tests can be dependent on eachother. The DependencyResolver put the tests in the proper order.
-The custom test packages can contains **tests**, **checks** and **patterns**.
+## Requirements
-The **tests** are checking the setted value by the actual attribute, and their scope is the test runner of the attribute. The runner has properties: attributes (the attributes which the model.validate has been called with), config (the config of the current test in the schema), value (the new value by the attribute), pending (used external by validator, need for pending count), name (the name of the current test).
+This plugin works only with the libraries contained by the following list.
-The **checks** are called by configuring the tests, so by creating the validator with the actual **schema**, and their scope is the validator itself. They can call the **related** method of the validator, and can add relations, for example by password verifying you have to call
+ * [underscore.js](http://underscorejs.org/)
+ * [backbone.js](http://backbonejs.org)
- this.related("password", "password2");
+### Usage in node.js
-or
+If you intend to use this plugin in node.js you'll need amdefine.
- this.related("password", ["password2"]);
-This results the call of the password2 test runner by changing the password.
-The validator contains the installed **patterns** too. So you can reach the installed patterns by name.
+ * [amdefine](https://npmjs.org/package/amdefine)
+The node.js tests are working with jasmine.
-If you want to create your custom test library, then please study the basic tests first.
+ * [jasmine-node](https://npmjs.org/package/jasmine-node)
+### Usage in browser
-**2.) Model and Validator**
+If you intend to use this plugin in browser you'll need require.js and jQuery.
-If you want to create a validable **Model** class, you can do it very simply, for example:
+ * [require.js](http://requirejs.org/)
+ * [jQuery](http://jquery.com/)
- var RegistrationModel = validation.Model.extend({
- schema:{
- email:{
- required:true,
- type:String,
- match:"email",
- max:127
- },
- password:{
- required:true,
- type:String,
- range:{
- min:5,
- max:14
- }
- },
- password2:{
- duplicate:"password"
- }
- }
- });
+The browser examples are working with several libraries. To ease the try out of the examples I attached this libraries in the example/lib directory.
-
-The **Model** uses the normal **Backbone.Model** ***validate*** method, but because the asynchronous validation it always sets the values. The **Model** does not use the **Backbone.Model** ***error*** event, the errors are collected by the **Validator**. By the instantiation of the **Model** class, a **Validator** instance will be created automatically with the given schema. You can reach this instance under the **model.validator** property. The **Validator** is an extension of **Backbone.Model**. It runs the tests on the attributes by any change of the **Model**, and the **Validator** attributes are the results of those tests. If there is no error by a **Model** attribute, the result is ***false***. In case of any error, the result is an object with the name of the failed test, and the code of the error, for example:
-
- {type:true}
-or
-
- {range:"min"}
-
-
-Normally a test has only two possible outputs: the error is **true** or **false**. In extreme cases there are more possible outputs, for example by a range test the error can be **false**, **"min"** and **"max"**. This is important if you want to display an error specific message. For example by **"min"**: **"Too short."**, by **"max"**: **"Too long."**. Btw. you can still use min and max tests instead of a range test.
-
-**3.) Validator views: Messenger and Aggregator**
-
-As I mentioned before; the **Validator** is a **Backbone.Model** extension also, and because of that, you can pass **Backbone.View**s to it. I created two classes of this kind: **Messenger** and **Aggregator**.
-
-The **Messenger** can give detailed international error messages.
-
- var InputErrors = validation.Messenger.extend({
- messages:{
- email:{
- required:"The email address is not given.",
- type:"Email address must be string",
- match:"Not an email address",
- max:"The given email address is too long."
- },
- password:{
- required:"The password is not given",
- type:"The password must be string",
- range:{
- min:"The password is too short.",
- max:"The password is too long."
- }
- },
- password2:{
- duplicate:"The verifier password does not equal with the password."
- }
- },
- display:function (attribute, chunks) {
- var $input = this.options.$inputs[attribute];
- var message = "";
- if (chunks)
- message = chunks.join("<br/>");
- $input.next().html(message);
- }
- });
-
-By any change of the **Validator**, the ***display*** method of the **Messenger** is called twice. By the first time it is called by the unRender without chunks, by the second time it is called by the render with message chunks depending on errors. The **Messenger** is prepared to multiple error messages per attribute handling, despite the fact, that this feature is not supported in my asynchronous test runners. My runners are running the tests in sequence, and they abort by every error. If you need, you can write a custom test runner, which supports multiple errors by attribute.
-
-The **Aggregator** can summarize the result of the tests, for example there are no errors, so you can send the form, etc...
-
- var SubmitButtonDisabler = validation.Aggregator.extend({
- display:function (errors) {
- if (errors)
- this.options.$submit.attr("disabled", "disabled");
- else
- this.options.$submit.removeAttr("disabled");
- }
- });
-
-If something is not clear, please check the example folder first, and after that you can still write an issue! Good work! :-)
+ * [require.js - domReady](https://github.com/requirejs/domReady)
+ * [require.js - tpl](https://github.com/ZeeAgency/requirejs-tpl)
View
15 doc/Aggregator.md
@@ -0,0 +1,15 @@
+# Aggregator
+
+The Aggregator is a Validator View, so it extends Backbone.View, and it's Model is the Validator itself.
+It's an abstract class, you can extend the display method, which gets error and pending counts by every change of the Validator.
+
+The Aggregator can summarize the result of the tests, for example:
+
+ var SubmitButtonDisabler = validation.Aggregator.extend({
+ display:function (errors, pending) {
+ if (errors || pending)
+ this.options.$submit.attr("disabled", "disabled");
+ else
+ this.options.$submit.removeAttr("disabled");
+ }
+ });
View
28 doc/AsyncModel.md
@@ -0,0 +1,28 @@
+# Model/AsyncModel
+
+If you want to create a validable **Model** class, you can do it very simply, for example:
+
+ var RegistrationModel = validation.Model.extend({
+ schema:{
+ email:{
+ required:true,
+ type:String,
+ match:"email",
+ max:127
+ },
+ password:{
+ required:true,
+ type:String,
+ range:{
+ min:5,
+ max:14
+ }
+ },
+ password2:{
+ duplicate:"password"
+ }
+ }
+ });
+
+
+The **Model** uses the normal **Backbone.Model** ***validate*** method, but because the asynchronous validation it always sets the values. The **Model** does not use the **Backbone.Model** ***error*** event, the errors are collected by the **Validator**. By the instantiation of the **Model** class, a **Validator** instance will be created automatically with the given schema. You can reach this instance under the **model.validator** property. You can pass **Backbone.View** instances to that **Validator** object.
View
3 doc/DependencyResolver.md
@@ -0,0 +1,3 @@
+# DependencyResolver
+
+This sets the proper test order by checking the dependency list given in the "tests" param object by extending the **Plugin**.
View
40 doc/Messenger.md
@@ -0,0 +1,40 @@
+# Messenger
+
+The Messenger is a Validator View, so it extends Backbone.View, and it's Model is the Validator itself.
+It's an abstract class, you can extend the display method, which gets attribute name, an array of error messages and a boolean which is true if the tests are pending.
+By the default Runner you can have only one error message with pending false, or no error messages with pending true if tests are pending. The display method is called by unrender too, so by any change of the Validator the display is called with an attribute param, and after that shortly with and attribute param, error messages and pending flag.
+
+The current state there are no default messages or message templates, so you have to write your custom test messages by every Validator. For example:
+
+ var InputErrors = validation.Messenger.extend({
+ messages:{
+ email:{
+ required:"The email address is not given.",
+ type:"Email address must be string",
+ match:"Not an email address",
+ max:"The given email address is too long."
+ },
+ password:{
+ required:"The password is not given",
+ type:"The password must be string",
+ range:{
+ min:"The password is too short.",
+ max:"The password is too long."
+ }
+ },
+ password2:{
+ duplicate:"The verifier password does not equal with the password."
+ }
+ },
+ display:function (attribute, chunks, pending) {
+ var $input = this.options.$inputs[attribute];
+ var message = "";
+ if (chunks)
+ message = chunks.join("<br/>");
+ else if (pending)
+ message = "pending ... ";
+ $input.next().html(message);
+ }
+ });
+
+You have to put your messages to the messages object which is multiple level deep. The first key in that object must be the attribute name in the model, the second key must be the name of the test, and if you have a third key, it must be the error message sent by the test, for example by range it can be "min" or "max".
View
88 doc/Plugin.md
@@ -0,0 +1,88 @@
+# Plugin
+
+By requiring the validation package you will get a Plugin instance. This instance is empty until you add one or more test libraries to it.
+You can add test libraries with the add method, or with the extend method. The add method adds the test to the current Plugin instance, the extend method creates a new Plugin instance, and adds the tests to that.
+
+If you would like to install a test library globally, it is recommended to use the add method, for example:
+
+ var validation = require("./validation");
+ validation.add(require("./testLibrary"));
+
+or
+
+ var validation = require("./validation");
+ validation.add({
+ checks: {
+ custom: function (config, name){
+ if (!config)
+ return this.patterns.word;
+ //if no config given, returns the default pattern
+ if (!(config instanceof RegExp))
+ throw new SyntaxError("The given config is not valid by test: " + name);
+ //if invalid config, throws exception
+ return config;
+ //if config is okay, returns that
+ }
+ },
+ tests: {
+ custom: ["my, "dependency", "list", function (done){
+ if (this.config.test(this.value))
+ done();
+ else
+ done("not.equal"));
+ }]
+ },
+ patterns: {
+ word: /\w+/
+ }
+ });
+
+The checks, tests and patterns attributes are "extendable". So if you set them multiple times with the add method, you will get an intersection of the param objects by each attribute, not the param object you gave last time. For example:
+
+ validation.add({
+ patterns: {
+ word: /\w+/
+ }
+ });
+ validation.add({
+ patterns: {
+ digit: /\d+/
+ }
+ });
+
+will result in **validation.Validator.prototype.patterns** :
+
+ {
+ word: /\w+/,
+ digit: /\d+/
+ }
+
+The method extend behaves a little different way. If you call that, it extends the Plugin, Validator and Model classes, and returns the extended Plugin instance. So the original Plugin instance won't change... This is useful if you have a specific test you want use locally. For example a dummy email check:
+
+ var validation = require("../src/validation");
+ var extendedValidation = validation.extend({
+ checks:{
+ registered:function (delay, key) {
+ return delay || 1;
+ }
+ },
+ tests:{
+ registered:function (done) {
+ var registeredEmails = {
+ "test@test.com":true,
+ "test@test.hu":true
+ };
+ setTimeout(function () {
+ done(registeredEmails[this.value]);
+ }.bind(this), this.config);
+ }
+ }
+ });
+
+By this example the **validation** and the **extendedValidation** are not the same objects. Only the **extendedValidation** contains the test called **registered**.
+
+You can use the Plugin as requireJS loader plugin. The resource name must be the path of your test libraries, and the result will be an extended Plugin instance. For example:
+
+ var extendedValidation = require("../src/validation!../src/basicTests:../custom/myTests");
+
+The resource name is stored by require.js, so you will get the same object by every require call with that resource order. Because of that it's recommended to use the extend method and not the add method if you want to add form specific local tests.
View
3 doc/Runner.md
@@ -0,0 +1,3 @@
+# Runner
+
+This runs the tests asynchronous in the proper order. The proper order is set by the DependencyResolver called by the Validator.
View
3 doc/SyncModel.md
@@ -0,0 +1,3 @@
+# SyncModel
+
+The SyncModel is similar to the AsyncModel, but it can call only synchronous tests, it prevents the attribute overriding if a validation error occures. By validation errors it triggers the "error" event with a complete error object. It is not recommended to use this kind of model. In a normal form, or post data validating situation you won't need it, but if you have a special problem it suites for, you may use it.
View
55 doc/Validator.md
@@ -0,0 +1,55 @@
+# Validator
+
+The **Validator** is an extension of **Backbone.Model**. It runs the tests on the attributes by any change of the validable **Model** which instantiated it. The Validator instantiation is done by the Model constructor. By the instantiation the Validator gets the schema property of the Model, it runs the checks on the schema params and creates test Runners with those tests in the proper dependency order. The test order is determined by the DependencyResolver.
+
+ var RegistrationModel = validation.Model.extend({
+ schema:{
+ email:{
+ required:true,
+ type:String,
+ match:"email",
+ max:127
+ },
+ password:{
+ required:true,
+ type:String,
+ range:{
+ min:5,
+ max:14
+ }
+ },
+ password2:{
+ duplicate:"password"
+ }
+ }
+ });
+
+By this schema the Validator creates 3 test Runner: one for each field (email, password, password2). The match, max, range tests depend on the type test, the duplicate, type tests depend on the required test. So the required test will be called first by every time. If no error occures by that test, or it does not call abort, then the type or duplicate test comes. If they pass, then come the other tests.
+
+The **Validator** attributes are the results of the tests. If there is no error by a validable **Model** attribute, the result is ***false***. In case of any error, the result is an object with the name of the failed test, and the code of the error, for example:
+
+ {type:true}
+or
+
+ {range:"min"}
+
+
+Normally a test has only two possible outputs: the error is **true** or **false**. In extreme cases there are more possible outputs, for example by a range test the error can be **false**, **"min"** and **"max"**. This is important if you want to display an error specific message. For example by **"min"**: **"Too short."**, by **"max"**: **"Too long."**. Btw. you can still use min and max tests instead of a range test.
+
+The Validator gets the schema you gave by the extension of you Model class. I calls the checks on the config params by the instantiation. After that, the tests will be called automatically on every Model attribute. By Model change the tests are called on the changed attribute and the bounded attributes of the changed attributes only. You can give the bounded attributes by the check section with the call of **Validator.prototype.related**, for example by a password verifier input field:
+
+ duplicate:function (duplicateOf, key, attribute) {
+ if (typeof(duplicate) != "string")
+ throw new Error("Invalid config. " + key + ": invalid attribute name given.");
+ this.related(duplicateOf, attribute);
+ //if the model.attributes[duplicateOf] changes, tests of model.attributes[attribute] will run too
+ return duplicateOf;
+ }
+
+The tests are called in the scope of the Runner which runs them. For example:
+
+ duplicate:["required", function (done) {
+ done(this.attributes[this.config] != this.value);
+ }]
+
+The "required" is the dependency of the test. The **this.attributes** are the **model.attributes** merged with the changes. The **this.config** is the output of the **validator.check** call with the config. By the schema in the first example it's "password". The **this.value** is the new value of the attribute, it can be reached by **this.attributes.password2** too. So this test compares the "password" and "password2" attributes, and if they are not the same it returns error.
View
2 package.json
@@ -1,6 +1,6 @@
{
"name":"bb-validation",
- "version":"1.0.2",
+ "version":"1.0.3",
"author":"inf3rno <laszlo.janszky@gmail.com>",
"description":"An extendable validation plugin for Backbone with asynchronous tests and custom messages support.",
"main":"./src/validation.js",
View
30 src/validation.js
@@ -38,7 +38,7 @@ define(function (require, exports, module) {
this.display(this.model.errors, this.model.pending);
return this;
},
- display:function (errors) {
+ display:function (errors, pending) {
}
});
@@ -315,16 +315,14 @@ define(function (require, exports, module) {
Validator.prototype.DependencyResolver = DependencyResolver;
-
- var Plugin = function (params) {
- this.params = params;
- _.extend(this, params);
+ var Plugin = function () {
};
_.extend(Plugin.prototype, {
- View:View,
+ Plugin:Plugin,
Aggregator:Aggregator,
Messenger:Messenger,
Model:AsyncModel,
+ AsyncModel:AsyncModel,
SyncModel:SyncModel,
Validator:Validator,
Runner:Runner,
@@ -352,16 +350,19 @@ define(function (require, exports, module) {
var Branch = function () {
};
Branch.prototype = Object.create(this.constructor.prototype);
- var localValidator = this.Validator.extend({});
+ var Validator = this.Validator.extend({});
+ var AsyncModel = this.Model.extend({
+ Validator:Validator
+ });
+ var SyncModel = this.SyncModel.extend({
+ Validator:Validator
+ });
_.extend(Branch.prototype, {
constructor:Branch,
- Validator:localValidator,
- Model:this.Model.extend({
- Validator:localValidator
- }),
- SyncModel:this.SyncModel.extend({
- Validator:localValidator
- })
+ Validator:Validator,
+ Model:AsyncModel,
+ AsyncModel:AsyncModel,
+ SyncModel:SyncModel
});
var branch = new Branch();
if (config)
@@ -374,6 +375,7 @@ define(function (require, exports, module) {
_.each(config, function (resource) {
this.Validator.customize(resource);
}, this);
+ return this;
}
});

0 comments on commit a0f00d0

Please sign in to comment.
Something went wrong with that request. Please try again.