Template #each & rendering - templates

Why does the template get rendered the number of times that correlates with the Each in my template.
<template name="carousel">
<div class="pikachoose">
<ul class="carousel" >
{{#each article}}
<li><img src="{{image}}" width="500" height="250" alt="picture"/><span>{{caption}}</span></li>
{{/each}}
</ul>
</div>
</template>
Template.carousel.article = function () {
return News.find({},{limit: 3});
}
Template.carousel.rendered = function() {
//$(".pika-stage").remove();
alert($(".carousel").html());
//$(".carousel").PikaChoose({animationFinished: updateNewsPreview});
}
In this case it would alert 3 times.

That's the way Meteor handles data updates. Your article data function returns a cursor that is to be used in the template. Initially, the cursor is empty, and data is pulled from the server, one article at a time. Each time an article is fetched, the contents of cursor are changed (it now has one more article), and therefore the reactive article method causes template to rerender.
If you need to be sure that your code runs only once, there are several possibilities depending on what you need.
The easiest one is just to use created instead of rendered.
If you modify DOM elements, you can also mark elements you modify so that you won't process them twice:
Template.carousel.rendered = function() {
_.each(this.findAll('.class'), function(element){
if($(element).data('modified')) return;
$(element).data('modified', true);
...
});
};
You can disable reactivity for the cursor, though it's a sad solution:
Articles.find(..., {reactive: false});
The most invasive, but also the most versatile is to observe when the data is fully loaded:
Deps.autorun(function() {
Meteor.subscribe('articles', {
ready: function() {
...
},
});
});

The issue may have to do with the use of the .rendered callback. Each time the loop runs the DOM is updated so the callback will run again.
When I had this problem in the past, I found it helpful to use Meteor event handlers whenever possible, to eliminate load order issues like this one. In this case, maybe you could try a timeout, so that that the .remove() and .PikaChoose() calls only run after the DOM has been quiet for a certain interval. Hope that works for you.

Related

How do I access template content in the same "event" as it gets visible in Polymer

Let me try and stub out my question
<polymer-element name="my-element">
<template>
<template if="{{show}}">
<my-other-element id="elementWithUnexposedInternals"></my-other-element>
</template>
</template>
<script>
Polymer({
show: false,
showChanged: function{
this.$.elementWithUnexposedInternals.someProperty = true;
}
});
</script>
</polymer-element>
It seems my-other-element is not accessible in the this.$ line as the template has not rendered it yet.
How can I get around this "order" issue?
Cheers
Actually, you have two problems. One is that the element isn't rendered yet, and the other is that the element will never get added to this.$, which is only populated with the element's static contents:
https://www.polymer-project.org/0.5/docs/polymer/polymer.html#automatic-node-finding
You can work around the second issue by adding a static container and using querySelector:
<polymer-element name="my-element">
<template>
<div id="container>
<template if="{{show}}">
<my-other-element id="elementWithUnexposedInternals"></my-other-element>
</template>
</div>
...
var el = this.$.container.querySelector('#elementWithUnexposedInternals');
As for the timing. In Polymer 0.5, data binding updates take place with microtask timing (after the current event handler, and before the next event is processed). As you're seeing, the changed handler is getting called before the data bound template has stamped its contents.
You should be able to solve this by delaying accessing the element with async. Combining these two fixes, you get:
showChanged: function(){
this.async(function() {
var myEl =
this.$.container.querySelector("#elementWithUnexposedInternals");
myEl.someProp = true;
});
},
setTimeout(function, 0); would probably work, too -- all you need to do is delay a single cycle in the event loop. But async is more idiomatic in Polymer (plus, it invokes the callback with this bound to the custom element instance, which is handy).
Working example here: http://jsbin.com/gicado/1/edit?html,output
You can set value for 'show' in 'domReady' event listener.

Handling MouseUp on Slider in Ember.js

I've been working on incorporating a slider into a page in an app I'm working on. My first attempt was to use the following in my template:
<span class="value">{{scaledDistThreshold}}%</span>
{{input type="range" min="0" max="100" step="1" value=scaledDistThreshold}}
<button class="set" {{action "setDistThreshold"}}>Set</button>
This works; that is, I can use the slider and see the bound value change, and pressing the button will persist whatever value to the server.
What I'd like to do now is to remove the 'Set' button and persist on mouseup from the slider itself, and I'd prefer to do it with Ember as opposed to, say, a direct jQuery hook. So far, my searches for sliders using Ember has turned up basically nothing.
Any ideas how I might accomplish this? Thanks in advance!
FOLLOW-UP EDIT
Per Justin's answer, I was able to do the following:
App.InputRangeComponent = Em.TextField.extend({
type: 'range',
action: 'mouseUp',
mouseUp: function () {
var value = this.get('value');
this.sendAction('action', value);
}
});
I was then able to use this component in my markup as follows:
{{input-range min="0" max="100" step="1" value=scaledDistThreshold action="setDistThreshold"}}
The action I named gets called on mouse-up and passes along the current value of the slider, all as intended. Thanks again to Justin for the answer!
My first thought, and this isn't, strictly-speaking, an answer to your question, but another option might be just saving the bound value on change, and throttling it to only do so, say, once every quarter-second or so.
Now, as for answering your actual question:
(Please note: I'm still fairly new to Ember, so if what I say doesn't make sense, it's probably me, not you)
I didn't know that type="range" was an option for the {{input}} helper. Learn something new every day. :)
Take a look at the Ember docs on the {{input}} helper. When you use type="text", Ember is creating an instance of Ember.TextField. You have a couple of options:
You can reopen Ember.TextField and add a mouseup event to it.
You can create your own, new helper, that uses a new view (that you create) that extends Ember.TextField.
I'd recommend #2 for fairly obvious reasons (you probably don't want every textfield you make doing the same thing on mouseup).
SO, option 2.
I think this is how it would go down. If not, hopefully, it will at least point you in the right direction, or someone will correct me.
First, you'll want to create a new view that extends Ember.TextEdit. Let's call it "rangeSlider".
var rangeSlider = Ember.TextField.extend({
});
Here is where you should add your mouseup event:
App.RangeSlider = Ember.TextField.extend({
mouseUp: function(){
//Do stuff here
}
});
Now, if you don't want to use {{view "App.RangeSlider"}}, you can create a new helper:
Ember.Handlebars.helper('rangeSlider', App.RangeSlider);
Then you just have to use {{rangeSlider}} to insert that view.
I'm not 100% sure how adding attributes via your new helper works, though.
Looking at the ember.js code, the {{input}} helper had a bit more going for it at declaration:
Ember.Handlebars.registerHelper('input', function(options) {
Ember.assert('You can only pass attributes to the `input` helper, not arguments', arguments.length < 2);
var hash = options.hash,
types = options.hashTypes,
inputType = hash.type,
onEvent = hash.on;
delete hash.type;
delete hash.on;
if (inputType === 'checkbox') {
Ember.assert("{{input type='checkbox'}} does not support setting `value=someBooleanValue`; you must use `checked=someBooleanValue` instead.", options.hashTypes.value !== 'ID');
return Ember.Handlebars.helpers.view.call(this, Ember.Checkbox, options);
} else {
if (inputType) { hash.type = inputType; }
hash.onEvent = onEvent || 'enter';
return Ember.Handlebars.helpers.view.call(this, Ember.TextField, options);
}
});
Now that I think about it, I wonder if there's a way to just extend an existing helper... Not sure. I'm not seeing anything in the API.
Anyway, hopefully, this will point you in the right direction. There may be a totally easier way, but this was the first thing that popped into my head when I read your question. :)

How to create a DOM Element and use it in real time using Meteor and Templates

everybody.
I am using Meteor in a personal project and I could not do something very important to this project: I need to create some parts of the DOM in runtime and then populate them still in runtime, but using the Tamplates method I could not do this work. Maybe I am doing something wrong. Please, take a look in this example and say what's wrong or if there is a better way to do it.
Consider this example situation:
<template name = "exampleTemplate">
<div class="divToPopulate">
{{#each topModels}}
<div id="{{_id}}"></div>
{{/each}}
</div>
</template>
Then, in client.js I provide the data for topModels helper:
Template.exampleTemplate.topModels = function() {
return ExampleModel.find({},{limit:9});
}
When the Template is ready, I try to use those DOM elements in a javascript function called drawGraph(var domElementId) that is a Raphael.js function which draws a graph inside the DIV:
Template.exampleTemplate.rendered = function() {
var models = ExampleModel.find({},{limit:9});
models.forEach(function(model){
drawGraph(model._id);
});
}
But when I run this I get an error saying that the DOM element doesn't exist. So I was wondering if there is a better way to append an DOM element and populate it on the fly.
Thanks for any help.

Appending child view to body / other element

I'm building a popover/dropdown with Ember which essentially boils down to:
<div class="popover">
<span {{action showPopover}}>Click</span>
{{#if popoverShowing}}
<div class="popover-body">The popover body</div>
{{/if}}
</div>
All works fine but I have other elements on the page which are absolutely positioned and due to them forming a new stacking context there's no way I can make the popover be displayed above them.
If this were plain old Javascript, I'd append the popover to the body much like how Bootstrap does with the container option but we don't have that level of control in Ember AFAICT.
The only solution I can think of is to use an {{outlet}} in the application template and render to that, but that means for every popover/dropdown I have to split the contents out to a different view/template/controller and have an action in the router which seems rather over-complicated!
Can anyone think of a better option?
One approach that seems to work is to detach the body element on didInsertElement and then manually destroy on willDestroyElement:
didInsertElement: function() {
Ember.$("body").append(this.$())
},
willDestroyElement: function() {
this.$().remove()
}
This appears to work fine, but there are probably bugs lurking!

Execute a handler only when a view is inserted and content is loaded

I need to execute a handler when two conditions are met:
The view associated to an ArrayController is inserted in the DOM
The arrayController content is fully loaded, through a store
I tried to add an observer on the controller.isLoaded property in the didInsertElement but the observer handler never gets called.
didInsertElement: function() {
this.addObserver('controller.isLoaded', function() {
console.info('inserted the element and store is loaded');
});
},
When I setup the observer outside the didInsertElement method, the observer calls too soon the handler, before the view is inserted in the dom.
handler: function() {
var content, controller = this.get('controller');
console.info(controller.get('content').get('isLoaded'));
}.observes('controller.isLoaded')
Also, I don't understand why the handler in the above scenario gets called twice and each time the controller.get('content') shows an empty array
Possible answer: You may find my solution here a good fit. The idea is to add a {{trigger}} handlebars helper that fires an event when a subsection of the view template is rendered. E.g., if you already have a block in your template like {{#if content.isLoaded}}, you can do this:
{{#if content.isLoaded}}
{{#each content}}
...
{{/each}}
{{trigger contentDidRender}}
{{else}}
<p>Loading...</p>
{{/if}}
And you should always get an event when the data displays. Since the handlebars templates already accomplish this pretty well, I just leveraged that.
As for why you're having problems with the things you've tried, you probably have a race condition between the view displaying and the content loading (it is undefined which may happen first)--i.e. you may have added the observer on isLoaded after it had already loaded, therefore you did not observe a change. And note that (I think) an observes handler gets called each time the observed property gets set, not each time it is changed... so it could be that it's being set twice but both times to false.
One more hint instead of a real solution, perhaps in the didInsertElement, the observer is never called since I think it is wrongly defined. (I don't know if it's support key chaining).I would do something like:
didInsertElement: function() {
this.get('controller.content').addObserver('isLoaded', function() {
console.info('inserted the element and store is loaded');
});
}
If this works, don't forget to remove this observer inside the willDestroyElement hook
I believe the syntax for addObserver requires the second parameter to be the context where your handler will be executed.
didInsertElement: function() {
this.addObserver('controller.isLoaded', this, function() {
console.info('inserted the element and store is loaded');
});
}
Note the second param is 'this'. Check out the docs for Observing Property Changes