Ember dynamically update template (array item) - javascript

I am using Ember version 2 and have my component template as below;
{{#each info as |item|}}
<span class="value">{{item.value}}</span>
<span class="label">{{t item.label}}</span>
{{/each}}
Now I want to write values to it dynamically (i.e. assume it is a dynamic total which initially is 0 & updates automatically based on user selection of some rows)
My question is while the below code works;
var info = self.get('gridParas.info');
info.popObject();
info.pushObject({
label: 'MyTotalField',
value: total
});
self.set('gridParas.info', info);
This does not work;
var info = self.get('gridParas.info');
info.label = "MyTotalField";
info.value = total;
self.set('gridParas.info', info);
Is the popObject(), pushObject() the correct way ? What is wrong with the 2nd approach ?
I wanted to avoid pop, push each time.

the set() methods notifies the model that a property has changed, triggering the template to update. Instead of using it, you can call notifyPropertyChange('info') or notifyPropertyChange('info.label') after you update the label and value, which should update the template. More info can be found in the docs here:
http://emberjs.com/api/classes/Ember.Observable.html#method_notifyPropertyChange

You can set the properties directly on the last element, without popping and pushing:
var info = self.get('gridParas.info');
info.getObjectAt(info.get('length') - 1).setProperties({
label: 'MyTotalField',
value: total
});
Or, if you don't want to reuse the current last item, use replace:
info.replace(info.get('length') - 1, 1, [{
label: 'MyTotalField',
value: total
}]);
As you can see, you need to pass replace the index, the count of items to be replaced, and an array of new items to stick in there.
In any case, you do not need this:
self.set('gridParas.info', info);
Because that's already gridParas.info's value.

Related

AngularJS Material - ng-checked on md-radio-button with ng-value as an object

I've looked around to see if anyone has answered this and it appears they have not. I want to use an object rather than a string or integer as the value of the radio button. Is this possible? Doesn't seem so because I'm having trouble with the tags md-radio-button recognizing an object rather than string or integer value as having been selected. I can see that it works after the page loads and I select something but I don't know how to check the radio button if the value already exists. You can see a very simple demonstration here: https://codepen.io/anon/pen/ZmjrLN
I've tried class="md-checked" to see if that works, it does not. I've tried ng-checked="selectedStatus.Name == status.Name", it doesn't work either. In fact ng-checked="true" also does not work.
I would think md-radio-button could work with an object!
--- EDIT FOR CLARIFICATION ---
From an answer below, referencing the code from codepen, if I use the same object in $scope.statuses to populate $scope.selectedStatus, it indeed selects the correct radio button on load. HOWEVER, in the real world $scope.selectedStatus is populated with the actual status from the server and $scope.statuses is also populated from that same call. They are the same but 2 different objects.
In a nutshell, I still want to check off the correct one, even though the objects aren't exactly the same, they should be treated like they are, because they are the same.
When comparing objects, ng-checked looks to see if checked is the same object or a reference and does not check the contend of the object. So to set the selected / checked radio button, make the value refer to the object. Here is an example of the Javascript updates for your codepen:
angular
.module('BlankApp')
.controller('AppController', function($scope) {
$scope.statuses = [
{id: 1, Name: 'Active'},
{id: 2, Name: 'Dormant'}
];
$scope.selectedStatus = $scope.statuses[0]; // reference the object directly
});
Or if the value is set outside of the scope and we have to compare, then we can compare manually and then set the selectedStatus by finding the correct object and assigning it.
angular
.module('BlankApp')
.controller('AppController', function($scope) {
$scope.statuses = [
{id: 1, Name: 'Active'},
{id: 2, Name: 'Dormant'}
];
$scope.selectedStatus = {id: 1, Name: 'Active'};
// Find the index of the selected that matches on id
let i = $scope.statuses.findIndex((val) => {
return val.id = $scope.selectedStatus.id;
});
$scope.selectedStatus = $scope.statuses[i]
});

Posting model back to controller containing complex collection

I've posted a question similar to this, but I don't believe it clearly stated my question correctly. So here goes a better explanation!
I'm working on a C# MVC 5 application. I have a page that dynamically lists the model's collection property A. Each item in this collection uses a bit of bootstrap to show/hide it's own collection property B. So just to clarify that: This model has a collection of objects each of which has a collection of objects associated with it. These objects each have a checkbox associated with them bound to an isChecked boolean too that can be used to select/deselect items. Checking a box of an item from collection A, automatically selects all checkboxes of its collection B. So, as you can perhaps see, the sole purpose of having a checkbox on collection A items, is to act as a select all for its associated collection B items.
I'm trying to pass the entire model back to the controller on POST and then do stuff with it, e.g. grab all items that have their , but am running into a few problems.
E.g.
#foreach (var category in Model.Categories)
{
var headerId = category.Name;
var childCounter = 0;
#*some divs and headers here*#
#if (category.Items.Any())
{
#*lets give it the ability to check all of its associated items*#
#Html.CheckBoxFor(d => category.IsChecked, new {id = #headerId, onclick = String.Format("CheckUncheckChildren({0}, {1})", #headerId, category.MenuItems.Count)})
}
else
{
#Html.CheckBoxFor(d => category.IsChecked)
}
#*some other divs and headings*#
#if (category.MenuItems.Any())
{
#foreach (var item in dealItemCategory.MenuItems)
{
var childId = childCounter++ + "_" + headerId;
var serverId = item.Id;
#*some other divs and headings*#
#Html.CheckBoxFor(d => item.IsChecked, new {id = #childId})
}
}
}
Lower down on the page I have:
for (var i = 0; i < Model.Categories.Count(); i++)
{
var category = Model.Categories.ElementAt(i);
var headerId = category.Name;
#Html.HiddenFor(d => category, new {id = #headerId})
for (var j = 0; j < Model.Categories.ElementAt(i).Items.Count(); j++)
{
var item = Model.Categories.ElementAt(i).Items.ElementAt(j);
#Html.HiddenFor(d => item, new {id = j + "_" + #headerId})
}
}
In my mind, I'm retaining these properties on the page for this complex collection... (not that my understanding is 100%) I've forced ids for these hidden fields so that they correlate with the ids of the Checkboxes. Maybe not the best idea, but it was just the last thing I tried to get this to work...
Now I have two options here:
1) Believe in the above to mean that whenever a checkbox is selected/deselected, the object in the collection it is associated with, will get its isChecked boolean changed. Then, POST back the entire model and hope I'll be able to cycle through all checked items and do awesome stuff.
or
2) Create a string property in the model to hold a JSON representation of this complex collection of objects. When the page is POSTED, update this JSON string to the state of the complex collection that has been hopefully updated to have some of its items checked/unchecked. Then, POST back the entire model and server-side I would deserialize this single object into a logically equivalent complex collection and work from there.
I.e.
My model change would be adding a property:
public string JsonCategories{ get; set; }
And then in the view I'd have to initiate a post back:
#Html.HiddenFor(d => d.JsonCategories);
function accept() {
var categories= #Html.Raw(Json.Encode(Model.Categories));
var jsonCategories = JSON.stringify(categories);
var a = $('#JsonCategories').val();
$('#JsonCategories').val(jsonCategories );
$("form").submit();
};
Attempting solution A, I either get a null for the Categories property, or if I add #Html.HiddenFor(d => d.Categories); I get an empty object, count of 0.
Attempting solution B, I do get a lovely JSON representation for the complex Categories back in server land, but I see checkboxes I checked aren't changing the isChecked bools leading me to believe that, while the JSON object is able to be set to the Categories object on POST client-side, everything done by the user isn't being kept so ultimately the Categories collection of the model hasn't changed since it was passed to the View initially.
Sorry if this all seems complicated, it is and for a junior like me, I thought it best I ask around as I've never posted back lists and stuff to the controller before.
I'd be happy to provide more information if it will help!
Warmest regards,
If you change your foreach loops to for loops you can make use of the loop index in the lambda expression. This should make the engine output the correct syntax for posting the values. For example:
#for (int i = 0; i < Model.Categories.Count(); i++)
{
...
#Html.CheckBoxFor(d => d.Categories[i].IsChecked)
...
}
This should correctly bind the values to the POST request. The resulting html will be similar to the following snippet (0 being the first item, 1 would be the second etc.):
<input name="Categories[0].IsChecked" ... ></input>
Alternatively you can create editor templates for your child collections, which will result in you writing out this snippet to replace where your for loop is currently.
#Html.EditorFor(m => m.Categories)
More on how to do this here.
This should help you go with option 1.

