First pass.
Showing
10 changed files
with
469 additions
and
0 deletions
Gruntfile.js
0 → 100644
1 | /* global module */ | ||
2 | |||
3 | module.exports = function (grunt) { | ||
4 | /* global require */ | ||
5 | 'use strict'; | ||
6 | |||
7 | var config = {}; | ||
8 | config.base = 'src'; | ||
9 | config.jshint = { | ||
10 | options: { | ||
11 | }, | ||
12 | browserOptions: { | ||
13 | }, | ||
14 | }; | ||
15 | config.bower = { | ||
16 | directory: 'lib/bower', | ||
17 | }; | ||
18 | config.jscs = { | ||
19 | options: { | ||
20 | validateIndentation: 4, | ||
21 | reporter: 'console', | ||
22 | maxErrors: -1, | ||
23 | }, | ||
24 | }; | ||
25 | config.jasmine = { | ||
26 | withCoverage: true, | ||
27 | }; | ||
28 | var montyPython = require('grunt-monty-python')(grunt); | ||
29 | montyPython.createConfig(config); | ||
30 | }; |
bower.json
0 → 100644
1 | { | ||
2 | "name": "rivets-bfeach-binder", | ||
3 | "version": "0.0.0", | ||
4 | "authors": [ | ||
5 | "Adam Heath <doogie@brainfood.com>" | ||
6 | ], | ||
7 | "main": [ | ||
8 | "src/scripts/rivets-bfeach-binder.js" | ||
9 | ], | ||
10 | "private": true, | ||
11 | "ignore": [ | ||
12 | "**/.*", | ||
13 | "node_modules", | ||
14 | "src/lib" | ||
15 | ], | ||
16 | "dependencies": { | ||
17 | "backbone": "", | ||
18 | "backbone-validation": "", | ||
19 | "rivets-backbone-adapter-brainfood": "git@gitlab.brainfood.com:brainfood/rivets-backbone-adapter-brainfood.git", | ||
20 | "backbone-model-overlay": "git@gitlab.brainfood.com:brainfood/backbone-model-overlay.git", | ||
21 | "jquery": "", | ||
22 | "requirejs": "", | ||
23 | "rivets": "", | ||
24 | "underscore": "" | ||
25 | } | ||
26 | } |
package.json
0 → 100644
1 | { | ||
2 | "name": "rivets-bfeach-binder", | ||
3 | "version": "0.0.0", | ||
4 | "main": [ | ||
5 | "src/scripts/rivets-bfeach-binder.js" | ||
6 | ], | ||
7 | "dependencies": { | ||
8 | "rivets": "", | ||
9 | "requirejs": "" | ||
10 | }, | ||
11 | "devDependencies": { | ||
12 | "bower-requirejs": "~0.9.2", | ||
13 | "grunt": "~0", | ||
14 | "grunt-monty-python": "git+ssh://git@gitlab.brainfood.com:brainfood/grunt-monty-python.git" | ||
15 | }, | ||
16 | "engines": { | ||
17 | "node": ">=0.8.0" | ||
18 | } | ||
19 | } | ||
20 |
src/scripts/config.js
0 → 100644
1 | /* global require:true */ | ||
2 | var require; | ||
3 | require = (function() { | ||
4 | 'use strict'; | ||
5 | |||
6 | var require = { | ||
7 | baseUrl: 'scripts', | ||
8 | config: {}, | ||
9 | shim: { | ||
10 | rivets: { | ||
11 | deps: ['jquery'], | ||
12 | }, | ||
13 | }, | ||
14 | _map: { | ||
15 | '*': { | ||
16 | 'sightglass': 'sightglass-overlay', | ||
17 | }, | ||
18 | 'sightglass-overlay': { | ||
19 | 'sightglass': 'sightglass', | ||
20 | }, | ||
21 | }, | ||
22 | paths: { | ||
23 | backbone: '../lib/bower/backbone/backbone', | ||
24 | underscore: '../lib/bower/underscore/underscore', | ||
25 | sightglass: '../lib/bower/sightglass/index', | ||
26 | rivets: '../lib/bower/rivets/dist/rivets', | ||
27 | jquery: '../lib/bower/jquery/dist/jquery', | ||
28 | 'rivets-backbone-adapter-brainfood': '../lib/bower/rivets-backbone-adapter-brainfood/src/scripts/rivets-backbone-adapter-brainfood', | ||
29 | 'backbone-model-overlay': '../lib/bower/backbone-model-overlay/src/scripts/backbone-model-overlay', | ||
30 | }, | ||
31 | }; | ||
32 | |||
33 | return require; | ||
34 | })(); |
src/scripts/main.js
0 → 100644
src/scripts/rivets-bfeach-binder.js
0 → 100644
1 | define(function(require) { | ||
2 | 'use strict'; | ||
3 | var Backbone = require('backbone'); | ||
4 | var rivets = require('rivets'); | ||
5 | var BackboneModelOverlay = require('backbone-model-overlay'); | ||
6 | |||
7 | var bfEachUtil = { | ||
8 | getIterator: function getIterator(adapter) { | ||
9 | return adapter.iterate || function iterate(obj, cb) { | ||
10 | obj = obj || []; | ||
11 | var i; | ||
12 | for (i = 0; i < obj.length; i++) { | ||
13 | cb(obj[i], i); | ||
14 | } | ||
15 | }; | ||
16 | }, | ||
17 | }; | ||
18 | (function(rivetsBinders) { | ||
19 | rivetsBinders['bfeach-*'] = { | ||
20 | block: true, | ||
21 | bind: function(el) { | ||
22 | var insertionPoint = this.insertionPoint; | ||
23 | if (this.insertionPoint === undefined) { | ||
24 | var type = this.type, parentNode = el.parentNode; | ||
25 | this.insertionPoint = insertionPoint = document.createComment(' rivets: ' + type + ' '); | ||
26 | el.removeAttribute([this.view.prefix, type].join('-').replace('--', '-')); | ||
27 | this.subItems = []; | ||
28 | parentNode.insertBefore(insertionPoint, el); | ||
29 | parentNode.removeChild(el); | ||
30 | } else { | ||
31 | var i; | ||
32 | var subItems = this.subItems; | ||
33 | for (i = 0; i < subItems.length; i++) { | ||
34 | subItems[i].view.bind(); | ||
35 | } | ||
36 | } | ||
37 | }, | ||
38 | unbind: function(el) { | ||
39 | var i; | ||
40 | var subItems = this.subItems; | ||
41 | for (i = 0; i < subItems.length; i++) { | ||
42 | subItems[i].view.unbind(); | ||
43 | } | ||
44 | }, | ||
45 | routine: function(el, value) { | ||
46 | var parentView = this.view; | ||
47 | var observer = this.observer; | ||
48 | var adapters = observer.options.adapters; | ||
49 | var iterate = bfEachUtil.getIterator(adapters[observer.key.i]); | ||
50 | |||
51 | var rootInterface = observer.tokens.length ? observer.tokens[0].i : observer.key.i; | ||
52 | var modelName = this.args[0]; | ||
53 | var iterationAlias = rivets.iterationAlias(modelName); | ||
54 | var subItems = this.subItems; | ||
55 | var insertionPoint = this.insertionPoint; | ||
56 | var newItemCount = 0; | ||
57 | iterate(value, function(value, index) { | ||
58 | var subItem = subItems[index]; | ||
59 | var subData = {}; | ||
60 | subData[modelName] = value; | ||
61 | subData['index'] = index; | ||
62 | subData[iterationAlias] = index; | ||
63 | if (subItem === undefined) { | ||
64 | var clonedEl = el.cloneNode(true); | ||
65 | subItem = {model: new BackboneModelOverlay(subData, {parent: parentView.models}), el: clonedEl}; | ||
66 | var options = parentView.options(); | ||
67 | options.preloadData = true; | ||
68 | //options.adapters = bfEachUtil.makeOverrideAdapters(options.adapters, subData); | ||
69 | subItem.view = rivets.bind(clonedEl, subItem.model, options); | ||
70 | subItems.push(subItem); | ||
71 | insertionPoint.parentNode.insertBefore(clonedEl, insertionPoint.nextSibling); | ||
72 | } else { | ||
73 | subItem.model.set(subData, {overlay: true}); | ||
74 | } | ||
75 | insertionPoint = subItem.el; | ||
76 | newItemCount++; | ||
77 | }); | ||
78 | while (subItems.length > newItemCount) { | ||
79 | var extraItem = subItems.pop(); | ||
80 | extraItem.view.unbind(); | ||
81 | extraItem.el.parentNode.removeChild(extraItem.el); | ||
82 | } | ||
83 | }, | ||
84 | // update function not needed | ||
85 | }; | ||
86 | })(rivets.binders); | ||
87 | return bfEachUtil; | ||
88 | }); |
src/scripts/rivets-bfeach-binder.spec.js
0 → 100644
1 | define(function(require) { | ||
2 | 'use strict'; | ||
3 | |||
4 | var $ = require('jquery'); | ||
5 | window.jQuery = $; | ||
6 | var RivetsBFEachUtil = require('rivets-bfeach-binder'); | ||
7 | require('rivets-backbone-adapter-brainfood'); | ||
8 | var _ = require('underscore'); | ||
9 | var Backbone = require('backbone'); | ||
10 | var rivets = require('rivets'); | ||
11 | |||
12 | //rivets.config.rootInterface = ':'; | ||
13 | /* global console:false */ | ||
14 | |||
15 | describe('RivetsBFEachUtil', function() { | ||
16 | var rootScope, rootNode; | ||
17 | beforeEach(function() { | ||
18 | rootScope = new Backbone.Model({ | ||
19 | sub: new Backbone.Model({ | ||
20 | subKey: 'sub-key', | ||
21 | list: new Backbone.Collection([ | ||
22 | {key: 'A'}, | ||
23 | {key: 'B'}, | ||
24 | {key: 'C'}, | ||
25 | ], {parse: true}), | ||
26 | }), | ||
27 | list: new Backbone.Collection([ | ||
28 | {key: 'A'}, | ||
29 | {key: 'B'}, | ||
30 | {key: 'C'}, | ||
31 | ], {parse: true}), | ||
32 | constant: 'this-is-a-constant', | ||
33 | }); | ||
34 | rootNode = $($.parseHTML('<div>{:constant}<div rv-bfeach-item=":list">key:{:item:key} constant:{:constant}<ul><li rv-bfeach-subitem=":sub:list">sub-key:{:subitem:key} constant:{:constant}<input rv-value=":inputValue" /></li></ul></div></div>')); | ||
35 | }); | ||
36 | it('returns defined', function() { | ||
37 | expect(RivetsBFEachUtil).toBeDefined(); | ||
38 | }); | ||
39 | describe('binder', function() { | ||
40 | var rivetsView; | ||
41 | beforeEach(function() { | ||
42 | rivetsView = rivets.bind(rootNode, rootScope); | ||
43 | }); | ||
44 | it('first-pass', function() { | ||
45 | var subListHtml = '<ul><!-- rivets: bfeach-subitem --><li>sub-key:A constant:this-is-a-constant<input rv-value=":inputValue"></li><li>sub-key:B constant:this-is-a-constant<input rv-value=":inputValue"></li><li>sub-key:C constant:this-is-a-constant<input rv-value=":inputValue"></li></ul>'; | ||
46 | expect(rootNode.html()).toEqual( | ||
47 | 'this-is-a-constant' + | ||
48 | '<!-- rivets: bfeach-item -->' + | ||
49 | '<div>key:A constant:this-is-a-constant' + subListHtml + '</div>' + | ||
50 | '<div>key:B constant:this-is-a-constant' + subListHtml + '</div>' + | ||
51 | '<div>key:C constant:this-is-a-constant' + subListHtml + '</div>' | ||
52 | ); | ||
53 | }); | ||
54 | describe('add-insert', function() { | ||
55 | beforeEach(function() { | ||
56 | rootScope.get('list').add({key: 'D'}, {at: -1, parse: true}); | ||
57 | rootScope.get('list').add({key: 'preA'}, {at: 0, parse: true}); | ||
58 | }); | ||
59 | it('test', function() { | ||
60 | var subListHtml = '<ul><!-- rivets: bfeach-subitem --><li>sub-key:A constant:this-is-a-constant<input rv-value=":inputValue"></li><li>sub-key:B constant:this-is-a-constant<input rv-value=":inputValue"></li><li>sub-key:C constant:this-is-a-constant<input rv-value=":inputValue"></li></ul>'; | ||
61 | expect(rootNode.html()).toEqual( | ||
62 | 'this-is-a-constant' + | ||
63 | '<!-- rivets: bfeach-item -->' + | ||
64 | '<div>key:preA constant:this-is-a-constant' + subListHtml + '</div>' + | ||
65 | '<div>key:A constant:this-is-a-constant' + subListHtml + '</div>' + | ||
66 | '<div>key:B constant:this-is-a-constant' + subListHtml + '</div>' + | ||
67 | '<div>key:C constant:this-is-a-constant' + subListHtml + '</div>' + | ||
68 | '<div>key:D constant:this-is-a-constant' + subListHtml + '</div>' | ||
69 | ); | ||
70 | }); | ||
71 | describe('update-constant', function() { | ||
72 | var subListHtml = '<ul><!-- rivets: bfeach-subitem --><li>sub-key:A constant:updated-constant<input rv-value=":inputValue"></li><li>sub-key:B constant:updated-constant<input rv-value=":inputValue"></li><li>sub-key:C constant:updated-constant<input rv-value=":inputValue"></li></ul>'; | ||
73 | var wantedHtml = | ||
74 | 'updated-constant' + | ||
75 | '<!-- rivets: bfeach-item -->' + | ||
76 | '<div>key:preA constant:updated-constant' + subListHtml + '</div>' + | ||
77 | '<div>key:A constant:updated-constant' + subListHtml + '</div>' + | ||
78 | '<div>key:B constant:updated-constant' + subListHtml + '</div>' + | ||
79 | '<div>key:C constant:updated-constant' + subListHtml + '</div>' + | ||
80 | '<div>key:D constant:updated-constant' + subListHtml + '</div>'; | ||
81 | beforeEach(function() { | ||
82 | rootScope.set('constant', 'updated-constant'); | ||
83 | }); | ||
84 | it('test', function() { | ||
85 | expect(rootNode.html()).toEqual(wantedHtml); | ||
86 | }); | ||
87 | describe('unbind+change', function() { | ||
88 | beforeEach(function() { | ||
89 | rivetsView.unbind(); | ||
90 | rootScope.set('constant', 'changed-constant'); | ||
91 | }); | ||
92 | it('test', function() { | ||
93 | expect(rootNode.html()).toEqual(wantedHtml); | ||
94 | }); | ||
95 | describe('bind', function() { | ||
96 | beforeEach(function() { | ||
97 | rivetsView.bind(); | ||
98 | }); | ||
99 | it('test', function() { | ||
100 | expect(rootNode.html()).toEqual(wantedHtml.replace(/updated-constant/g, 'changed-constant')); | ||
101 | }); | ||
102 | describe('shift', function() { | ||
103 | beforeEach(function() { | ||
104 | rootScope.get('sub').get('list').shift(); | ||
105 | }); | ||
106 | it('test', function() { | ||
107 | expect(rootNode.html()).toEqual(wantedHtml.replace(/updated-constant/g, 'changed-constant').replace(/<li>sub-key:A constant:changed-constant<input rv-value=":inputValue"><\/li>/g, '')); | ||
108 | expect(rootScope.get('inputValue')).toBeUndefined(); | ||
109 | }); | ||
110 | describe('input-set-value', function() { | ||
111 | beforeEach(function() { | ||
112 | var lastInput = rootNode.find('input:last'); | ||
113 | lastInput.trigger('focus'); | ||
114 | lastInput[0].value = 'typed'; | ||
115 | lastInput.trigger('input'); | ||
116 | lastInput.trigger('change').trigger('blur'); | ||
117 | }); | ||
118 | it('test', function() { | ||
119 | expect(rootScope.get('inputValue')).toEqual('typed'); | ||
120 | var inputElementValues = []; | ||
121 | rootNode.find('input').each(function(i) { | ||
122 | inputElementValues.push(this.value); | ||
123 | }); | ||
124 | expect(inputElementValues).toEqual([ | ||
125 | 'typed', 'typed', // preA | ||
126 | 'typed', 'typed', // A | ||
127 | 'typed', 'typed', // B | ||
128 | 'typed', 'typed', // C | ||
129 | 'typed', 'typed', // D | ||
130 | ]); | ||
131 | }); | ||
132 | }); | ||
133 | }); | ||
134 | }); | ||
135 | }); | ||
136 | }); | ||
137 | }); | ||
138 | }); | ||
139 | }); | ||
140 | }); |
src/scripts/sightglass-overlay.js
0 → 100644
1 | define(function(require) { | ||
2 | 'use strict'; | ||
3 | var Sightglass = require('sightglass'); | ||
4 | |||
5 | function callIt(container, methodName, args) { | ||
6 | return container[methodName].apply(container, args); | ||
7 | } | ||
8 | var tokenStack; | ||
9 | function sightglass() { | ||
10 | tokenStack = []; | ||
11 | try { | ||
12 | return Sightglass.apply(this, arguments); | ||
13 | } finally { | ||
14 | tokenStack = []; | ||
15 | } | ||
16 | }; | ||
17 | sightglass.makeOverrideAdapters = function makeOverrideAdapters(parentAdapters, roData) { | ||
18 | function getStackPath() { | ||
19 | var currentStackPath = ''; | ||
20 | _.each(tokenStack, function(token) { | ||
21 | currentStackPath += token.i + token.path; | ||
22 | }); | ||
23 | return currentStackPath; | ||
24 | } | ||
25 | var subAdapters = {}; | ||
26 | _.each(parentAdapters, function(adapterFunctions, interfaceKey) { | ||
27 | var subAdapter = {}; | ||
28 | _.each(['observe', 'unobserve', 'get', 'set'], function wrapApiMethod(methodName) { | ||
29 | subAdapter[methodName] = function(obj, key, cbOrValue) { | ||
30 | var stackPath = getStackPath(); | ||
31 | var fullKeyPath = stackPath + interfaceKey + key; | ||
32 | if (fullKeyPath in roData) { | ||
33 | return callIt(parentAdapters['.'], methodName, [roData, fullKeyPath, cbOrValue]); | ||
34 | } else if (stackPath in roData) { | ||
35 | obj = roData[stackPath]; | ||
36 | } | ||
37 | try { | ||
38 | return callIt(adapterFunctions, methodName, [obj, key, cbOrValue]); | ||
39 | } finally { | ||
40 | tokenStack.push({i: interfaceKey, path: key}); | ||
41 | } | ||
42 | }; | ||
43 | }); | ||
44 | if (adapterFunctions.iterate) { | ||
45 | subAdapter.iterate = adapterFunctions.iterate; | ||
46 | } | ||
47 | subAdapters[interfaceKey] = subAdapter; | ||
48 | }); | ||
49 | return subAdapters; | ||
50 | }; | ||
51 | |||
52 | return sightglass; | ||
53 | }); |
src/scripts/sightglass-overlay.spec.js
0 → 100644
1 | define(function(require) { | ||
2 | 'use strict'; | ||
3 | |||
4 | var $ = require('jquery'); | ||
5 | window.jQuery = $; | ||
6 | var SightglassOverlay = require('sightglass-overlay'); | ||
7 | require('rivets-backbone-adapter-brainfood'); | ||
8 | var _ = require('underscore'); | ||
9 | var Backbone = require('backbone'); | ||
10 | var rivets = require('rivets'); | ||
11 | |||
12 | //rivets.config.rootInterface = ':'; | ||
13 | /* global console:false */ | ||
14 | |||
15 | describe('SightglassOverlay', function() { | ||
16 | var rootScope, rootNode; | ||
17 | beforeEach(function() { | ||
18 | rootScope = new Backbone.Model({ | ||
19 | sub: new Backbone.Model({ | ||
20 | subKey: 'sub-key', | ||
21 | list: new Backbone.Collection([ | ||
22 | {key: 'A'}, | ||
23 | {key: 'B'}, | ||
24 | {key: 'C'}, | ||
25 | ], {parse: true}), | ||
26 | }), | ||
27 | list: new Backbone.Collection([ | ||
28 | {key: 'A'}, | ||
29 | {key: 'B'}, | ||
30 | {key: 'C'}, | ||
31 | ], {parse: true}), | ||
32 | constant: 'this-is-a-constant', | ||
33 | }); | ||
34 | rootNode = $($.parseHTML('<div>{:constant}<div rv-bfeach-item=":list">key:{:item:key} constant:{:constant}<ul><li rv-bfeach-subitem=":sub:list">sub-key:{:subitem:key} constant:{:constant}<input rv-value=":inputValue" /></li></ul></div></div>')); | ||
35 | }); | ||
36 | it('returns defined', function() { | ||
37 | expect(SightglassOverlay).toBeDefined(); | ||
38 | }); | ||
39 | describe('makeOverrideAdapters', function() { | ||
40 | var newAdapters, roData, rivetsView; | ||
41 | beforeEach(function() { | ||
42 | roData = {}; | ||
43 | roData[':sub:list'] = new Backbone.Collection([ | ||
44 | {key: 'sub1'}, | ||
45 | {key: 'sub2'}, | ||
46 | {key: 'sub3'}, | ||
47 | ], {parse: true}); | ||
48 | roData[':list'] = new Backbone.Collection([ | ||
49 | {key: '1'}, | ||
50 | {key: '2'}, | ||
51 | {key: '3'}, | ||
52 | ], {parse: true}); | ||
53 | newAdapters = SightglassOverlay.makeOverrideAdapters(rivets.adapters, roData); | ||
54 | rivetsView = rivets.bind(rootNode, rootScope, {adapters: newAdapters}); | ||
55 | }); | ||
56 | xit('test', function() { | ||
57 | var subListHtml = '<ul><!-- rivets: bfeach-subitem --><li>sub-key:sub1 constant:this-is-a-constant<input rv-value=":inputValue"></li><li>sub-key:sub2 constant:this-is-a-constant<input rv-value=":inputValue"></li><li>sub-key:sub3 constant:this-is-a-constant<input rv-value=":inputValue"></li></ul>'; | ||
58 | expect(rootNode.html()).toEqual( | ||
59 | 'this-is-a-constant' + | ||
60 | '<!-- rivets: bfeach-item -->' + | ||
61 | '<div>key:1 constant:this-is-a-constant' + subListHtml + '</div>' + | ||
62 | '<div>key:2 constant:this-is-a-constant' + subListHtml + '</div>' + | ||
63 | '<div>key:3 constant:this-is-a-constant' + subListHtml + '</div>' | ||
64 | ); | ||
65 | }); | ||
66 | }); | ||
67 | }); | ||
68 | }); |
-
Please register or sign in to post a comment