So I'm trying to display via a template some data from a JSON request. The data has some nested objects (of varying amounts) similar to this:
data: "Some data",
nested: [
{
nested_data: "foo",
bar: "foobar"
}, ...
],
...
I've managed to parse the JSON fine and store it in a WinJS.Binding.List object, and bound the result to a template. The problem I've got is actually displaying the nested JSON data in my template. The template looks something like this:
<div class="appTemplate">
<div class="innerTemplate">
<h1 data-win-bind="innerText: data">
<h2 data-win-bind="innerText: nested">
</div>
</div>
When I run the program those, the 'nested' part of the template is just a bunch of [object Object] rather than the data I want to display.
Is there some way I can create the template such that it can handle the nested data? Would it be easier to define a template function? Not sure what to do here...
There is no built in way to do this -- the only built in control for iterating over data is the List View. However, you cannot nest List Views.
There are two ways to solve this problem, depending on your desired outcome:
1) Stringifying the nested data: You can do this by writing a WinJS.Binding.converter that will convert your array of data into a single string value. Example
Code:
WinJS.Namespace.define("Examples.Converters", {
nestedDataConverter: WinJS.Binding.converter(function(input) {
// Assume input is array
var result = "";
input.forEach(function(data) {
result += data.nested_data + ", " + bar + ";";
});
result result;
}),
});
Template:
My recommended solution would to build your own control that will take your array (or WinJS.Binding.List) and create the elements / layouts needed. I have done this in a project I work on, and it's super simple.
Example Template:
<div class="appTemplate" data-win-control="WinJS.Binding.Template">
<div class="innerTemplate">
<h1 data-win-bind="innerText: data">
<h2 data-win-bind="innerText: nested Examples.Converters.nestedDataConverter">
</div>
</div>
Now, the h2 will have the single-string version of that data.
2) Create a control to display the data richly: To do this you need to create a new WinJS control and use it in your template.
Control example:
WinJS.Namespace.define("Examples.Controls", {
Stamper: WinJS.Class.define(function(element, options) {
WinJS.UI.setOptions(this, options);
this.domElement = element;
}, {
domElement: nullm
_data: null,
data: {
set: function(val) {
this._data = val;
updateLayout();
}
},
updateLayout: function() {
this.domElement.innerHTML = "";
if(!this._data) {
return;
}
this._data.forEach(function(item) {
var el = document.createElement("div");
el.textContent = "Data: " + item.nested_data + ", " + item.bar;
this.domElement.appendChild(el);
}.bind(this));
}
}),
});
Template:
<div class="appTemplate" data-win-control="WinJS.Binding.Template">
<div class="innerTemplate">
<h1 data-win-bind="innerText: data"></h1>
<div data-win-control="Examples.Controls.Stamper"
data-win-bind="winControl.data: nested"></div>
</div>
</div>
This control can be extended to render nested templates, and other items. It's all a question of how complex you want to get.
Try data-win-bind="innerText: nested.nested_data"
This will be much easier with a template function.
The built in bindable templates are great for templates that are logicless, but fall short when you need the template to make decisions based on data values, and in your case, deeper properties.
Related
I have a simple <firebase-query> tag, and I'd like to manipulate some of the data before having it displayed through a <dom-repeat>. For example, I need to turn some fields into links, and also parse some dates.
So, I need to get the data once it's ready, loop through each item, and change some of the values.
To do that, I have an observer on the data to detect when it's ready. However, I can't figure out how to loop through the data from that JavaScript function. For some reason, for(var i in items) doesn't work, although the items do exist.
Here is the component:
<dom-module id="cool-stuff">
<template>
<firebase-query id="query" path="/items" data="{{items}}"></firebase-query>
<template is="dom-repeat" items="{{items}}" as="item">
[[item.name]]<br />
[[item.date]]<br />
</template>
</template>
<script>
Polymer({
is: 'ix-table',
properties: {
items: {type: Object, observer: "_itemsChanged"},
}
itemsChanged: function(data) {
// how do I loop through the data received from firebase-query?
console.log(data);
}
});
</script>
</dom-module>
Ideally, all I'd want to do in the observer function is something like:
for(var i in data) {
obj = data[i];
obj.name = '<a href="/item/"+obj.key>'+ojb.name+'</a>';
}
But I can't seem to be able to loop through the data.
Inside the observer function, console.log(data) returns some weird stuff like this:
Array[o]
0: Object (which contains a proper item)
1: Object (same)
2: Object (same)
Update:
Here is a screenshot of what console.log(data) returns (from inside the observer):
The array seems to be populated with all the objects, but it shows as Array[0]. So it won't let me loop through them.
Update 2:
Thanks to arfost here is the solution:
<script>
Polymer({
is: 'ix-table',
properties: {
items: {type: Object},
}
observers: [
'_itemsChanged(items.splices)'
],
_itemsChanged: function(changeRecord) {
if (changeRecord) {
changeRecord.indexSplices.forEach(function(s) {
for (var i=0; i<s.addedCount; i++) {
var index = s.index + i;
var item = s.object[index];
console.log('Item ' + item.name + ' added at index ' + index);
// do whatever needed with the item here:
this.items[index].name = "New name";
}
}, this);
}
},
});
</script>
<firebase-query> results
Note that <firebase-query> results in an array of objects. Let's say your database contained the following items under /notes/<USER_ID>/:
Your <firebase-query> would look similar to this:
<firebase-query
id="query"
app-name="notes"
path="/notes/[[user.uid]]"
data="{{notes}}">
</firebase-query>
(where user is bound to <firebase-auth>.user).
Assuming the user is logged in, <firebase-query> would then populate its data property (i.e., bound to notes) with the following array:
Note how each object contains a $key property, which corresponds to the item's key seen in the Firebase console's Database view.
You could then iterate notes directly with <dom-repeat>:
<template is="dom-repeat" items="[[notes]]">
<li>
<div>key: [[item.$key]]</div>
<div>body: [[item.body]]</div>
<div>title: [[item.title]]</div>
</li>
</template>
Binding to HTML strings
You should be aware that the string data bindings are rendered literally in this case, so attempting to set name to obj.name = '<a href="...">' would render the literal string instead of an anchor. Instead, you should declare the tags in your template, and bind the key and name properties inside those tags. So, your observer could be replaced with this:
<template is="dom-repeat" items="{{items}}" as="item">
<a href$="/item/[[item.key]]">[[item.name]]</a><br />
[[item.date]]<br />
</template>
Iterating an array
The following note is only relevant if you prefer to mutate the data before displaying it...
When iterating an array, you should avoid for..in because it doesn't guarantee order of iteration, and because it may iterate over enumerable properties you might not necessarily care about. Instead, you could use for..of (assuming ES6 is available to your app):
for (let note of notes) {
note.title += ' ...';
}
or Array.prototype.forEach():
notes.forEach(function(note) {
note.title += ' ...';
});
I thinks I have run into the same issue as you.
It come from the way firebase query is getting the array, the way polymer obersvers works, and is hidden by the fact that the javascript console is reference based when it show the objects.
In fact what really happen here, is that firebase query is creating an empty array, which trigger your polymer observer.
So your function is called as soon as the array is created, but still empty and you can't iterate through, since it's empty. You then log it, where the primitives sub-properties are correctly displayed (array[0])
Then firebase begin to populate the array with the datas. The arrays reference stay the same, so polymer won't fire the observer again, and in the console, when it try to display the array it display the array referenced in the log, which now contains the datas.
I recommend that you use a array mutation observer in place of your simple one as follow
`properties: {
items: {type: Object},
},
,
observers: [
'_itemsChanged(items.splices)'
],`
It will fire every time an object is added to your array, and you would be able to do the work you need :)
I had the link for the documentation on array mutation observer :)
polymer array mutation observer
I hope this will solve your issue,
have a good day.
i don't think i can think of a scenario where you'd need to mutate the data by looping through the array rather than just using computed bindings. like this:
<template is="dom-repeat" items="{{items}}" as="item">
<child-el date="{{_computeDate(item.date)}}"></child-el><br />
<child-el attr1="{{_someOtherConversion(item.prop1)}}"></child-el><br />
<child-el attr2="{{_iPromiseAnyConversionCanBeDoneLikeThis(item.prop2)}}"></child-el><br />
</template>
<script>
_computeDate: function(item) {
//do date converstion
}
I'm using the Handlebars library to make templates for my website. But, because templates should be logicless, basic Boolean logic (e.g. print a red or a green div based on a less-than check) is difficult without resorting to hacks. How can I resolve these kinds of problems without adding logic to my templates?
Logicless doesn't mean you can't use logic at all, it just means that you can't use logic in templates.
You should prepare all of your data before passing it to template.
For example, consider this common use case on an MV* app (like a Backbone-powered app):
Model:
{
name: 'Roger',
age: 50
}
View:
...
getTemplateData: function (model) {
var data = model;
if (model.age >= 50) {
data.isTooOld= true;
}
return data;
}
...
render: function () {
var data = this.getTemplateData(model) || {};
this.el.innerHTML = this.template(data);
}
...
Template:
<p>
OK, {{name}},
{{#if isTooOld}}
you're too old for this shit!
{{else}}
let's do this!
{{/if}}
</p>
Please see this pen for a demo of the issue (based on the slideshow from the tutorial). When clicking on "next" and "prev" arrows, you'll notice that the imgIndex mustache updates correctly, but the expression mustaches such as <p>{{ curImageCaption() }}</p> do not recognize when their values are changing.
That is, the object is mutated such that the mustache value would change if the expressions were re-evaluated, but ractive doesn't seem to realize that. Is there any way to get this to work, barring writing adaptors? Am I misunderstanding how magic mode works? The interesting thing is that even if I explicitly call ractive.update() inside the event handlers, ractive still doesn't respond.
UPDATE WITH NEW INFO
After more fiddling, I came up with this hack that gets it working. The hack is to change, eg, <p>{{ curImageCaption() }}</p> to <p>{{ curImageCaption(imgIndex) }}</p> -- adding a simple primitive to the mustache expression which ractive understands how to watch correctly.
I think I see what's going on now, but having to explicitly add arguments to the mustache expression containing changing primitives defeats much of the purpose of having the separate domain object -- that is, now you are coding your domain object with ractive in mind, using changing primitives a sort of basic pub/sub mechanism for notifying ractive of changes.
Having to create a real pub/sub mechanism on my custom objects, which ractive then explicitly subscribes to, would be fine. The problem is, as I noted in the OP, even when ractive is notified of a change via ractive.update(), it still doesn't know it should recompute the mustaches unless I use the fake argument hack. So it's not clear what callback ractive should be registering to make everything work.
I don't understand the inner-working of ractive well enough to do this, but I suspect what's needed is the ability to directly work with the _deps stuff, and manually trigger recomputes for expressions. If this sounds right, an example of how to accomplish it would be appreciated.
UPDATE 2 -- A decent solution
Here is a proof of concept for a not-too-hacky workaround.
The idea is to use ECMA5 properties to decorate your custom domain object, providing properties that delegate to the existing methods you want to use but which don't work inside ractive templates. The properties, otoh, work just fine.
So instead of <p>{{ curImageCaption() }}</p> we simply write <p>{{ imageCaption }}</p>, and then we decorate our custom domain object like so:
Object.defineProperty(mySlideshow, "imageCaption", {
configurable: true,
get: function() { return this.curImageCaption() },
set: function() { }
});
This decoration, a bit verbose in my demo, can easily be slimmed down by creating a helper method which accepts an object mapping your new ractive-friendly property names to names of existing methods on your object, and takes care of the above boilerplate for you.
NOTE: One drawback of this method is that you do have to call ractive.update() manually in your event handlers. I'd like to know if there's a way of getting around that. And if there is not, how big of a performance hit does this cause? Does it defeat the whole purpose of ractive's surgical updates?
Update 3 -- A better decent solution?
This pen takes yet another approach, in which link our custom domain model with ractive via a generic dispatcher object (an object that implements notify()). I think this is my favorite of the approaches so far....
It's similar to the official ractive adaptors, but we are using DI to pass our unofficial ractive adapter to our domain object, rather than wrapping our object. At first glance it might seem we are "coding to ractive," but in fact this is only partially true. Even if we were using another framework, we'd need to use some notification mechanism to broadcast changes to our view model so that views could react to it. This DI approach seems to require less boilerplate than official ractive adaptors, though I don't understand them well enough to know this for sure. It is not as completely general a solution as the official adaptors either.
Code from pen for posterity
HTML
<div id='output'></div>
<script id='template' type='text/ractive'>
<div class='slideshow'>
<div class='main'>
<a class='prev' on-tap='prev'><span>«</span></a>
<div class='main-image' style='background-image: url({{ curImageSrc() }});'></div>
<a class='next' on-tap='next'><span>»</span></a>
</div>
<div class='caption'>
<p>{{ curImageCaption() }}</p>
<p>Image index: {{ imgIndex }} </p>
</div>
</div>
</script>
JS
// Fix JS modular arithmetic to always return positive numbers
function mod(m, n) { return ((m%n)+n)%n; }
function SlideshowViewModel(imageData) {
var self = this;
self.imgIndex = 0;
self.next = function() { self.setLegalIndex(self.imgIndex+1); }
self.prev = function() { self.setLegalIndex(self.imgIndex-1); }
self.curImage = function() { return imageData[self.imgIndex]; }
self.curImageSrc = function() { return self.curImage().src; }
self.curImageCaption = function() { return self.curImage().caption; }
self.setLegalIndex = function(newIndex) { self.imgIndex = mod(newIndex, imageData.length); }
}
var mySlideshow = new SlideshowViewModel(
[
{ src: imgPath('problem.gif'), caption: 'Trying to work out a problem after the 5th hour' },
{ src: imgPath('css.gif'), caption: 'Trying to fix someone else\'s CSS' },
{ src: imgPath('ie.gif'), caption: 'Testing interface on Internet Explorer' },
{ src: imgPath('w3c.gif'), caption: 'Trying to code to W3C standards' },
{ src: imgPath('build.gif'), caption: 'Visiting the guy that wrote the build scripts' },
{ src: imgPath('test.gif'), caption: 'I don\'t need to test that. What can possibly go wrong?' }
]
);
var ractive = new Ractive({
el: '#output',
template: '#template',
data: mySlideshow,
magic: true
});
ractive.on( 'next', function(event) {
ractive.data.next();
});
ractive.on( 'prev', function(event) {
ractive.data.prev();
});
function imgPath(name) { return 'http://learn.ractivejs.org/files/gifs/' + name; }
I'll try and explain what's going on under the hood before presenting a possible solution:
Wrapping objects in magic mode
In magic mode, when Ractive encounters an unwrapped data descriptor of an object, it wraps it by replacing it with an accessor descriptor - the get()/set() functions. (More info on MDN, for those interested.) So when you do self.imgIndex = 1, you're actually triggering the set() function, which knows how to notify all the dependants of the imgIndex property.
The key word here is 'encounters'. The only way Ractive knows that it needs to wrap imgIndex is if we do ractive.get('imgIndex'). This happens internally because we have an {{imgIndex}} mustache.
So that's why the index property updates.
Dependency tracking with computed values
Within an ordinary template, you can have what basically amount to computed values, using the get() method:
<p>{{ curImageCaption() }}</p>
ractive = new Ractive({
el: 'body',
template: template,
data: {
images: images,
imgIndex: 0,
curImageCaption: function () {
var imgIndex = this.get( 'imgIndex' );
return this.get( 'images' )[ imgIndex ].caption;
}
}
});
Here, because we're calling ractive.get() inside the curImageCaption function, Ractive knows that it needs to rerun the function each time either images or imgIndex changes.
What you're in effect asking is a reasonable question: why doesn't retrieving the value of self.imgIndex in magic mode work the same as doing ractive.get('imgIndex')?
The answer comes in two parts: Firstly, I hadn't thought of adding that feature, and secondly, it turns out it doesn't work! Or rather, it's extremely fragile. I changed magic mode so that the get() accessor captured the dependency the same way ractive.get() does - but self.imgIndex is only an accessor descriptor (as opposed to a data descriptor) if Ractive has already encountered it. So it worked when we had <p>Image index: {{ imgIndex }} </p> at the top of the template, but not when it's at the bottom!
Normally the prescription would be fairly simple: use ractive.get() to make the dependency on self.imgIndex explicit inside curImageSrc() and curImageCaption(). But because you're using a custom viewmodel object, that's not ideal because it effectively means hard-coding keypaths.
A solution - creating a custom adaptor
Here's what I'd recommend - making an adaptor that works with the custom viewmodel object:
Ractive.adaptors.slides = {
filter: function ( object ) {
return object instanceof SlideshowViewModel;
},
wrap: function ( ractive, slides, keypath, prefix ) {
var originalNext, originalPrev;
// intercept next() and prev()
originalNext = slides.next;
slides.next = function () {
originalNext.call( slides );
ractive.update( keypath );
};
originalPrev = slides.prev;
slides.prev = function () {
originalPrev.call( slides );
ractive.update( keypath );
};
return {
get: function () {
return {
current: slides.curImage(),
index: slides.imgIndex
};
},
teardown: function () {
slides.next = originalNext;
slides.prev = originalPrev;
}
};
}
};
var ractive = new Ractive({
el: '#output',
template: '#template',
data: mySlideshow,
adaptors: [ 'slides' ]
});
This is a very simple adaptor, and it could probably be improved, but you get the gist - we're intercepting calls to next() and prev(), and letting Ractive know (via ractive.update()) that it needs to do some dirty checking. Note that we're presenting a facade (via the get() method of the wrapper), so the template looks slightly different - see this pen.
Hope this helps.
Maybe this is an academic exercise, and I'm new to Ractive, but it seems the problem lies in the template not having a context to the current image.
EDITED: Use current Image as a context block instead of looping through collection.
<div class='slideshow'>
{{#curImage}}
<div class='main'>
<a class='prev' on-tap='prev'><span>«</span></a>
<div class='main-image' style='background-image: url({{ src }});'></div>
<a class='next' on-tap='next'><span>»</span></a>
</div>
<div class='caption'>
<p>{{ caption }}</p>
<p>Image index: {{ imgIndex }} </p>
</div>
</div>
...
function SlideshowViewModel(imageData) {
...
self.curImage = imageData[self.imgIndex]
...
self.setLegalIndex = function(newIndex) {
self.imgIndex = mod(newIndex,imageData.length);
self.curImage = imageData[self.imgIndex]
}
}
This is using your original pen with just the key modifications. Here is new pen.
I would still move the buttons into an outer part of the template so the display in the middle could be made into a partial:
<div class='main'>
<a class='prev' on-tap='prev'><span>«</span></a>
{{#current}}
{{>partial}}
{{/}}
{{/current}}
<a class='next' on-tap='next'><span>»</span></a>
</div>
and encapsulate in Ractive.extend, but if ViewModel works for you...
How do I access one 'sibling' variable in a meteor template helper, when I am in the context of another? I want to determine whether the user that is logged in and viewing the page is the same user that posted the ride offering, so that I can hide or show the "bid" button accordingly.
For example, here is my template (html) file:
<!-- client/views/ride_offers.html -->
<template name="RideOfferPage">
<p>UserIsOwner:{{UserIsOwner}}</p>
{{#with CurrentRideOffer}}
{{> RideOffer}}
{{/with}}
</template>
<template name="RideOffer">
<div class="post">
<div class="post-content">
<p>Details, Author: {{author}}, From: {{origin}}, To: {{destination}}, between {{earliest}} and {{latest}} for {{nseats}} person(s). Asking ${{price}}.
<button type="button" class="btn btn-primary" >Bid</button><p>
<p>UserIsOwner:{{UserIsOwner}}</p>
</div>
</div>
</template>
And here is my JavaScript file:
Template.RideOfferPage.helpers({
CurrentRideOffer: function() {
return RideOffers.findOne(Session.get('CurrentOfferId'));
},
UserIsOwner: function() {
return RideOffers.find({_id: Session.get('CurrentOfferId'), userId: Meteor.userId()}).count() > 0;
}
});
In the "RideOffer" template, I am able access the variables author, origin, ..., price. But I am unable to access the boolean UserIsOwner. I am, however, able to access the boolean UserIsOwner in the "RideOfferPage" template.
Does anyone know how I can access the boolean UserIsOwner in the "RideOffer" template?
Cheers,
Put the userIsOwner function outside the helper as an anonymous function and then call it from both templates.
Template.RideOfferPage.helpers({
CurrentRideOffer: function() {
return RideOffers.findOne(Session.get('CurrentOfferId'));
},
UserIsOwner: checkUserIsOwner()
});
Template.RideOffer.helpers({
UserIsOwner: checkUserIsOwner()
});
checkUserIsOwner= function() {
return RideOffers.find({_id: Session.get('CurrentOfferId'), userId: Meteor.userId()}).count() > 0;
}
There are several ways to do what you're asking.
In your particular example you are not asking about siblings, but about parents, since the RideOfferPage template renders the RideOffer template. You can access variables in the parent data context (but not helpers) like so:
<template name="RideOffer">
<div class="post">
<div class="post-content">
<!--
other stuff
-->
<p>UserIsOwner:{{../UserIsOwner}}</p>
</div>
</div>
</template>
In other cases, you may have a template being rendered as a sibling of this one. In that case, you can't actually know what the sibling is until the template is actually on the page; however, you can find it in the rendered callback:
Template.foo.rendered = function() {
var current = this.firstNode;
var next = $(currentItem).next(); // or .prev()
if (next.length) {
nextData = Spark.getDataContext(next[0]);
}
// Do something with nextData
};
Finally, you can get the parent context of any rendered DOM element by repeatedly iterating through its parents. This isn't super efficient but I've used it in places where there is extensive drag and drop with DOMFragments moving around on the page:
Template.bar.events = {
"click .something": function(e) {
var target = e.target;
var context = Spark.getDataContext(target);
var parentContext = context;
while (parentContext === context) {
parentContext = Spark.getDataContext(target = target.parentNode);
}
// Do something with parentContext
}
};
I'm curious to know if there is a better way to do the last thing, which may potentially have to iterate through many DOM elements. In any case, you may want to check out my meteor-autocomplete package for this and other cool tricks.
Below I have a basic template that has a numerical input form. When you type a number in the form and click Add a list of Divs get created. The Divs are created with a class of "synth" and an id of "synth" + a number. The numbers go in succession based on a counter.
I want to not only store this information in the database but do so in a manner that (eventually) when a user logs in they will have access to their list of Divs as a "saved state" from their previous log in.
I am not even sure if I am going about this in an appropriate manner. I am simply sticking the createSynth() function in the Collection insert for lists. I have a feeling to do this "correctly" I should have two events that work in parallel - one sending to the lists Collection and the other to the dom/Template. These two blocks would then exchange data (some how) which in conjunction create the illusion of a "saved state".
Below is the code I have thus far.
HTML
<head>
<title></title>
</head>
<body>
{{> start}}
</body>
<template name="start">
<input id ="amount" type ="number" />
<input id ="submit" type="button" value="Add" />
<div id="applicationArea"></div>
</template>
Javascript
var lists = new Meteor.Collection("Lists");
var counter = 0;
counterSynth = 0;
if (Meteor.isClient) {
'use strict';
Template.start.events({
'mousedown #submit' : function () {
var amount = document.getElementById("amount").value;
for(i=0;i<amount;i++) {
lists.insert({SoundCircle:createSynth()}); // I am inserting the entire function call, is this the right path?
}
function createSynth() {
var synth = document.createElement("div");
synth.className = "synth";
synth.id = "synth" + (counterSynth++);
applicationArea.appendChild(synth);
};
},
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
You have to use a slightly different approach to this, basically just insert your stuff into the collection, and use handlebars to get it out. I'm not entirely sure what you were doing but you should get a good idea with the below
Server js
synths = new Meteor.Collection('synths'); //This will store our synths
Client js:
synths = new Meteor.Collection('synths'); //This will store our synths
Template.start.events({
'mousedown #submit' : function () {
var amount = document.getElementById("amount").value;
for(i=0;i<amount;i++) {
lists.insert({class:"synth", id:counterSynth});
}
},
});
Template.start.synth = function() {
return synths.find(); //This gives data to the html below
}
HTML:
{{#each synth}}
<div class="{{class}}" id="synth{{id}}">
Synth stuff here
</div>
{{/each}
It's probably best to dynamically recreate the DIVs every time you need them on the client, so the DIV is not stored on the server. If you really want to hard code/store the DIV on the server you would need to simply save the HTML as a string, to a Meteor collection.