Ember : How to update a single field in an array of JSON objects in a controller?

How can I update Einstein's score to a 100 in the controller?
In a controller, I have an array of JSON objects like :
items = [
{title:"John", score:24},
{title:"Einstein", score:2},
{title:"Mary", score:19}
];
This is rendered in the template using component like this :
{{#each items as |item|}}
{{some-child-component scoreVal=item.score}}
{{/each}}
What should I do to update Einstein's score to a 100? I just want to change that particular field and have it reflect in the app.
I want to avoid replacing the entire array with a new (almost same one), because that causes a refresh for all components in the template.
[FAILED] I tried using :
var allItems = this.get('items');
allItems[1]['score'] = 100; //ERROR
Also
this.set('items[1][score]',100); //ERROR
I discovered the Ember way of doing this:
var elem = items.objectAt(1);
Ember.set(elem, 'score', 100);
You could use .findBy to find the record and give it a new value.
If you aren't using Ember Data for your data layer then I would also make the array of objects Ember Objects so you can use .get and .set to update the attributes.
Here's a full JSBin of what you're trying to accomplish.
Where exactly are you trying to set the score to 100?
If you make your items Ember objects
items = [
{title:"John", score:24},
{title:"Einstein", score:2},
{title:"Mary", score:19}
].map(function(item) { return Ember.Object.create(item) });
you will be able to use the setter as items[1].set('score', 100)

Looping through json object from a checkbox loop

I have a json object thats being loaded through a checkbox in an ng-repeat. It lists possible services and I'm trying to get access to the ones that have been checked when I submit the form.
I'm loading the checkbox through:
<label style="margin-right:10px" ng-repeat="item in dsServces">
<input
type="checkbox"
name="selectedFruits"
value="{{item.DefaultServiceID}}"
ng-model="expense.dsService[item.DefaultServiceName]"
> {{item.DefaultServiceName}}
</label>
I can see the expense is being built by testing with:
{{ expense.dsService }}
returns (when a couple are selected)
{"Email":true,"Backups":true}
But I can't work out how to loop through so it says just the service name so I can add them against the customer.
In the back end I have:
$scope.expense = {
tags: []
};
And on the submit:
angular.forEach($scope.expense, function(item) {
console.log(item);
});
Which spits out Object {Email: true, Backups: true} but I can't work out to have a loop where it just returns which ever objects are true.
My main aim is to have something like
angular.forEach(forloop) {
//service.item should be like 'Email' whatever the item.DefaultServiceName is
// the loop should be all items that are checked
addService(service.item);
});
If I am understanding properly, you want to push the key values of the object into an array. If that is the case, just make a little change to your forEach()
Like so:
// The first parameter is what you want to loop through
// Second is the iterator, you can separate out the key and values here
// The third is the context, since I put the array we want to push to in here
// we just need to call `this.push()` within the iterator
angular.forEach($scope.expense, function(value, key) {
this.push(key);
}, $scope.expense.tags);
Let me know if this helps.

AngularJS: add element to array if not already there works before but not after content reload

I have some food-related inputs. A user may select "unit" from a select list and enter a quantity using an AngularUI slider. When a user enters a unit or quantity value for any food, the food and its values are added to an array, editedFoods.
My page loads content via http requests when a category header is clicked. The intention of the editedFoods array is that it may be used to persist the user-selected values after a content reload.
Currently, an element in editedFoods maintains a live connection to its original food element, UNTIL the content is reloaded. Then the changed food element gets treated as a new element and added newly to the array.
When a select or slider is changed, i call addSelection(food). This checks if the food is already in the array, and if not, adds it, by checking the indexOf food:
//Add changed food to array
$scope.editedFood = [];
// toggle selection for a given food
$scope.addSelection = function addSelection(food) {
var idx = $scope.editedFood.indexOf(food);
// is currently selected
if (idx === -1) {
$scope.editedFood.push(food);
}
Right now, this works until the food category gets reloaded. After that happens, it adds a new food element even if a matching one already exists.
Here's the Plunkr
Why does it not find food in editedFood, and add it anew, after the Food content is reloaded? Can I improve this search by searching for something more specific than food- e.g the food's unique id (food.uid)- so that this qualifier works even after reload?
It's happens because AngularJS injects a unique property called $$hashKey on your data and, when you reload your foodList, the resources got via ajax has a different $$hashKey.
If you console.log your editedFood (edited after and before reload) you will see this:
[Object, Object]
0: Object
$$hashKey: "004"
code: "X 39 2000000"
final_factor: "0"
slider: 7
sorting: "0"
title: "Nussbrot"
uid: "56"
unit: Array[2]
__proto__: Object
1: Object
$$hashKey: "00B"
code: "X 39 2000000"
final_factor: "0"
slider: 5
sorting: "0"
title: "Nussbrot"
uid: "56"
unit: Array[2]
__proto__: Object
Note how the $$hashKey differs. That's what make the indexOf function doesn't works after reload.
You should implement your own indexOf that looks only in the essential property (problably the uid).
Edit: You can do like this:
myApp.filter('find', function(){
return function(food, foods) {
for (var index in foods) {
if (foods[index].uid == food.uid) {
return index;
}
}
return -1;
}
});
And, in your controller:
var idx = $filter('find')(food, $scope.editedFood);
Don't forget to inject the $filter dependency on your controller.
Working Plunker

Categories

Resources