sprockets-jsrender 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 );
|