Ember Computed Property using JS apply() - ember.js

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')
});

Related

Ember JS Component combine three sources of CSS class names

I want to be able to write a component which has three sources of classNames:
A set of static classes from the classNames property of the component (static JS code)
A set of user provided classes provided through the classNames property on the HB-helper
Up to here I have tested successfully -- Ember will merge the two sources which I think is nice. I cannot however find where this behaviour is documented.
Now I want to be able to add further classes to this list with a computed property.
The only way to do this seems to be with classNameBindings but this is not sufficient in my case! As the list of classes is quite dynamic and I want to calculate it explicitly.
What I have also tried is to define the classNames as property() but this seems not to be possible according to this issue.
Does anyone know a way to achieve this?
This is one case where I think you don't want to use Ember and instead use jQuery directly. Use Ember to take care of the static classes and the classes provided through the Handlebars helper, but use jQuery to add the computed property classes. For instance, let's says that you have a computed property called extraClasses. Try something like this:
extraClasses: function() {
return ['one', 'two', 'three'];
}.property(),
previousExtraClasses: [],
manageClasses: function() {
var previousExtraClasses = this.get('previousExtraClasses');
var extraClasses = this.get('extraClasses');
// Remove the classes that got removed from `extraClasses`
previousExtraClasses.forEach(function(className) {
if (extraClasses.indexOf(className) < 0) {
this.$().removeClass(className);
}
}, this);
// Add the classes that got added to `extraClasses`
extraClasses.forEach(function(className) {
if (previousExtraClasses.indexOf(className) < 0) {
this.$().addClass(className);
}
}, this);
this.set('previousExtraClasses', extraClasses);
).observesImmediately('extraClasses')
Every time you update extraClasses with a new set of classes, the manageClasses observer will take care of managing the classes on the component. As long as extraClasses returns an array of strings, it can be computed any way you like.
I'm not sure I fully understood your reasoning against classNameBindings. You can get dynamic classes list with help of that property. I've prepared an example in jsbin.
It uses rule that applies to classNameBindings: if no conditional classes have been listed (eg. classNamesBindings: ['isEnabled:enabled:disabled']), than use provided by computed property class name as is (eg. true||false would be in classes list of HTMLElement). So, you need to declare a computed property which returns a string with classes names and to list this CP in component's classNameBindings.

Can you use observes() with {{yield}}

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!

Emberjs one way & two way bindings?

Lets say I want that my page's title will change depending on a really simple field what is the Ember way of doing it?
I didn't really understand the bindings in Ember, do I have to create an object even if all I need is just 1 field?
Does Ember support two way bindings? if it does so how can I constrain the bindings to one-way only?
I think i'm a bit confused with Ember-data & regular Ember, when I use Ember-data do I need to care about bindings at all?
Thanks in advance :)
This is a little vague (or I just don't fully understand what you're asking), so I'll shotgun approach and we can narrow down as you ask more questions.
Preface: Ember Data is a client side record management library, Ember works completely fine without it.
Title
A page's title is a little tricky since it's kind of out of the scope of the viewable dom, but the best way to handle it would be with an observer. In the example below, as the title property changes inside of my application controller I'm setting the document.title.
App.ApplicationController = Em.Controller.extend({
title:undefined,
watchTitle: function(){
document.title = this.get('title');
}.observes('title')
})
Example: http://emberjs.jsbin.com/haducafu/1
Computed Properties
Ember does support one way bindings (though rarely do you need to care about bindings). More often you want to care about dependent properties. eg if property a has changed, property b should be updated etc. In the case below, b is a computed property that depends on a, if a changed, b is dirty, and ember should re-computed it.
App.ApplicationController = Em.Controller.extend({
a:'foo',
b: function(){
return 'Hello: ' + this.get('a');
}.property('a')
})
Example: http://jsbin.com/haducafu/2/edit
Simple Binding
Additionally Ember can do just simple bindings (you can actually skip defining name, since ember would define it the first time it uses it).
App.ApplicationController = Em.Controller.extend({
name:undefined
});
<h2>Hello {{name}}</h2>
Name: {{input value=name}}
Example: http://jsbin.com/haducafu/3/edit
One Way/Read Only:
One way will take the value from its host property, unless you set it, if you set it it stops following the dependent property and becomes its own (not modifying the dependent property).
Read only will take values form the host property, and if you try and set it it will blow chunks.
App.ApplicationController = Em.Controller.extend({
name:'billy',
oneWay: Em.computed.oneWay('name'),
readOnly: Em.computed.readOnly('name')
});
Try changing name first, they will all update, then change oneWay and it will diverge and never return, then change readOnly and it will throw errors.
Example: http://jsbin.com/haducafu/4/edit

