Skip to content
This repository
Browse code

live template binding

  • Loading branch information...
commit f78e0e86c1a189f39611d7e755f5b96039b80504 1 parent 3528bf9
Justin Meyer justinbmeyer authored
5 lang/observe/observe.js
@@ -315,8 +315,9 @@ steal('jquery/class/class_core.js',function() {
315 315 var tAttr= typeof attr;
316 316 if(tAttr != 'string' && tAttr != 'number'){
317 317 return this._attrs(attr, val)
318   - }else if ( val === undefined ) {
319   - // if we are getting a value
  318 + }else if ( val === undefined ) {// if we are getting a value
  319 + // let people know we are reading (
  320 + $.Observe.__reading && $.Observe.__reading(this, attr)
320 321 return this._get(attr)
321 322 } else {
322 323 // otherwise we are setting
3  view/ejs/binding.ejs
... ... @@ -0,0 +1,3 @@
  1 +<div class='<%= task.attr('completed') ? "complete" %>'>
  2 + <%== task.attr('name') %>
  3 +</div>
16 view/ejs/ejs.html
... ... @@ -0,0 +1,16 @@
  1 +<html>
  2 + <head>
  3 +
  4 + </head>
  5 + <body>
  6 + <script type='text/javascript' src='../../../steal/steal.js'></script>
  7 + <script type='text/javascript'>
  8 + steal('jquery/view/ejs/ejs2.js', function(){
  9 + $.get('test_template.ejs',{},function(text){
  10 + console.log(EJS(text,{}))
  11 + })
  12 + })
  13 + </script>
  14 + </body>
  15 +
  16 +</html>
152 view/ejs/ejs.js
@@ -213,10 +213,23 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
213 213 var v = new EJS.Helpers(object, extraHelpers || {});
214 214 return this.template.fn.call(object, object, v);
215 215 };
  216 + var liveBind = function(observed, el, cb){
  217 + $.each(observed, function(i, ob){
  218 + ob.cb = function(){
  219 + console.log('prop changed!')
  220 + cb()
  221 + }
  222 + ob.obj.bind(ob.attr, ob.cb)
  223 + })
  224 + $(el).bind('destroyed', function(){
  225 + $.each(observed, function(i, ob){
  226 + ob.obj.unbind(ob.attr, ob.cb)
  227 + })
  228 + })
  229 + };
216 230 /**
217 231 * @Static
218 232 */
219   -
220 233 extend(EJS, {
221 234 /**
222 235 * Used to convert what's in &lt;%= %> magic tags to a string
@@ -245,7 +258,64 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
245 258 * * a function - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View
246 259 * * an array - the attribute "data-view-id='XX'", where XX is a hookup number for jQuery.View
247 260 */
248   - text: function( input ) {
  261 + text: function(status, self, func ) {
  262 + console.log('holler')
  263 + // wire up observe listener
  264 + var observed = [];
  265 + $.Observe.__reading = function(obj, attr){
  266 + observed.push({
  267 + obj : obj,
  268 + attr : attr
  269 + })
  270 + }
  271 + var input = func.call(self);
  272 + delete $.Observe.__reading;
  273 + if(observed.length) {
  274 + if(status === 0){ // we are in some html ... (we can't know this!)
  275 + // return a span with a hookup function ...
  276 + return "<span "
  277 + + "data-view-id='" + $View.hookup(function(el){
  278 + // remove child, bind on parent
  279 + var parent = el.parentNode;
  280 + // get where el is ...
  281 + for(var i=0; i < parent.childNodes.length; i++){
  282 + if(parent.childNodes[i] === el){
  283 + break;
  284 + }
  285 + }
  286 + var node = document.createTextNode(input);
  287 + parent.insertBefore(node, el)
  288 + parent.removeChild(el);
  289 +
  290 +
  291 + // create textNode
  292 + liveBind(observed, parent, function(){
  293 + node.nodeValue = func.call(self)
  294 + })
  295 + }) + "'></span>"
  296 + } else if(status === 1){
  297 + return input;
  298 + // mark at end!
  299 + } else {
  300 +
  301 +
  302 + pendingHookups.push(function(el){
  303 + var attr = el.getAttribute(status);
  304 + var parts = attr.split("__!@#$%__")
  305 + parts.splice(1,0,input)
  306 + el.setAttribute(status, parts.join(""))
  307 +
  308 + liveBind(observed, parent, function(){
  309 + parts[1] = func.call(self)
  310 + el.setAttribute(status, parts.join(""))
  311 + })
  312 + })
  313 + return "__!@#$%__";
  314 + return input;
  315 + }
  316 +
  317 + }
  318 + console.log(observed)
249 319 // if it's a string, return
250 320 if ( typeof input == 'string' ) {
251 321 return input;
@@ -280,6 +350,19 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
280 350 // finally, if all else false, toString it
281 351 return input.toString ? input.toString() : "";
282 352 },
  353 + pending : function(){
  354 + if(pendingHookups.length){
  355 + var hooks = pendingHookups.slice(0);
  356 + pendingHookups = [];
  357 + return " data-view-id='" + $View.hookup(function(el){
  358 + $.each(hooks, function(i, fn){
  359 + fn(el);
  360 + })
  361 + }) + "'";
  362 + }else {
  363 + return "";
  364 + }
  365 + },
283 366 /**
284 367 * Escapes the text provided as html if it's a string.
285 368 * Otherwise, the value is passed to EJS.text(text).
@@ -327,6 +410,17 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
327 410 }
328 411
329 412 },
  413 + htmlTag =null,
  414 + quote = null,
  415 + beforeQuote = null,
  416 + status = function(){
  417 + // t - 1
  418 + // h - 0
  419 + // q - string beforeQuote
  420 +
  421 + return quote ? "'"+beforeQuote.match(/([^\s]+)=$/)[1]+"'" : (htmlTag ? 1 : 0)
  422 + },
  423 + pendingHookups = [],
330 424 scanline = function( scanner, line, block ) {
331 425 scanner.lines++;
332 426 var line_split = rSplit(line, scanner.splitter),
@@ -355,7 +449,9 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
355 449 scan: scan,
356 450 lines: 0
357 451 });
358   - scanner.splitter = new RegExp("(" + [scanner.dLeft, scanner.dRight, scanner.eeLeft, scanner.eLeft, scanner.cmnt, scanner.left, scanner.right + '\n', scanner.right, '\n'].join(")|(").
  452 + scanner.splitter = new RegExp("(" + [scanner.dLeft, scanner.dRight, scanner.eeLeft,
  453 + scanner.eLeft, scanner.cmnt, scanner.left,
  454 + scanner.right + '\n', scanner.right, '\n',"<",">","'",'"'].join(")|(").
359 455 replace(/\[/g, "\\[").replace(/\]/g, "\\]") + ")");
360 456 return scanner;
361 457 },
@@ -387,11 +483,10 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
387 483 content = '',
388 484 // adds something to be inserted into the view template
389 485 // this comes out looking like __v1ew.push("CONENT")
390   - put = function( content ) {
391   - buff.push(put_cmd, '"', clean(content), '");');
  486 + put = function( content, bonus ) {
  487 + buff.push(put_cmd, '"', clean(content), '"'+(bonus||'')+');');
392 488 },
393   - // the starting magic tag
394   - startTag = null,
  489 +
395 490 // cleans the running content
396 491 empty = function() {
397 492 content = ''
@@ -401,8 +496,13 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
401 496 // a stack used to keep track of how we should end a bracket }
402 497 // once we have a <%= %> with a leftBracket
403 498 // we store how the file should end here (either '))' or ';' )
404   - endStack =[];
  499 + endStack =[],
  500 + lastToken,
  501 + startTag = null;
405 502
  503 + // re-init the tag goodness
  504 + htmlTag = quote = beforeQuote = null;
  505 +
406 506 // start going token to token
407 507 scan(makeScanner(left, left === '[' ? ']' : '>'), source || "", function( token, scanner ) {
408 508 // if we don't have a start pair
@@ -434,6 +534,36 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
434 534 // replace <%% with <%
435 535 content += scanner.left;
436 536 break;
  537 + case '<':
  538 + htmlTag = '<'
  539 + content += token;
  540 + console.log('<');
  541 +
  542 + break;
  543 + case '>':
  544 + htmlTag = null;
  545 + console.log('>');
  546 + // add some code that checks for pending hookups?
  547 +
  548 +
  549 + put(content, ",jQuery.EJS.pending(),\">\"");
  550 + empty();
  551 +
  552 +
  553 + break;
  554 + case "'":
  555 + case '"':
  556 + console.log('q')
  557 + if(htmlTag){
  558 + if(quote && quote === token){
  559 + quote = null;
  560 + console.log('eq')
  561 + } else if(quote === null){
  562 + quote = token;
  563 + beforeQuote = lastToken;
  564 + console.log('Q')
  565 + }
  566 + }
437 567 default:
438 568 content += token;
439 569 break;
@@ -492,13 +622,13 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
492 622 endStack.push(doubleParen)
493 623 }
494 624
495   - buff.push(insert_cmd, "jQuery.EJS.text(", content,
  625 + buff.push(insert_cmd, "jQuery.EJS.text("+status()+",this,function(){ return ", content,
496 626 // if we have a block
497 627 bn ?
498 628 // start w/ startTxt "var _v1ew = [])"
499 629 startTxt :
500 630 // if not, add doubleParent to close push and text
501   - doubleParen
  631 + "}"+doubleParen
502 632 );
503 633 break;
504 634 }
@@ -512,7 +642,9 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) {
512 642 content += token;
513 643 break;
514 644 }
  645 +
515 646 }
  647 + lastToken = token;
516 648 })
517 649 if ( content.length > 0 ) {
518 650 // Should be content.dump in Ruby
147 view/ejs/ejs2.js
... ... @@ -0,0 +1,147 @@
  1 +steal('jquery',function(){
  2 +
  3 + EJS = function(template){
  4 + var split = template.split(tokens);
  5 + scan(split)
  6 + return split;
  7 + }
  8 + var returnReg = /\r\n/g,
  9 + retReg = /\r/g,
  10 + newReg = /\n/g,
  11 + nReg = /\n/,
  12 + slashReg = /\\/g,
  13 + quoteReg = /"/g,
  14 + singleQuoteReg = /'/g,
  15 + tabReg = /\t/g;
  16 + var clean = function( content ) {
  17 + return content.replace(slashReg, '\\\\').replace(newReg, '\\n').replace(quoteReg, '\\"').replace(tabReg, '\\t');
  18 + }
  19 + var put_cmd = "___v1ew.push(",
  20 + insert_cmd = put_cmd,
  21 + // the text that starts the view code (or block function)
  22 + startTxt = 'var ___v1ew = [];',
  23 + // the text that ends the view code (or block function)
  24 + finishTxt = "return ___v1ew.join('')",
  25 + setTokenAndSaveContent = function(state, token){
  26 + state.tag = token;
  27 + put(state)
  28 + },
  29 + put = function( state ) {
  30 + if(state.content.length > 0) {
  31 + state.buff.push(put_cmd, '"', clean(state.content.join('')), '");');
  32 + }
  33 +
  34 + }
  35 + var rules = ["<%%","%%>","<%==","<%=","<%#","<%","%>","<",">",'"',"'"];
  36 + var tokens = new RegExp("(" +rules.join("|")+")")
  37 +
  38 + var scan = function(tokens){
  39 + var content = [],
  40 + buff= [],
  41 + startTag =null,
  42 + htmlTag = null;
  43 +
  44 + while(tokens.length){
  45 + var token = tokens.shift();
  46 +
  47 + if ( startTag === null ) {
  48 + switch ( token ) {
  49 + case scanner.left:
  50 + case scanner.eLeft:
  51 + case scanner.eeLeft:
  52 + case scanner.cmnt:
  53 + // a new line, just add whatever content w/i a clean
  54 + // reset everything
  55 + startTag = token;
  56 + if ( content.length > 0 ) {
  57 + put(content);
  58 + }
  59 + empty();
  60 + break;
  61 +
  62 + case scanner.dLeft:
  63 + // replace <%% with <%
  64 + content += scanner.left;
  65 + break;
  66 + default:
  67 + content += token;
  68 + break;
  69 + }
  70 + }
  71 + else {
  72 + //we have a start tag
  73 + switch ( token ) {
  74 + case scanner.right:
  75 + // %>
  76 + switch ( startTag ) {
  77 + case scanner.left:
  78 + // <%
  79 +
  80 + // get the number of { minus }
  81 + bn = bracketNum(content);
  82 + // how are we ending this statement
  83 + var last =
  84 + // if the stack has value and we are ending a block
  85 + endStack.length && bn == -1 ?
  86 + // use the last item in the block stack
  87 + endStack.pop() :
  88 + // or use the default ending
  89 + ";";
  90 +
  91 + // if we are ending a returning block
  92 + // add the finish text which returns the result of the
  93 + // block
  94 + if(last === doubleParen) {
  95 + buff.push(finishTxt)
  96 + }
  97 + // add the remaining content
  98 + buff.push(content, last);
  99 +
  100 + // if we have a block, start counting
  101 + if(bn === 1 ){
  102 + endStack.push(";")
  103 + }
  104 + break;
  105 + case scanner.eLeft:
  106 + // <%= clean content
  107 + bn = bracketNum(content);
  108 + if( bn ) {
  109 + endStack.push(doubleParen)
  110 + }
  111 + buff.push(insert_cmd, "jQuery.EJS.clean(", content,bn ? startTxt : doubleParen);
  112 + break;
  113 + case scanner.eeLeft:
  114 + // <%== content
  115 +
  116 + // get the number of { minus }
  117 + bn = bracketNum(content);
  118 + // if we have more {, it means there is a block
  119 + if( bn ){
  120 + // when we return to the same # of { vs } end wiht a doubleParen
  121 + endStack.push(doubleParen)
  122 + }
  123 +
  124 + buff.push(insert_cmd, "jQuery.EJS.text(", content,
  125 + // if we have a block
  126 + bn ?
  127 + // start w/ startTxt "var _v1ew = [])"
  128 + startTxt :
  129 + // if not, add doubleParent to close push and text
  130 + doubleParen
  131 + );
  132 + break;
  133 + }
  134 + startTag = null;
  135 + empty();
  136 + break;
  137 + case scanner.dRight:
  138 + content += scanner.right;
  139 + break;
  140 + default:
  141 + content += token;
  142 + break;
  143 + }
  144 + }
  145 + }
  146 + }
  147 +});
21 view/ejs/ejs_test.js
... ... @@ -1,4 +1,5 @@
1   -steal('funcunit/qunit','jquery/view/ejs', function(){
  1 +steal('funcunit/qunit','jquery/view/ejs',
  2 + 'jquery/lang/observe',function(){
2 3 module("jquery/view/ejs, rendering",{
3 4 setup : function(){
4 5
@@ -98,4 +99,22 @@ test("returning blocks", function(){
98 99 equals(res.match(/ItemsLength4/g).length, 4, "innerBlock and each")
99 100 });
100 101
  102 +test("binding", function(){
  103 + var text = "<div class='<%== task.attr('completed') ? 'complete' : ''%>'><%== task.attr('name') %></div>";
  104 + var task = new $.Observe({
  105 + name : 'dishes'
  106 + })
  107 + var compiled = new $.EJS({text: text}).render({task: task}) ;
  108 + var div = $('<div/>').html(compiled)
  109 +
  110 + console.log(div.html());
  111 + task.attr('name','lawn')
  112 + console.log(div.html());
  113 + task.attr('completed', true);
  114 + console.log(div.html());
  115 +});
  116 +
  117 +
  118 +
  119 +
101 120 })

0 comments on commit f78e0e8

Please sign in to comment.
Something went wrong with that request. Please try again.