I am reading the docs of Google Polymer,there are two types of data binding,Node.bind() and Template Binding,so, what is the difference between Node.bind() and Template Binding
They're both ways of achieving data-binding. One in the context of DOM nodes and the other in the context of templates.
Node.bind()
Node.bind() allows you to tell DOM nodes to bind a named property to some data that you provide. So below, we're binding the property value (object.path.to.value) to the .textContent in our textNode.
var obj = {
path: {
to: {
value: 'hi'
}
}
};
var textNode = document.createTextNode('mytext');
textNode.bind('textContent', obj, 'path.to.value');
This is neat because anytime value changes, Node.bind() keeps your .textContent updated.
TemplateBinding
The Polymer docs state that this extends what you can do with the HTML <template> tag - specifically, giving you access to four new attributes: bind, repeat, if and ref.
So let's say that we wanted to pass a propery foo to a <template> which we would like to use in our template content, but want to keep in sync so that anytime foo changes, the template also gets updated. With bind that's as straight-forward as:
<template bind="{{ foo }}">
Creates a single instance with {{ foo }} when singleton model data is provided.
</template>
repeat is really useful for when you're working with lists or collections of data - like users:
<template repeat="{{ user in users }}">
<li>{{user.name}}</li>
</template>
if is really handy for conditional values in templates:
<template if="{{ conditionalValue }}">
Binds if and only if conditionalValue is truthy. (same as *bind if*)
</template>
and so on. You can find more examples of what can be done with these attributes in the TemplateBinding docs.
Related
I have a few lines of markup that I'd like to avoid keeping in an external partial file, but it should be rendered based on a variable value, either IN_PROCESS or DONE. I can make inline partials and render them based on static names:
{{#* inline "IN_PROCESS"}}IN_PROCESS{{/inline}}
{{#* inline "DONE"}}DONE{{/inline}}
{{> IN_PROCESS }}
{{> DONE }}
However I cannot figure out how to combine that with the () dynamic values syntax I have read about here.
So something like,
{{> (data.status) }}
└─────────┘ data.status would be either 'IN_PROCESS' or 'DONE'
Is this possible?
I believe you want something similar to this:
{{#* inline "IN_PROCESS"}}IN_PROCESS{{/inline}}
{{#* inline "DONE"}}DONE{{/inline}}
{{> (lookup . 'status')}}
{{> (lookup . 'status')}} will look through the JSON data object for its status attribute.
That is if you're passing your data in something like this:
var template = Handlebars.compile(**your source**);
$('body').append(template(data));
JSFiddle Example
The dot represents the data object that has been passed into the template function. The template function has no way of knowing what the object's name was:
template(inputData){
// This function wouldn't know that inputData.status was originally data.status
}
var data = {status: "DONE"};
template(data);
The dot is therefore used to tell "template" when searching for a parent that inputData should be the parent and we're looking for "status" as a child. I believe that is the use of it. I actually can't find any documentation regarding its use but all lookup's seem to be in the format lookup parent child so I assume that's the reasoning.
I'm essentially trying to add a CSS class to my VueJS components based on the component-name it's registered under (to give all those specific types of components the same style).
For example:
Vue.component('dragdropfile', {
// This refers to a unique template element (see HTML below)
template: "#dragdropfile-tmp",
props: ['action']
});
And in the Vue component template:
<template id="dragdropfile-tmp">
<form action="{{action}}" method="post" enctype="multipart/form-data">
<div class="fallback">
<input name="file" type="file" multiple />
</div>
<div class="dz-message" data-dz-message>
<div class="dz-default">
<!--
According to VueJS docs / forums, "slot" gets replaced by any innerHTML
passed from the incoming component usage.
-->
<slot></slot>
</div>
</div>
</form>
</template>
And finally, how it's used in the "index.html" page is like this:
<dragdropfile id="someDragDropFiles" action="/upload-please">
Do you have files to upload?<br/>
<b>Drop it here!</b>
</dragdropfile>
Now, although I could put in the component-name manually for each component HTML templates, I want to automate this.
Are there any special built-in {{binding}} names that Vue uses internally so that I can inject the component-name into the resulting component on the page?
To result something like so:
<form class="dragdropfile" id="someDragDropFiles" action="/upload-please" ... > ...</form>
Or do I simply need to pass it myself as a new component property? As in:
Manually call it like props: ["componentName", ...] and;
Refer to it in the template as <form class='{{componentName}}' ...>
Is this the only feasible way?
Using VueJS version: 1.0.17
(https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.17/vue.js)
Solution #1
After a few minutes of inspecting what the object holds using the create: function() { console.log(this); } in the Vue.component(...) registration call, I found the name in it's this.$options.name property.
In other words:
<form class="{{this.$options.name}}" ...> ... </form>
Or even shorter:
<form class="{{$options.name}}" ...> ... </form>
Come to think of it, it's still a bit of manual work to enter on each component templates, but there's probably a way to auto-append the class via the created method.
Solution #2
This is the automated approach I was looking for!
Here it goes, basically I made a wrapper function to call whenever I need to register new components, which internally calls the usual Vue.component(...) method.
NOTE: This example depends on jQuery to add the class and underscore.js for object merging via _.assign, but could probably be replaced by a direct *.classList.addClass() call instead. These are just the helper methods I'm familiar with, use what you like! :)
makeVueComponent(name, params)
/*
* Creates a Vue Component with a standardized template ID name (ie: #tagname-tmp)
* combined with given params.
*/
function makeVueComponent(name, params) {
//Ensure params is assigned something:
params = params || {};
//Backup a reference of an existing *.created() method, or just an empty function
var onCreated = params.created || function(){};
/*
Delete the original `created` method so the `_.assign()` call near the end
doesn't accidentally merge and overwrite our defaultParams.created() method.
*/
delete params.created;
var defaultParams = {
//Standardized template components to expect a '-tmp' suffix
// (this gets auto-generated by my NodeJS/Express routing)
template: "#"+name+"-tmp",
// This part auto-adds a class name matching the registered component-name
created: function() {
var $el = $(this.$options.el);
$el.addClass(this.$options.name);
//Then forward this to any potential custom 'created' methods we had in 'params':
onCreated.apply(this, arguments);
}
};
//Merge default params with given custom params:
params = _.assign(defaultParams, params);
Vue.component(name, params);
}
And then just use it like so:
//Register your Vue Components:
makeVueComponent("dragdropfile", {props:['action']});
You can then leave out those {{$options.name}} from your actual component templates that I mentioned in Solution 1.
If I have the following polymer element, how do I let the users of this element know about the structure of the object in dataSource. i.e If I have to declare and share Employee function, so that they can new Employee() and create an array. Whats the polymer way of sharing common data objects? Some seem to suggest behavior, but this isn't a behavior of the component, this is a data object with properties and no behavior.
<dom-module id="employee-list">
<template>
<ul>
<template is="dom-repeat" items="{{ dataSource }}">
<li>
<span>{{item.firstName}}</span>
</li>
</template>
</ul>
</template>
<script>
Polymer({
is: 'employee-list',
properties: {
dataSource: {
type: Array,
value: [{ firstName: "First 1", lastName: "Last 1" }]
}
}
});
</script>
</dom-module>
Use behaviors! Although the name is misleading, you can use behaviors to share properties.
A behavior is an object that looks similar to a typical Polymer
prototype. A behavior can define lifecycle callbacks, declared
properties, default attributes, observers, and listeners.
https://www.polymer-project.org/1.0/docs/devguide/behaviors.html
JS Bin example: http://jsbin.com/vuqumo/edit?html,output
Your users are going to use your element like this
<employee-list datasource="[[usersEmployeeList]]"></employee-list>
You need to tell them the format of the datasource in a jsdoc comment on the property. I don't know the format of the jsdoc comments properly, but look at the way the existing polymer elements do it.
I have a JavaScript object that I am attempting to bind to a Vue view.
I am running a function to update the JavaScript object using AJAX and I was expecting Vue to bind to the JS object and update the view when the object is updated though that isn't happening.
Research suggests making the AJAX call within the Vue declaration but due other constraits I would rather not do that.
I've created a fiddle to illustrate what the issue is since it's reproducable without the AJAX portion as well as pasted the code below.
https://jsfiddle.net/g6u2tph7/5/
Thanks in advance for your time and wisdom.
Thanks,
vmitchell85
JavaScript
window.changeTheData = function (){
externalJSSystems = [{description: 'Baz'}, {description: 'Car'}];
document.getElementById("log").innerHTML = 'function has ran...';
// This doesn't update the Vue data
}
var externalJSSystems = [{description: 'Foo'}, {description: 'Bar'}];
Vue.component('systable', {
template: '#sysTable-template',
data() {
return {
systems: externalJSSystems
};
}
});
new Vue({
el: 'body'
});
HTML
<systable :systems="systems"></systable>
<button type="button" onclick="changeTheData()">Change</button>
<br><br>
<div id="log"></div>
<template id="sysTable-template">
<table>
<thead>
<tr>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr v-for="sys in systems">
<td>{{ sys.description }}</td>
</tr>
</tbody>
</table>
</template>
Try this out :
externalJSSystems.push({description: 'Baz'}, {description: 'Car'});
It will append the new objects to externalJSSystems and the view will be updated. Why doesn't your example work ? Because you are assigning a new Array reference to externalJSSystems but Vue is still watching the old one.
To achieve what you want, don't assign a new Array instance but clear it. For example :
window.changeTheData = function (){
externalJSSystems.length = 0
externalJSSystems.push({description: 'Baz'}, {description: 'Car'});
}
When that instance of the systable Component is instantiated, Vue adds an "Observer" class to the initial externalJSSystems Array — extending the Array's prototype, adding getter/setters for each of the properties, and maintaining the two-way binding between the Component's data and the original Array. The changeTheData() method is overwriting that Vue-modified externalJSSystems Array with a completely new Array (that lacks the Observer), thus breaking the two-way binding.
In this way, externalJSSystems.push( … ) works because the default Array methods ('push', 'pop', 'shift', 'unshift', 'splice', 'sort', and 'reverse') have been mutated such that they are handled by the Observer.
I think the key to the behavior you're looking for lies in the Vue Component "props" — http://vuejs.org/guide/components.html#Props. In fact, it looks like your component markup — <systable :systems="systems"></systable> — is already set up to pass dynamic data to the Component instance. Right now, that :systems="systems" isn't doing anything. By defining systems in the Parent Vue scope, and defining systems as a prop(s) within the Component registration, you can pass dynamic data to Components within that Parent's scope.
Component
Vue.component('systable', {
template: '#sysTable-template',
props: {
systems: Array
}
});
Vue Instance
var vm = new Vue({
el: 'body',
data: {
systems: externalJSSystems
}
});
You can see it in action in this fiddle: https://jsfiddle.net/itopizarro/ycr12dgw/
I cached the Vue instance — var vm = new Vue({ … }) — so the changeTheData method had access to its systems data. This gives your external changeTheData() method a reference to the Vue instance where you defined system — thus giving it access to modify (without replacing, or iteratively adding/removing items from…) the Array of data.
Rather than making systems a data property, you can make it a computed property. Like the other answer said, the reference is to the old object. But if you make systems a computed property, it will automatically watch any variable used in the calculation (like externalJSSystems) and re-calculate the computed property.
Vue.component('systable', {
template: '#sysTable-template',
computed: {
systems() {
return externalJSSystems;
}
}
});
A quick question on the context of the event handlers for templates in Meteor (with Handlebars).
In the section of Documentation on template instances (http://docs.meteor.com/#template_inst) it is mentioned that "Template instance objects are found as the value of this in the created, rendered, and destroyed template callbacks and as an argument to event handlers"
In the Templates section (http://docs.meteor.com/#templates) it says "Finally, you can use an events declaration on a template function to set up a table of event handlers. The format is documented at Event Maps. The this argument to the event handler will be the data context of the element that triggered the event."
Well, this is only partially true. Let's use an example from the docs:
<template name="scores">
{{#each player}}
{{> playerScore}}
{{/each}}
</template>
<template name="playerScore">
<div>{{name}}: {{score}}
<span class="givePoints">Give points</span>
</div>
</template
Template.playerScore.events({
'click .givePoints': function () {
Users.update({_id: this._id}, {$inc: {score: 2}});
});
Here the "this" context of the 'click .givePoints' event handler is indeed the template instance of playerScore. Let's modify the html:
<template name="scores">
<span class="click-me">Y U NO click me?<span>
{{#each player}}
{{> playerScore}}
{{/each}}
</template>
<template name="playerScore">
<div>{{name}}: {{score}}
<span class="givePoints">Give points</span>
</div>
</template>
... and add an event handler for .click-me on the scores template:
Template.scores.events({
'click .click-me': function () {
console.log(this);
}
});
Now, if you click the span, what do you get logged? The Window object! What did I expect to get? The template object! Or maybe the data context, but it's neither. However, inside the callbacks (e.g. Template.scores.rendered = function(){ ... }) the context of "this" is always the template instance.
I guess my real question would be: is this something to do with
a bug in Handlebars, Meteor or somewhere in between?
slightly incomplete documentation on the templates?
me completely misinterpreting the docs or not understanding something fundamental
about Meteor or Handlebars?
Thanks!
This video explains the concepts:
http://www.eventedmind.com/posts/meteor-spark-data-annotation-and-data-contexts.
The direct answer to your question:
The thisArg inside an event handler should point to a data context. But sometimes the data context is undefined. When you use the Function.prototype.call(thisArg, ...) in JavaScript, if the thisArg is undefined (e.g. a dataContext is undefined) the browser will set this equal to window. So, the docs aren't wrong per se but the event handling code isn't guarding against the possibility of a data context being undefined. I'm guessing that will be fixed in short order.
So, what produces a data context for a template? Normally your root template won't even have a data context. In other words, the Template function is called without an object. But if you use the {{#with block helper or the {{#each iterator, a data context will be created for each item in the list, or in the case of the with helper, the object.
Example:
var context = {};
<template name="withHelper">
{{#with context}}
// data context is the context object
{{/with}}
</template>
var list = [ {name: "one"}, {name: "two"} ];
<template name="list">
{{#each list}}
{{ > listItem }} // data context set to the list item object
{{/each}}
</template>
The first parameter in the function is the event. So you could use the target of the event to grab your element.
Template.scores.events({
'click .click-me': function (event, template) {
console.log(event.target);
$(event.target).text("O but I did!");
}
});