How to update the view in AngularJS when the data changes - javascript

I can't seem to get this right: I've got an array with categories (objects) and a post object:
var categories = $http.get('/api/categories').success(function (data) {
$scope.categories = data;
});
// The code below uses a Rails gem to transport data between the JS and Rails.
$scope.post = gon.post;
// Suffice to say the $scope.post is an object like so:
...
_id: Object { $oid="54f4706f6364653c7cb60000"}
_slugs: ["first-post"]
author_id: Object { $oid="54ef30d063646514c1000000"}
category_ids: [Object, Object]
...
As you can see the post object has a property category_ids which is an array whit all categories associated with this post. In my view (haml) I have the following:
%label
%input{"type" => "checkbox", "ng-model" => "cat.add", "ng-change" => "addCategory(cat)", "ng-checked" => "currentCat(cat)"} {{cat.name}}
As you can see, the ng-checked fires the currentCat() function:
$scope.currentCat = function (cat) {
for (var i = 0; i < cat.post_ids.length; i++){
if (cat.post_ids[i].$oid == $scope.post._id.$oid) {
return true;
}
}
};
The function above loops through the categories in the post (the category_ids property of the post object) and compares it with the parameter given. It works fine with existing categories. The problem appears when I dynamically add a new category and push it in the categories array:
$scope.addCatBtn = function () {
var category = $scope.cat;
$http.post('/api/categories', category).success(function (data) {
$scope.categories.push(data.category);
$scope.cat = '';
$scope.addCategory(data.category);
});
};
The new category does not appear 'checked' in the view. What am I missing?
Any thoughts would be appreciated.
EDIT: Adding addCategory function:
$scope.addCategory = function (cat) {
var found = false;
for (var i in $scope.post.category_ids) {
if (cat._id.$oid === $scope.post.category_ids[i].$oid) {
$scope.post.category_ids.splice(i, 1); // splice, not slice
found = true;
}
}
if (!found) { // add only if it wasn't found
$scope.post.category_ids.push(cat._id);
}
console.log($scope.post);
}

ngModel and ngChecked are not meant to be used together.
You should be fine to just use ngModel.

Related

angularJS: Dynamic header sorting not working

I am trying to implement a solution to sort a table by clicking its headers, using AngularJS.
I found a good example after doing a Google search: https://scotch.io/tutorials/sort-and-filter-a-table-using-angular
I am able to see the up and down arrows, but the table does not sort when I click them.
I think the problem resides in how the JSON object is formatted in my situation. I have not been able to figure it out, and I am hoping that with the information that I am providing on this post, I can get some help to understand what I am doing incorrectly.
Here is a copy of the JavaScript:
(function (define, angular) {
'use strict';
define(function () {
var opportunityController = function ($scope, Metadata, Factory) {
var vm = this;
//set the default sort type
vm.sortType = 'Products';
//set the default sort order
vm.sortReverse = false;
Factory.Data(caller.sp, caller.filter).then(function (payload) {
var data = angular.fromJson(payload.data).Table;
ProcessData(data);
});
function ProcessData(data) {
if (angular.isDefined(data)) {
var counter = 0;
vm.products = [];
vm.productsSet = FindByAsObjectArray(function (x) {
return (x.TypeName == "Product");
}, data);
for (var index = 0, length = vm.productsSet.length; index < length; index++) {
vm.products[index] = {
data: vm.productsSet[index]
};
}
}
}
};
return ['$scope','Metadata','Factory',opportunityController];
});
})(define, angular);
I got it to work, final version: https://jsfiddle.net/itortu/nhhppf53/
Many thanks.
You are using controllerAs
ng-controller="Company:OpportunityController as opportunity"
therefor you have to reference sortType and sortReverse in your ng-click like so:
ng-click="opportunity.sortType = 'Products'; opportunity.sortReverse = !opportunity.sortReverse"

Best way to remove the object from an array using AngularJs

I have an array of objects. I want to remove multiple objects from that array.
I have used below code which is working absolutely fine, but I need to check with you guys if there is better way to do that or its fine.
I have done it with angularjs and js.
Orders is the main array on which operations are performed.
Order is array of selected items to remove from main array Orders
$scope.Order = {};
$scope.removeOrders = function () {
angular.forEach($scope.Order, function (data) {
for (var i = $scope.Orders.length - 1; i >= 0; i--) {
if ($scope.Orders[i].Name == data.Name) {
$scope.Orders.splice(i, 1);
}
}
});
}
You can make it quite a bit shorter using filter:
$scope.removeOrders = function () {
$scope.Orders = $scope.Orders.filter(function(order){
return !$scope.Order.some(function(remove){
return remove.Name === order.Name;
});
}); // remove the order from $scope.Orders, if it's name is found in $scope.Order
};

Why does my WinJS Grouped Binding List play musical chairs with some items?

