Skip to content
This repository
Browse code

Switch to using slash based path separator in attribute references

  • Loading branch information...
commit 2d6b26081f7d0999cfeddbf97263b54d30d00507 1 parent c4d54a9
Kris Zyp authored

Showing 3 changed files with 176 additions and 95 deletions. Show diff stats Hide diff stats

  1. +1 1  test/converter.js
  2. +5 6 test/try-it.html
  3. +170 88 xstyle.js
2  test/converter.js
... ... @@ -1,7 +1,7 @@
1 1 define(['dbind/bind', 'xstyle/util/create-style-sheet', 'xstyle/elemental'], function(bind, createStyleSheet, elemental){
2 2 var model = {
3 3 data: '{\n "first": "Web",\n "last": "Developer",\n "favorites": [\n "Data Bindings", "CSS Extensions"\n ]\n}',
4   - ui: "#target {\n => h2 {\n innerHTML = data.first+' '+data.last;\n },\n ul {\n from: data.favorites;\neach:span {from:item[0];}; };\n background-color: red;\n width: 100px;\n height: 100px;\n}",
  4 + ui: "#target {\n => h2 {\n innerHTML = body/data/first+' '+body/data/last;\n },\n ul {\n from: /data/favorites;\neach:span {from:item[0];}; };\n background-color: red;\n width: 100px;\n height: 100px;\n}",
5 5 parsed: {}};
6 6 var converter = bind(model);
7 7 converter.get('data', update);
11 test/try-it.html
@@ -13,7 +13,7 @@
13 13 body {
14 14 save-parser: true;
15 15 converter = require('xstyle/test/converter');
16   - data = converter.parsed;
  16 + data = /converter/parsed;
17 17 font-family: "Segoe UI", "Segoe WP", "Helvetica Neue", Roboto, sans-serif;
18 18 =>
19 19 h1 'xstyle',
@@ -21,25 +21,24 @@
21 21 div.column {=>
22 22 h2 'data:',
23 23 textarea[data-continuous].live-source {
24   - value = converter.data;
  24 + value = body/converter/data;
25 25 },
26 26 span.error {
27   - value = converter.data.error;
  27 + value = body/converter/data/error;
28 28 }
29 29 },
30 30 div.column {=>
31 31 h2 'presentation:',
32 32 textarea[data-continuous].live-source {
33   - value = converter.ui;
  33 + value = body/converter/ui;
34 34 },
35 35 span.error {
36   - value = converter.ui.error
  36 + value = body/converter/ui/error
37 37 }
38 38 },
39 39 div.column {=>
40 40 h2 'result: #target',
41 41 div#target {
42   - innerHTML = converter.result;
43 42 title = 'The result of the data rendered according to the presentation CSS';
44 43 }
45 44 },
258 xstyle.js
@@ -21,13 +21,55 @@ define("xstyle/xstyle", ["require", "put-selector/put"], function (require, put)
21 21 var doubleQuoteScan = /((?:\\.|[^"])*)"/g;
22 22 var commentScan = /\*\//g;
23 23 var nextId = 0;
24   - var globalAttributes = {Math:Math, require: function(mid){
25   - return {
26   - then: function(callback){
27   - require([mid], callback);
  24 + function when(value, callback){
  25 + return value && value.then ?
  26 + value.then(callback) : callback(value);
  27 + }
  28 + function whenever(value, callback){
  29 + return value && value.then ?
  30 + value.then(function(value){
  31 + return whenever(value, callback);
  32 + }) :
  33 + value && value.to ?
  34 + value.to(callback) :
  35 + callback(value);
  36 +
  37 + }
  38 + function get(target, path, callback){
  39 + return when(target, function(target){
  40 + if(path.length){
  41 + var name = path[0];
  42 + return get(target && (target.get ?
  43 + target.get(name) :
  44 + target = target[name]), path.slice(1), callback);
  45 + }else{
  46 + return target && target.to ?
  47 + target.to(callback) :
  48 + callback(target);
28 49 }
29   - };
30   - }};
  50 + });
  51 + }
  52 +
  53 + var globalAttributes = {
  54 + Math:Math,
  55 + require: function(mid){
  56 + return {
  57 + then: function(callback){
  58 + require([mid], callback);
  59 + }
  60 + };
  61 + },
  62 + get: function(target){
  63 + // used by the constructed reactive functions
  64 + for(var i = 1; i < arguments.length; i++){
  65 + var name = arguments[i];
  66 + target = target && (target.get ?
  67 + target.get(name) :
  68 + target = target[name]);
  69 + }
  70 + return target && target.value || target;
  71 + }
  72 + };
31 73 var undef, testDiv = document.createElement("div");
32 74 function search(tag){
33 75 var elements = document.getElementsByTagName(tag);
@@ -215,32 +257,53 @@ console.log("add", selector, cssText);
215 257 return;
216 258 }
217 259 }else if(name){
218   - var target, variables = [], id = 0, variableLength = 0, callbacks = [],
219   - parameterized = false;
220   - var expression = [];
221   - var parts = value.sort ? value : [value];
  260 + var target, variables = [], parameters = [], id = 0, variableLength = 0, callbacks = [],
  261 + attributeParts, expression = value.join ? value.join("") : value.toString(),
  262 + simpleExpression = expression.match(/^[\w_$\/\.]*$/);
222 263 // Do the parsing and function creation just once, and adapt the dependencies for the element at creation time
223 264 // deal with an array, converting strings to JS-eval'able strings
224   - for(var i = 0;i < parts.length;i++){
225   - var part = parts[i];
226 265 // find all the variables in the expression
227   - parts[i] = part.toString().replace(/("[^\"]*")|([a-zA-Z_$][\w_$\.]*)/g, function(t, string, variable){
228   - if(variable){
229   - // for each reference, we break apart into variable reference and property references after each dot
230   - var parts = variable.split('.');
231   - variables.push(parts[0]);
232   - // we will reference the variable a function argument in the function we will create
233   - if(parts.length > 1){
234   - return 'this.get(' + parts[0] + ',' + parts.slice(1).map(JSON.stringify).join(',') + ')';
  266 + expression = expression.replace(/("[^\"]*")|([\w_]*\/[\w_$\/]*)|([\w_$\.]+)/g, function(t, string, attribute, global){
  267 + if(attribute){
  268 + // for each reference, we break apart into variable reference and property references after each dot
  269 + attributeParts = attribute.split('/');
  270 + var parameterName = attributeParts.join('_');
  271 + parameters.push(parameterName);
  272 + attributeParts[0] = attributeParts[0].toUpperCase();
  273 + variables.push(attributeParts);
  274 + // we will reference the variable a function argument in the function we will create
  275 + return parameterName;
  276 + }else if(global){
  277 + return 'this.' + global;
  278 + }
  279 + return t;
  280 + })
  281 +
  282 + if(simpleExpression){
  283 + // a direct reversible reference
  284 + // no forward reactive needed
  285 + // create the reverse function
  286 + var reversal = function(element, name, value){
  287 + when(findAttributeInAncestors(element, attributeParts[0], attributeParts[1]), function(target){
  288 + for(var i = 2; i < attributeParts.length -1; i++){
  289 + var name = attributeParts[i];
  290 + target = target.get ?
  291 + target.get(name) :
  292 + target[name];
235 293 }
236   - }
237   - return t;
238   - })
239   - }
240   - expression = parts.join('');
241   - if(expression.length > variableLength){
  294 + var name = attributeParts[i];
  295 + if(target.set){
  296 + target.set(name, value);
  297 + }else{
  298 + target[name] = value;
  299 + }
  300 + });
  301 + };
  302 + reversal.rule = this;
  303 + (reversalOfAttributes[name] || (reversalOfAttributes[name] = [])).push(reversal);
  304 + }else{
242 305 // it's a full expression, so we create a time-varying bound function with the expression
243   - var reactiveFunction = Function.apply(xstyle, variables.concat(['return ' + expression]));
  306 + var reactiveFunction = Function.apply(globalAttributes, parameters.concat(['return ' + expression]));
244 307 }
245 308 }
246 309 var rule = this; // TODO: can this be passed by addRenderer?
@@ -250,41 +313,36 @@ console.log("add", selector, cssText);
250 313 var waiting = 1;
251 314 var callbacks = [];
252 315 for(var i = 0; i < variables.length; i++){
253   - // TODO: add support for promises
254   - var value = findAttributeInAncestors(element, variables[i], setupRule);
255   -/* for(var j = 1; j < parts.length; j++){
256   - value = value && (value.get ? value.get(part[i]) : value[parts[i]]);
257   - }*/
258   - satisfied[i] = value;
259   - if(value && value.then){
260   - waiting++;
261   - (function(i){
262   - value.then(function(value){
  316 + var path = variables[i];
  317 + var value = findAttributeInAncestors(element, path[0], path[1], setupRule);
  318 + waiting++;
  319 + get(value, path.slice(2), (function(i){
  320 + return function(value){
  321 + // TODO: use another array to keep track of which have been satisfied
263 322 satisfied[i] = value;
264 323 done();
265   - });
266   - })(i);
267   - }
  324 + };
  325 + })(i));
268 326 }
269   - done(value);
270   - function done(value){
271   - if(--waiting == 0){
272   - value = reactiveFunction ? reactiveFunction.apply(xstyle, satisfied) : value;
273   - }
274   - for(var i = 0; i < callbacks.length; i++){
275   - callbacks[i](value);
  327 + done();
  328 + function done(){
  329 + if(--waiting < 1){
  330 + var value = reactiveFunction ? reactiveFunction.apply(globalAttributes, satisfied) : satisfied[0];
  331 + for(var i = 0; i < callbacks.length; i++){
  332 + callbacks[i](value);
  333 + }
  334 + setAttribute(element, name, value, false);
276 335 }
277   - element[name] = value;
278 336 }
279 337 if(waiting != 0){
280   - element[name] = {
  338 + setAttribute(element, name, {
281 339 then: function(callback){
282 340 callbacks.push(callback);
283 341 },
284 342 toString: function(){
285 343 return "Loading";
286 344 }
287   - }
  345 + }, false);
288 346 }
289 347 }
290 348 recompute(element, rule);
@@ -310,6 +368,9 @@ console.log("add", selector, cssText);
310 368 properties.push(name);
311 369 properties[name] = property;
312 370 },
  371 + set: function(element, name, value){
  372 +
  373 + },
313 374 cssText: ""
314 375 };
315 376 function Call(value){
@@ -583,51 +644,80 @@ console.log("add", selector, cssText);
583 644 search('style');
584 645
585 646
586   - var rulesListeningToAttribute = {};
587   -
588   - function findAttributeInAncestors(element, name, listeningRule){
  647 + var rulesListeningToAttribute = {},
  648 + reversalOfAttributes = {};
  649 +
  650 + function findAttributeInAncestors(element, tag, name, listeningRule){
589 651 var elementAncestor = element;
590 652 if(listeningRule){
  653 + // TODO: make it specific to tag as well
591 654 var rules = (rulesListeningToAttribute[name] || (rulesListeningToAttribute[name] = []));
592 655 rules.push(listeningRule);
593 656 }
594 657 do{
595   - var value = elementAncestor[name];
596   - // if we have a callback, setup a listener
597   - if(listeningRule){
598   - var oldDescriptor = Object.getOwnPropertyDescriptor(elementAncestor, name);
599   - var descriptor, setter = oldDescriptor && oldDescriptor.set;
600   - if(!setter){ // only if the a setter hasn't already been defined
601   - if(!descriptor){ // create the descriptor just once, and reuse
602   - // determine if we should call setAttribute
603   - var useSetAttribute = typeof value == 'string'; descriptor = {
604   - get: function(){
605   - return this._values && this._values[name];
606   - },
607   - set: function(value){
608   - (this._values || (this._values = {}))[name] = value;
609   - // set the attribute, the default action
610   - useSetAttribute && this.setAttribute(name, value);
611   - for(var i = 0; i < rules.length; i++){
612   - var rule = rules[i];
613   - var nodeList = this.querySelectorAll(rule.selector);
614   - for(var j = 0; j < nodeList.length; j++){
615   - rule.recomputeAttribute(nodeList[j], name);
  658 + if(!tag || tag == elementAncestor.tagName){
  659 + var value = elementAncestor[name];
  660 + // if we have a callback, setup a listener
  661 + if(listeningRule && xstyle.configDOMSetters){
  662 + var oldDescriptor = Object.getOwnPropertyDescriptor(elementAncestor, name);
  663 + var descriptor, setter = oldDescriptor && oldDescriptor.set;
  664 + if(!setter){ // only if the a setter hasn't already been defined
  665 + if(!descriptor){ // create the descriptor just once, and reuse
  666 + // determine if we should call setAttribute
  667 + var useSetAttribute = typeof value == 'string'; descriptor = {
  668 + get: function(){
  669 + return this._values && this._values[name];
  670 + },
  671 + set: function(value){
  672 + (this._values || (this._values = {}))[name] = value;
  673 + if(!callingFromSetAttribute){
  674 + setAttribute(this, name, value, undefined, useSetAttribute);
616 675 }
617 676 }
618 677 }
619 678 }
  679 + value && ((elementAncestor._values || (elementAncestor._values = {}))[name] = value);
  680 + Object.defineProperty(elementAncestor, name, descriptor);
620 681 }
621   - value && ((elementAncestor._values || (elementAncestor._values = {}))[name] = value);
622   - Object.defineProperty(elementAncestor, name, descriptor);
623 682 }
624 683 }
625   -
626 684 }while(!value && (elementAncestor != globalAttributes) &&
627 685 (elementAncestor = elementAncestor.parentNode || globalAttributes));
628 686 return value;
629 687 }
630   -
  688 + var callingFromSetAttribute;
  689 + function setAttribute(element, name, value, put, useSetAttribute){
  690 + callingFromSetAttribute = true;
  691 + element[name] = value;
  692 + callingFromSetAttribute = false;
  693 + // set the attribute, the default action
  694 + useSetAttribute && this.setAttribute(name, value);
  695 + if(put !== true){
  696 + var rules = rulesListeningToAttribute[name] || 0;
  697 + for(var i = 0; i < rules.length; i++){
  698 + var rule = rules[i];
  699 + var nodeList = element.querySelectorAll(rule.selector);
  700 + for(var j = 0; j < nodeList.length; j++){
  701 + rule.recomputeAttribute(nodeList[j], name);
  702 + }
  703 + }
  704 + }
  705 + if(put !== false){
  706 + var reversals = reversalOfAttributes[name] || 0;
  707 + for(var i = 0; i < reversals.length; i++){
  708 + var reversal = reversals[i];
  709 + if(matchesSelector.call(element, reversal.rule.selector)){
  710 + reversal(element, name, value);
  711 + }
  712 + }
  713 + }
  714 + }
  715 + // using delegation, listen for any input changes in the document and "put" the value
  716 + // TODO: add a hook so one could add support for IE8
  717 + document.addEventListener('change', function(event){
  718 + var element = event.target;
  719 + setAttribute(element, 'value', element.value, true);
  720 + });
631 721 // elemental section
632 722 var testDiv = document.createElement("div");
633 723 var features = {
@@ -828,16 +918,6 @@ console.log("add", selector, cssText);
828 918 });
829 919 },
830 920 parse: parse,
831   - get: function(target){
832   - // used by the constructed reactive functions
833   - for(var i = 1; i < arguments.length; i++){
834   - var name = arguments[i];
835   - target = target && (target.get ?
836   - target.get(name) :
837   - target = target[name]);
838   - }
839   - return target && target.value || target;
840   - },
841 921
842 922 addRenderer: function(propertyName, propertyValue, rule, handler){
843 923 var renderer = {
@@ -866,6 +946,8 @@ console.log("add", selector, cssText);
866 946 // clears all the renderers in use
867 947 selectorRenderers = [];
868 948 },
  949 + set: setAttribute,
  950 + configDOMSetters: true, // disable this for better performance
869 951 globalAttributes: globalAttributes
870 952 };
871 953 return xstyle;

0 comments on commit 2d6b260

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