I have a Component which receives a model attribute in some template:
{{#client-user-pipeline-step model=step tasks=step.tasks routeOnOpenAddTaskModal=(route-action "onOpenAddTaskModal")
routeOnOpenEditTextModal=(route-action "onOpenEditTextModal")}}
{{/client-user-pipeline-step}}
As per my understanding, this will be stored as an Ember.Object instance complete with property observers and stuff inside the component. Now when I try to change some property of the model by simply assigning a value to it I get this error:
You must use Ember.set() to set the `name` property (of [object Object])
Which is exactly what I expect as the property is being observed due to data binding. Now when I try to change the value using Ember.set like in this.getModel().set('name', name) ; I get this error:
this.getModel(...).set is not a function
Wow great! I couldn't find a precise explanation of how this data binding thing is supposed to work in the docs, so I'd appreciate if someone could enlighten me on the whole idea and this issue in particular. Thanks!
UPDATE:
The getModel() function is a shorthand for this.get('model').
Related
I am trying to use a fairly simple Handlebars helper within an #each loop going over the items in a controller's model (it's a collection of models supplied by EmberData using the fixtureAdapter). Here's the basic layout of the template:
{{#each}}
....
{{#each clusters}}
<span>{{prettifyTimestampTime cluster_timestamp}}</span>
{{/each}}
{{/each}}
Here is my helper (in coffeescript):
Ember.Handlebars.registerBoundHelper 'prettifyTimestampTime', (timestamp, options) ->
d = new Date timestamp
hours = String(d.getHours() % 12)
hours = "0#{hours}" if hours.length is 1
"#{hours}:#{d.getMinutes()}:#{d.getMinutes()}"
I originally set this helper on Handlebars itself with Handelbars.registerHelper, but I kept getting the string "cluster_timestamp" passed in (no matter what I put after prettifyTimestampTime in the template, it would get resolved to a String).
I then followed suit and attempted to give stukennedy's answer a try by wrapping the property in quotes and doing a lookup on options.data.keywords, but the key wasn't in that dictionary.
When I to tried to use Ember.Handlebars.registerBoundHelper instead, per Bradley Preist's suggestion here, the timestamp argument is simply undefined. I do notice that, when I try to access any properties on options.contexts[0], the values they point to are undefined, but the keys are there.
I feel completely lost at this point. Any direction is welcome. Is this really a known bug in Ember, as stukennedy has pointed out in the previous SO questions? Having just started with Ember and Handlebars, I would rather have this just be some dumb error on my end, considering how difficult it was for me to also to set up fixtures with Ember data in the first place. :-P
EDIT: After seeing this question, I realize why registerHelper did not work (because it does not try to associate what is passed in with the property of the current object in context). However, I'm still just as lost since the lookup of the property isn't working. Perhaps this is an Ember Data issue with promises? The only thing that makes me confused about that being the case is that I am using fixtures (no request made), and I am able to get at the property cluster_timestamp normally with a normal expression like {{cluster_timestamp}}.
don't use registerHelper, http://emberjs.com/guides/templates/writing-helpers/
Ember.Handlebars.helper 'prettifyTimestampTime', (timestamp, options) ->
d = new Date timestamp
hours = String(d.getHours() % 12)
hours = "0#{hours}" if hours.length is 1
"#{hours}:#{d.getMinutes()}:#{d.getMinutes()}"
I have a 'new' action within a component (Ember.Component) that has the following code in it:
var store = this.get('store');
store.createRecord('child');
yet I'm getting the following error:
TypeError: 'undefined' is not an object (evaluating 'store.createRecord')
The only way I've been able to proceed with this is to find the parent object (always set in the component) by using:
store = this.get('parent.store');
and then proceeding with the createRecord call from above. Is this the "normal" way to proceed?
The store doesn't exist inside of a component, so either you need to pass it into the component, or you have to get it from some parent controller (or passed in controller).
Via Ember Data Transition document (https://github.com/emberjs/data/blob/master/TRANSITION.md)
In general, looking up models directly in a component is an anti-pattern, and you should prefer to pass in any model you need in the template that included the component.
The bad part about it, is you are adding a dependency to ember data in your component, which is supposed to be agnostic of the outside world.
Is there a way to get Ember to log a warning or error if you reference a property that doesn't exist? Currently if you misspell a the name of a property bound in your handlebar template there is no warning, it just doesn't show anything, and it can be hard to find which property is incorrect.
I have LOG_BINDINGS enabled, which helps somewhat, but there is a lot of unrelated stuff to sort through.
There isn't any sort of general built-in debugging that I have found, but there is a mechanism to add your own.
Ember.Object calls a method 'unknownProperty' any time a 'get' call returns undefined. You can add a console.warn to this method to log the property. The documentation describes it as a way to make custom abstract method type handling.
http://emberjs.com/api/classes/Ember.Observable.html#method_get
Ember.Object.reopen(
unknownProperty: (property) ->
unless property is 'App' or property is 'Ember'
console.warn "Unknown property #{property} in #{#toString()}"
)
Notice the filtering of the global namespaces 'App' and 'Ember' - all calls to global properties still go through this interface, but for what we care about they are red herrings.
Unfortunately, if you try to do this by reopening Ember.Object itself, you get a bunch of junk you don't care about, because apparently this happens all the time, especially in the EventManager classes. I have gotten around this by applying it to Ember.ArrayController, Ember.ObjectController, and a Model class that all of my models inherit from.
I now get a neat warning message on the console instead of a blank page every time I accidentally type "hight" into handlebars instead of "height"
In a production solution one would want to link this to some kind of "debug" option in the build, I assume.
One half solution might be to use the log handlebars helper to log the property before using it, unfortunately a non existent property causes the template to not display at all. This is a common problem with handlebars not displaying errors.
{{log myProperty}}
I want to use the replaceRoute in an ember controller but need to pass an object id. Something along the lines of:
this.replaceRoute('projects.edit', 4)
Is that possible at all or should it be done differently? It's going to the route I tell it to but with an object id of undefined.
Is that possible at all or should it be done differently?
replaceRoute expects to be passed the route's context, not an id. You can use the id to locate the context, for example:
this.replaceRoute('projects.edit', App.Project.find(4));
Here is a jsfiddle demonstrating how this approach using the fixture adapter
http://jsfiddle.net/mgrassotti/mhyjG/1/
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')