Passing parameters between routes

What is the "appropriate" way in Ember to send a parameter from one route to another? For instance, I have two routes defined as such:
this.resource('activities', { path: '/activities/:on_date' }, function() {
this.route('new');
});
when on the ActivitiesRoute the user is presented with a dropdown of possible activities. When they choose something it transitions to the ActivitiesNewRoute:
this.transitionToRoute('activities.new');
and I know there is a second parameter available in the transitionToRoute(route,model) method but it's meant for passing in a model and I'm assuming this shouldn't be repurposed for other parameter passing. In this case the dropdown choice is picking an Action model id and the model for ActivitiesNew is a Activity.
Here are my three guesses at ways that might work:
1) Make it a router parameter
I supposed I could change ActivitiesNew to include a "parameter" as part of the route:
this.route('new', { path: '/new/:my_parameter' });
I'm not sure I'd really like to have it becoming part of the URL path but if this was the prevailing convention then I'd live with that.
2) Get a handle, post transition
Immediately following the transitionToRoute call I could set a property of the new controller class. Not sure if the controller would be setup yet but I'm imagining something like:
this.transitionToRoute('activities.new');
this.get('target').controllerFor('activities.new').set('my_parameter', myValue);
3) Use model parameter
this.transitionToRoute('activities.new',myValue);
I suspect that this is a major no-no. I haven't looked into the Ember code to know if this could work but it seems against convention so this is my "bad option".
transitionTo & transitionToRoute return a "promise-like" object. The parameter this object is resolved with is the route, from which you can access controller and currentModel. So a nice clean way to pass information to a route to which you are transitioning is:
var my_param = ....;
this.transitionToRoute('activities.new').then(function(newRoute) {
newRoute.currentModel.set('someProperty', my_param);
//or
newRoute.controller.set('someProperty', my_param);
});
EDIT/RANT:
note that in most cases, you do want to use needs, and bind things between controllers. However, there are certainly instances when you have things that depend on the logic of a route transition -- eg., controllerB has state X if we came to routeA from routeB, but state Y if we came from routeC. In that case, my answer is valuable.
The primary value of stack overflow to the development community is not the immediate answers you get to questions you post, but the massive ever growing wealth of googleable development knowledge. When you "infer" from a user's question that they "should" be doing something other than what they are asking how to do, you may be right (or you may be just incapable of imagining their particular circumstance), but if you answer only with your recommendation/rule/aphorism/cargo-cult-dictum instead of answering the ACTUAL QUESTION, you diminish the value of everybody else's google searches. If you want to tell someone to do something other than what they're asking, do it in a comment, or in a footnote to an answer to the actual question.
You can use the needs API (Read about it here):
App.ActivitiesNewController = Ember.ObjectController.extend({
needs: ['activities']
// Bind the property you need
actionTemplateBinding: 'controllers.activities.actionTemplate'
});
So what you actually need is to pass a parameter between controllers, which is exactly what needs is for. Plus, binding the property with needs you ensure it is in sync at all times, instead of relying on setupController being called.
You could use query-params (http://guides.emberjs.com/v1.10.0/routing/query-params/), as follows:
this.transitionToRoute('activities.new', {queryParams: {my_param: 'my_value'});
In order to be able to receive my_param in the new controller, you would also need to define the following lines:
App.ActivitiesNewController = Ember.ObjectController.extend({
queryParams: ['my_param'],
my_param: ''
...
});
A drawback of this solution is that the value of my_param will be serialized in URL - so it would not be suitable for some sensitive information you may want to pass between routes.
I'll answer my question with what I've decided to go with for now but keep it open for a a few days to see if anyone comes back with a more experienced answer. My answer may very well be perfectly fine ... it works anyway.
I've gone with a variation of #2 from the question. The difference is that that rather than trying to set a property in the ActivitiesNew controller from Activities controller I do the the opposite:
In ActivitiesNewRoute:
App.ActivitiesNewRoute = Ember.Route.extend({
model: function(params) {
return this.store.createRecord('activity');
},
setupController: function(controller,model) {
controller.set('actionTemplate', this.controllerFor('activities').get('actionTemplate'));
}
});
Still interested in hearing from people if there's a better way of doing this.
Transition to route with params and set model
yourAction:->
model = 'your-model'
route = 'your.path.to.toute'
routeLoad = #transitionToRoute route,
id: model.get 'id'
routeLoad.then (route) ->
route.set 'controller.model', model
return

ember js compare values in DOM if statement or at least in a View with value from DOM

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')