I'm creating a grouped list of my pupils as per this example: http://msdn.microsoft.com/en-us/library/windows/apps/hh465464.aspx
The actual code for creating the grouping as well as the HTML are almost identical (safe for name changes).
I'm then push()ing some items into the original List() which then also updates the GroupedList(). That part works fine.
However, what I'm seeing is this:
This list should be grouped by firstnames (on display is "Lastname, Firstname"). What I'm seeing here is that item #1 should be in "S", #3 should be in "A" and #6 should be in "I".
The only thing that I'm doing different from the example is the DataSource, insofar as I'm push()ing an actual WinJS Class in there (with getter and setter functions for the attributes displayed in the List).
However, the getGroupKey(dataItem) and other grouping functions are working as they should, i.e. return the proper values.
Any ideas? Because otherwise I'd have to look at using two arrays (one being the List() and another the array where the class instances live) for which I'd then have to program sync routines to keep the data consistent and that's something I actually wanted to escape from...
Code follows below, relevant snippets only.
Defining the Lists and grouping functions:
function compareGroups(leftKey, rightKey) {
return leftKey.charCodeAt(0) - rightKey.charCodeAt(0);
}
function getGroupKey(dataItem) {
return dataItem.lastname.toUpperCase().charAt(0);
}
function getGroupData(dataItem) {
return {
title: dataItem.lastname.toUpperCase().charAt(0)
};
}
var pupilsList = new WinJS.Binding.List({ binding: true });
var groupedPupilsList = pupilsList.createGrouped(getGroupKey, getGroupData, compareGroups);
Where the Data comes from:
var Schueler = WinJS.Class.define(function (original, id, firstname, lastname, tutor_id, picture, email, phone, notes, birthday, classes) {
var that = this;
this._classnames = new Array();
if (original) {
[... irrelevant part snipped ...]
});
} else {
var row = id;
this._id = row.rowid;
this._firstname = row.firstname_enc;
this._lastname = row.lastname_enc;
this._tutor_id = row.tutor_id;
this._picture = row.picture_enc;
this._email = row.email_enc;
this._phone = row.phone_enc;
this._notes = row.notes_enc;
this._birthday = row.birthday_enc;
this._guid = row.guid;
this.updateClassnames();
}
},
{
id: {
get: function () {
return this._id;
},
set: function (id) {
this._id = id;
}
},
firstname: {
get: function () {
return this._firstname;
},
set: function () {
//TODO
}
},
lastname: {
get: function () {
return this._lastname;
},
set: function () {
//TODO
}
},
[... irrelevant parts snipped ...]
classnames: {
get: function () {
return this._classnames.join(", ");
},
set: function (names) {
this._classnames = names;
}
},
updateClassnames: function () {
var that = this;
SQLite3JS.openAsync(DataLayer.db_path)
.then(function (db) {
var sql = "SELECT Classes.name_enc FROM Classes JOIN Classes_Pupils ON Classes.rowid = Classes_Pupils.class_id JOIN Pupils ON Classes_Pupils.pupil_id = Pupils.rowid WHERE Pupils.rowid = {0};".format(that._id);
return db.allAsync(sql)
.then(function (results) {
db.close();
var names = new Array();
for (var i = 0; i < results.length; i++) {
names.push(results[i].name_enc.toString().decrypt());
}
that.classnames = names;
DataLayer.PupilsList.dispatchEvent("reload");
}, function (error) {
if (error.message.indexOf("database is locked") > -1) {
console.log("DB locked, will try again in 50 ms");
window.setTimeout(that.updateClassnames(), 50);
}
});
});
}
},
{
reconstructAll: function () {
DataLayer.retrieveSeveralRows("Pupils", function (results) {
for (var i = 0; i < results.length; i++) {
DataLayer.PupilsList.push(new Schueler(false, results[i]));
}
});
}
});
WinJS.Namespace.define("DataLayer", {
Schueler: Schueler
});
Workflow is as follows: First empty lists are created, then another routine checks for DB availability. As soon as that routine gives a green light, Schueler.reconstructAll() is called.
DataLayer.retrieveSeveralRows(table, callback) is a wrapper function for a call to the SQLite database, essentially doing a SELECT * FROM Pupils and returning the results to the callback function.
This callback then creates a new instance of the Schueler() class and pushes that to the list.
Addendum: If I use createSorted() everything is just dandy. Will use that for now.
Edit: As suggested by Kraig Brockschmidt, it seems to have indeed been a localization issues, so adding one line and modifying one function as follows fixes everything right up:
var charGroups = Windows.Globalization.Collation.CharacterGroupings();
function getGroupKey(dataItem) {
return charGroups.lookup(dataItem.lastname.toUpperCase().charAt(0));
}
I see that you're working with createSorted now, but there are a couple of other things you can do to diagnose the original issue.
First, try using some static data instead of populating your list dynamically.
Second, put some console.log output inside your getGroupKey and getGroupData functions so you can evaluate what you're returning, exactly.
The other thing I should mention is that the MSDN docs page shows code that isn't sensitive to all local languages. That is, using the first character of a string for sort order isn't always the right thing. There is an API in Windows.Globalization.Collation (http://msdn.microsoft.com/en-us/library/windows/apps/windows.globalization.collation.aspx) that is built to handle sort ordering properly. If you look at the [HTML ListView Grouping and Semantic Zoom sample][1], in the file groupeddata.js, you'll see how this is used. Offhand this shouldn't affect your data, but I wanted to mention it.

Replacing specific items within observable arrays in knockout.js

I am quite new to knockout.js, and I am enjoying learning how to make interfaces with it. But I have a bit of a wall while trying to make my interface more efficient. What I am trying to achieve is remove only the elements selected by $('.document_checkbox').serializeArray(), which contains the revision_id. I will then re-add the entries to the view model with a modified call to self.getDocument(), passing only the modified records which will be re-added. Can anyone help me how to remove the entries from the arrays based on the 'revision_id' values of $('.document_checkbox').serializeArray()
?
function Document(data) {
this.line_id = data.line_id
this.revision_id = ko.observable(data.revision_id);
this.status_id = ko.observable(data.status_id);
}
function DocumentViewModel() {
var self = this;
self.documents = ko.observableArray([]);
self.getDocument = function(){
//Reset arrays
self.documents.removeAll();
//Dynamically build section arrays
$.getJSON("/Documentation/Get-Section", function(allData) {
$.map(allData, function(item) {
var section = { name: item.array_name, display_name: item.display_name, documents: ko.observableArray([])};
self.documents.push(section);
})
//Add document objects to the arrays
$.getJSON("/Documentation/Get-Document", function(allData){
$.map(allData, function(item) {
var section = ko.utils.arrayFirst(self.documents(), function(documentSection) {
return documentSection.name === item.array_name;
});
section.documents.push(new Document(item));
});
});
});
}
self.updateStatusBatch = function(data,event){
$.post('/Documentation/Update-Status-Batch',
{
revision_id : $('.document_checkbox').serializeArray(),
status_id : event.currentTarget.value
}).done(
function(){
//This is where I get confused.
});
}
}
You should modify the /Documentation/Update-Status-Batch in order that it returns the deleted item id. So you will be able to remove it on the client side.
Try this "done" function:
function(removedItemId) {
self.documents.remove(function(doc){
return doc.status_id == removedItemId;
})
}
Take a look at the remove function.
I hope it helps.

Push element into JSON object

I want to go through a JSON, if a certain condition applies then push some extra elements in that index.
I have this JS code:
$scope.addRoleToUser = function() {
var userid = $scope.selectedUser;
var tmpdata = [];
var index = 0;
//alert(userid);
angular.forEach($scope.users, function(data) {
if (data.id == $scope.selectedUser) {
tmpdata.push(data,{"roles":[{"id":"00","name":"newrole"}]});
}
else {
tmpdata.push(data);
}
index++;
});
$scope.users = tmpdata;
};
This is my initial JSON element:
$scope.users = [
{"id":"0","name":"User1","roles":[{}]},
{"id":"1","name":"User2","roles":[{}]},
]
I'm trying to get it to look like this after the function runs:
$scope.users = [
{"id":"0","name":"User1","roles":[{"id":"00","name":"newrole"}]},
{"id":"1","name":"User2","roles":[{}]},
]
But instead I'm getting this:
[{"id":"0","name":"User1","roles":[{}]},{"roles":[{"id":"00","name":"newrole"}]},{"id":"1","name":"User2","roles":[{}]}]
Just replace this inside your function
if (data.id == $scope.selectedUser) {
data.roles = [{"id":"00","name":"newrole"}];
}
Or, if you know that roles is not empty, you can do:
if (data.id == $scope.selectedUser) {
data.roles.push({"id":"00","name":"newrole"});
}
And after this line you can add your data to tmpdata!
That snippet now will look like this:
if (data.id == $scope.selectedUser) {
data.roles = [{"id":"00","name":"newrole"}]}); //or the other one
}
tmpdata.push(data);
Inside the forEach() callback you're just working with objects and as such, you can modify them directly inside the callback:
angular.forEach($scope.users, function(data) {
if (data.id == $scope.selectedUser) {
data.roles = [{"id":"00","name":"newrole"}];
}
});
Similarly you could modify almost anything of each entry by manipulating the respective data object.
Example Fiddle
The Array.prototype.push method is variadic: (https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/push).
When you call tmpdata.push(a,b,c), you are in essence appending the array [a,b,c] to tmpdata.
You can also decompose the problem with something like:
$scope.addRoleToUser = function() {
var thisUserid = $scope.selectedUser;
function addRolesFor(user) {
if (user.id === thisUserId){ user.roles = [{"id":"00","name":"newrole"}] };
return user;
}
retrun $scope.users.map(addRoles);
}
Please use the map function that is appropriate for your environment (like _.map), because the Array.prototype.map method is not supported by all browsers.

Categories