I would like to define a function like this (in a component in this case):
doSomething: function() {
// do something
}.observes('yield')
Where the component handlebars file could basically be:
...
{{yield}}
...
Will this do what I want? That is, when the yield value changes, will the doSomething() function be called?
U should use a parameter not the yield content for this! Like:
call this from the template:
{{tool-tip value=myValue"}}
and the controller:
myValue: function() {
return 'Hi %#, welcome to %#'.fmt(this.get('val1'), this.get('val2'));
}.property('val1', 'val2');
instead of the solution that u probably wanted to use:
{{tool-tip}}
Hi {{val1}}, welcome to {{val2}}
{{tool-tip}}
Let me explain u the problems with your idea:
what u have there where the {{yield}} is, is a template. U could observe the template, but the template itself never changes. The template is a compiled handlebars template!
So there is not even the string Hi {{val1}}, welcome to {{val2}} anymore, but there is a compiled version from this. So a javascript function that will produce Hi Krutius, welcome to StackOverflow if u call it with the JSON { val1: "Krutius", val2: "StackOverflow" }.
U see, this function will always be the same!
So you have two problems:
The first is to get resulting HTML that u need to insert into the title attribute of your tooltip containing thing, or to give to the tooltip() function as the template property. This is tricky. A working solution is to still use the {{yield}} in a hidden tag, grab the html and put it into your tooltip. This will work (as long the values don't change) but is definitely a dirty solution. I think u maybe have already done this, and thats why you want to observe {{yield}}.
But its important to know that the view won't rerender! For each value there is a view created and some strange <script> tags inserted into the DOM, and then a observer is attached to that single value, and when the value changes the specific place in the DOM can be found due the <script> tags and the value will be updated.
This behavior will change in the future when HTMLBars will come up. So you can't relay on it. Maybe a better working way is to call the handlebars function yourself to get the html.
But u still have another problem. When will the result of the handlebars function change? For that you would need to know what property the generated handlebars function requires. You probably could do this by analysing it, but maybe the function is different with HTMLBars? And it would need a lot of hacking into the internal ways of ember rendering the HTML!
So, over all, just don't do it. Solve it by giving a single value to the component and render the HTML for this value yourself!
Related
There's a piece of code in my template that's repeated multiple times through it... So I want to DRY it.
It's a simple piece of code with no logic on it.
First, I though about creating an array with the title and values for a loop, but then I remembered Handlebars does not allow me to do it.
Second, I though on partials. They're only a part of a template, afterall. But then I noticed it apparently ignores additional arguments. Thus, there's no way to alter it's contents besides what's already in my model. In this and in the prior case, I didn't want to mess my route/controller with template-only variables.
Third, my current solution is a Component, but it seems so dirty. However, it's quite troublesome to have a JS file with only an empty object; furthermore, components have no file structure, and I wanted this piece of code to live along with it's original template.
This is a simplification of what I wanted to achieve:
project/index.hbs
{{partial "counter_block" value=model.count_success title="Yeah!"}}
{{partial "counter_block" value=model.count_failures title="Boo :("}}
project/_counter_block.hbs
<div>
<h2>{{title}}</h2>
<span>{{value}}</span>
</div>
Is there any solution for this matter or is this just another case where Handlebars oversimplification causes coding issues?
That's when you create a component, the js file isn't necessary, you can just have a template components/counter-block
{{counter-block value=model.count_success title='Yeah!'}}
{{counter-block value=model.count_failures title='Booo!'}}
http://emberjs.jsbin.com/suxebehaqe/1/edit
This is a follow-up from my previous question, thought it deserves a different post.
I what to set a data subset to a different property and have it available (and update-able) on the template. So I thought I 'd create an empty property and simply set its value.
The updated template:
{{#each test12}}
{{businessname}}
{{/each}}
The controller:
test12: [],
test11: function(){
var bms = this.get('businessmatches').filterBy('type', 'hardware');
this.set('test12', bms);
return bms;
}.property('businessmatches.#each.[type]'),
The problem is that this does not work. The interesting thing is that if I change the template to
{{#each test12}}
{{businessname}}
{{/each}}
<hr/>
{{#each test11}}
{{businessname}}
{{/each}}
then it works!!! :o
I could hide the second part on a display:none; div or I could have the logic directly on test12 but the behavior was a complete surprise (I thought set didn't work at first). Am I missing something? Is there a proper way of doing it?
In general, side effects in computed properties (in your case, setting test12) are best avoided. They make the program hard to reason about.
In your case, the problem is that merely referring to test12 does not compute anything. Ember has no way to know that test12 is getting set inside test11 and that it might need to run that. When you then refer to test11 from the template, it is computed, with the side effect of setting test12, which updates the template (so fast you can't see it; to test this, you could add a {{debugger}} between the test12 and test11 parts of the template, and when you stop in the debugger, you will notice that the test12 part is still empty).
Depending on what you are trying to accomplish, you could do something like test12: function() { return this.get('test11'); }.property('test11'), or test12: Ember.computed.alias('test11'), which is the same thing.
I would like to make the following code work if at all possible. The goal is to have a computed property that depends on a list of strings by using Javascripts "apply" method.
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
App.View = Em.View.extend
myProps: ['content.prop1', 'content.prop2']
myComputed : (->
return "super valuable things"
).property.apply(#get("myCompted"), #get("myProps"))
So far...no luck. It just seems to reject my most earnest desire to make this work.
Any help would be supremely appreciated!
Steve
You don't need to use the apply function and I'm not sure why you think you need to do that. The property function only wants an argument of the property it depends upon. In this case it is 'myProps'. You do not need to get that property either, Ember will work it all our for you.
Take a look at this jsfiddle and you'll see what I mean. You can do all your manipulation of the array inside the myComputed property and the array will have been made available for you already.
App.ApplicationView = Em.View.extend({
myProps: ['123', '456'],
myComputed : function () {
return this.get('myProps');
}.property('myProps')
});
I been trying to compare some values in handlebars if statement {{#if value == 'otherValue'}}, obviously unsuccessfully because handlebars do not like this and expecting a string, boolean, or function name. Well that would be ok, but then I tried to pass parameter in the function like you can do with {{action}} helper, and well that didn't workout either, got this in console
Error: assertion failed: You must pass exactly one argument to the if helper
So then I decided to do this in a View, even so ember js guides points that accessing template values in-scope is unusual and they provide only poor paragraph with no examples.
http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_accessing-template-variables-from-views
So when I tried to do this, I got a problem of accessing those variables, I tried this way this.get('controller.templateVariables') and via full path to View, but value was either undefined or .get() wasn't exists as a method.
So at this moment I decided to save variable in the DOM data property, but turns out this {{#view App.TabsView data-title="{{tab}}"}} is going to literately give me a string {{tab}} when I try to access it from View with this.get('data-title').
The only way left to me was to insert additional element inside view and store variable there, and afterwards access it with jQuery class selector. but element is not yet exist in the DOM at the time of isVisible function gets executed, so I have no access to values at that time. That explains why this.get('element') was returning null.
Similar examples on ember js mostly ends up with something like if (someLogic) {}, but how I can do any logic when there is no variables available to me.
Question
To simplify my question - is there a way how I can do such a thing in ember js? Simple as
// have objects stored in controller
var obj = [{title:'Tab1'}, {title:'Tab2'}, {title:'Tab3'}];
// loop via them in the DOM
obj.forEach(function(tab) {
// do this kind of comparison
if( tab.title == currentTab() ) {
// do something here
}
});
If that is not possible, then what would be the other way to achieve similar functionality?
You can write a handlerbar helper to do this
{{activeTab tab}}
Handlebars.registerHelper('activeTab', function(tab) {
})
See a question about the same issue
Active Tab
Or look at existing helpers to write your own
Bind Helper
Template Helper
I think the best way for me to demonstrate this is with a heavily commented JSFiddle that I've put together for you: http://jsfiddle.net/PbLnm/
Please ask any questions below if you're not sure about anything.
The main part which determines when to add the active class is in the computed property:
// Determine if the object we have for this view is the same as the activeTab's object. If it is the same, then this view is the current active tab.
active: function() {
return Boolean(this.get('parentView.activeTab') == this.get('tab'));
}.property('parentView.activeTab')
After I call render on my jsRender template, it seems to be consumed, and thus is removed from the DOM. This is frustrating as I have a page where the template needs to be rendered several times depending on user interaction.
console.log($('#tpl'));
$('#container').html($('#tpl').render(json));
console.log($('#tpl'));
The second console.log is an empty array, and I can confirm the template no longer exists using the DOM inspector and the exception that jsRender throws: Uncaught JsRender Error: Unknown template: "#tpl" -- the page must be reloaded to re-inject the template into the DOM.
How can I persist the jsRender template between renderings?
I'm still not sure why it has to be consumed and simply can't stay in the DOM after rendering the first time, but I found a workaround. If anybody knows the reason for removing the template from the DOM, I'm still interested.
Update: Actual answer (Thanks, Boris)
My template was within my #container element, so the html() method was of course overwriting it. Silly me.
Workaround Neat little trick anyway
Using this 'variant' example, I saved the template in a local variable. Then I call render on the variable name instead of the jQuery selector:
var tpl = $.templates('#tpl');
.
.
.
console.log(tpl);
$('#container').html(tpl.render(json));
console.log(tpl);
This has also managed to preserve the template across renderings.
I also had a similar problem today where I had two target divs and two script block templates in the body. My problem was that I hadn't closed the div element tags correctly (too much xaml) and the result was the second template was never rendered as it couldn't be found.
Here's a JsFiddle showing the correct usage (rather than the /> self-closing syntax):
http://jsfiddle.net/jgoldsmith/XvvPC/
Hope that helps someone else.