|
1 /*! |
|
2 PURE Unobtrusive Rendering Engine for HTML |
|
3 |
|
4 Licensed under the MIT licenses. |
|
5 More information at: http://www.opensource.org |
|
6 |
|
7 Copyright (c) 2012 Michael Cvilic - BeeBole.com |
|
8 |
|
9 Thanks to Rog Peppe for the functional JS jump |
|
10 revision: 2.72 |
|
11 */ |
|
12 |
|
13 var $p, pure = $p = function(){ |
|
14 var sel = arguments[0], |
|
15 ctxt = false; |
|
16 |
|
17 if(typeof sel === 'string'){ |
|
18 ctxt = arguments[1] || false; |
|
19 }else if(sel && !sel[0] && !sel.length){ |
|
20 sel = [sel]; |
|
21 } |
|
22 return $p.core(sel, ctxt); |
|
23 }; |
|
24 |
|
25 $p.core = function(sel, ctxt, plugins){ |
|
26 //get an instance of the plugins |
|
27 var templates = []; |
|
28 plugins = plugins || getPlugins(); |
|
29 |
|
30 //search for the template node(s) |
|
31 switch(typeof sel){ |
|
32 case 'string': |
|
33 templates = plugins.find(ctxt || document, sel); |
|
34 if(templates.length === 0) { |
|
35 error('The template "' + sel + '" was not found'); |
|
36 } |
|
37 break; |
|
38 case 'undefined': |
|
39 error('The root of the template is undefined, check your selector'); |
|
40 break; |
|
41 default: |
|
42 templates = sel; |
|
43 } |
|
44 |
|
45 for(var i = 0, ii = templates.length; i < ii; i++){ |
|
46 plugins[i] = templates[i]; |
|
47 } |
|
48 plugins.length = ii; |
|
49 |
|
50 // set the signature string that will be replaced at render time |
|
51 var Sig = '_s' + Math.floor( Math.random() * 1000000 ) + '_', |
|
52 // another signature to prepend to attributes and avoid checks: style, height, on[events]... |
|
53 attPfx = '_a' + Math.floor( Math.random() * 1000000 ) + '_', |
|
54 // rx to parse selectors, e.g. "+tr.foo[class]" |
|
55 selRx = /^(\+)?([^\@\+]+)?\@?([^\+]+)?(\+)?$/, |
|
56 // set automatically attributes for some tags |
|
57 autoAttr = { |
|
58 IMG:'src', |
|
59 INPUT:'value' |
|
60 }, |
|
61 // check if the argument is an array - thanks salty-horse (Ori Avtalion) |
|
62 isArray = Array.isArray ? |
|
63 function(o) { |
|
64 return Array.isArray(o); |
|
65 } : |
|
66 function(o) { |
|
67 return Object.prototype.toString.call(o) === "[object Array]"; |
|
68 }; |
|
69 |
|
70 /* * * * * * * * * * * * * * * * * * * * * * * * * * |
|
71 core functions |
|
72 * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
73 |
|
74 |
|
75 // error utility |
|
76 function error(e){ |
|
77 if(typeof console !== 'undefined'){ |
|
78 console.log(e); |
|
79 debugger; |
|
80 } |
|
81 throw('pure error: ' + e); |
|
82 } |
|
83 |
|
84 //return a new instance of plugins |
|
85 function getPlugins(){ |
|
86 var plugins = $p.plugins, |
|
87 f = function(){}; |
|
88 f.prototype = plugins; |
|
89 |
|
90 // do not overwrite functions if external definition |
|
91 f.prototype.compile = plugins.compile || compile; |
|
92 f.prototype.render = plugins.render || render; |
|
93 f.prototype.autoRender = plugins.autoRender || autoRender; |
|
94 f.prototype.find = plugins.find || find; |
|
95 |
|
96 // give the compiler and the error handling to the plugin context |
|
97 f.prototype._compiler = compiler; |
|
98 f.prototype._error = error; |
|
99 |
|
100 return new f(); |
|
101 } |
|
102 |
|
103 // returns the outer HTML of a node |
|
104 function outerHTML(node){ |
|
105 // if IE, Chrome take the internal method otherwise build one |
|
106 return node.outerHTML || ( |
|
107 function(n){ |
|
108 var div = document.createElement('div'), h; |
|
109 div.appendChild( n.cloneNode(true) ); |
|
110 h = div.innerHTML; |
|
111 div = null; |
|
112 return h; |
|
113 })(node); |
|
114 } |
|
115 |
|
116 // returns the string generator function |
|
117 function wrapquote(qfn, f){ |
|
118 return function(ctxt){ |
|
119 return qfn('' + f.call(ctxt.item || ctxt.context, ctxt)); |
|
120 }; |
|
121 } |
|
122 |
|
123 // default find using querySelector when available on the browser |
|
124 function find(n, sel){ |
|
125 if(typeof n === 'string'){ |
|
126 sel = n; |
|
127 n = false; |
|
128 } |
|
129 if(typeof document.querySelectorAll !== 'undefined'){ |
|
130 return (n||document).querySelectorAll( sel ); |
|
131 }else{ |
|
132 return error('You can test PURE standalone with: iPhone, FF3.5+, Safari4+ and IE8+\n\nTo run PURE on your browser, you need a JS library/framework with a CSS selector engine'); |
|
133 } |
|
134 } |
|
135 |
|
136 // create a function that concatenates constant string |
|
137 // sections (given in parts) and the results of called |
|
138 // functions to fill in the gaps between parts (fns). |
|
139 // fns[n] fills in the gap between parts[n-1] and parts[n]; |
|
140 // fns[0] is unused. |
|
141 // this is the inner template evaluation loop. |
|
142 function concatenator(parts, fns){ |
|
143 return function(ctxt){ |
|
144 var strs = [ parts[ 0 ] ], |
|
145 n = parts.length, |
|
146 fnVal, pVal, attLine, pos; |
|
147 |
|
148 for(var i = 1; i < n; i++){ |
|
149 fnVal = fns[i].call( this, ctxt ); |
|
150 pVal = parts[i]; |
|
151 |
|
152 // if the value is empty and attribute, remove it |
|
153 if(fnVal === ''){ |
|
154 attLine = strs[ strs.length - 1 ]; |
|
155 if( ( pos = attLine.search( /[^\s]+=\"?$/ ) ) > -1){ |
|
156 strs[ strs.length - 1 ] = attLine.substring( 0, pos ); |
|
157 pVal = pVal.substr( 1 ); |
|
158 } |
|
159 } |
|
160 |
|
161 strs[ strs.length ] = fnVal; |
|
162 strs[ strs.length ] = pVal; |
|
163 } |
|
164 return strs.join(''); |
|
165 }; |
|
166 } |
|
167 |
|
168 // parse and check the loop directive |
|
169 function parseloopspec(p){ |
|
170 var m = p.match( /^(\w+)\s*<-\s*(\S+)?$/ ); |
|
171 if(m === null){ |
|
172 error('bad loop spec: "' + p + '"'); |
|
173 } |
|
174 if(m[1] === 'item'){ |
|
175 error('"item<-..." is a reserved word for the current running iteration.\n\nPlease choose another name for your loop.'); |
|
176 } |
|
177 if( !m[2] || (m[2] && (/context/i).test(m[2]))){ //undefined or space(IE) |
|
178 m[2] = function(ctxt){return ctxt.context;}; |
|
179 } |
|
180 return {name: m[1], sel: m[2]}; |
|
181 } |
|
182 |
|
183 // parse a data selector and return a function that |
|
184 // can traverse the data accordingly, given a context. |
|
185 function dataselectfn (sel){ |
|
186 if( typeof(sel) === 'function' ){ |
|
187 //handle false values in function directive |
|
188 return function ( ctxt ){ |
|
189 var r = sel.call( ctxt.item || ctxt.context || ctxt, ctxt ); |
|
190 return !r && r !== 0 ? '' : r; |
|
191 }; |
|
192 } |
|
193 //check for a valid js variable name with hyphen(for properties only), $, _ and : |
|
194 var m = sel.match(/^[a-zA-Z\$_\@][\w\$:-]*(\.[\w\$:-]*[^\.])*$/); |
|
195 if(m === null){ |
|
196 var found = false, s = sel, parts = [], pfns = [], i = 0, retStr; |
|
197 // check if literal |
|
198 if(/\'|\"/.test( s.charAt(0) )){ |
|
199 if(/\'|\"/.test( s.charAt(s.length-1) )){ |
|
200 retStr = s.substring(1, s.length-1); |
|
201 return function(){ return retStr; }; |
|
202 } |
|
203 }else{ |
|
204 // check if literal + #{var} |
|
205 while((m = s.match(/#\{([^{}]+)\}/)) !== null){ |
|
206 found = true; |
|
207 parts[i++] = s.slice(0, m.index); |
|
208 pfns[i] = dataselectfn(m[1]); |
|
209 s = s.slice(m.index + m[0].length, s.length); |
|
210 } |
|
211 } |
|
212 if(!found){ |
|
213 return function(){ return sel; }; |
|
214 } |
|
215 parts[i] = s; |
|
216 return concatenator(parts, pfns); |
|
217 } |
|
218 m = sel.split('.'); |
|
219 return function(ctxt){ |
|
220 var data = ctxt.context || ctxt, |
|
221 v = ctxt[m[0]], |
|
222 i = 0; |
|
223 if(v && v.item){ |
|
224 i += 1; |
|
225 if(m[i] === 'pos'){ |
|
226 //allow pos to be kept by string. Tx to Adam Freidin |
|
227 return v.pos; |
|
228 }else{ |
|
229 data = v.item; |
|
230 } |
|
231 } |
|
232 var n = m.length; |
|
233 for(; i < n; i++){ |
|
234 if(!data){break;} |
|
235 data = data[m[i]]; |
|
236 } |
|
237 return (!data && data !== 0) ? '':data; |
|
238 }; |
|
239 } |
|
240 |
|
241 // wrap in an object the target node/attr and their properties |
|
242 function gettarget(dom, sel, isloop){ |
|
243 var osel, prepend, selector, attr, append, target = []; |
|
244 if( typeof sel === 'string' ){ |
|
245 osel = sel; |
|
246 var m = sel.match(selRx); |
|
247 if( !m ){ |
|
248 error( 'bad selector syntax: ' + sel ); |
|
249 } |
|
250 |
|
251 prepend = m[1]; |
|
252 selector = m[2]; |
|
253 attr = m[3]; |
|
254 append = m[4]; |
|
255 |
|
256 if(selector === '.' || ( !selector && attr ) ){ |
|
257 target[0] = dom; |
|
258 }else{ |
|
259 target = plugins.find(dom, selector); |
|
260 } |
|
261 if(!target || target.length === 0){ |
|
262 return error('The node "' + sel + '" was not found in the template:\n' + outerHTML(dom).replace(/\t/g,' ')); |
|
263 } |
|
264 }else{ |
|
265 // autoRender node |
|
266 prepend = sel.prepend; |
|
267 attr = sel.attr; |
|
268 append = sel.append; |
|
269 target = [dom]; |
|
270 } |
|
271 |
|
272 if( prepend || append ){ |
|
273 if( prepend && append ){ |
|
274 error('append/prepend cannot take place at the same time'); |
|
275 }else if( isloop ){ |
|
276 error('no append/prepend/replace modifiers allowed for loop target'); |
|
277 }else if( append && isloop ){ |
|
278 error('cannot append with loop (sel: ' + osel + ')'); |
|
279 } |
|
280 } |
|
281 var setstr, getstr, quotefn, isStyle, isClass, attName, setfn; |
|
282 if(attr){ |
|
283 isStyle = (/^style$/i).test(attr); |
|
284 isClass = (/^class$/i).test(attr); |
|
285 attName = isClass ? 'className' : attr; |
|
286 setstr = function(node, s) { |
|
287 node.setAttribute(attPfx + attr, s); |
|
288 if (attName in node && !isStyle) { |
|
289 try{node[attName] = '';}catch(e){} //FF4 gives an error sometimes |
|
290 } |
|
291 if (node.nodeType === 1) { |
|
292 node.removeAttribute(attr); |
|
293 isClass && node.removeAttribute(attName); |
|
294 } |
|
295 }; |
|
296 if (isStyle || isClass) {//IE no quotes special care |
|
297 if(isStyle){ |
|
298 getstr = function(n){ return n.style.cssText; }; |
|
299 }else{ |
|
300 getstr = function(n){ return n.className; }; |
|
301 } |
|
302 }else { |
|
303 getstr = function(n){ return n.getAttribute(attr); }; |
|
304 } |
|
305 quotefn = function(s){ return s.replace(/\"/g, '"'); }; |
|
306 if(prepend){ |
|
307 setfn = function(node, s){ setstr( node, s + getstr( node )); }; |
|
308 }else if(append){ |
|
309 setfn = function(node, s){ setstr( node, getstr( node ) + s); }; |
|
310 }else{ |
|
311 setfn = function(node, s){ setstr( node, s ); }; |
|
312 } |
|
313 }else{ |
|
314 if (isloop) { |
|
315 setfn = function(node, s) { |
|
316 var pn = node.parentNode; |
|
317 if (pn) { |
|
318 //replace node with s |
|
319 pn.insertBefore(document.createTextNode(s), node.nextSibling); |
|
320 pn.removeChild(node); |
|
321 } |
|
322 }; |
|
323 } else { |
|
324 if (prepend) { |
|
325 setfn = function(node, s) { node.insertBefore(document.createTextNode(s), node.firstChild); }; |
|
326 } else if (append) { |
|
327 setfn = function(node, s) { node.appendChild(document.createTextNode(s));}; |
|
328 } else { |
|
329 setfn = function(node, s) { |
|
330 while (node.firstChild) { node.removeChild(node.firstChild); } |
|
331 node.appendChild(document.createTextNode(s)); |
|
332 }; |
|
333 } |
|
334 } |
|
335 quotefn = function(s) { return s; }; |
|
336 } |
|
337 return { attr: attr, nodes: target, set: setfn, sel: osel, quotefn: quotefn }; |
|
338 } |
|
339 |
|
340 function setsig(target, n){ |
|
341 var sig = Sig + n + ':'; |
|
342 for(var i = 0; i < target.nodes.length; i++){ |
|
343 // could check for overlapping targets here. |
|
344 target.set( target.nodes[i], sig ); |
|
345 } |
|
346 } |
|
347 |
|
348 // read de loop data, and pass it to the inner rendering function |
|
349 function loopfn(name, dselect, inner, sorter, filter){ |
|
350 return function(ctxt){ |
|
351 var a = dselect(ctxt), |
|
352 old = ctxt[name], |
|
353 temp = { items : a }, |
|
354 filtered = 0, |
|
355 length, |
|
356 strs = [], |
|
357 buildArg = function(idx, temp, ftr, len){ |
|
358 //keep the current loop. Tx to Adam Freidin |
|
359 var save_pos = ctxt.pos, |
|
360 save_item = ctxt.item, |
|
361 save_items = ctxt.items; |
|
362 ctxt.pos = temp.pos = idx; |
|
363 ctxt.item = temp.item = a[ idx ]; |
|
364 ctxt.items = a; |
|
365 //if array, set a length property - filtered items |
|
366 typeof len !== 'undefined' && (ctxt.length = len); |
|
367 //if filter directive |
|
368 if(typeof ftr === 'function' && ftr.call(ctxt.item, ctxt) === false){ |
|
369 filtered++; |
|
370 return; |
|
371 } |
|
372 strs.push( inner.call(ctxt.item, ctxt ) ); |
|
373 //restore the current loop |
|
374 ctxt.pos = save_pos; |
|
375 ctxt.item = save_item; |
|
376 ctxt.items = save_items; |
|
377 }; |
|
378 ctxt[name] = temp; |
|
379 if( isArray(a) ){ |
|
380 length = a.length || 0; |
|
381 // if sort directive |
|
382 if(typeof sorter === 'function'){ |
|
383 a.sort(sorter); |
|
384 } |
|
385 //loop on array |
|
386 for(var i = 0, ii = length; i < ii; i++){ |
|
387 buildArg(i, temp, filter, length - filtered); |
|
388 } |
|
389 }else{ |
|
390 if(a && typeof sorter !== 'undefined'){ |
|
391 error('sort is only available on arrays, not objects'); |
|
392 } |
|
393 //loop on collections |
|
394 for(var prop in a){ |
|
395 a.hasOwnProperty( prop ) && buildArg(prop, temp, filter); |
|
396 } |
|
397 } |
|
398 |
|
399 typeof old !== 'undefined' ? ctxt[name] = old : delete ctxt[name]; |
|
400 return strs.join(''); |
|
401 }; |
|
402 } |
|
403 // generate the template for a loop node |
|
404 function loopgen(dom, sel, loop, fns){ |
|
405 var already = false, ls, sorter, filter, prop; |
|
406 for(prop in loop){ |
|
407 if(loop.hasOwnProperty(prop)){ |
|
408 if(prop === 'sort'){ |
|
409 sorter = loop.sort; |
|
410 continue; |
|
411 }else if(prop === 'filter'){ |
|
412 filter = loop.filter; |
|
413 continue; |
|
414 } |
|
415 if(already){ |
|
416 error('cannot have more than one loop on a target'); |
|
417 } |
|
418 ls = prop; |
|
419 already = true; |
|
420 } |
|
421 } |
|
422 if(!ls){ |
|
423 error('Error in the selector: ' + sel + '\nA directive action must be a string, a function or a loop(<-)'); |
|
424 } |
|
425 var dsel = loop[ls]; |
|
426 // if it's a simple data selector then we default to contents, not replacement. |
|
427 if(typeof(dsel) === 'string' || typeof(dsel) === 'function'){ |
|
428 loop = {}; |
|
429 loop[ls] = {root: dsel}; |
|
430 return loopgen(dom, sel, loop, fns); |
|
431 } |
|
432 var spec = parseloopspec(ls), |
|
433 itersel = dataselectfn(spec.sel), |
|
434 target = gettarget(dom, sel, true), |
|
435 nodes = target.nodes; |
|
436 |
|
437 for(i = 0; i < nodes.length; i++){ |
|
438 var node = nodes[i], |
|
439 inner = compiler(node, dsel); |
|
440 fns[fns.length] = wrapquote(target.quotefn, loopfn(spec.name, itersel, inner, sorter, filter)); |
|
441 target.nodes = [node]; // N.B. side effect on target. |
|
442 setsig(target, fns.length - 1); |
|
443 } |
|
444 return target; |
|
445 } |
|
446 |
|
447 function getAutoNodes(n, data){ |
|
448 var ns = n.getElementsByTagName('*'), |
|
449 an = [], |
|
450 openLoops = {a:[],l:{}}, |
|
451 cspec, |
|
452 isNodeValue, |
|
453 i, ii, j, jj, ni, cs, cj; |
|
454 //for each node found in the template |
|
455 for(i = -1, ii = ns.length; i < ii; i++){ |
|
456 ni = i > -1 ?ns[i]:n; |
|
457 if(ni.nodeType === 1 && ni.className !== ''){ |
|
458 //when a className is found |
|
459 cs = ni.className.split(' '); |
|
460 // for each className |
|
461 for(j = 0, jj=cs.length;j<jj;j++){ |
|
462 cj = cs[j]; |
|
463 // check if it is related to a context property |
|
464 cspec = checkClass(cj, ni.tagName); |
|
465 // if so, store the node, plus the type of data |
|
466 if(cspec !== false){ |
|
467 isNodeValue = (/nodevalue/i).test(cspec.attr); |
|
468 if(cspec.sel.indexOf('@') > -1 || isNodeValue){ |
|
469 ni.className = ni.className.replace('@'+cspec.attr, ''); |
|
470 if(isNodeValue){ |
|
471 cspec.attr = false; |
|
472 } |
|
473 } |
|
474 an.push({n:ni, cspec:cspec}); |
|
475 } |
|
476 } |
|
477 } |
|
478 } |
|
479 |
|
480 function checkClass(c, tagName){ |
|
481 // read the class |
|
482 var ca = c.match(selRx), |
|
483 attr = ca[3] || autoAttr[tagName], |
|
484 cspec = {prepend:!!ca[1], prop:ca[2], attr:attr, append:!!ca[4], sel:c}, |
|
485 i, ii, loopi, loopil, val; |
|
486 // check in existing open loops |
|
487 for(i = openLoops.a.length-1; i >= 0; i--){ |
|
488 loopi = openLoops.a[i]; |
|
489 loopil = loopi.l[0]; |
|
490 val = loopil && loopil[cspec.prop]; |
|
491 if(typeof val !== 'undefined'){ |
|
492 cspec.prop = loopi.p + '.' + cspec.prop; |
|
493 if(openLoops.l[cspec.prop] === true){ |
|
494 val = val[0]; |
|
495 } |
|
496 break; |
|
497 } |
|
498 } |
|
499 // not found check first level of data |
|
500 if(typeof val === 'undefined'){ |
|
501 val = dataselectfn(cspec.prop)(isArray(data) ? data[0] : data); |
|
502 // nothing found return |
|
503 if(val === ''){ |
|
504 return false; |
|
505 } |
|
506 } |
|
507 // set the spec for autoNode |
|
508 if(isArray(val)){ |
|
509 openLoops.a.push( {l:val, p:cspec.prop} ); |
|
510 openLoops.l[cspec.prop] = true; |
|
511 cspec.t = 'loop'; |
|
512 }else{ |
|
513 cspec.t = 'str'; |
|
514 } |
|
515 return cspec; |
|
516 } |
|
517 |
|
518 return an; |
|
519 |
|
520 } |
|
521 |
|
522 // returns a function that, given a context argument, |
|
523 // will render the template defined by dom and directive. |
|
524 function compiler(dom, directive, data, ans){ |
|
525 var fns = [], j, jj, cspec, n, target, nodes, itersel, node, inner, dsel, sels, sel, sl, i, h, parts, pfns = [], p; |
|
526 // autoRendering nodes parsing -> auto-nodes |
|
527 ans = ans || data && getAutoNodes(dom, data); |
|
528 if(data){ |
|
529 // for each auto-nodes |
|
530 while(ans.length > 0){ |
|
531 cspec = ans[0].cspec; |
|
532 n = ans[0].n; |
|
533 ans.splice(0, 1); |
|
534 if(cspec.t === 'str'){ |
|
535 // if the target is a value |
|
536 target = gettarget(n, cspec, false); |
|
537 setsig(target, fns.length); |
|
538 fns[fns.length] = wrapquote(target.quotefn, dataselectfn(cspec.prop)); |
|
539 }else{ |
|
540 // if the target is a loop |
|
541 itersel = dataselectfn(cspec.sel); |
|
542 target = gettarget(n, cspec, true); |
|
543 nodes = target.nodes; |
|
544 for(j = 0, jj = nodes.length; j < jj; j++){ |
|
545 node = nodes[j]; |
|
546 inner = compiler(node, false, data, ans); |
|
547 fns[fns.length] = wrapquote(target.quotefn, loopfn(cspec.sel, itersel, inner)); |
|
548 target.nodes = [node]; |
|
549 setsig(target, fns.length - 1); |
|
550 } |
|
551 } |
|
552 } |
|
553 } |
|
554 // read directives |
|
555 for(sel in directive){ |
|
556 if(directive.hasOwnProperty(sel)){ |
|
557 i = 0; |
|
558 dsel = directive[sel]; |
|
559 sels = sel.split(/\s*,\s*/); //allow selector separation by quotes |
|
560 sl = sels.length; |
|
561 do{ |
|
562 if(typeof(dsel) === 'function' || typeof(dsel) === 'string'){ |
|
563 // set the value for the node/attr |
|
564 sel = sels[i]; |
|
565 target = gettarget(dom, sel, false); |
|
566 setsig(target, fns.length); |
|
567 fns[fns.length] = wrapquote(target.quotefn, dataselectfn(dsel)); |
|
568 }else{ |
|
569 // loop on node |
|
570 loopgen(dom, sel, dsel, fns); |
|
571 } |
|
572 }while(++i < sl); |
|
573 } |
|
574 } |
|
575 // convert node to a string |
|
576 h = outerHTML(dom); |
|
577 // IE adds an unremovable "selected, value" attribute |
|
578 // hard replace while waiting for a better solution |
|
579 h = h.replace(/<([^>]+)\s(value\=""|selected)\s?([^>]*)>/ig, "<$1 $3>"); |
|
580 |
|
581 // remove attribute prefix |
|
582 h = h.split(attPfx).join(''); |
|
583 |
|
584 // slice the html string at "Sig" |
|
585 parts = h.split( Sig ); |
|
586 // for each slice add the return string of |
|
587 for(i = 1; i < parts.length; i++){ |
|
588 p = parts[i]; |
|
589 // part is of the form "fn-number:..." as placed there by setsig. |
|
590 pfns[i] = fns[ parseInt(p, 10) ]; |
|
591 parts[i] = p.substring( p.indexOf(':') + 1 ); |
|
592 } |
|
593 return concatenator(parts, pfns); |
|
594 } |
|
595 // compile the template with directive |
|
596 // if a context is passed, the autoRendering is triggered automatically |
|
597 // return a function waiting the data as argument |
|
598 function compile(directive, ctxt, template){ |
|
599 var rfn = compiler( ( template || this[0] ).cloneNode(true), directive, ctxt); |
|
600 return function(context){ |
|
601 return rfn({context:context}); |
|
602 }; |
|
603 } |
|
604 //compile with the directive as argument |
|
605 // run the template function on the context argument |
|
606 // return an HTML string |
|
607 // should replace the template and return this |
|
608 function render(ctxt, directive){ |
|
609 var fn = typeof directive === 'function' && directive, i = 0, ii = this.length; |
|
610 for(; i < ii; i++){ |
|
611 this[i] = replaceWith( this[i], (fn || plugins.compile( directive, false, this[i] ))( ctxt, false )); |
|
612 } |
|
613 context = null; |
|
614 return this; |
|
615 } |
|
616 |
|
617 // compile the template with autoRender |
|
618 // run the template function on the context argument |
|
619 // return an HTML string |
|
620 function autoRender(ctxt, directive){ |
|
621 var fn = plugins.compile( directive, ctxt, this[0] ); |
|
622 for(var i = 0, ii = this.length; i < ii; i++){ |
|
623 this[i] = replaceWith( this[i], fn( ctxt, false)); |
|
624 } |
|
625 context = null; |
|
626 return this; |
|
627 } |
|
628 |
|
629 function replaceWith(elm, html) { |
|
630 var ne, |
|
631 ep = elm.parentNode, |
|
632 depth = 0; |
|
633 if(!ep){ //if no parents |
|
634 ep = document.createElement('DIV'); |
|
635 ep.appendChild(elm); |
|
636 } |
|
637 switch (elm.tagName) { |
|
638 case 'TBODY': case 'THEAD': case 'TFOOT': |
|
639 html = '<TABLE>' + html + '</TABLE>'; |
|
640 depth = 1; |
|
641 break; |
|
642 case 'TR': |
|
643 html = '<TABLE><TBODY>' + html + '</TBODY></TABLE>'; |
|
644 depth = 2; |
|
645 break; |
|
646 case 'TD': case 'TH': |
|
647 html = '<TABLE><TBODY><TR>' + html + '</TR></TBODY></TABLE>'; |
|
648 depth = 3; |
|
649 break; |
|
650 } |
|
651 tmp = document.createElement('SPAN'); |
|
652 tmp.style.display = 'none'; |
|
653 document.body.appendChild(tmp); |
|
654 tmp.innerHTML = html; |
|
655 ne = tmp.firstChild; |
|
656 while (depth--) { |
|
657 ne = ne.firstChild; |
|
658 } |
|
659 ep.insertBefore(ne, elm); |
|
660 ep.removeChild(elm); |
|
661 document.body.removeChild(tmp); |
|
662 elm = ne; |
|
663 |
|
664 ne = ep = null; |
|
665 return elm; |
|
666 } |
|
667 |
|
668 return plugins; |
|
669 }; |
|
670 |
|
671 $p.plugins = {}; |
|
672 |
|
673 $p.libs = { |
|
674 dojo:function(){ |
|
675 if(typeof document.querySelector === 'undefined'){ |
|
676 $p.plugins.find = function(n, sel){ |
|
677 return dojo.query(sel, n); |
|
678 }; |
|
679 } |
|
680 }, |
|
681 domassistant:function(){ |
|
682 if(typeof document.querySelector === 'undefined'){ |
|
683 $p.plugins.find = function(n, sel){ |
|
684 return $(n).cssSelect(sel); |
|
685 }; |
|
686 } |
|
687 DOMAssistant.attach({ |
|
688 publicMethods : [ 'compile', 'render', 'autoRender'], |
|
689 compile:function(directive, ctxt){ |
|
690 return $p([this]).compile(directive, ctxt); |
|
691 }, |
|
692 render:function(ctxt, directive){ |
|
693 return $( $p([this]).render(ctxt, directive) )[0]; |
|
694 }, |
|
695 autoRender:function(ctxt, directive){ |
|
696 return $( $p([this]).autoRender(ctxt, directive) )[0]; |
|
697 } |
|
698 }); |
|
699 }, |
|
700 jquery:function(){ |
|
701 if(typeof document.querySelector === 'undefined'){ |
|
702 $p.plugins.find = function(n, sel){ |
|
703 return jQuery(n).find(sel); |
|
704 }; |
|
705 } |
|
706 jQuery.fn.extend({ |
|
707 directives:function(directive){ |
|
708 this._pure_d = directive; return this; |
|
709 }, |
|
710 compile:function(directive, ctxt){ |
|
711 return $p(this).compile(this._pure_d || directive, ctxt); |
|
712 }, |
|
713 render:function(ctxt, directive){ |
|
714 return jQuery( $p( this ).render( ctxt, this._pure_d || directive ) ); |
|
715 }, |
|
716 autoRender:function(ctxt, directive){ |
|
717 return jQuery( $p( this ).autoRender( ctxt, this._pure_d || directive ) ); |
|
718 } |
|
719 }); |
|
720 }, |
|
721 mootools:function(){ |
|
722 if(typeof document.querySelector === 'undefined'){ |
|
723 $p.plugins.find = function(n, sel){ |
|
724 return $(n).getElements(sel); |
|
725 }; |
|
726 } |
|
727 Element.implement({ |
|
728 compile:function(directive, ctxt){ |
|
729 return $p(this).compile(directive, ctxt); |
|
730 }, |
|
731 render:function(ctxt, directive){ |
|
732 return $p([this]).render(ctxt, directive); |
|
733 }, |
|
734 autoRender:function(ctxt, directive){ |
|
735 return $p([this]).autoRender(ctxt, directive); |
|
736 } |
|
737 }); |
|
738 }, |
|
739 prototype:function(){ |
|
740 if(typeof document.querySelector === 'undefined'){ |
|
741 $p.plugins.find = function(n, sel){ |
|
742 n = n === document ? n.body : n; |
|
743 return typeof n === 'string' ? $$(n) : $(n).select(sel); |
|
744 }; |
|
745 } |
|
746 Element.addMethods({ |
|
747 compile:function(element, directive, ctxt){ |
|
748 return $p([element]).compile(directive, ctxt); |
|
749 }, |
|
750 render:function(element, ctxt, directive){ |
|
751 return $p([element]).render(ctxt, directive); |
|
752 }, |
|
753 autoRender:function(element, ctxt, directive){ |
|
754 return $p([element]).autoRender(ctxt, directive); |
|
755 } |
|
756 }); |
|
757 }, |
|
758 sizzle:function(){ |
|
759 if(typeof document.querySelector === 'undefined'){ |
|
760 $p.plugins.find = function(n, sel){ |
|
761 return Sizzle(sel, n); |
|
762 }; |
|
763 } |
|
764 }, |
|
765 sly:function(){ |
|
766 if(typeof document.querySelector === 'undefined'){ |
|
767 $p.plugins.find = function(n, sel){ |
|
768 return Sly(sel, n); |
|
769 }; |
|
770 } |
|
771 }, |
|
772 yui:function(){ //Thanks to https://github.com/soljin |
|
773 if(typeof document.querySelector === 'undefined'){ |
|
774 YUI().use("node",function(Y){ |
|
775 $p.plugins.find = function(n, sel){ |
|
776 return Y.NodeList.getDOMNodes(Y.one(n).all(sel)); |
|
777 }; |
|
778 }); |
|
779 } |
|
780 YUI.add("pure-yui",function(Y){ |
|
781 Y.Node.prototype.directives = function(directive){ |
|
782 this._pure_d = directive; return this; |
|
783 }; |
|
784 Y.Node.prototype.compile = function(directive, ctxt){ |
|
785 return $p([this._node]).compile(this._pure_d || directive, ctxt); |
|
786 }; |
|
787 Y.Node.prototype.render = function(ctxt, directive){ |
|
788 return Y.one($p([this._node]).render(ctxt, this._pure_d || directive)); |
|
789 }; |
|
790 Y.Node.prototype.autoRender = function(ctxt, directive){ |
|
791 return Y.one($p([this._node]).autoRender(ctxt, this._pure_d || directive)); |
|
792 }; |
|
793 },"0.1",{requires:["node"]}); |
|
794 } |
|
795 }; |
|
796 |
|
797 // get lib specifics if available |
|
798 (function(){ |
|
799 var libkey = |
|
800 typeof dojo !== 'undefined' && 'dojo' || |
|
801 typeof DOMAssistant !== 'undefined' && 'domassistant' || |
|
802 typeof jQuery !== 'undefined' && 'jquery' || |
|
803 typeof MooTools !== 'undefined' && 'mootools' || |
|
804 typeof Prototype !== 'undefined' && 'prototype' || |
|
805 typeof Sizzle !== 'undefined' && 'sizzle' || |
|
806 typeof Sly !== 'undefined' && 'sly' || |
|
807 typeof YUI !== 'undefined' && 'yui'; |
|
808 |
|
809 libkey && $p.libs[libkey](); |
|
810 |
|
811 //for node.js |
|
812 if(typeof exports !== 'undefined'){ |
|
813 exports.$p = $p; |
|
814 } |
|
815 })(); |