Select an input after its inserted using `Ember.run` - ember.js

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.

Related

Ember.js 2.3 implement #each.property observer on a HasMany relationship?

Say I have a hasMany relationship Procedure => hasMany Steps, with async:true, and I have a Procedure Component (on the procedure route) called procedure-main, which lists the steps as so:
{{#each steps as |step| }}
{{step.title}}
{{/each}}
I need to observe a property on each step (say, stepStatus) on change to the stepStatus on any of the steps. In Ember 1.7, I had something like this on the procedure controller:
stepsStatusObserver: function(){
...
}.observes('steps.#each.stepStatus')
This was fired on change of stepStatus on any on the steps, and whatever I had in this function was fired whenever the status changed as well. However, in Ember 2.3, I cannot implement this. I have tried with
stepsStatusObserver: Ember.observer('steps.[].stepStatus', function(){
...
})
but this only fires once when the steps are being listed on the page. When I change the status of one step to a new value, the function is never fired.
How can I replicate this functionality in Ember 2.3?
Note: In my use case, I cannot rely on manually setting off the function inside the observer on the click of a button as it must automatically fire if the stepStatus property was changed on any step.
The problem is that steps.[].stepStatus is not a valid dependent key anymore. You should replace it by steps.#each.stepStatus.
Here is a summary of valid and invalid dependent keys in current Ember versions:
array - this observes if the array reference itself changes, e.g replacing the entire array with another value or array such as that oldValue !== newValue.
array.[] - this observes both when the array itself changes (above) and when the array length changes (functionally equivalent of array.length)
array.#each.property - observes both cases above and when property of some of the array's items change
array.#each.{prop,anotherProp} - you can also observe multiple properties while specifying only one key. This expands to array.#each.prop and array.#each.anotherProp.
array.#each - isn't valid anymore, no trailing #each. Use .[] instead.
array.#each.property.property - Also not valid. Note that #each only works one level deep. You cannot use nested forms like todos.#each.owner.name or todos.#each.owner.#each.name.
array.[].property - not valid anymore. Use #each form instead.
You should still use #each
stepsStatusObserver: Ember.observer('steps.#each.stepStatus', function(){
...
})

Ember - Issue with class binding within helper

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'}}"}}

Emberjs - how to set another property from computed property when the latter is not on the template?

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.

Passing Controller Model Properties to Handlebars Helpers

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()}"

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