700cfddc by Ean Schuessler

Merge branch 'BF-2656' of /home/git/repositories/brainfood/rivets-error-binder

2 parents 5a5b030e a1ae100f
1 {
2 "directory": "src/lib"
3 }
1 .*.swp
2 .tmp/
3 bin/coverage/
4 dist/
5 node_modules/
6 src/lib/
7 .grunt/
8 _SpecRunner.html
This diff is collapsed. Click to expand it.
1 {
2 "name": "rivets-error-binder",
3 "version": "0.0.0",
4 "authors": [
5 "Adam Heath <doogie@brainfood.com>"
6 ],
7 "main": [
8 "src/scripts/rivets-error-binder.js"
9 ],
10 "private": true,
11 "ignore": [
12 "**/.*",
13 "node_modules",
14 "src/lib",
15 "test"
16 ],
17 "dependencies": {
18 "backbone-seen": "git@gitlab.brainfood.com:brainfood/backbone-seen.git",
19 "backbone": "~1.1.0",
20 "backbone-validation": "0.9.1",
21 "jquery": "~1.10.2",
22 "requirejs": "~2.1.10",
23 "rivets": "~0.6.6",
24 "underscore": "~1.6.0"
25 },
26 "devDependencies": {
27 "rivets-backbone-adapter": "~1.1.1"
28 }
29 }
1 {
2 "name": "rivets-error-binder",
3 "version": "0.0.0",
4 "main": [
5 "src/scripts/rivets-error-binder.js"
6 ],
7 "dependencies": {
8 "rivets": "~0.6.6",
9 "requirejs": "~2.1.10"
10 },
11 "devDependencies": {
12 "bower-requirejs": "~0.9.2",
13 "grunt": "~0.4.1",
14 "grunt-contrib-copy": "~0.4.1",
15 "grunt-contrib-concat": "~0.3.0",
16 "grunt-contrib-uglify": "~0.2.0",
17 "grunt-contrib-jshint": "~0.7.0",
18 "grunt-contrib-cssmin": "~0.7.0",
19 "grunt-contrib-connect": "~0.5.0",
20 "grunt-contrib-clean": "~0.5.0",
21 "grunt-contrib-htmlmin": "~0.1.3",
22 "grunt-bower-install": "~0.7.0",
23 "grunt-contrib-imagemin": "~0.2.0",
24 "grunt-contrib-watch": "~0.5.2",
25 "grunt-rev": "~0.1.0",
26 "grunt-autoprefixer": "~0.5.0",
27 "grunt-usemin": "~0.1.10",
28 "grunt-mocha": "~0.4.0",
29 "grunt-newer": "~0.6.0",
30 "grunt-svgmin": "~0.2.0",
31 "grunt-concurrent": "~0.4.0",
32 "load-grunt-tasks": "~0.2.0",
33 "time-grunt": "~0.2.0",
34 "jshint-stylish": "~0.1.3",
35 "grunt-contrib-requirejs": "~0.4.0",
36 "grunt-bower-requirejs": "~0.8.4",
37 "grunt-template-jasmine-istanbul": "~0.2.6",
38 "grunt-template-jasmine-requirejs": "~0.1.10",
39 "grunt-contrib-jasmine": "~0.5.3"
40 },
41 "engines": {
42 "node": ">=0.8.0"
43 }
44 }
45
1 define(['rivets', 'bootstrap'], function(rivets) {
2 var rivetsBinderCall = function(binding, binderName, methodName, args) {
3 var binder = rivets.binders[binderName];
4 if (binder instanceof Function) {
5 if (methodName == 'routine') {
6 binder.apply(binding, args);
7 };
8 } else if (binder) {
9 if (binder[methodName]) {
10 binder[methodName].apply(binding, args);
11 }
12 }
13 }
14
15 var diveIntoObject = function(obj, keypath, callback) {
16 if (!keypath) {
17 return callback(obj, null);
18 }
19 //return callback(obj, keypath);
20 var keyparts = keypath.replace(/^:/, '').split(/\:/);
21 //console.log('diveIntoObject(keyparts):', obj, keyparts);
22 while (keyparts.length > 1) {
23 var part = keyparts.shift();
24 if (part.length == 0) {
25 continue;
26 }
27 //console.log('diveIntoObject:', obj, part);
28 obj = doObjectRead(obj, part);
29 }
30 //console.log('callback:', obj, keyparts[0]);
31 return callback(obj, keyparts.shift());
32 };
33
34 var doObjectRead = function(obj, id) {
35 if (obj === null) return obj;
36 if (!id) return obj;
37 //console.log('doObjectRead:', obj, id, obj instanceof Backbone.Model, obj instanceof Backbone.Collection);
38 if (obj instanceof Backbone.Model) {
39 return obj.get(id);
40 } else if (obj instanceof Backbone.Collection) {
41 return obj.at(id);
42 } else if (obj != null) {
43 return obj[id];
44 }
45 };
46
47 rivets.binders['error-*'] = {
48 bind: function(el) {
49 var self = this;
50 var holder = this.validationHolder = {
51 //marker: el.parentNode.insertBefore(document.createComment(" rivets: " + this.type + " "), el),
52 focus: function() {
53 $(holder.container).removeClass('focused');
54 },
55 blur: function() {
56 if (holder.lastObj) holder.lastObj.seen(holder.lastId, true);
57 $(holder.container).addClass('focused');
58 if (holder.lastObj) holder.lastObj.validate();
59 },
60 validated: function(isValid, model, errors) {
61 var errorList = errors[holder.lastId];
62 if (errorList && holder.lastObj.seen(holder.lastId)) {
63 $(el).tooltip({title: errorList, trigger: 'focus'});
64 $(el).tooltip('show');
65 $(el).parent().addClass('has-error');
66 } else {
67 $(el).tooltip('destroy');
68 $(el).parent().removeClass('has-error');
69 }
70 }
71 };
72 $(el).on('focus', holder.focus).on('blur', holder.blur);
73 rivetsBinderCall(this, this.args[0], 'bind', arguments);
74 },
75 unbind: function(el) {
76 var holder = this.validationHolder;
77 $(this.validationHolder.marker).after(el).remove();
78 $(el).off('focus', holder.focus).off('blur', holder.blur);
79 diveIntoObject(this.model, this.keypath, function(obj, id) {
80 obj.off('validated', holder.validated);
81 });
82 delete this.validationHolder;
83 rivetsBinderCall(this, this.args[0], 'unbind', arguments);
84 },
85 routine: function(el, value) {
86 var holder = this.validationHolder;
87 if (holder.lastObj) {
88 holder.lastObj.off('validated', holder.validated);
89 }
90 diveIntoObject(this.observer.target, this.observer.key.path, function(obj, id) {
91 holder.lastObj = obj;
92 holder.lastId = id;
93 obj.on('validated', holder.validated);
94 });
95 rivetsBinderCall(this, this.args[0], 'routine', arguments);
96 }
97 };
98 });
1 define(['backbone'], function(Backbone) {
2 'use strict';
3 return Backbone;
4 });
1 /* global require:true */
2 var require;
3 require = (function() {
4 'use strict';
5
6 var require = {
7 baseUrl: 'scripts',
8 config: {
9 'rivets-error-binder': {}
10 },
11 shim: {
12 bootstrap: {
13 deps: [
14 'jquery'
15 ]
16 },
17 rivets: {
18 deps: [
19 'jquery'
20 ]
21 }
22 },
23 paths: {
24 'backbone-validation': '../lib/backbone-validation/dist/backbone-validation-amd',
25 backbone: '../lib/backbone/backbone',
26 underscore: '../lib/underscore/underscore',
27 rivets: '../lib/rivets/dist/rivets',
28 bootstrap: '../lib/bootstrap/dist/js/bootstrap',
29 jquery: '../lib/jquery/dist/jquery',
30 'rivets-backbone-adapter': '../lib/rivets-backbone-adapter/rivets-backbone',
31 'backbone-seen': '../lib/backbone-seen/src/scripts/backbone-seen'
32 }
33 };
34
35 return require;
36 })();
1 /* global require */
2 require(
3 [],
4 function() {
5 'use strict';
6 }
7 );
1 define([
2 'module',
3 'rivets',
4 ], function(
5 module,
6 rivets
7 ) {
8 'use strict';
9 var rivetsBinderCall = function(binding, binderName, methodName, args) {
10 var binder = rivets.binders[binderName];
11 binder[methodName].apply(binding, args);
12 };
13
14 var render = function() {
15 var renderImpl = module.config().render;
16 if (renderImpl) {
17 return renderImpl.apply(this, arguments);
18 }
19 };
20
21 rivets.binders['error-*'] = {
22 bind: function(el) {
23 var holder = this.validationHolder = {
24 //marker: el.parentNode.insertBefore(document.createComment(" rivets: " + this.type + " "), el),
25 focus: function() {
26 render(el, 'focus', false);
27 },
28 blur: function() {
29 if (holder.observer && holder.observer.target) {
30 holder.observer.target.seen(holder.observer.key.path, true);
31 }
32 render(el, 'blur', false);
33 if (holder.observer && holder.observer.target) {
34 holder.observer.target.validate();
35 }
36 },
37 validated: function(isValid, model, errors) {
38 var errorList = errors[holder.observer.key.path];
39 if (errorList && holder.observer.target && holder.observer.target.seen(holder.observer.key.path)) {
40 render(el, 'validated', errorList);
41 } else {
42 render(el, 'validated', false);
43 }
44 }
45 };
46 $(el).on('focus', holder.focus).on('blur', holder.blur);
47 rivetsBinderCall(this, this.args[0], 'bind', arguments);
48 },
49 unbind: function(el) {
50 var holder = this.validationHolder;
51 $(this.validationHolder.marker).after(el).remove();
52 $(el).off('focus', holder.focus).off('blur', holder.blur);
53 if (holder.observer.target) {
54 holder.observer.target.off('validated', holder.validated);
55 }
56 delete this.validationHolder;
57 rivetsBinderCall(this, this.args[0], 'unbind', arguments);
58 },
59 routine: function() {
60 var holder = this.validationHolder;
61 if (holder.observer) {
62 holder.observer.target.off('validated', holder.validated);
63 }
64 holder.observer = this.observer;
65 if (this.observer.target) {
66 this.observer.target.on('validated', holder.validated);
67 }
68 rivetsBinderCall(this, this.args[0], 'routine', arguments);
69 }
70 };
71 });
1 define(function(require) {
2 'use strict';
3
4 var $ = require('jquery');
5 window.jQuery = $;
6 var RivetsErrorBinder = require('rivets-error-binder');
7 var _ = require('underscore');
8 var Backbone = require('backbone');
9 var rivets = require('rivets');
10 require('backbone-validation');
11 var BackboneSeen = require('backbone-seen');
12 require('rivets-backbone-adapter');
13 _.extend(Backbone.Model.prototype, Backbone.Validation.mixin);
14 //rivets.config.rootInterface = ':';
15
16 describe('RivetsErrorBinder', function() {
17 it('exists', function() {
18 expect(RivetsErrorBinder).toBeUndefined();
19 });
20 });
21 describe('RivetsErrorBinder', function() {
22 var Model, Collection;
23 var scope;
24 var test;
25 var view;
26 var render;
27 beforeEach(function() {
28 render = function(el, cmd, errorList) {
29 render.lastCmd = cmd;
30 render.lastErrorList = errorList;
31 switch (cmd) {
32 case 'focus':
33 render.counts.focus++;
34 break;
35 case 'blur':
36 render.counts.blur++;
37 break;
38 case 'validated':
39 if (errorList) {
40 render.counts.validatedError++;
41 } else {
42 render.counts.validatedClean++;
43 }
44 break;
45 }
46 };
47 render.counts = {focus: 0, blur: 0, validatedError: 0, validatedClean: 0};
48
49 jasmine.Clock.useMock();
50 Model = BackboneSeen.mixin(Backbone.Model.extend());
51 Collection = Backbone.Collection.extend({model: Model});
52
53 scope = new Model({
54 test: test = new Model({
55 constant: 'CONSTANT'
56 })
57 });
58 test.validation = {
59 constant: {required: true}
60 };
61 });
62 afterEach(function() {
63 view.unbind();
64 });
65
66
67 it('existing-no-render', function() {
68 var $node = $($.parseHTML('<div><form><input id="_1" rv-error-value="test:constant" /></form></div>'));
69 $(document).find('body').append($node[0]);
70 var $1 = $node.find('#_1');
71 expect($1.length).toEqual(1);
72 expect($1.val()).toEqual('');
73 expect(render.counts).toEqual({focus : 0, blur : 0, validatedError : 0, validatedClean : 0});
74 view = rivets.bind($node, scope.attributes);
75 expect(view).toBeTruthy();
76 expect($1.val()).toEqual('CONSTANT');
77 //expect($node.html()).toBe('');
78 test.set('constant', 'one');
79 expect($1.val()).toEqual('one');
80 test.set('constant', '');
81 expect($1.val()).toEqual('');
82 test.set('constant', 'CONSTANT');
83 expect($1.val()).toEqual('CONSTANT');
84
85 expect(render.counts).toEqual({focus : 0, blur : 0, validatedError : 0, validatedClean : 0});
86 $1.focus().val('one').change().blur();
87 jasmine.Clock.tick(1);
88 expect(test.get('constant')).toEqual('one');
89 expect(render.counts).toEqual({focus : 0, blur : 0, validatedError : 0, validatedClean : 0});
90
91 $1.focus().val('').change().blur();
92 jasmine.Clock.tick(1);
93 expect(test.get('constant')).toEqual('');
94 expect(render.counts).toEqual({focus : 0, blur : 0, validatedError : 0, validatedClean : 0});
95 jasmine.Clock.tick(1);
96 expect(render.lastErrorList).toBeUndefined();
97 });
98 it('existing-with-render', function() {
99 /* global requirejs */
100 requirejs.config({config: {'rivets-error-binder': { render: render } } });
101 var $node = $($.parseHTML('<div><form><input id="_1" rv-error-value="test:constant" /></form></div>'));
102 $(document).find('body').append($node[0]);
103 var $1 = $node.find('#_1');
104 expect($1.length).toEqual(1);
105 expect($1.val()).toEqual('');
106 expect(render.counts).toEqual({focus : 0, blur : 0, validatedError : 0, validatedClean : 0});
107 view = rivets.bind($node, scope.attributes);
108 expect(view).toBeTruthy();
109 expect($1.val()).toEqual('CONSTANT');
110 //expect($node.html()).toBe('');
111 test.set('constant', 'one');
112 expect($1.val()).toEqual('one');
113 test.set('constant', '');
114 expect($1.val()).toEqual('');
115 test.set('constant', 'CONSTANT');
116 expect($1.val()).toEqual('CONSTANT');
117
118 expect(render.counts).toEqual({focus : 0, blur : 0, validatedError : 0, validatedClean : 0});
119 $1.focus().val('one').change().blur();
120 jasmine.Clock.tick(1);
121 expect(test.get('constant')).toEqual('one');
122 expect(render.counts).toEqual({focus : 1, blur : 1, validatedError : 0, validatedClean : 1});
123
124 $1.focus().val('').change().blur();
125 jasmine.Clock.tick(1);
126 expect(test.get('constant')).toEqual('');
127 expect(render.counts).toEqual({focus : 2, blur : 2, validatedError : 1, validatedClean : 1});
128 jasmine.Clock.tick(1);
129 expect(render.lastErrorList).toEqual(jasmine.any(String));
130 });
131 it('missing', function() {
132 var $node = $($.parseHTML('<div><form><input id="_1" rv-error-value="test:missing:child" /></form></div>'));
133 $(document).find('body').append($node[0]);
134 var $1 = $node.find('#_1');
135 expect($1.length).toEqual(1);
136 expect($1.val()).toEqual('');
137 view = rivets.bind($node, scope.attributes);
138 expect(view).toBeTruthy();
139 expect($1.val()).toEqual('');
140
141 $1.focus().val('one').change().blur();
142 jasmine.Clock.tick(1);
143 });
144 });
145 });