We upgraded to Ember 1.11.1 and Ember-validations 2.0.0-alpha.3.
Controller
export default Ember.Controller.extend(EmberValidations.Mixin, {
canValidate: true,
validationModel: Ember.computed.alias("model"),
validations: {
'validationModel.name': {
presence: { 'if': 'canValidate', message: 'Please enter a name'},
},
},
}
Template
{{log "error value" errors.validationModel.name}}
{{input classBinding="errors.validationModel.name:app_input_box_error:app_input_box" placeholder="Document Name" value=model.name}}
With the above template code in place, validations works as expected and the input's class is swopped out depending on whether there is a validation error or not. However, when the {{log}} line is removed, the class binding seems to be lost and the input class is no longer appropriately updated. Any advice on the cause/fix please?
Note Class bindings outside helpers e.g. in a standard div continues to work properly
Maybe try this:
{{input classBinding="errors.validationModel.name:app_input_box_error:app_input_box" placeholder="Document Name" value=validationModel.name}}
I'm not seeing classBinding but classNameBindings in the docs, I'm not sure if something was deprecated along the way.
I would suspect that the classBinding is not triggering the property to be updated, I seem to recall some issues with this on ember-validations at one point not always triggering. Have a look at https://github.com/aceofspades/ember-validations/commit/85ecaa348f2a1ccfb52f614037c4b4dbf77bceb4 and see if that might help.
From a higher level, I would think you'd be repeating this pattern often, adding a class name based on errors ties to the specific field. Personally I might spend some time looking for or building an input component that handles the annotation for ember-validations, where you can have a fieldName property and have it look at the appropriate errors.validation.${fieldName}. Coding in JS might help or at least make it easier to debug.
This is not precisely related to individual fields but might also be of help to you, in particular moving to HTMLBars syntax, i.e.
{{input class="{{if errors.validationModel.name 'app_input_box_error' 'app_input_box'}}"}}
Related
Updated — The problem with my solution below was with if statement around Ember.run. Removing it solved the problem, but my approach was also incorrect. Instead of scheduling the focus for after rendering, I should make a custom component as acorncom suggested.
Original Question:
I'm building a list of text inputs based on an array on my model. On newline I'd like to insert a new text field and then select it. I'm trying to do this with Ember.run.scheduleOnce, but the function I provide is never called.
I've reopened the textfield to expose a data-index attribute binding, and in my template am rendering:
{{#each model.entries as |entry index|}}
{{input value=entry.text class='field--entry' data-index=index insert-newline=(action 'insertEntry' index)}}
{{/each}}
In my controller:
actions: {
insertEntry(index){
// This works fine and a new textfield is added.
this.get('model.entries').addObject({text: 'Example'});
if (index){
Ember.run.scheduleOnce('afterRender', this, function(){
// This doesn't seem to work at all.
console.log("This is never logged");
$(".field--entry[data-index='" + index + 1 + "']").focus();
});
}
}
}
Per a work-in-progress branch here (https://github.com/ember-best-practices/recommendations/blob/initial-pass/guides/run.md), you generally want to steer clear of using afterRender if there are other approaches that would work better.
I'd suggest using a component to handle rendering your list of items, as you'd be able to use the component lifecycle hooks to do what you're after. If you check the guides (specifically the section discussing didRender), you'll see an example that closely matches what you're after ... https://guides.emberjs.com/v2.17.0/components/the-component-lifecycle/#toc_making-updates-to-the-rendered-dom-with-code-didrender-code
I don't think you need to manually manipulate the focus:
This Ember can set the autofocus attritbute on render.
"(eq index 0)" sets it to true on the first element only
{{#each model.entries as |entry index|}}
{{input value=entry.text autofocus=(eq index 0)}}
{{/each}}
There was a syntactic mistake in your template and I fixed it. I suspect that in your case the action was never called somehow. Can you check the twiddle I prepared for you? What it does is simply adding a new input and giving focus to it. I just changed the selection logic for jquery focusing to
Ember.$(`.field--entry:eq(${index+1})`).focus();
and it simply works as expected.
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
Is there a standard way of handling errors when a 'findHasMany' call fails? Use case:
Model: App.User
{
DS.hasMany('comments', {'async': true});
}
Template
{{#each comment in comments}}
<p>{{comment.title}}</p>
{{/each}}
The issue is that when the lazy loading of comments fails, due to some server issue for example, I want to be able to respond to that error in the UI (by routing somewhere else, showing a popup about errors on the page, etc). At the moment the promise just rejects. I thought that Ember Data might have some hook on the ManyArray for cases like this, but it doesn't seem to, and the store seems to define precisely nothing as the action to carry out in such cases: https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/ember-data/lib/system/store.js#L1758 - the promise is given a 'resolve' method, but not a reject method.
My options seem to be either subclassing the store, and adding in some reject code there, or subclassing DS.PromiseArray and observing the 'isRejected' property. Any thoughts would be very welcome!
EDIT:
This issue seems to boil down to the fact that, when handling models defined in a route, Ember and Ember Data work well together (you can catch rejecting promises in an error action) there is no similar structure for async requests directly through a template. One solution might be to have an observer in the controller that observes something like 'model.isError', but a failing hasMany relationship does not trigger an error on the owning model. I suppose instead I can do 'comments.isRejected', but again, I would have to code that in for every controller that has a model with a hasMany relationship, in other words, all of them, which doesn't seem very satisfactory. If models had an observable enumerable property (like "hasManyIsError": {comments: false, posts: true}) then it would be easy to observe any of them with 'hasManyIsError.length
Assuming a var called user that has been fetched, you'd do this:
var itWorked = function(comments) { return comments; }
var itFailed = function(error) { return error; }
user.get("comments").then(itWorked, itFailed);
async: true means it'll get using a promise... so you can use then... you can't do that on a relationship that doesn't specify async: true.
[edit] sorry I just realised it might not be obvous that whatever you put in the itFailed function will eval when the request for comments fails, and likewise inversely for itWorked... :)
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()}"
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}}