diff --git a/lib/plates.js b/lib/plates.js index 85b9723..94657be 100644 --- a/lib/plates.js +++ b/lib/plates.js @@ -75,6 +75,14 @@ var Plates = (typeof module !== 'undefined' && 'id' in module && typeof exports if (!map1.attribute) return 1; if (!map2.attribute) return -1; + // Attribute setters should be before tag content setters + if (('replace' in map1) && ! ('replace' in map2)) { + return 1; + } + if (! ('replace' in map1) && ('replace' in map2)) { + return -1; + } + if (map1.attribute !== map2.attribute) { return map1.attribute < map2.attribute ? -1 : 1; } @@ -142,6 +150,15 @@ function matchClosing(input, tagname, html) { // attr: /([\-\w]*)\s*=\s*(?:["\']([\-\.\w\s\/:;&#]*)["\'])/gi, + // + // For using 'data-bind' and 'data-bind-(attr)' to automatically map data. + // + defaults: { + where: 'data-bind', + reGlobal: /data-bind-([\-\w]*?)=(["\'])(.{0,}?)\2/gi, + re: /data-bind-([\-\w]*?)=(["\'])(.{0,}?)\2/i + }, + // // In HTML5 it's allowed to have to use self closing tags without closing // separators. So we need to detect these elements based on the tag name. @@ -296,14 +313,95 @@ function matchClosing(input, tagname, html) { // // if there is a match in progress and // + + // + // General mapping that matches 'data-bind-(attr)' attributes. + // (Should be applied before the explicit mappings as 'tag content' matches there push content to the buffer.) + // + var defaults = this.defaults; + var dataBinds = []; + var matches = tagbody.match(defaults.reGlobal); + if (matches) { + for (var ii = 0; ii < matches.length; ii++) { + var match = matches[ii].match(defaults.re); + var replace = match[1]; + var dataKey = match[3]; + var attribute = match[0].slice(0, match[0].indexOf('=')); + var hasMapping = false; + + // Check if data exists + if (data[dataKey] !== undefined) { + // Don't change values that are mapped explicitly + if (mappings && mappings.length > 0) { + for (var iii = mappings.length - 1; iii >= 0; iii--) { + if (mappings[iii].attribute === attribute && mappings[iii].value === dataKey) { + hasMapping = true; + continue; + } + } + } + + // Only change attributes without a mapping + if (!hasMapping) { + var regex = new RegExp('^'+replace+'=(?:\\w+|["|\'](?:.*)["|\'])', 'i'); + dataBinds.push({ + replace: replace, + dataKey: dataKey, + re: regex, + setAttribute: false + }); + } + } + } + } + + if (dataBinds) { + for (var ii = 0; ii < dataBinds.length; ii++) { + var bind = dataBinds[ii]; + + tagbody = tagbody.replace(this.attr, function(str, key, value, a) { + + if(str.match(bind.re)) { + bind.setAttribute=true; + return key+'="'+data[bind.dataKey]+'"'; + } + return str; + }); + + if(!bind.setAttribute) { + var spliced = isSelfClosing? 2 : 1; + var close = isSelfClosing? '/>': '>'; + var left = tagbody.substr(0, tagbody.length - spliced); + if (left[left.length - 1] == ' ') { + left = left.substr(0, left.length - 1); + if (isSelfClosing) { + close = ' ' + close; + } + } + tagbody = [ + left, + ' ', + bind.replace, + '="', + data[bind.dataKey], + '"', + close + ].join(''); + } + } + } + + // + // Explicit mappings + // if (mappings && mappings.length > 0) { for (var ii = mappings.length - 1; ii >= 0; ii--) { var setAttribute = false , mapping = mappings[ii] - , shouldSetAttribute = mapping.re && attributes.match(mapping.re); + , shouldSetAttribute = mapping.re && attributes && attributes.match(mapping.re); // - // check if we are targetting a element only or attributes + // check if we are targeting a element only or attributes // if ('tag' in mapping && !this.attr.test(tagbody) && mapping.tag === tagname) { tagbody = tagbody + fetch(data, mapping, '', tagbody); @@ -341,8 +439,8 @@ function matchClosing(input, tagname, html) { if (mapping.remove) { // // only increase the remove counter if it's not a self - // closing element. As matchmode is suffectient to - // remove tose + // closing element. As matchmode is sufficient to + // remove those // if (!isSelfClosing) remove++; matchmode = true; @@ -404,26 +502,28 @@ function matchClosing(input, tagname, html) { ].join(''); } } - } else { - // - // if there is no map, we are just looking to match - // the specified id to a data key in the data object. - // + } + + // + // If there is no match (in mappings), try to match + // the specified 'id', 'data-bind' or map.conf.where + // to a data key in the data object. + // + if (!matchmode) { tagbody.replace(this.attr, function (attr, key, value, idx) { - if (key === map && map.conf.where || 'id' && data[value]) { - var v = data[value], - nest = Array.isArray(v), - output = (nest || typeof v === 'object') ? that.iterate(html, v, components, tagname, value, map) : v; + if ((data[value] !== undefined) && (key === (map && map.conf.where) || key === defaults.where || key === 'id')) { + var v = data[value], + nest = Array.isArray(v), + output = (nest || typeof v === 'object') ? that.iterate(html, v, components, tagname, value, map) : v; - // If the item is an array, then we need to tell - // Plates that we're dealing with nests - if (nest) { that.nest.push(tagname); } + // If the item is an array, then we need to tell + // Plates that we're dealing with nests + if (nest) { that.nest.push(tagname); } - buffer += nest ? output : tagbody + output; - matchmode = true; - } + buffer += nest ? output : tagbody + output; + matchmode = true; } - ); + }); } } diff --git a/test/api-test.js b/test/api-test.js index 5d2630e..b0c4041 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -107,7 +107,7 @@ vows.describe('merge data into markup').addBatch({ create: true }); - map.class('logo').use('url').as('src'); + map.className('logo').use('url').as('src'); map.where('name').is('first_name').use('fname').as('value'); map.where('name').is('last_name').use('lname').as('value'); @@ -131,7 +131,11 @@ vows.describe('merge data into markup').addBatch({ '(12) iterate a collection of over an element with children.': ( function() { - return common.createTest('test-12'); + var map = Plates.Map({ + where: 'class' + }); + + return common.createTest('test-12', map); }() ), @@ -151,9 +155,9 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class("names").use("names"); - map.class("first").use("first"); - map.class("last").use("last"); + map.className("names").use("names"); + map.className("first").use("first"); + map.className("last").use("last"); return common.createTest('test-14', map); }() @@ -165,10 +169,10 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); map.where("href").is("placeholder").insert("link"); - map.class("names").use("names"); - map.class("first").use("first"); - map.class("middle").use("middle"); - map.class("last").use("last"); + map.className("names").use("names"); + map.className("first").use("first"); + map.className("middle").use("middle"); + map.className("last").use("last"); return common.createTest('test-15', map); }() @@ -178,7 +182,11 @@ vows.describe('merge data into markup').addBatch({ '(16) It should be able to iterate over simple arrays': ( function() { - return common.createTest('test-16'); + var map = Plates.Map({ + where: 'class' + }); + + return common.createTest('test-16', map); }() ), @@ -186,7 +194,11 @@ vows.describe('merge data into markup').addBatch({ '(17) It should be able to iterate over deeply nested objects': ( function() { - return common.createTest('test-17'); + var map = Plates.Map({ + where: 'class' + }); + + return common.createTest('test-17', map); }() ), @@ -195,8 +207,8 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class("username").use("username"); - map.class("name").use("name"); + map.className("username").use("username"); + map.className("name").use("name"); return common.createTest('test-18', map); }() @@ -229,7 +241,7 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class('removed').remove(); + map.className('removed').remove(); return common.createTest('test-21', map); }() @@ -262,7 +274,7 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class('insert').append('
'); + map.className('insert').append('
'); return common.createTest('test-24', map); }() @@ -275,8 +287,8 @@ vows.describe('merge data into markup').addBatch({ var map = Plates.Map(); var partial = Plates.Map(); - partial.class('trolling').to('boink'); - map.class('insert').append('
', { boink: 'moo' }, partial); + partial.className('trolling').to('boink'); + map.className('insert').append('
', { boink: 'moo' }, partial); return common.createTest('test-25', map); }() @@ -289,8 +301,8 @@ vows.describe('merge data into markup').addBatch({ var map = Plates.Map(); var partial = Plates.Map(); - partial.class('trolling').to('boink'); - map.class('insert').append('
', 'partial', partial); + partial.className('trolling').to('boink'); + map.className('insert').append('
', 'partial', partial); return common.createTest('test-26', map); }() @@ -303,7 +315,7 @@ vows.describe('merge data into markup').addBatch({ var map = Plates.Map(); var partial = Plates.Map(); - map.class('insert').append('./test/fixtures/partial-1.html'); + map.className('insert').append('./test/fixtures/partial-1.html'); return common.createTest('test-27', map); }() @@ -316,7 +328,7 @@ vows.describe('merge data into markup').addBatch({ var map = Plates.Map(); var partial = Plates.Map(); - map.class('insert').append('./test/fixtures/partial-1.html', [{}, {}, {}]); + map.className('insert').append('./test/fixtures/partial-1.html', [{}, {}, {}]); return common.createTest('test-28', map); }() @@ -338,7 +350,7 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class('transformation').use(function (data, key) { + map.className('transformation').use(function (data, key) { return data.uppercase.toLowerCase(); }); @@ -351,8 +363,8 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class('author').to('name'); - map.class('author').use('url').as('href'); + map.className('author').to('name'); + map.className('author').use('url').as('href'); return common.createTest('test-31', map); }() @@ -363,9 +375,9 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class('author').use('url').as('href'); - map.class('doesnotexist').to('donotcare'); - map.class('author').to('name'); + map.className('author').use('url').as('href'); + map.className('doesnotexist').to('donotcare'); + map.className('author').to('name'); return common.createTest('test-32', map); }() @@ -374,12 +386,11 @@ vows.describe('merge data into markup').addBatch({ '(33) Two maps on the same attribute and value should throw': ( - function() { var map = Plates.Map(); - map.class('author').use('url').as('href'); - map.class('author').to('name'); - map.class('author').to('name'); + map.className('author').use('url').as('href'); + map.className('author').to('name'); + map.className('author').to('name'); return { topic: function() { @@ -399,30 +410,29 @@ vows.describe('merge data into markup').addBatch({ assert.equal(result.error.message, 'Conflicting mappings for attribute class and value author'); } }; - }() ), - '(34) Two maps for thr same class, updating two attributes should update both attributes': ( + '(34) Two maps for the same class, updating two attributes should update both attributes': ( function() { var map = Plates.Map(); - map.class('author').use('url').as('href'); - map.class('author').use('class').as('class'); + map.className('author').use('url').as('href'); + map.className('author').use('class').as('class'); return common.createTest('test-34', map); }() ), - '(35) Two maps for thr same class, updating two attributes plus a body class map should update both attributes': ( + '(35) Two maps for the same class, updating two attributes plus a body class map should update both attributes': ( function() { var map = Plates.Map(); - map.class('author').use('url').as('href'); - map.class('author').use('class').as('class'); - map.class('author').to('name'); + map.className('author').use('url').as('href'); + map.className('author').use('class').as('class'); + map.className('author').to('name'); return common.createTest('test-35', map); }() @@ -433,11 +443,11 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class('author').use('author'); - map.class('name').use('name'); - map.class('name').use('link').as('href'); - map.class('title').use('title'); - map.class('inner').use('inner'); + map.className('author').use('author'); + map.className('name').use('name'); + map.className('name').use('link').as('href'); + map.className('title').use('title'); + map.className('inner').use('inner'); return common.createTest('test-36', map); }() @@ -448,11 +458,11 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class('author').use('author'); - map.class('name').use('name'); - map.class('name').use('link').as('href'); - map.class('title').use('title'); - map.class('inner').use('inner'); + map.className('author').use('author'); + map.className('name').use('name'); + map.className('name').use('link').as('href'); + map.className('title').use('title'); + map.className('inner').use('inner'); return common.createTest('test-37', map); }() @@ -463,7 +473,7 @@ vows.describe('merge data into markup').addBatch({ function() { var map = Plates.Map(); - map.class('inner').use('inner'); + map.className('inner').use('inner'); return common.createTest('test-38', map); @@ -480,6 +490,7 @@ vows.describe('merge data into markup').addBatch({ return common.createTest('test-39', map); }() + ), '(40) Nested objects can be accessed using dot notiation': ( @@ -493,6 +504,7 @@ vows.describe('merge data into markup').addBatch({ return common.createTest('test-40', map); }() + ), '(41) Should remove the correct element': ( @@ -504,6 +516,110 @@ vows.describe('merge data into markup').addBatch({ return common.createTest('test-41', map); }() + + ), + + '(42) Support data-bind without mapping': ( + + function() { + return common.createTest('test-42'); + }() + + ), + + '(43) Support data-bind-attr without mapping': ( + + function() { + return common.createTest('test-43'); + }() + + ), + + '(44) Non matching data-bind should do nothing': ( + + function() { + return common.createTest('test-44'); + }() + + ), + + '(45) Should allow the value of 0 in convention mapping case': ( + + function() { + return common.createTest('test-45'); + }() + + ), + + '(46) Should allow mixing convention and configuration': ( + + function() { + var map = Plates.Map(); + + map.className('test').to('one'); + + return common.createTest('test-46', map); + }() + + ), + + '(47) Should allow mixing convention and configuration for attributes': ( + + function() { + var map = Plates.Map(); + + map.where('data-bind-class').is('test').use('one').as('class'); + + return common.createTest('test-47', map); + }() + + ), + + '(48) Should allow mixing convention (for an attribute) and configuration (for tag content)': ( + + function() { + var map = Plates.Map(); + + map.where('data-bind').is('test').use('one'); + + return common.createTest('test-48', map); + }() + + ), + + '(49) Should only set attribute (data-bind-attr) if data exists': ( + + function() { + return common.createTest('test-49'); + }() + + ), + + '(50) Do not apply default mapping if explicit mapping is set': ( + + function() { + var map = Plates.Map(); + + map.where('data-bind').is('test').use(function (data, key) { + return (data['one']); + }); + + return common.createTest('test-50', map); + }() + + ), + + '(51) Allow two mappings matching one tag, one attribute and one content': ( + + function() { + var map = Plates.Map(); + + map.where('class').is('replace').insert('replaced'); + map.where('data-bind').is('test').use('one'); + + return common.createTest('test-51', map); + }() + ) } diff --git a/test/browser.html b/test/browser.html index 6889d69..317b849 100644 --- a/test/browser.html +++ b/test/browser.html @@ -484,20 +484,177 @@

FooBar

value1
+ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -523,6 +833,30 @@

FooBar

+ + + + + + @@ -537,7 +871,7 @@

FooBar

diff --git a/test/fixtures/test-42.html b/test/fixtures/test-42.html new file mode 100644 index 0000000..f6d2f9c --- /dev/null +++ b/test/fixtures/test-42.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/fixtures/test-42.json b/test/fixtures/test-42.json new file mode 100644 index 0000000..7a9e864 --- /dev/null +++ b/test/fixtures/test-42.json @@ -0,0 +1,3 @@ +{ + "key": "value" +} diff --git a/test/fixtures/test-42.out b/test/fixtures/test-42.out new file mode 100644 index 0000000..b2f9deb --- /dev/null +++ b/test/fixtures/test-42.out @@ -0,0 +1 @@ +
value
\ No newline at end of file diff --git a/test/fixtures/test-43.html b/test/fixtures/test-43.html new file mode 100644 index 0000000..0cddf4a --- /dev/null +++ b/test/fixtures/test-43.html @@ -0,0 +1 @@ +Value \ No newline at end of file diff --git a/test/fixtures/test-43.json b/test/fixtures/test-43.json new file mode 100644 index 0000000..cc023b5 --- /dev/null +++ b/test/fixtures/test-43.json @@ -0,0 +1,3 @@ +{ + "key": "/bar" +} \ No newline at end of file diff --git a/test/fixtures/test-43.out b/test/fixtures/test-43.out new file mode 100644 index 0000000..ad6b297 --- /dev/null +++ b/test/fixtures/test-43.out @@ -0,0 +1 @@ +Value \ No newline at end of file diff --git a/test/fixtures/test-44.html b/test/fixtures/test-44.html new file mode 100644 index 0000000..afa247c --- /dev/null +++ b/test/fixtures/test-44.html @@ -0,0 +1 @@ +
Should not be changed
\ No newline at end of file diff --git a/test/fixtures/test-44.json b/test/fixtures/test-44.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/test/fixtures/test-44.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/test-44.out b/test/fixtures/test-44.out new file mode 100644 index 0000000..afa247c --- /dev/null +++ b/test/fixtures/test-44.out @@ -0,0 +1 @@ +
Should not be changed
\ No newline at end of file diff --git a/test/fixtures/test-45.html b/test/fixtures/test-45.html new file mode 100644 index 0000000..63f382a --- /dev/null +++ b/test/fixtures/test-45.html @@ -0,0 +1 @@ +
  • diff --git a/test/fixtures/test-45.json b/test/fixtures/test-45.json new file mode 100644 index 0000000..75c7ce1 --- /dev/null +++ b/test/fixtures/test-45.json @@ -0,0 +1,3 @@ +{ + "zero": 0 +} diff --git a/test/fixtures/test-45.out b/test/fixtures/test-45.out new file mode 100644 index 0000000..b5ac11d --- /dev/null +++ b/test/fixtures/test-45.out @@ -0,0 +1 @@ +
  • 0
  • diff --git a/test/fixtures/test-46.html b/test/fixtures/test-46.html new file mode 100644 index 0000000..095b012 --- /dev/null +++ b/test/fixtures/test-46.html @@ -0,0 +1,2 @@ +
  • +
  • diff --git a/test/fixtures/test-46.json b/test/fixtures/test-46.json new file mode 100644 index 0000000..192669e --- /dev/null +++ b/test/fixtures/test-46.json @@ -0,0 +1,4 @@ +{ + "zero": 0, + "one": 1 +} diff --git a/test/fixtures/test-46.out b/test/fixtures/test-46.out new file mode 100644 index 0000000..4eba15e --- /dev/null +++ b/test/fixtures/test-46.out @@ -0,0 +1,2 @@ +
  • 0
  • +
  • 1
  • diff --git a/test/fixtures/test-47.html b/test/fixtures/test-47.html new file mode 100644 index 0000000..370a62a --- /dev/null +++ b/test/fixtures/test-47.html @@ -0,0 +1 @@ +link diff --git a/test/fixtures/test-47.json b/test/fixtures/test-47.json new file mode 100644 index 0000000..7b6b874 --- /dev/null +++ b/test/fixtures/test-47.json @@ -0,0 +1,4 @@ +{ + "zero": "/sub-zero", + "one": "oneclass" +} diff --git a/test/fixtures/test-47.out b/test/fixtures/test-47.out new file mode 100644 index 0000000..b780b34 --- /dev/null +++ b/test/fixtures/test-47.out @@ -0,0 +1 @@ +link diff --git a/test/fixtures/test-48.html b/test/fixtures/test-48.html new file mode 100644 index 0000000..35ebca0 --- /dev/null +++ b/test/fixtures/test-48.html @@ -0,0 +1 @@ +link diff --git a/test/fixtures/test-48.json b/test/fixtures/test-48.json new file mode 100644 index 0000000..c3e6618 --- /dev/null +++ b/test/fixtures/test-48.json @@ -0,0 +1,4 @@ +{ + "zero": "/sub-zero", + "one": "Super link" +} diff --git a/test/fixtures/test-48.out b/test/fixtures/test-48.out new file mode 100644 index 0000000..1ac5900 --- /dev/null +++ b/test/fixtures/test-48.out @@ -0,0 +1 @@ +Super link diff --git a/test/fixtures/test-49.html b/test/fixtures/test-49.html new file mode 100644 index 0000000..6b863c6 --- /dev/null +++ b/test/fixtures/test-49.html @@ -0,0 +1 @@ +link diff --git a/test/fixtures/test-49.json b/test/fixtures/test-49.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/test/fixtures/test-49.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/test-49.out b/test/fixtures/test-49.out new file mode 100644 index 0000000..6b863c6 --- /dev/null +++ b/test/fixtures/test-49.out @@ -0,0 +1 @@ +link diff --git a/test/fixtures/test-50.html b/test/fixtures/test-50.html new file mode 100644 index 0000000..5dfd149 --- /dev/null +++ b/test/fixtures/test-50.html @@ -0,0 +1 @@ +link diff --git a/test/fixtures/test-50.json b/test/fixtures/test-50.json new file mode 100644 index 0000000..d9e5ca5 --- /dev/null +++ b/test/fixtures/test-50.json @@ -0,0 +1,4 @@ +{ + "one": "Super link", + "test": "Not wanted" +} diff --git a/test/fixtures/test-50.out b/test/fixtures/test-50.out new file mode 100644 index 0000000..86200d8 --- /dev/null +++ b/test/fixtures/test-50.out @@ -0,0 +1 @@ +Super link diff --git a/test/fixtures/test-51.html b/test/fixtures/test-51.html new file mode 100644 index 0000000..0abccc9 --- /dev/null +++ b/test/fixtures/test-51.html @@ -0,0 +1 @@ +link diff --git a/test/fixtures/test-51.json b/test/fixtures/test-51.json new file mode 100644 index 0000000..ea384a9 --- /dev/null +++ b/test/fixtures/test-51.json @@ -0,0 +1,4 @@ +{ + "one": "Super link", + "replaced": "replaced" +} diff --git a/test/fixtures/test-51.out b/test/fixtures/test-51.out new file mode 100644 index 0000000..559f7eb --- /dev/null +++ b/test/fixtures/test-51.out @@ -0,0 +1 @@ +Super link diff --git a/test/fixtures/test-7.html b/test/fixtures/test-7.html index 8147dd7..02509ba 100644 --- a/test/fixtures/test-7.html +++ b/test/fixtures/test-7.html @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/fixtures/test-7.out b/test/fixtures/test-7.out index 5f3aac7..6a03e52 100644 --- a/test/fixtures/test-7.out +++ b/test/fixtures/test-7.out @@ -1,2 +1,2 @@ Alpha -Alpha \ No newline at end of file + \ No newline at end of file