sprockets-jsrender 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README +22 -0
- data/Rakefile +1 -0
- data/app/assets/javascripts/jquery.observable.js +174 -0
- data/app/assets/javascripts/jquery.views.js +802 -0
- data/app/assets/javascripts/jsrender.js +847 -0
- data/lib/sprockets-jsrender.rb +3 -0
- data/lib/sprockets/jsrender/engine.rb +6 -0
- data/lib/sprockets/jsrender/jsrender_processor.rb +31 -0
- data/lib/sprockets/jsrender/version.rb +5 -0
- data/sprockets-jsrender.gemspec +22 -0
- metadata +16 -3
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.3@sprockets-jsrender --create
|
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Sprockets jsRender/jsViews
|
2
|
+
==========================
|
3
|
+
|
4
|
+
This gem adds jsRender/jsViews templates as tilt templates for Sprockets 2 in Rails 3.1.
|
5
|
+
|
6
|
+
Thanks
|
7
|
+
======
|
8
|
+
Inpired by sprockets-jquery-tmpl by Ryan Dy (https://github.com/rdy/sprockets-jquery-tmpl)
|
9
|
+
jsrender and jsviews are created bu Boris Moore (https://github.com/BorisMoore/jsrender)
|
10
|
+
|
11
|
+
Installing
|
12
|
+
==========
|
13
|
+
|
14
|
+
1. Add the gem to bundler or install: `gem install sprockets-jsrender`
|
15
|
+
2. Add to your app/assets/javascripts/application.js the following lines
|
16
|
+
//= require jsrender
|
17
|
+
//= require jquery.observable
|
18
|
+
//= require jquery.views
|
19
|
+
|
20
|
+
Any files in assets/javascripts/jsrender will be automatically added to list of templates using the path from that directory (i.e. assets/javascripts/jsrender/examples/index.jsr will be mapped to $.render("examples/index") )
|
21
|
+
|
22
|
+
Copyright (c) 2012 Enrico Rubboli, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,174 @@
|
|
1
|
+
/*! jsObservable: http://github.com/BorisMoore/jsviews */
|
2
|
+
/*
|
3
|
+
* Subcomponent of JsViews
|
4
|
+
* Data change events for data-linking
|
5
|
+
*
|
6
|
+
* Copyright 2012, Boris Moore and Brad Olenick
|
7
|
+
* Released under the MIT License.
|
8
|
+
*/
|
9
|
+
(function ( $, undefined ) {
|
10
|
+
$.observable = function( data, options ) {
|
11
|
+
return $.isArray( data )
|
12
|
+
? new ArrayObservable( data )
|
13
|
+
: new ObjectObservable( data );
|
14
|
+
};
|
15
|
+
|
16
|
+
var splice = [].splice;
|
17
|
+
|
18
|
+
function ObjectObservable( data ) {
|
19
|
+
if ( !this.data ) {
|
20
|
+
return new ObjectObservable( data );
|
21
|
+
}
|
22
|
+
|
23
|
+
this._data = data;
|
24
|
+
return this;
|
25
|
+
};
|
26
|
+
|
27
|
+
$.observable.Object = ObjectObservable;
|
28
|
+
|
29
|
+
ObjectObservable.prototype = {
|
30
|
+
_data: null,
|
31
|
+
|
32
|
+
data: function() {
|
33
|
+
return this._data;
|
34
|
+
},
|
35
|
+
|
36
|
+
setProperty: function( path, value ) { // TODO in the case of multiple changes (object): raise single propertyChanges event (which may span different objects, via paths) with set of changes.
|
37
|
+
if ( $.isArray( path ) ) {
|
38
|
+
// This is the array format generated by serializeArray. However, this has the problem that it coerces types to string,
|
39
|
+
// and does not provide simple support of convertTo and convertFrom functions.
|
40
|
+
// TODO: We've discussed an "objectchange" event to capture all N property updates here. See TODO note above about propertyChanges.
|
41
|
+
for ( var i = 0, l = path.length; i < l; i++ ) {
|
42
|
+
var pair = path[i];
|
43
|
+
this.setProperty( pair.name, pair.value );
|
44
|
+
}
|
45
|
+
} else
|
46
|
+
if ( typeof( path ) === "object" ) {
|
47
|
+
// Object representation where property name is path and property value is value.
|
48
|
+
// TODO: We've discussed an "objectchange" event to capture all N property updates here. See TODO note above about propertyChanges.
|
49
|
+
for ( var key in path ) {
|
50
|
+
this.setProperty( key, path[ key ]);
|
51
|
+
}
|
52
|
+
} else {
|
53
|
+
// Simple single property case.
|
54
|
+
var setter, property,
|
55
|
+
object = this._data,
|
56
|
+
leaf = getLeafObject( object, path );
|
57
|
+
|
58
|
+
path = leaf[1];
|
59
|
+
leaf = leaf[0];
|
60
|
+
if ( leaf ) {
|
61
|
+
property = leaf[ path ];
|
62
|
+
if ( $.isFunction( property )) {
|
63
|
+
// Case of property setter/getter - with convention that property() is getter and property( value ) is setter
|
64
|
+
setter = property;
|
65
|
+
property = property.call( leaf ); //get
|
66
|
+
}
|
67
|
+
if ( property != value ) { // test for non-strict equality, since serializeArray, and form-based editors can map numbers to strings, etc.
|
68
|
+
if ( setter ) {
|
69
|
+
setter.call( leaf, value ); //set
|
70
|
+
value = setter.call( leaf ); //get updated value
|
71
|
+
} else {
|
72
|
+
leaf[ path ] = value;
|
73
|
+
}
|
74
|
+
$( leaf ).triggerHandler( "propertyChange", { path: path, value: value, oldValue: property });
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
return this;
|
79
|
+
}
|
80
|
+
};
|
81
|
+
|
82
|
+
function getLeafObject( object, path ) {
|
83
|
+
if ( object && path ) {
|
84
|
+
var parts = path.split(".");
|
85
|
+
|
86
|
+
path = parts.pop();
|
87
|
+
while ( object && parts.length ) {
|
88
|
+
object = object[ parts.shift() ];
|
89
|
+
}
|
90
|
+
return [ object, path ];
|
91
|
+
}
|
92
|
+
return [];
|
93
|
+
};
|
94
|
+
|
95
|
+
function ArrayObservable( data ) {
|
96
|
+
if ( !this.data ) {
|
97
|
+
return new ArrayObservable( data );
|
98
|
+
}
|
99
|
+
|
100
|
+
this._data = data;
|
101
|
+
return this;
|
102
|
+
};
|
103
|
+
|
104
|
+
function triggerArrayEvent( array, eventArgs ) {
|
105
|
+
$([ array ]).triggerHandler( "arrayChange", eventArgs );
|
106
|
+
};
|
107
|
+
|
108
|
+
function validateIndex( index ) {
|
109
|
+
if ( typeof index !== "number" ) {
|
110
|
+
throw "Invalid index.";
|
111
|
+
}
|
112
|
+
};
|
113
|
+
|
114
|
+
$.observable.Array = ArrayObservable;
|
115
|
+
|
116
|
+
ArrayObservable.prototype = {
|
117
|
+
_data: null,
|
118
|
+
|
119
|
+
data: function() {
|
120
|
+
return this._data;
|
121
|
+
},
|
122
|
+
|
123
|
+
insert: function( index, data ) {
|
124
|
+
validateIndex( index );
|
125
|
+
|
126
|
+
if ( arguments.length > 1 ) {
|
127
|
+
data = $.isArray( data ) ? data : [ data ]; // TODO: Clone array here?
|
128
|
+
// data can be a single item (including a null/undefined value) or an array of items.
|
129
|
+
|
130
|
+
if ( data.length > 0 ) {
|
131
|
+
splice.apply( this._data, [ index, 0 ].concat( data ));
|
132
|
+
triggerArrayEvent( this._data, { change: "insert", index: index, items: data });
|
133
|
+
}
|
134
|
+
}
|
135
|
+
return this;
|
136
|
+
},
|
137
|
+
|
138
|
+
remove: function( index, numToRemove ) {
|
139
|
+
validateIndex( index );
|
140
|
+
|
141
|
+
numToRemove = ( numToRemove === undefined || numToRemove === null ) ? 1 : numToRemove;
|
142
|
+
if ( numToRemove && index > -1 ) {
|
143
|
+
var items = this._data.slice( index, index + numToRemove );
|
144
|
+
numToRemove = items.length;
|
145
|
+
if ( numToRemove ) {
|
146
|
+
this._data.splice( index, numToRemove );
|
147
|
+
triggerArrayEvent( this._data, { change: "remove", index: index, items: items });
|
148
|
+
}
|
149
|
+
}
|
150
|
+
return this;
|
151
|
+
},
|
152
|
+
|
153
|
+
move: function( oldIndex, newIndex, numToMove ) {
|
154
|
+
validateIndex( oldIndex );
|
155
|
+
validateIndex( newIndex );
|
156
|
+
|
157
|
+
numToMove = ( numToMove === undefined || numToMove === null ) ? 1 : numToMove;
|
158
|
+
if ( numToMove ) {
|
159
|
+
var items = this._data.slice( oldIndex, oldIndex + numToMove );
|
160
|
+
this._data.splice( oldIndex, numToMove );
|
161
|
+
this._data.splice.apply( this._data, [ newIndex, 0 ].concat( items ) );
|
162
|
+
triggerArrayEvent( this._data, { change: "move", oldIndex: oldIndex, index: newIndex, items: items });
|
163
|
+
}
|
164
|
+
return this;
|
165
|
+
},
|
166
|
+
|
167
|
+
refresh: function( newItems ) {
|
168
|
+
var oldItems = this._data.slice( 0 );
|
169
|
+
splice.apply( this._data, [ 0, this._data.length ].concat( newItems ));
|
170
|
+
triggerArrayEvent( this._data, { change: "refresh", oldItems: oldItems });
|
171
|
+
return this;
|
172
|
+
}
|
173
|
+
};
|
174
|
+
})(jQuery);
|
@@ -0,0 +1,802 @@
|
|
1
|
+
/*! JsViews v1.0pre: http://github.com/BorisMoore/jsviews */
|
2
|
+
/*
|
3
|
+
* Interactive data-driven views using templates and data-linking.
|
4
|
+
* Requires jQuery, and jsrender.js (next-generation jQuery Templates, optimized for pure string-based rendering)
|
5
|
+
* See JsRender at http://github.com/BorisMoore/jsrender
|
6
|
+
*
|
7
|
+
* Copyright 2012, Boris Moore
|
8
|
+
* Released under the MIT License.
|
9
|
+
*/
|
10
|
+
// informal pre beta commit counter: 2
|
11
|
+
|
12
|
+
this.jQuery && jQuery.link || (function( global, undefined ) {
|
13
|
+
// global is the this object, which is window when running in the usual browser environment.
|
14
|
+
|
15
|
+
//========================== Top-level vars ==========================
|
16
|
+
|
17
|
+
var versionNumber = "v1.0pre",
|
18
|
+
|
19
|
+
rTag, delimOpen0, delimOpen1, delimClose0, delimClose1,
|
20
|
+
$ = global.jQuery,
|
21
|
+
|
22
|
+
// jsviews object (=== $.views) Note: JsViews requires jQuery is loaded)
|
23
|
+
jsv = $.views,
|
24
|
+
sub = jsv.sub,
|
25
|
+
FALSE = false, TRUE = true,
|
26
|
+
topView = jsv.topView,
|
27
|
+
templates = jsv.templates,
|
28
|
+
observable = $.observable,
|
29
|
+
jsvData = "_jsvData",
|
30
|
+
linkStr = "link",
|
31
|
+
viewStr = "view",
|
32
|
+
propertyChangeStr = "propertyChange",
|
33
|
+
arrayChangeStr = "arrayChange",
|
34
|
+
fnSetters = {
|
35
|
+
value: "val",
|
36
|
+
html: "html",
|
37
|
+
text: "text"
|
38
|
+
},
|
39
|
+
oldCleanData = $.cleanData,
|
40
|
+
oldJsvDelimiters = jsv.delimiters,
|
41
|
+
rTmplOrItemComment = /^(\/?)(?:(item)|(?:(tmpl)(?:\((.*),([^,)]*)\))?(?:\s+([^\s]+))?))$/,
|
42
|
+
// tokens: [ all, slash, 'item', 'tmpl', path, index, tmplParam ]
|
43
|
+
//rTmplOrItemComment = /^(\/?)(?:(item)|(?:(tmpl)(?:\(([^,R]*),([^,)]*)\))?(?:\s+([^\s]+))?))$/,
|
44
|
+
|
45
|
+
rStartTag = /^item|^tmpl(\(\$?[\w.,]*\))?(\s+[^\s]+)?$/;
|
46
|
+
|
47
|
+
if ( !$ ) {
|
48
|
+
// jQuery is not loaded.
|
49
|
+
throw "requires jQuery"; // for Beta (at least) we require jQuery
|
50
|
+
}
|
51
|
+
|
52
|
+
if( !(jsv )) {
|
53
|
+
throw "requires JsRender";
|
54
|
+
}
|
55
|
+
|
56
|
+
//========================== Top-level functions ==========================
|
57
|
+
|
58
|
+
//===============
|
59
|
+
// event handlers
|
60
|
+
//===============
|
61
|
+
|
62
|
+
function elemChangeHandler( ev ) {
|
63
|
+
var setter, cancel, fromAttr, to, linkContext, sourceValue, cnvtBack, target,
|
64
|
+
source = ev.target,
|
65
|
+
$source = $( source ),
|
66
|
+
view = $.view( source ),
|
67
|
+
context = view.ctx,
|
68
|
+
beforeChange = context.beforeChange;
|
69
|
+
|
70
|
+
if ( source.getAttribute( jsv.linkAttr ) && (to = jsViewsData( source, "to" ))) {
|
71
|
+
fromAttr = defaultAttr( source );
|
72
|
+
setter = fnSetters[ fromAttr ];
|
73
|
+
sourceValue = $.isFunction( fromAttr ) ? fromAttr( source ) : setter ? $source[setter]() : $source.attr( fromAttr );
|
74
|
+
|
75
|
+
if ((!beforeChange || !(cancel = beforeChange.call( view, ev ) === FALSE )) && sourceValue !== undefined ) {
|
76
|
+
cnvtBack = jsv.converters[ to[ 2 ]];
|
77
|
+
target = to[ 0 ];
|
78
|
+
to = to[ 1 ];
|
79
|
+
linkContext = {
|
80
|
+
src: source,
|
81
|
+
tgt: target,
|
82
|
+
cnvtBack: cnvtBack,
|
83
|
+
path: to
|
84
|
+
};
|
85
|
+
if ( cnvtBack ) {
|
86
|
+
sourceValue = cnvtBack( sourceValue );
|
87
|
+
}
|
88
|
+
if ( sourceValue !== undefined && target ) {
|
89
|
+
observable( target ).setProperty( to, sourceValue );
|
90
|
+
if ( context.afterChange ) { //TODO only call this if the target property changed
|
91
|
+
context.afterChange.call( linkContext, ev );
|
92
|
+
}
|
93
|
+
}
|
94
|
+
ev.stopPropagation(); // Stop bubbling
|
95
|
+
}
|
96
|
+
if ( cancel ) {
|
97
|
+
ev.stopImmediatePropagation();
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
function propertyChangeHandler( ev, eventArgs, bind ) {
|
103
|
+
var setter, changed, sourceValue, css,
|
104
|
+
link = this,
|
105
|
+
source = link.src,
|
106
|
+
target = link.tgt,
|
107
|
+
$target = $( target ),
|
108
|
+
attr = link.attr || defaultAttr( target, TRUE ),
|
109
|
+
view = link.view,
|
110
|
+
context = view.ctx,
|
111
|
+
beforeChange = context.beforeChange;
|
112
|
+
|
113
|
+
// TODO for <input data-link="a.b" />
|
114
|
+
//Currently the following scenarios do work:
|
115
|
+
//$.observable(model).setProperty("a.b", "bar");
|
116
|
+
//$.observable(model.a).setProperty("b", "bar");
|
117
|
+
// TODO Add support for $.observable(model).setProperty("a", { b: "bar" });
|
118
|
+
// var testsourceValue = ev.expr( source, view, jsv, ev.bind );
|
119
|
+
|
120
|
+
// TODO call beforeChange on data-link initialization.
|
121
|
+
// if ( changed && context.afterChange ) {
|
122
|
+
// context.afterChange.call( link, ev, eventArgs );
|
123
|
+
// }
|
124
|
+
|
125
|
+
|
126
|
+
if ((!beforeChange || !(eventArgs && beforeChange.call( this, ev, eventArgs ) === FALSE ))
|
127
|
+
// && (!view || view.onDataChanged( eventArgs ) !== FALSE ) // Not currently supported or needed for property change
|
128
|
+
) {
|
129
|
+
sourceValue = link.fn( source, link.view, jsv, bind || returnVal );
|
130
|
+
if ( $.isFunction( sourceValue )) {
|
131
|
+
sourceValue = sourceValue.call( source );
|
132
|
+
}
|
133
|
+
if ( css = attr.lastIndexOf( "css-", 0 ) === 0 && attr.substr( 4 )) {
|
134
|
+
if ( changed = $target.css( css ) !== sourceValue ) {
|
135
|
+
$target.css( css, sourceValue );
|
136
|
+
}
|
137
|
+
} else {
|
138
|
+
setter = fnSetters[ attr ];
|
139
|
+
if ( setter ) {
|
140
|
+
if ( changed = $target[setter]() !== sourceValue ) {
|
141
|
+
$target[setter]( sourceValue );
|
142
|
+
if ( target.nodeName.toLowerCase() === "input" ) {
|
143
|
+
$target.blur(); // Issue with IE. This ensures HTML rendering is updated.
|
144
|
+
}
|
145
|
+
}
|
146
|
+
} else if ( changed = $target.attr( attr ) !== sourceValue ) {
|
147
|
+
$target.attr( attr, sourceValue );
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
if ( eventArgs && changed && context.afterChange ) {
|
152
|
+
context.afterChange.call( link, ev, eventArgs );
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
function arrayChangeHandler( ev, eventArgs ) {
|
158
|
+
var context = this.ctx,
|
159
|
+
beforeChange = context.beforeChange;
|
160
|
+
|
161
|
+
if ( !beforeChange || beforeChange.call( this, ev, eventArgs ) !== FALSE ) {
|
162
|
+
this.onDataChanged( eventArgs );
|
163
|
+
if ( context.afterChange ) {
|
164
|
+
context.afterChange.call( this, ev, eventArgs );
|
165
|
+
}
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
function setArrayChangeLink( view ) {
|
170
|
+
var handler,
|
171
|
+
data = view.data,
|
172
|
+
onArrayChange = view._onArrayChange;
|
173
|
+
|
174
|
+
if ( onArrayChange ) {
|
175
|
+
if ( onArrayChange[ 1 ] === data ) {
|
176
|
+
return;
|
177
|
+
}
|
178
|
+
$([ onArrayChange[ 1 ]]).unbind( arrayChangeStr, onArrayChange[ 0 ]);
|
179
|
+
}
|
180
|
+
|
181
|
+
if ( $.isArray( data )) {
|
182
|
+
handler = function() {
|
183
|
+
arrayChangeHandler.apply( view, arguments );
|
184
|
+
};
|
185
|
+
$([ data ]).bind( arrayChangeStr, handler );
|
186
|
+
view._onArrayChange = [ handler, data ];
|
187
|
+
}
|
188
|
+
}
|
189
|
+
|
190
|
+
function defaultAttr( elem, to ) {
|
191
|
+
// Merge in the default attribute bindings for this target element
|
192
|
+
var attr = jsv.merge[ elem.nodeName.toLowerCase() ];
|
193
|
+
return attr
|
194
|
+
? (to
|
195
|
+
? attr.to.toAttr
|
196
|
+
: attr.from.fromAttr)
|
197
|
+
: "html";
|
198
|
+
}
|
199
|
+
|
200
|
+
function returnVal( value ) {
|
201
|
+
return value;
|
202
|
+
}
|
203
|
+
|
204
|
+
//===============
|
205
|
+
// view hierarchy
|
206
|
+
//===============
|
207
|
+
|
208
|
+
function linkedView( view ) {
|
209
|
+
var i, views, viewsCount;
|
210
|
+
if ( !view.render ) {
|
211
|
+
view.onDataChanged = view_onDataChanged;
|
212
|
+
view.render = view_render;
|
213
|
+
view.addViews = view_addViews;
|
214
|
+
view.removeViews = view_removeViews;
|
215
|
+
view.content = view_content;
|
216
|
+
if (view.parent) {
|
217
|
+
if ( !$.isArray( view.data )) {
|
218
|
+
view.nodes = [];
|
219
|
+
view._lnk = 0; // compiled link index.
|
220
|
+
}
|
221
|
+
views = view.parent.views;
|
222
|
+
if ( $.isArray( views )) {
|
223
|
+
i = view.index;
|
224
|
+
viewsCount = views.length;
|
225
|
+
while ( i++ < viewsCount-1 ) {
|
226
|
+
observable( views[ i ] ).setProperty( "index", i );
|
227
|
+
}
|
228
|
+
}
|
229
|
+
setArrayChangeLink( view );
|
230
|
+
}
|
231
|
+
}
|
232
|
+
return view;
|
233
|
+
}
|
234
|
+
|
235
|
+
// Additional methods on view object for linked views (i.e. when JsViews is loaded)
|
236
|
+
|
237
|
+
function view_onDataChanged( eventArgs ) {
|
238
|
+
if ( eventArgs ) {
|
239
|
+
// This is an observable action (not a trigger/handler call from pushValues, or similar, for which eventArgs will be null)
|
240
|
+
var self = this,
|
241
|
+
action = eventArgs.change,
|
242
|
+
index = eventArgs.index,
|
243
|
+
items = eventArgs.items;
|
244
|
+
switch ( action ) {
|
245
|
+
case "insert":
|
246
|
+
self.addViews( index, items );
|
247
|
+
break;
|
248
|
+
case "remove":
|
249
|
+
self.removeViews( index, items.length );
|
250
|
+
break;
|
251
|
+
case "move":
|
252
|
+
self.render(); // Could optimize this
|
253
|
+
break;
|
254
|
+
case "refresh":
|
255
|
+
self.render();
|
256
|
+
// Othercases: (e.g.undefined, for setProperty on observable object) etc. do nothing
|
257
|
+
}
|
258
|
+
}
|
259
|
+
return TRUE;
|
260
|
+
}
|
261
|
+
|
262
|
+
function view_render() {
|
263
|
+
var self = this,
|
264
|
+
tmpl = self.tmpl = getTemplate( self.tmpl ),
|
265
|
+
prevNode = self.prevNode,
|
266
|
+
nextNode = self.nextNode,
|
267
|
+
parentNode = prevNode.parentNode;
|
268
|
+
|
269
|
+
if ( tmpl ) {
|
270
|
+
// Remove HTML nodes
|
271
|
+
$( self.nodes ).remove(); // Also triggers cleanData which removes child views.
|
272
|
+
// Remove child views
|
273
|
+
self.removeViews();
|
274
|
+
self.nodes = [];
|
275
|
+
$( prevNode ).after( tmpl.render( self.data, self.ctx, self, self.path, true ) );
|
276
|
+
// Need to the update the annotation info on the prevNode comment marker
|
277
|
+
// TODO - Include the following two lines, but modified, to keep <!-- item --> comments, but add template info: <!-- item fooTemplate -->
|
278
|
+
// prevNode.nodeValue = prevNode.nextSibling.nodeValue;
|
279
|
+
// nextNode.nodeValue = nextNode.previousSibling.nodeValue;
|
280
|
+
// Remove the extra comment nodes
|
281
|
+
parentNode.removeChild( prevNode.nextSibling );
|
282
|
+
parentNode.removeChild( nextNode.previousSibling );
|
283
|
+
// Link the new HTML nodes to the data
|
284
|
+
linkViews( parentNode, self, nextNode, 0, undefined, undefined, prevNode, 0 ); //this.index
|
285
|
+
setArrayChangeLink( self );
|
286
|
+
}
|
287
|
+
return self;
|
288
|
+
}
|
289
|
+
|
290
|
+
function view_addViews( index, dataItems, tmpl ) {
|
291
|
+
var self = this,
|
292
|
+
itemsCount = dataItems.length,
|
293
|
+
context = self.ctx,
|
294
|
+
views = self.views;
|
295
|
+
|
296
|
+
if ( index && !views[index-1] ) {
|
297
|
+
return; // If subview for provided index does not exist, do nothing
|
298
|
+
}
|
299
|
+
if ( itemsCount && (tmpl = getTemplate( tmpl || self.tmpl ))) {
|
300
|
+
var prevNode = index ? views[ index-1 ].nextNode : self.prevNode,
|
301
|
+
nextNode = prevNode.nextSibling,
|
302
|
+
parentNode = prevNode.parentNode;
|
303
|
+
|
304
|
+
// Use passed-in template if provided, since self added view may use a different template than the original one used to render the array.
|
305
|
+
$( prevNode ).after( tmpl.render( dataItems, context, self, undefined, index ) );
|
306
|
+
// Need to the update the annotation info on the prevNode comment marker
|
307
|
+
// self.prevNode.nodeValue = prevNode.nextSibling.nodeValue;
|
308
|
+
// Remove the extra comment nodes
|
309
|
+
parentNode.removeChild( prevNode.nextSibling );
|
310
|
+
parentNode.removeChild( nextNode.previousSibling );
|
311
|
+
// Link the new HTML nodes to the data
|
312
|
+
linkViews( parentNode, self, nextNode, 0, undefined, undefined, prevNode, index );
|
313
|
+
}
|
314
|
+
return self;
|
315
|
+
}
|
316
|
+
|
317
|
+
function view_removeViews( index, itemsCount ) {
|
318
|
+
// view.removeViews() removes all the child views
|
319
|
+
// view.removeViews( index ) removes the child view with specified index or key
|
320
|
+
// view.removeViews( index, count ) removes the specified nummber of child views, starting with the specified index
|
321
|
+
function removeView( index ) {
|
322
|
+
var parentElViews, i,
|
323
|
+
view = views[ index ],
|
324
|
+
node = view.prevNode,
|
325
|
+
nextNode = view.nextNode,
|
326
|
+
nodes = [ node ];
|
327
|
+
if ( !nextNode ) {
|
328
|
+
// this view has not been linked, so nothing to remove.
|
329
|
+
return;
|
330
|
+
}
|
331
|
+
parentElViews = parentElViews || jsViewsData( nextNode.parentNode, viewStr );
|
332
|
+
i = parentElViews.length;
|
333
|
+
if ( i ) {
|
334
|
+
view.removeViews();
|
335
|
+
}
|
336
|
+
|
337
|
+
// Remove this view from the parentElViews collection
|
338
|
+
while ( i-- ) {
|
339
|
+
if ( parentElViews[ i ] === view ) {
|
340
|
+
parentElViews.splice( i, 1 );
|
341
|
+
break;
|
342
|
+
}
|
343
|
+
}
|
344
|
+
// Remove the HTML nodes from the DOM
|
345
|
+
while ( node !== nextNode ) {
|
346
|
+
node = node.nextSibling;
|
347
|
+
nodes.push( node );
|
348
|
+
}
|
349
|
+
$( nodes ).remove();
|
350
|
+
view.data = undefined;
|
351
|
+
setArrayChangeLink( view );
|
352
|
+
}
|
353
|
+
|
354
|
+
var current,
|
355
|
+
self = this,
|
356
|
+
views = self.views,
|
357
|
+
viewsCount = views.length;
|
358
|
+
|
359
|
+
if ( index === undefined ) {
|
360
|
+
// Remove all child views
|
361
|
+
if ( viewsCount === undefined ) {
|
362
|
+
// views and data are objects
|
363
|
+
for ( index in views ) {
|
364
|
+
// Remove by key
|
365
|
+
removeView( index );
|
366
|
+
}
|
367
|
+
self.views = {};
|
368
|
+
} else {
|
369
|
+
// views and data are arrays
|
370
|
+
current = viewsCount;
|
371
|
+
while ( current-- ) {
|
372
|
+
removeView( current );
|
373
|
+
}
|
374
|
+
self.views = [];
|
375
|
+
}
|
376
|
+
} else {
|
377
|
+
if ( itemsCount === undefined ) {
|
378
|
+
if ( viewsCount === undefined ) {
|
379
|
+
// Remove child view with key 'index'
|
380
|
+
removeView( index );
|
381
|
+
delete views[ index ];
|
382
|
+
} else {
|
383
|
+
// The parentView is data array view.
|
384
|
+
// Set itemsCount to 1, to remove this item
|
385
|
+
itemsCount = 1;
|
386
|
+
}
|
387
|
+
}
|
388
|
+
if ( itemsCount ) {
|
389
|
+
current = index + itemsCount;
|
390
|
+
// Remove indexed items (parentView is data array view);
|
391
|
+
while ( current-- > index ) {
|
392
|
+
removeView( current );
|
393
|
+
}
|
394
|
+
views.splice( index, itemsCount );
|
395
|
+
if ( viewsCount = views.length ) {
|
396
|
+
// Fixup index on following view items...
|
397
|
+
while ( index < viewsCount ) {
|
398
|
+
observable( views[ index ] ).setProperty( "index", index++ );
|
399
|
+
}
|
400
|
+
}
|
401
|
+
}
|
402
|
+
}
|
403
|
+
return this;
|
404
|
+
}
|
405
|
+
|
406
|
+
function view_content( select ) {
|
407
|
+
return select ? $( select, this.nodes ) : $( this.nodes );
|
408
|
+
}
|
409
|
+
|
410
|
+
//===============
|
411
|
+
// data-linking
|
412
|
+
//===============
|
413
|
+
|
414
|
+
function linkViews( node, parent, nextNode, depth, data, context, prevNode, index ) {
|
415
|
+
|
416
|
+
var tokens, links, link, attr, linkIndex, parentElViews, convertBack, cbLength, view, parentNode, linkMarkup, expression,
|
417
|
+
currentView = parent,
|
418
|
+
viewDepth = depth;
|
419
|
+
context = context || {};
|
420
|
+
node = prevNode || node;
|
421
|
+
|
422
|
+
if ( !prevNode && node.nodeType === 1 ) {
|
423
|
+
if ( viewDepth++ === 0 ) {
|
424
|
+
// Add top-level element nodes to view.nodes
|
425
|
+
currentView.nodes.push( node );
|
426
|
+
}
|
427
|
+
if ( linkMarkup = node.getAttribute( jsv.linkAttr ) ) {
|
428
|
+
linkIndex = currentView._lnk++;
|
429
|
+
// Compiled linkFn expressions are stored in the tmpl.links array of the template
|
430
|
+
links = currentView.links || currentView.tmpl.links;
|
431
|
+
if ( !(link = links[ linkIndex ] )) {
|
432
|
+
link = links [ linkIndex ] = {};
|
433
|
+
if ( linkMarkup.charAt(linkMarkup.length-1) !== "}" ) {
|
434
|
+
// Simplified syntax is used: data-link="expression"
|
435
|
+
// Convert to data-link="{:expression}", or for inputs, data-link="{:expression:}" for (default) two-way binding
|
436
|
+
linkMarkup = delimOpen1 + ":" + linkMarkup + ($.nodeName( node, "input" ) ? ":" : "") + delimClose0;
|
437
|
+
}
|
438
|
+
while( tokens = rTag.exec( linkMarkup )) { // TODO require } to be followed by whitespace or $, and remove the \}(!\}) option.
|
439
|
+
// Iterate over the data-link expressions, for different target attrs, e.g. <input data-link="{:firstName:} title{:~description(firstName, lastName)}"
|
440
|
+
// tokens: [all, attr, tag, converter, colon, html, code, linkedParams]
|
441
|
+
attr = tokens[ 1 ];
|
442
|
+
expression = tokens[ 2 ];
|
443
|
+
if ( tokens[ 5 ]) {
|
444
|
+
// Only for {:} link"
|
445
|
+
if ( !attr && (convertBack = /^.*:([\w$]*)$/.exec( tokens[ 8 ] ))) {
|
446
|
+
// two-way binding
|
447
|
+
convertBack = convertBack[ 1 ];
|
448
|
+
if ( cbLength = convertBack.length ) {
|
449
|
+
// There is a convertBack function
|
450
|
+
expression = tokens[ 2 ].slice( 0, -cbLength - 1 ) + delimClose0; // Remove the convertBack string from expression.
|
451
|
+
}
|
452
|
+
}
|
453
|
+
if ( convertBack === null ) {
|
454
|
+
convertBack = undefined;
|
455
|
+
}
|
456
|
+
}
|
457
|
+
// Compile the linkFn expression which evaluates and binds a data-link expression
|
458
|
+
// TODO - optimize for the case of simple data path with no conversion, helpers, etc.:
|
459
|
+
// i.e. data-link="a.b.c". Avoid creating new instances of Function every time. Can use a default function for all of these...
|
460
|
+
link[ attr ] = jsv.tmplFn( delimOpen0 + expression + delimClose1, undefined, TRUE );
|
461
|
+
if ( !attr && convertBack !== undefined ) {
|
462
|
+
link[ attr ].to = convertBack;
|
463
|
+
}
|
464
|
+
}
|
465
|
+
}
|
466
|
+
for ( attr in link ) {
|
467
|
+
bindDataLinkTarget(
|
468
|
+
currentView.data|| data, //source
|
469
|
+
node, //target
|
470
|
+
attr, //attr
|
471
|
+
link[ attr ], //compiled link markup expression
|
472
|
+
currentView //view
|
473
|
+
);
|
474
|
+
}
|
475
|
+
// TODO - Add one-way-to-source support
|
476
|
+
// if ( linkMarkup.lastIndexOf( "toSrc{", 0 ) === 0 ) {
|
477
|
+
// linkMarkup = "{toSrc " + linkMarkup.slice(6);
|
478
|
+
// }
|
479
|
+
}
|
480
|
+
node = node.firstChild;
|
481
|
+
} else {
|
482
|
+
node = node.nextSibling;
|
483
|
+
}
|
484
|
+
|
485
|
+
while ( node && node !== nextNode ) {
|
486
|
+
if ( node.nodeType === 1 ) {
|
487
|
+
linkViews( node, currentView, nextNode, viewDepth, data, context );
|
488
|
+
} else if ( node.nodeType === 8 && (tokens = rTmplOrItemComment.exec( node.nodeValue ))) {
|
489
|
+
// tokens: [ all, slash, 'item', 'tmpl', path, index, tmplParam ]
|
490
|
+
parentNode = node.parentNode;
|
491
|
+
if ( tokens[ 1 ]) {
|
492
|
+
// <!--/item--> or <!--/tmpl-->
|
493
|
+
currentView.nextNode = node;
|
494
|
+
if ( currentView.ctx.onAfterCreate ) {
|
495
|
+
currentView.ctx.onAfterCreate.call( currentView, currentView );
|
496
|
+
}
|
497
|
+
if ( tokens[ 2 ]) {
|
498
|
+
// An item close tag: <!--/item-->
|
499
|
+
currentView = parent;
|
500
|
+
} else {
|
501
|
+
// A tmpl close tag: <!--/tmpl-->
|
502
|
+
return node;
|
503
|
+
}
|
504
|
+
} else {
|
505
|
+
// <!--item--> or <!--tmpl-->
|
506
|
+
parentElViews = parentElViews || jsViewsData( parentNode, viewStr, TRUE );
|
507
|
+
if ( tokens[ 2 ]) {
|
508
|
+
// An item open tag: <!--item-->
|
509
|
+
parentElViews.push(
|
510
|
+
currentView = linkedView( currentView.views[ index ] )
|
511
|
+
);
|
512
|
+
index++;
|
513
|
+
currentView.prevNode = node;
|
514
|
+
} else {
|
515
|
+
// A tmpl open tag: <!--tmpl(path) name-->
|
516
|
+
parentElViews.push(
|
517
|
+
view = linkedView( currentView.views[ tokens[ 5 ]] )
|
518
|
+
);
|
519
|
+
view.prevNode = node;
|
520
|
+
// Jump to the nextNode of the tmpl view
|
521
|
+
node = linkViews( node, view, nextNode, 0, undefined, undefined, undefined, 0 );
|
522
|
+
}
|
523
|
+
}
|
524
|
+
} else if ( viewDepth === 0 ) {
|
525
|
+
// Add top-level non-element nodes to view.nodes
|
526
|
+
currentView.nodes.push( node );
|
527
|
+
}
|
528
|
+
node = node.nextSibling;
|
529
|
+
}
|
530
|
+
}
|
531
|
+
|
532
|
+
function bindDataLinkTarget( source, target, attr, linkFn, view ) {
|
533
|
+
//Add data link bindings for a link expression in data-link attribute markup
|
534
|
+
var boundParams = [],
|
535
|
+
storedLinks = jsViewsData( target, linkStr, TRUE ),
|
536
|
+
handler = function() {
|
537
|
+
propertyChangeHandler.apply({ tgt: target, src: source, attr: attr, fn: linkFn, view: view }, arguments );
|
538
|
+
};
|
539
|
+
|
540
|
+
// Store for unbinding
|
541
|
+
storedLinks[ attr ] = { srcs: boundParams, hlr: handler };
|
542
|
+
|
543
|
+
// Call the handler for initialization and parameter binding
|
544
|
+
handler( undefined, undefined, function ( object, leafToken ) {
|
545
|
+
// Binding callback called on each dependent object (parameter) that the link expression depends on.
|
546
|
+
// For each path add a propertyChange binding to the leaf object, to trigger the compiled link expression,
|
547
|
+
// and upate the target attribute on the target element
|
548
|
+
boundParams.push( object );
|
549
|
+
if ( linkFn.to !== undefined ) {
|
550
|
+
// If this link is a two-way binding, add the linkTo info to JsViews stored data
|
551
|
+
$.data( target, jsvData ).to = [ object, leafToken, linkFn.to ];
|
552
|
+
// For two-way binding, there should be only one path. If not, will bind to the last one.
|
553
|
+
}
|
554
|
+
if ( $.isArray( object )) {
|
555
|
+
$([ object ]).bind( arrayChangeStr, function() {
|
556
|
+
handler();
|
557
|
+
});
|
558
|
+
} else {
|
559
|
+
$( object ).bind( propertyChangeStr, handler );
|
560
|
+
}
|
561
|
+
return object;
|
562
|
+
});
|
563
|
+
// Note that until observable deals with managing listeners on object graphs, we can't support changing objects higher up the chain, so there is no reason
|
564
|
+
// to attach listeners to them. Even $.observable( person ).setProperty( "address.city", ... ); is in fact triggering propertyChange on the leaf object (address)
|
565
|
+
}
|
566
|
+
|
567
|
+
//===============
|
568
|
+
// helpers
|
569
|
+
//===============
|
570
|
+
|
571
|
+
function jsViewsData( el, type, create ) {
|
572
|
+
var jqData = $.data( el, jsvData ) || (create && $.data( el, jsvData, { view: [], link: {} }));
|
573
|
+
return jqData ? jqData[ type ] : {};
|
574
|
+
}
|
575
|
+
|
576
|
+
function inputAttrib( elem ) {
|
577
|
+
return elem.type === "checkbox" ? elem.checked : $( elem ).val();
|
578
|
+
}
|
579
|
+
|
580
|
+
function getTemplate( tmpl ) {
|
581
|
+
// Get nested templates from path
|
582
|
+
if ( "" + tmpl === tmpl ) {
|
583
|
+
var tokens = tmpl.split("[");
|
584
|
+
tmpl = templates[ tokens.shift() ];
|
585
|
+
while( tmpl && tokens.length ) {
|
586
|
+
tmpl = tmpl.tmpls[ tokens.shift().slice( 0, -1 )];
|
587
|
+
}
|
588
|
+
}
|
589
|
+
return tmpl;
|
590
|
+
}
|
591
|
+
|
592
|
+
//========================== Initialize ==========================
|
593
|
+
|
594
|
+
//=======================
|
595
|
+
// JsRender integration
|
596
|
+
//=======================
|
597
|
+
|
598
|
+
sub.onStoreItem = function( store, name, item, process ) {
|
599
|
+
|
600
|
+
if ( name && item && store === templates ) {
|
601
|
+
item.link = function( container, data, context, parentView ) {
|
602
|
+
$.link( container, data, context, parentView, item );
|
603
|
+
};
|
604
|
+
$.link[ name ] = function() {
|
605
|
+
return item.link.apply( item, arguments );
|
606
|
+
};
|
607
|
+
}
|
608
|
+
};
|
609
|
+
sub.onRenderItem = function( value, props ) {
|
610
|
+
return "<!--item-->" + value + "<!--/item-->";
|
611
|
+
};
|
612
|
+
sub.onRenderItems = function( value, path, index, tmpl, props ) {
|
613
|
+
return "<!--tmpl(" + (path||"") + "," + index + ") " + tmpl.name + "-->" + value + "<!--/tmpl-->";
|
614
|
+
};
|
615
|
+
|
616
|
+
//=======================
|
617
|
+
// Extend $.views namespace
|
618
|
+
//=======================
|
619
|
+
|
620
|
+
$.extend( jsv, {
|
621
|
+
linkAttr: "data-link",
|
622
|
+
merge: {
|
623
|
+
input: {
|
624
|
+
from: {
|
625
|
+
fromAttr: inputAttrib
|
626
|
+
},
|
627
|
+
to: {
|
628
|
+
toAttr: "value"
|
629
|
+
}
|
630
|
+
}
|
631
|
+
},
|
632
|
+
delimiters: function( openChars, closeChars ) {
|
633
|
+
oldJsvDelimiters( openChars, closeChars );
|
634
|
+
rTag = new RegExp( "(?:^|s*)([\\w-]*)(" + jsv.rTag + ")", "g" );
|
635
|
+
delimOpen0 = openChars.charAt( 0 );
|
636
|
+
delimOpen1 = openChars.charAt( 1 );
|
637
|
+
delimClose0 = closeChars.charAt( 0 );
|
638
|
+
delimClose1 = closeChars.charAt( 1 );
|
639
|
+
return this;
|
640
|
+
}
|
641
|
+
});
|
642
|
+
|
643
|
+
//=======================
|
644
|
+
// Extend jQuery namespace
|
645
|
+
//=======================
|
646
|
+
|
647
|
+
$.extend({
|
648
|
+
|
649
|
+
//=======================
|
650
|
+
// jQuery $.view() plugin
|
651
|
+
//=======================
|
652
|
+
|
653
|
+
view: function( node, inner ) {
|
654
|
+
// $.view() returns top node
|
655
|
+
// $.view( node ) returns view that contains node
|
656
|
+
// $.view( selector ) returns view that contains first selected element
|
657
|
+
|
658
|
+
node = ("" + node === node ? $( node )[0] : node);
|
659
|
+
var returnView, view, parentElViews, i, finish,
|
660
|
+
topNode = global.document.body,
|
661
|
+
startNode = node;
|
662
|
+
|
663
|
+
if ( inner ) {
|
664
|
+
// Treat supplied node as a container element, step through content, and return the first view encountered.
|
665
|
+
finish = node.nextSibling || node.parentNode;
|
666
|
+
while ( finish !== (node = node.firstChild || node.nextSibling || node.parentNode.nextSibling )) {
|
667
|
+
if ( node.nodeType === 8 && rStartTag.test( node.nodeValue )) {
|
668
|
+
view = $.view( node );
|
669
|
+
if ( view.prevNode === node ) {
|
670
|
+
return view;
|
671
|
+
}
|
672
|
+
}
|
673
|
+
}
|
674
|
+
return;
|
675
|
+
}
|
676
|
+
|
677
|
+
node = node || topNode;
|
678
|
+
if ( $.isEmptyObject( topView.views )) {
|
679
|
+
returnView = topView; // Perf optimization for common case
|
680
|
+
} else {
|
681
|
+
// Step up through parents to find an element which is a views container, or if none found, create the top-level view for the page
|
682
|
+
while( !(parentElViews = jsViewsData( finish = node.parentNode || topNode, viewStr )).length ) {
|
683
|
+
if ( !finish || node === topNode ) {
|
684
|
+
jsViewsData( topNode.parentNode, viewStr, TRUE ).push( returnView = topView );
|
685
|
+
break;
|
686
|
+
}
|
687
|
+
node = finish;
|
688
|
+
}
|
689
|
+
if ( !returnView && node === topNode ) {
|
690
|
+
returnView = topView; //parentElViews[0];
|
691
|
+
}
|
692
|
+
while ( !returnView && node ) {
|
693
|
+
// Step back through the nodes, until we find an item or tmpl open tag - in which case that is the view we want
|
694
|
+
if ( node === finish ) {
|
695
|
+
returnView = view;
|
696
|
+
break;
|
697
|
+
}
|
698
|
+
if ( node.nodeType === 8 ) {
|
699
|
+
if ( /^\/item|^\/tmpl$/.test( node.nodeValue )) {
|
700
|
+
// A tmpl or item close tag: <!--/tmpl--> or <!--/item-->
|
701
|
+
i = parentElViews.length;
|
702
|
+
while ( i-- ) {
|
703
|
+
view = parentElViews[ i ];
|
704
|
+
if ( view.nextNode === node ) {
|
705
|
+
// If this was the node originally passed in, this is the view we want.
|
706
|
+
returnView = (node === startNode && view);
|
707
|
+
// If not, jump to the beginning of this item/tmpl and continue from there
|
708
|
+
node = view.prevNode;
|
709
|
+
break;
|
710
|
+
}
|
711
|
+
}
|
712
|
+
} else if ( rStartTag.test( node.nodeValue )) {
|
713
|
+
// A tmpl or item open tag: <!--tmpl--> or <!--item-->
|
714
|
+
i = parentElViews.length;
|
715
|
+
while ( i-- ) {
|
716
|
+
view = parentElViews[ i ];
|
717
|
+
if ( view.prevNode === node ) {
|
718
|
+
returnView = view;
|
719
|
+
break;
|
720
|
+
}
|
721
|
+
}
|
722
|
+
}
|
723
|
+
}
|
724
|
+
node = node.previousSibling;
|
725
|
+
}
|
726
|
+
// If not within any of the views in the current parentElViews collection, move up through parent nodes to find next parentElViews collection
|
727
|
+
returnView = returnView || $.view( finish );
|
728
|
+
}
|
729
|
+
return returnView;
|
730
|
+
},
|
731
|
+
|
732
|
+
link: function( container, data, context, parentView, template ) {
|
733
|
+
// Bind elementChange on the root element, for links from elements within the content, to data;
|
734
|
+
function dataToElem() {
|
735
|
+
elemChangeHandler.apply({
|
736
|
+
tgt: data
|
737
|
+
}, arguments );
|
738
|
+
}
|
739
|
+
|
740
|
+
parentView = parentView || topView;
|
741
|
+
template = template && (templates[ template ] || (template.markup ? template : $.templates( template )));
|
742
|
+
context = context || parentView.ctx;
|
743
|
+
context.link = TRUE;
|
744
|
+
container = $( container )
|
745
|
+
.bind( "change", dataToElem );
|
746
|
+
|
747
|
+
if ( template ) {
|
748
|
+
// TODO/BUG Currently this will re-render if called a second time, and will leave stale views under the parentView.views.
|
749
|
+
// So TODO: make it smart about when to render and when to link on already rendered content
|
750
|
+
container.empty().append( template.render( data, context, parentView )); // Supply non-jQuery version of this...
|
751
|
+
// Using append, rather than html, as workaround for issues in IE compat mode. (Using innerHTML leads to initial comments being stripped)
|
752
|
+
}
|
753
|
+
linkViews( container[0], parentView, undefined, undefined, data, context );
|
754
|
+
},
|
755
|
+
|
756
|
+
//=======================
|
757
|
+
// override $.cleanData
|
758
|
+
//=======================
|
759
|
+
cleanData: function( elems ) {
|
760
|
+
var l, el, link, attr, parentView, view, srcs, linksAndViews, collData,
|
761
|
+
i = elems.length;
|
762
|
+
while ( i-- ) {
|
763
|
+
el = elems[ i ];
|
764
|
+
if ( linksAndViews = $.data( el, jsvData )) {
|
765
|
+
|
766
|
+
// Get links and unbind propertyChange
|
767
|
+
collData = linksAndViews.link;
|
768
|
+
for ( attr in collData) {
|
769
|
+
link = collData[ attr ];
|
770
|
+
srcs = link.srcs;
|
771
|
+
l = srcs.length;
|
772
|
+
while( l-- ) {
|
773
|
+
$( srcs[ l ] ).unbind( propertyChangeStr, link.hlr );
|
774
|
+
}
|
775
|
+
}
|
776
|
+
|
777
|
+
// Get views and remove from parent view
|
778
|
+
collData = linksAndViews.view;
|
779
|
+
if ( l = collData.length ) {
|
780
|
+
parentView = $.view( el );
|
781
|
+
while( l-- ) {
|
782
|
+
view = collData[ l ];
|
783
|
+
if ( view.parent === parentView ) {
|
784
|
+
parentView.removeViews( view.index ); // NO - ONLY remove view if its top-level nodes are all.. (TODO)
|
785
|
+
}
|
786
|
+
}
|
787
|
+
}
|
788
|
+
}
|
789
|
+
}
|
790
|
+
oldCleanData.call( $, elems );
|
791
|
+
}
|
792
|
+
});
|
793
|
+
|
794
|
+
// Initialize default delimiters
|
795
|
+
jsv.delimiters( "{{", "}}" );
|
796
|
+
|
797
|
+
topView._lnk = 0;
|
798
|
+
topView.links = [];
|
799
|
+
topView.ctx.link = TRUE; // Set this as the default, when JsViews is loaded
|
800
|
+
linkedView(topView);
|
801
|
+
|
802
|
+
})( this );
|