Ember - Having problems rerendering template - ember.js

Im having problems with letting hbs know that a property has changed.
Ive tried "propertyDidchange" etc. Nothing seems to be working.
I've a form counter
personFormCount: [{id: 1}, {id: 2}]
And form hbs
{{#each personFormCount}}
<br>Form ID: {{id}}
<button type="submit" {{action 'getID' id}}>ID</button> // sends ID
{{view 'person'}}
{{/each}}
JS
getID: function(id){
var index = id - 1;
this.get('personFormCount').splice(index, 1);
this.notifyPropertyChange('personFormCount');
},
No changes happening on template. Array changes itself properly when action triggered. Function should take care of deleting.

splice does change the array, but Ember isn't watching to see if you're splicing the array or not. You'll really want to use the built in remove functions.
http://emberjs.com/api/classes/Ember.ArrayProxy.html#method_removeObjects
http://emberjs.com/api/classes/Ember.ArrayProxy.html#method_removeObject
http://emberjs.com/api/classes/Ember.ArrayProxy.html#method_removeAt
In your case this would be a good implementation
{{#each personFormCount}}
<br>Form ID: {{id}}
<button type="submit" {{action 'removeItem' this}}>ID</button> // removes this item
{{view 'person'}}
{{/each}}
removeItem: function(item){
this.get('personFormCount').removeObject(item);
},

Related

Ember action on itemController not working

Here are my controllers:
App.DesignPhotosController = Ember.ArrayController.extend({
itemController: 'designPhoto'
});
App.DesignPhotoController = Ember.ObjectController.extend({
needs: ['designPhotos'],
toDelete: false,
actions: {
toggleDelete: function() {
this.set('toDelete', !this.get('toDelete'));
}
}
});
And my template:
{{#each}}
<ul>
<li>
{{title}}
{{#if toDelete}}
<button class="restore" {{action "toggleDelete"}}>Restore</button>
{{else}}
<button class="delete" {{action "toggleDelete"}}>Delete</button>
{{/if}}
</li>
</ul>
{{/each}}
However, when I click on the "Delete" button, I get a message logged:
Error: Nothing handled the action 'toggleDelete'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.
As far as I can tell I am doing this correctly, bit I tried forcing it by various combinations of:
Adding target=this to the action
Adding {{#each item in controller}} and target=item
Changing the action to {{action toggleDelete this}}, with and without quotes
Nothing works.
Can anyone point out what I am doing wrong?
One possible reason of this behaviour is a bug present in 1.13 (up until the very last 1.x release, 1.13.13), which breaks actions that should be processed by item controllers.
In case you can't revert to release 1.12, the second workaround from the question
changing {{#each}} to {{#each item in controller}} and adding target=item to the action
is the officially recommended one and has worked for my code, although the first one
adding target=this to the action (with simple {{#each}})
should probably work too.
(I suspect that OP left either of these in the code and didn't register that it fixed the issue, because both came up as false negatives earlier due to Firefox's very persistent caching.)

Ember checkbox and value

I have checkboxes created in a loop. I set checked to a property name, and want to reach a dynamic value from the checkbox on click. In the property (for key, value) I'm getting the wrong thing - the label of my property instead of the value. Is there a simple way in Ember to get the value out of the checkbox?
Any help is much appreciated.
In HTML:
{{#each url in controllers.application.env.urls}}
<div>
{{view Ember.Checkbox checked=updateServerList valueBinding="url"}}{{url}}
</div>
{{/each}}
In javascript:
updateServerList:function(key,value)
{
if(value!=undefined)
{
console.log("----1 ", key, value);
}
}.property(''),
Per your comment, I started actually playing around with this and realized that it was not as simple as I thought. What I ended up doing was just building a custom component rather than working with Ember.Checkbox.
In your template:
{{#each}}
{{check-box url=url}}
{{/each}}
Component Template:
<script type='text/x-handlebars' id="components/check-box">
{{input type="checkbox" checked=toggleURL}}{{url}}
</script>
The component code:
App.CheckBoxComponent = Ember.Component.extend({
toggleURL: false,
logURL: function() {
console.log(this.url);
// Do something with the URL
}.observes('toggleURL')
});

ember.js attribute binding and the event object

So far, it looks like Ember does not work as I expected, and I'm hoping I've just missed something. What I need to do is iterate over my model, a blob of JSON made up of arrays and objects, and build out a form. When the user marks a checkbox, the controllers Action updates the model.
Here's how I want it to work...
<form {{action 'answerSupplied'}}>
{{#each model.questions}}
<h3>{{text}}</h3>
{{#each answers}}
{{input type='../type' answerId='id' data-bind-questionNum='../id' text}}
{{/each}}
{{/each}}
</form>
Here's how I can get close to that...
<form {{action 'answerSupplied' this}}>
{{#each model.questions}}
<h3>{{text}}</h3>
{{#each answers}}
{{formbuilder ../type id ../id text}}
{{/each}}
{{/each}}
</form>
==================
Handlebars.registerHelper('formbuilder', function(type, id, qnum, text, options)
{
// console.log(options);
var q_type = options.contexts[0][type],
a_id = options.contexts[1].id,
q_number = options.contexts[0][qnum],
a_text = options.contexts[1].text;
return new Handlebars.SafeString(
'<input type='+ q_type +' id='+ a_id +' name='+
q_number +'>'+ a_text + '</input><br/>'
);
}
});
The big problem wit this is, I can not identify which element was clicked because I don't have access to the event object.
Can I manually bind the action with something like 'data-ember-action', and pass in params? Or, is this too far outside the Ember way?
== update ==
Here's the above JSFiddle, improved by passing parameters to the Action. The event propagation seems to get halted in the Action resulting in the inputs not getting properly marked. Radio buttons loose their grouping, and checkboxes do not get checked.
Ember can easily handle what you're doing here. First it's easier to keep your scope and generate named each loops.
<form {{action 'answerSupplied'}}>
{{#each question in model.questions}}
<h3>{{question.text}}</h3>
{{#each answer in question.answers}}
{{input type=question.type answerId=answer.id data-bind-questionNum=question.id placeHolder=answer.text}}
{{/each}}
{{/each}}
</form>
You may have to do some if statements for labels etc around your input statements.
http://emberjs.jsbin.com/zofoqeje/1/edit

getting back reference to a specific model using Ember's Array Controller

I'm new to Ember and am finding some of their concepts a bit opaque. I have a app that manages inventory for a company. There is a screen that lists the entirety of their inventory and allows them to edit each inventory item. The text fields are disabled by default and I want to have an 'edit item' button that will set disabled / true to disabled / false. I have created the following which renders out correctly:
Inv.InventoryitemsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON("/arc/v1/api/inventory_items/" + params.location_id);
}
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" data-id=id}}>edit item</button>
<button {{action "saveInventoryItem" data-id=id}}>save item</button>
</div>
{{/each}}
</script>
So this renders in the UI fine but I am not sure how to access the specific model to change the text input from disabled/true to disabled/false. If I were just doing this as normal jQuery, I would add the id value of that specific model and place an id in the text input so that I could set the textfield. Based upon reading through docs, it seems like I would want a controller - would I want an ArrayController for this model instance or could Ember figure that out on its own?
I'm thinking I want to do something like the following but alerting the id give me undefined:
Inv.InventoryitemsController=Ember.ArrayController.extend({
isEditing: false,
actions: {
editInventoryItem: function(){
var model = this.get('model');
/*
^^^^
should this be a reference to that specific instance of a single model or the list of models provided by the InventoryitemsRoute
*/
alert('you want to edit this:' + model.id); // <-undefined
}
}
});
In the Ember docs, they use a playlist example (here: http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/) like this:
App.SongsRoute = Ember.Route.extend({
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
But this example is a bit confusing (for a couple of reasons) but in this particular case - how would I map their concept of playlist to me trying to edit a single inventory item?
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" this}}>edit item</button>
<button {{action "saveInventoryItem" this}}>save item</button>
</div>
{{/each}}
</script>
and
actions: {
editInventoryItem: function(object){
alert('you want to edit this:' + object.id);
}
}
Is what you need. But let me explain in a bit more detail:
First of all, terminology: Your "model" is the entire object tied to your controller. When you call this.get('model') on an action within an array controller, you will receive the entire model, in this case an array of inventory items.
The {{#each}} handlebars tag iterates through a selected array (by default it uses your entire model as the selected array). While within the {{#each}} block helper, you can reference the specific object you are currently on by saying this. You could also name the iteration object instead of relying on a this declaration by typing {{#each thing in model}}, within which each object would be referenced as thing.
Lastly, your actions are capable of taking inputs. You can declare these inputs simply by giving the variable name after the action name. Above, I demonstrated this with {{action "saveInventoryItem" this}} which will pass this to the action saveInventoryItem. You also need to add an input parameter to that action in order for it to be accepted.
Ok, that's because as you said, you're just starting with Ember. I would probably do this:
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled=headerEnabled}}</p>
<p>{{input type="text" value=detail disabled=detailEnabled}}</p>
<button {{action "editInventoryItem"}}>edit item</button>
<button {{action "saveInventoryItem"}}>save item</button>
</div>
{{/each}}
</script>
with this, you need to define a headerEnabled property in the InventoryitemController(Note that it is singular, not the one that contains all the items), and the same for detailEnabled, and the actions, you can define them also either in the same controller or in the route:
App.InventoryitemController = Ember.ObjectController.extend({
headerEnabled: false,
detailEnabled: false,
actions: {
editInventoryItem: function() {
this.set('headerEnabled', true);
this.set('detailEnabled', true);
}
}
});
that's just an example how you can access the data, in case the same property will enable both text fields, then you only need one, instead of the two that I put . In case the 'each' loop doesn't pick up the right controller, just specify itemController.

Ember.js not sideloading

I have the need to load random items in my Ember application. To do this, I do the following:
Test.ApplicationRoute = Ember.Route.extend({
events: {
randomItem: function() {
var route = this;
$.getJSON('item/random.json', function(data) {
Test.Item.find(data).then(function(item) {
route.transitionTo('items.show', item);
});
});
}, // ..... etc
This works fine, except for one thing: nested-sideloaded data is not shown. When items.show is visited via a {{#linkTo 'items.show' item}}, the item's child data is also loaded and visible. However, when this randomItem event is fired, only the direct children are shown. The children of the children are not.
Why is this and/or how do I fix this?
It turns out, the reason the artifacts are not being shown is that I try to listen to controller.content.isLoaded to adapt the view to show a loading image if the content is still loading.
For some reason, the following shows the loading... text and icon forever on subsequent usages of the route:
{{#if controller.model.isLoaded}}
<div class="row">
</div>
{{#each artifact in controller}}
{{render "artifacts.show" artifact}}
{{else}}
<span class="muted">There are no artifacts.</span>
{{/each}}
{{else}}
<i class="icon-spin icon-spinner icon-large"></i> Loading...
{{/if}}
If anyone knows why, please let me know, so I can solve this problem.