flatiron / plates Goto Github PK
View Code? Open in Web Editor NEWLight-weight, logic-less, DSL-free, templates for all javascript environments!
License: MIT License
Light-weight, logic-less, DSL-free, templates for all javascript environments!
License: MIT License
;var Plates = (typeof process !== 'undefined' && ~process.title.indexOf('node')) ? exports : {};
This checks if running in node and if yes, uses exports else it just creates a new empty object on the global namespace called Plates.
Well I don't want this global variable, and I'm using a custom require() function to use node code on the client side. Libraries are bundled into one file, including Plates, which does not export, as process is not defined.
Maybe you could just check for exports and or require to be defined and if yes use it, I don't want to create a process shim.
I can't find documentation or a test for this anywhere.
I am trying to substitute a value of an object in an array as the value of an attribute(href). Not sure if it's not supported or if I just can't find how to map it
I've tried variations along the lines of:
map.where('href').has('id').insert('items.id')
for the html/json in this gist:
https://gist.github.com/2467056
Seems like a pretty common use-case where you might want to bind an array to a table or list (like a .forEach scenario), but I don't see how that would work in Plates; is there such a concept?
when providing both data and markup for simple template
✗ should merge data to markup
» expected "<div id='a.b'>ok</div>\n",
got "<div id='a.b'></div>\n" (==) // common.js:32
This is after getting other tests of the same form running fine.
instead of this:
var html = '<span class="name">User</span>...<span class="name">User</span>'
var data = { "username": "John Smith" }
var map = Plates.Map()
map.class('name').to('username')
console.log(Plates.bind(html, data, map))
i want to do this:
var html = '<span class="name">User</span>...<span id="name">User</span>'
var data = { ".username": "John Smith", "#username": "Bob Hope" }
console.log(Plates.bind(html, data, map))
There is jsdoc-like markup embedded inside the markdown file, but not there in the plates.js file.
Why not?
It is common practice to embed templates in html as script like so :
<script id="foo" type="text/x-plates-tmpl"> Here goes all your template text..... The plates team should decide on a mime type (text/x-plates-tmpl or text/x-plates comes to mind) and put that, including the example from issue 7) on the readme. Make it simple for casual users to start without having to think.Would love to do it myself, but I am way to short on time the next couple of days.
Basically, it is Plates now, not Plate and you need to invoke it either
tmpl = new Plates("<......")
or
tmpl = new Plates ("<......", { "name" : "Frank" } )
or
tmpl = new Plates ("<......", { "name2" : "Frank" }, {"name2" : "name"} ) // mapping might be the other way around
and you can set data and html explicitly as well with data(...), html(..)
to get the result you simply call tmpl.bind()
To use it with jquery and embedded in html (for example to preprocess the html with haml, jade do something like this):
%script{:id=>'customers_template', :type=>'text/x-plates-tmpl'}
%ul{:'data-bind'=>'customers'}
and load the template with $('#customers_template').text()
With a completely fresh project, running "npm install plates", and then running the example code of..
var Plates = require('plates');
var html = '<div id="test">Old Value</div>';
var data = { "test": "New Value" };
var output = Plates.bind(html, data);
The following error is given:
node.js:134
throw e; // process.nextTick error, or 'error' event on first tick
^
TypeError: Object #<Object> has no method 'bind'
at Object.<anonymous> (/mnt/ws/users/leeolayvar/136705/test.js:6:21)
at Module._compile (module.js:411:26)
at Object..js (module.js:417:10)
at Module.load (module.js:343:31)
at Function._load (module.js:302:12)
at Array.<anonymous> (module.js:430:10)
at EventEmitter._tickCallback (node.js:126:26)
Thoughts? Note that Plates also does not have the Map attribute either.
Yeah, not so good.
While writing a tool, I had the following template:
<h2><span class="logo">DNS<small>lookup</small></span> HAS LOCATED THE SUSPECT'S IP ADDRESS.</h2>
<h2><strong>IP Address For <span id="domain">domain</span>:</strong> <span id="ipaddress">127.0.0.1</span></h2>
with the "plates" bind step looking like:
res.end( (new Plate(templates['caught'], {
"ipaddress": ip,
"domain": domain
})).bind() );
The output looks like this:
<h2><span class="logo">DNS<small>lookup</small></span> HAS LOCATED THE SUSPECT'S IP ADDRESS.</h2>
<h2><strong>IP Address For <span id="domain">this.isadomain.comdomain</span>:</strong> <span id="ipaddress">192.168.0.1127.0.0.1</span></h2>
In other words, it simply concats the new value to the beginning of the existing values instead of overwriting it.
Just a small request. Vows in the terminal is an absolute pain to troubleshoot when there's a ton of whitespace/newline characters in the output. Would it be too much trouble in the future to strip all all whitespace from these elements?
Would be cool if we could load plates via either of these 3 methods.
I'm working on using this pattern to change this -> https://github.com/umdjs/umd/blob/master/commonjsStrict.js
I think there needs to be a way exclude tags and attributes from the generated HTML if the data value it is mapped to is false.
For instance, if you want to include checked="checked"
from a checkbox input based on if a data value done
is true or false. This is currently not possible in Plates (if it is, please show me how!).
In addition, it would be really nice to exclude an entire HTML tag and its children if a data value is false.
I've changed this line:
https://github.com/tauren/plates/blob/master/lib/plates.js#L122
To this:
return newdata ? key + '="' + (newdata || '') + '"' : '';
And it excludes the checked
attribute for my simple situation, but I'm concerned it would break other scenarios. Would appreciate any feedback.
Note that I'm unable to push changes to my Plates fork right now (I'm behind a restrictive firewall), but I can push this change tonight and submit a pull request.
Consider a typical Todo app. Next to each item in the Todo list is a checkmark to indicate that the item is done. The todo list data might look like this:
var todos = [
{content:'item one',done:false},
{content:'item two',done:true)
];
The template to render might look something like this:
<script type="text/x-plates-tmpl" id="todo">
<div class="todo">
<div class="display">
<input class="check" type="checkbox" />
<div class="todo-content"></div>
<span class="todo-destroy"></span>
</div>
<div class="edit">
<input class="todo-input" type="text" value="" />
</div>
</div>
</script>
The code to render the template would be similar to this:
var html = $('script#todo').text();
var data = {
content: 'Todo item',
done: false
};
var map = Plates.Map();
map.class('todo-content').to('content');
map.class('check').use('done').as('checked');
console.log(Plates.bind(html, data, map));
First of all, the checked attribute doesn't get added if it isn't already in the template. This means the template currently needs to include <input class="check" type="checkbox" checked="checked" />
. It looks like this pull request might fix this:
#21
Secondly, there is currently no way to set an attribute value to anything besides a value in the data. So if done
is true
, then checked="true"
would be generated instead of the checked="checked"
that I want. I would prefer to not add extra redundant data to my model simply for the templating solution to work.
Lastly, and most importantly, I don't see a way to enable/disable an attribute based on a data value. If done
is false
, I don't want any checked
attribute to appear.
I posted some of this into a gist:
https://gist.github.com/1784972
When dealing with a nested object, plates seems to lose track of something if the template uses higher-level data after dealing with the sub-object. I've created a test case, and a gist to illustrate: https://gist.github.com/2370943
As you'll see, plates seems to be outputting the unprocessed template snippet, then the copy with substitutions made.
The readme should mention collections and how to use them. An example wouldn't hurt either!
It seems to me class mapping fails for unquoted class attributes.
html5 does not require quoted attributes. We're in html5 now.
Here's a printout demonstrating the issue:
function testPlates() {
console.log(arguments.callee.toString())
map.class('appName').to('title')
var o = { title: 'Boss' }
console.log('1', plates.bind('<div class=appName></div>', o, map))
console.log('2', plates.bind('<div class="appName"></div>', o, map))
}
1 <div class=appName></div>
2 <div class="appName">Boss</div>
case 2 works as expected
BUG: case 1 was expected to have Boss inserted into the tags body
1 <div class=appName>Boss</div>
Here is an example from my code which works with the current version of plates. I am not happy with the complexity. How can this be simplified?
A couple of notes: I tried to map the creator object using . notation, but that did not work, so I had to resort to creating a custom view object to avoid polluting the data object.
I tried to use class names like data-bind-name and map them to name, that would be a less intrusive version than what we have now. The reason I cannot use a 1 to 1 match (class name to name data property) is because it gets unmaintainable and undebuggable given the amount of css present. With a common name prefix I can do a text search over data-bind and find all the instances.
Another problem was the a tag that contains two elements to be mapped, hence the data-bind-href and data-bind.
I am reallz open for any suggestion how this could be simplified. In a perfect world I want to have a clean html and the least amount of code.
{
"scottyId" : "23423445546456",
"name" : "Frank",
"creator" : {
"avatarUrl30" : "http:// ...."
}
}
<li>
<p class='left'>
<a data-bind-href='linkToScotty' data-bind='name'></a>
</p>
<img class='tiny_person right' data-bind='creator_avatarUrl30' >
<div class='clearfix'></div>
</li>
_render_scotty_ref: function(scottyRef) {
var html, mappedData, mapping;
html = '... see above ...';
mappedData = {
name: scottyRef.name,
creator_avatarUrl30: scottyRef.creator.avatarUrl30,
linkToScotty: window.routingController.getDesignScottyPath(scottyRef.scottyId)
};
mapping = {
'name': ['data-bind'],
'creator_avatarUrl30': ['data-bind', 'src'],
'linkToScotty': ['data-bind-href', 'href']
};
return Plates.bind(html, mappedData, mapping);
}
Short update: This actually does work except for the linkToScotty binding, which is not replacing my href. Which is why I am doing this temporarily:
var $res;
$res = $(Plates.bind(html, mappedData, mapping));
$res.find("[data-bind-href]").each(function(idx, item) {
$(item).attr("href", $(item).attr("data-bind-href"));
});
return $res;
Apologies in advance for nit-picking, but is there a specific scenario where the defensive semicolon at the beginning of plates.js
is necessary?
;var Plates = (typeof process !== 'undefined' && typeof process.title !== 'undefined') ? exports : {};
My understanding is a defensive defensive semicolon is needed before a line that starts with an open parenthesis, such as an immediately executed anonymous function:
;(function() {})();
But I don't see why it would be needed before var
. I'm asking because currently there is no way for plates.js
to pass jshint without removing the semicolon. I requested a jshint option to be more lenient on defensive semicolons, but @antonkovalyov didn't see the reasoning either and it was understandably rejected:
jshint/jshint#487
If there is a good reason for it, let me know, and I'll reopen the jshint issue.
I agree 100% with this approach, here is a suggestion for binding attributes in a clean intuitive manner...
{ 'url':'https://github.com/', title:'github' }
I am using plate.js in the browser.
If I use it the way described in the readme (changing Plate to Plates) it does not work, so I ended up with the following:
var plate = new Plates('
', {(gist here: https://gist.github.com/7b32c71c681e7c7a048a)
which blows on line 189 (within the reduce function, as it tried to access the treeMap[...] [d] where treeMap[...] is undefined
Not sure if I am using this wrong or there is a bug, but I need help with this.
<input type="text" data-bind="name" data-bind-placeholder="placeholder"></input>
Expected output ==>
<input type="text" data-bind="name" data-bind-placeholder="placeholder"
value="Frank" placeholder="Some dude"></input>
data = { "name" : "Frank", "placeholder" : "Some dude" };
map = {"name" : ["data-bind", "value"] ,"placeholder" : ["data-bind-placeholder", "placeholder"] };
It seems that this might have something to do with the attr regext somewhere around line 100.
The usage of Plates.Map is explained well and easy to understand. But I'm actually still not sure what the standard behavior of plates is (simple usage).
# Usage
## Simple case
By default, `plates` will try to match the `data-key` in the data to an `ID` in the tag, since both should are uniqe.
// typo at uniqe
```js
var Plates = require('plates');
var html = '<div id="test">Old Value</div>';
var data = { "test": "New Value" };
var output = Plates.bind(html, data);
```
Reading the tests, plates can do a lot more without the map. What exactly?
I'm especially out to omit map.class("barfoo").to("foo"), as it occurs a lot and shows errors in my IDE.
For example, binding a page with the following causes mangled html because the >
character is interpreted as ending the tag:
<span data-bind="visible: foobar > 1"></span>
This is rather inconvenient in conjunction with knockout. Note that this
The wiki docs sugest using data-bind="name" or data-bind-href="url" for clean templating, which I totally agree with ( https://github.com/flatiron/plates/wiki/Using-data-bind-for-Clean-Plating).
However, right now it looks like to support that, you have to manually write your map for every data item -
map.where('data-bind-href').is('url').use('url').as('href')
It seems like either the default behavior should support the best practice, or there should be a simple way to hand the map a regex and a function such that data-bind-attr="key" will always bind attr="data[key]". If we could agree on the best approach I would be glad to take a shot at implementing.
I'd like to request the ability to bind and/or map onto tags themselves.
A simple example would be to bind additional <link>
or <script>
tags into the body of the <head>
element.
Note that i am aware, in this example, that you could give <head>
an id, or a class, but that just seems silly. I think it would be a perfectly reasonable use case to be able to insert data into the bodies of elements, especially for single element types such as head or body.
Some additional thoughts..
<head>
or <body>
you'll often run into only one, but if you try and bind data to <li>
, you'll likely match many more. With that said, you should be able to resolve which match(s) you would like to bind the data to.. or all of them?var plates = require('plates')
var data = {
first : 'john',
last : 'smith'
}
var map = plates.Map();
map.where('name').is('first').use('first').as('value');
map.where('name').is('last').use('last').as('value');
var html = '';
console.log(plates.bind(html, data, map))
OUTPUT:
EXPECTED:
It would be nice to provide some ViewModel-like mapping logic when doing an explicit map to .to
. For example:
var data = { list: [1, 2, 3, 4, 5] }
var html = '<div class="length-of-list"></div>';
var map = Plates.Map()
.class('length-of-list').to(function (data) {
return data.list.length;
});
console.log(Plates.bind(html, data, map));
Outputs:
'<div class="length-of-list">5</div>'
This would save the extra step of having to create a separate object containing the length
property.
Excited to be using plates :)
Cheers & thanks for it!
D
I'm having some trouble getting collections to iterate properly for the following test case:
https://gist.github.com/1869320
This may, of course, be a failure to properly configure Plates to generate the desired output. All the same, I'd love to know (and possibly update the docs)
On the server side you still need JSDOM to implement partials, since partials are needed in most real-world cases, they should probably be part of the server side light weight DOM implementation.
I have been working on something very similar to plates for some time and have some suggestions on how this can be done without any special template syntax. I'm on #nodejitsu IRC if you want to discuss.
Thanks,
Mike
You fixed the module exports in 8934e2d, 55d66e. It looks like you marked version 0.2.2, but didn't publish it to npm. This is causing people problems (See http://stackoverflow.com/questions/8319107/cannot-require-local-modules-in-node). Pushing 0.2.2 to npm would be great.
Thanks
Writing a mini-parser for server-side usage was a huge win, but I suspect that a native dom would be faster on the client.
I stumbled upon the following scenario:
{ "name": "frank",
"creator" : {
userName : "user x",
url: "http://...."
}}
And I would like to do the following:
resulting in
I just attempted to run my app that uses Plates in IE8 and it blew up. I noticed pull request #53 makes plates more compatible with ES3, so I thought Plates might work in IE8.
Is there already support for IE7/IE8? If not, is it something that is planned? If there is support, what shims are required to make it work? My application needs to support IE7+, and I'm trying to determine how much effort will be involved to use Plates.
I'm already using this style of accessing reserved words:
this.mapping['class']('check').use('done').as('checked');
Instead of this:
this.mapping.class.('check').use('done').as('checked');
Given the following template:
<script type="text/x-plates-tmpl" id="testTmpl">
<input type="text" id="x_username" placeholder="Enter username..."/>
<br/>
<input type="password" id="x_password" placeholder="Enter password..."/>
</script>
<div id="output"></div>
And the following code:
var user = {
name: 'Sally Smith',
username: 'sally',
password: 'mypass'
};
var html = $('script#testTmpl').text();
var map = Plates.Map();
$('#output').html(Plates.bind(html, user, map));
This doesn't render properly. The value mypass is rendered after the input
. The template should be rendered exactly as is without any variable replacements.
See in action here:
http://jsfiddle.net/tauren/HRDaD/
When I run this code:
npm install plates
It give the error: plates' is not in the npm registry.
A common feature in template engines is the ability to iterate through an array to generate multiple lines eg. multiple script tags based on an array with filenames.
This is missing in plates.
I am trying to bind an array of names to the entries in list A, but not to list B.
<div>
<ul id="A">
<li data-bind="names"></li>
</ul>
<ul id="B">
<li></li>
</ul>
</div>
var data = {names: ['woody allen', 'wu tang clan']};
map.where('data-bind').is('names').to('names');
plates.bind(html, data, map);
Expected output:
<div>
<ul id="A">
<li data-bind="names">woody allen</li>
<li data-bind="names">wu tang clan</li>
</ul>
<ul id="B">
<li></li>
</ul>
</div>
Actual output:
<div>
<ul id="A">
<li data-bind="names">woody allen</li>
</ul>
<ul id="B">
<li></li>
<li data-bind="names">wu tang clan</li>
</ul>
<ul id="B">
<li></li>
</ul>
<ul id="B">
<li></li>
</ul>
</div>
If I remove list B, the data gets bound to list A properly. Any thoughts on what is happening?
Thanks
Hi there
I like the concept of client side template rendering and started playing with your promising lib.
Unfortunately I get the following behavior when using this code snippet:
https://gist.github.com/2025054
Any comment on this? Am I doing something wrong?
Thx in advance. cheers
Cédric
Plates uses the reserved word class
as a method of Plates.Map. This works but it causes linters to throw warnings. For instance, JSHint:
Expected an identifier and instead saw 'class' (a reserved word). "this.mapping.class('first').to('firstName');"
Perhaps it would be better to rename the method? For instance:
Plates.Map.clazz
Plates.Map.className
There is an issue in JSHint already:
Plates needs a way to enhance an attribute based on a data value, not just replace it with the data value.
For instance, in my Todo app, if a todo item is checked as done, I want to add a CSS class to the div, but I don't want to remove the existing class.
HTML of an uncompleted item {content: 'My Todo Item', done: false}
:
<div class="todo-content">My Todo Item</div>
HTML of a completed item {content: 'My Todo Item', done: true}
:
<div class="todo-content done">My Todo Item</div>
I'd be happy to implement this and submit a pull request, but welcome any input or suggestions before doing so. Is there already a way to accomplish this?
It seems like there needs to be a way to supply a Map with custom functions so we can better control the output. For instance:
// Function to pass into Plates.Map.use()
// It should be passed the existing attribute value and the data model
function addClass(currentAttr, data) {
return currentAttr.indexOf('done') === -1 ? currentAttr + ' done' : currentAttr;
}
var map = Plates.Map();
map.class('todo-content').to('content');
map.class('todo-content').use(addClass).as('class');
The above will need more work since the use() method does not yet know which attribute it pertains to, since as('class')
is chained after it. But I hope it gets the concept across.
With weld, you can do something like:
<ul class="employees">
<li><strong class="name">John Doe</strong>: <span class="title">Salaryman</span></li>
</ul>
{
"employees": [
{ "name": "Josh", "title": "engineer" },
{ "name": "Paolo", "title": "CTO" }
]
}
and have it populate the whole list:
<ul class="employees">
<li><strong class="name">Josh</strong>: <span class="title">engineer</span></li>
<li><strong class="name">Paolo</strong>: <span class="title">CTO</span></li>
</ul>
but it seems that Plates can't do this? For example, check out this gist:
When you run this, the list gets stringified and instead of what you want to see, you get [object Object],[object Object]
and such.
It's certainly possible to get around this with multiple applications of plates, as in this gist:
but it feels wrong.
So, the way I see it, this was either an intentional decision that makes Plates' complexity manageable, or it's a bug, and I'm not sure which.
according to this: https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words
(for future use)
Hi,
Just wondering if it would be possible to get the latest version of Plates (with collection support) to be added to npm.
Just as this will make things a lil easier for may when i am deploying my stuff to no.de via git push.
Cheers!
How about adding support for calling functions on binded values ?
I can think of having the "with" method as in:
map.class('snippet').with(toUpper).to('val');
or
map.class('snippet').with(function(val) { val == null ? 'i'm not here' : val}).to('val');
see bae1fae for an example implementation
I'm trying to insert a new class into the class attribute of a tag, but the Mapping behavior doesn't quite run as expected:
I'm using:
map.class('home').insert('selected');
(with data: {selected:"selected"}
)
and this happens: <div class="home"></div>
becomes <div class="selected"></div>
As soon as any other class is mixed in, it fails:
<div class="something home"></div>
is skipped
Ideally, I'd expect an "insert" action to actually insert data, instead of replacing it:
<div class="someclass home"></div>
would become <div class="someclass home selected"></div>
Any suggestions?
On a side note, this works the same as above:
map.where('class').is('home').insert('selected');
Perhaps a 'has' conditional would be appropriate?
map.where('class').has('home').insert('selected');
In most libraries where conventions are used, they're used as a default, on which further config can be placed.
Either I'm missing something or this is not the case in Plates, which is mildly annoying because it means for cases like
var model = {} // Some really cool model
var map = Plates.Map()
.where('rel').is('self').use('id').as('href')
var html = Plates.bind(template, model, map)
Will result in only the rel being mapped
Where as with the code previously
var model = {} // Some really cool model
var html = Plates.bind(template, model)
This worked fine and all the data got mapped by class beautifully.
I'm happy to make this change if it is agreed with.
This doesn't substitute names correctly because of the whitespace after class=.
var plates = require('plates');
var html = '<div class= "name"></div>';
var collection = [
{'name': 'Louis'}
, {'name': 'Andy Kindler'}
, {'name': 'Greg Giraldo'}
];
console.log(plates.bind(html, collection));
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.