Knockout.js computed array property - javascript

I've a KnockoutViewModel ViewData this has an array of Items. These items are read from a server with $post.
Now this items have each an id and a link field. the id can be changed in the ui via a combobox. When the id changed, i want to update the link.
For this i re-assigned the id property to be observable and create the link property as computed.
As a result the computed event is raised. But i don't know how to reference the link field correctly. using the item reference, i always get the latest element which was processed in the the for loop.
$.post(url,
function (data, status) {
for (var index = 0; index < data.length; ++index) {
item.Id = ko.observable(data[index].Id);
item.Link = ko.computed(function () {
return url.replace(urlTemplate, item.Id());;
});
}
self.items(data);
});
}
I also tried to create a ItemModel to represent the item and convert each server element to a new ItemModel,but then i see for each entry the data of the last item.
what am i doing wrong ?
$.post(url,
function (data, status) {
var items = $.map(data, function (item) { return new ItemModel(item) });
self.items(items);
});

Try creating a new item object for each in the in the for loop. Something like this,
self.items = ko.observableArray();
for (var index = 0; index < data.length; ++index) {
var item = {
Id: ko.observable(data[index].Id),
Link: ko.computed(function () {
return url.replace(urlTemplate, data[index].Id);
});
};
self.items.push(item);
}

Related

How to update JavaScript array dynamically

I have an empty javascript array(matrix) that I created to achieve refresh of divs. I created a function to dynamically put data in it. Then I created a function to update the Array (which I have issues).
The Data populated in the Array are data attributes that I put in a JSON file.
To better undertand, here are my data attributes which i put in json file:
var currentAge = $(this).data("age");
var currentDate = $(this).data("date");
var currentFullName = $(this).data("fullname");
var currentIDPerson = $(this).data("idPerson");
var currentGender = $(this).data("gender");
Creation of the array:
var arrayData = [];
Here is the function a created to initiate and addind element to the Array :
function initMatrix(p_currentIDPerson, p_currentGender, p_currentFullName, p_currentDate, p_currentAge) {
var isFound = false;
// search if the unique index match the ID of the HTML one
for (var i = 0; i < arrayData.length; i++) {
if(arrayData[i].idPerson== p_currentIDPerson) {
isFound = true;
}
}
// If it doesn't exist we add elements
if(isFound == false) {
var tempArray = [
{
currentIDPerson: p_currentIDPerson,
currentGender: p_currentGender,
currentFullName: p_currentFullName,
currentDate: p_currentDate, currentAge: p_currentAge
}
];
arrayData.push(tempArray);
}
}
The update function here is what I tried, but it doesn't work, maybe I'm not coding it the right way. If you can help please.
function updateMatrix(p_currentIDPerson, p_currentGender, p_currentFullName, p_currentDate, p_currentAge) {
for (var i = 0; i < arguments.length; i++) {
for (var key in arguments[i]) {
arrayData[i] = arguments[i][key];
}
}
}
To understand the '$this' and elm: elm is the clickableDivs where I put click event:
(function( $ ) {
// Plugin to manage clickable divs
$.fn.infoClickable = function() {
this.each(function() {
var elm = $( this );
//Call init function
initMatrixRefresh(elm.attr("idPerson"), elm.data("gender"), elm.data("fullname"), elm.data("date"), elm.data("age"));
//call function update
updateMatrix("idTest", "Alarme", "none", "10-02-17 08:20", 10);
// Définition de l'evenement click
elm.on("click", function(){});
});
}
$('.clickableDiv').infoClickable();
}( jQuery ));
Thank you in advance
Well... I would recommend you to use an object in which each key is a person id for keeping this list, instead of an array. This way you can write cleaner code that achieves the same results but with improved performance. For example:
var myDataCollection = {};
function initMatrix(p_currentIDPerson, p_currentGender, p_currentFullName, p_currentDate, p_currentAge) {
if (!myDataCollection[p_currentIDPerson]) {
myDataCollection[p_currentIDPerson] = {
currentIDPerson: p_currentIDPerson,
currentGender: p_currentGender,
currentFullName: p_currentFullName,
currentDate: p_currentDate,
currentAge: p_currentAge
};
}
}
function updateMatrix(p_currentIDPerson, p_currentGender, p_currentFullName, p_currentDate, p_currentAge) {
if (myDataCollection[p_currentIDPerson]) {
myDataCollection[p_currentIDPerson] = {
currentGender: p_currentGender,
currentFullName: p_currentFullName,
currentDate: p_currentDate,
currentAge: p_currentAge
};
}
}
Depending on your business logic, you can remove the if statements and keep only one function that adds the object when there is no object with the specified id and updates the object when there is one.
I think the shape of the resulting matrix is different than you think. Specifically, the matrix after init looks like [ [ {id, ...} ] ]. Your update function isn't looping enough. It seems like you are trying to create a data structure for storing and updating a list of users. I would recommend a flat list or an object indexed by userID since thats your lookup.
var userStorage = {}
// add/update users
userStorage[id] = {id:u_id};
// list of users
var users = Object.keys(users);

Knockout JS not updating when changing array

I know this was asked multiple times already, but none of that answered my question.
I have the following:
I get data over JSON to Javascript into a two dimensional array.
When I load the site, the table shows up like wanted.
Now when I click a button (just for testing), it is updating one value from the array and logging that array in the console, where I see the changed array.
The problem is that the change is not showing up in the table.
When I just add a value to the array, it is showing up in the table.
What am I doing wrong?
HTML:
<table id="sortsTable">
<tbody data-bind="foreach: sorts">
<tr>
<td data-bind="text: $data.name"></td>
<td data-bind="text: $data.ingName"></td>
</tr>
</tbody>
</table>
<button data-bind="click: addPerson">Add</button>
JS:
var sorts = ko.observableArray([]);
$(function() {
var request = new XMLHttpRequest();
var formData = new FormData();
var responseElements = [];
request.open("POST", "scripts.php", true);
formData.append("action", "getSorts");
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
responseElements = JSON.parse(request.responseText);
sorts = convertList(responseElements);
ko.applyBindings(new AppViewModel(sorts));
}
}
request.send(formData);
});
function convertList(response) { //just the function to convert the json object to a more useful array
var names = [];
var ingredients = [];
var sorts = [];
for (var index = 0; index < response.length; index++) {
var name = response[index]['name'];
var ing = response[index]['ingName'];
if (names.indexOf(name) == -1) {
names.push(name);
}
if (ingredients.indexOf(ing) == -1) {
var nameIndex = names.indexOf(name);
if (ingredients[nameIndex] == undefined) {
ingredients[nameIndex] = ing;
} else {
ingredients[nameIndex] = ingredients[nameIndex] + ", " + ing;
}
}
}
for (var i = 0; i < names.length; i++) {
sorts[i] = {};
sorts[i]['name'] = names[i];
sorts[i]['ingName'] = ingredients[i];
}
return sorts;
}
function AppViewModel(data) {
var self = this;
self.sorts = data;
self.addPerson = function() {
console.log("click");
self.sorts[0]["name"] = "test"; //doesn't update table
//self.sorts.push({name: "qwer", ingName: "we"}); //works like expected
console.log(self.sorts);
};
}
Thanks.
The observable array only monitors which items are added to it, not the items themselves, so
self.sorts.push({name: "qwer", ingName: "we"}); //works like expected
works because you're getting the observable array to add to it's items, but
self.sorts[0]["name"] = "test"; //doesn't update table
doesn't work because the observable array has no way of knowing that an item inside it has changed. For this to work the properties of the items in the array will need to be observable.
In convertList switch to:
for (var i = 0; i < names.length; i++) {
sorts[i] = {};
sorts[i]['name'] = ko.observable(names[i]);
sorts[i]['ingName'] = ko.observable(ingredients[i]);
}
And they must be set by calling the observable setter method like so:
self.addPerson = function() {
console.log("click");
self.sorts[0]["name"]("test");
...
Also as an aside, you seem to have some other issues here. You define sorts as an observable array on the first line, but you overwrite it with the return value of convertList which is a normal array, not an observable one.
sorts = convertList(responseElements);
ko.applyBindings(new AppViewModel(sorts));
I'd remove the first line and create sorts as an observable array
function convertList(response) { //just the function to convert the json object to a more useful array
var names = [];
var ingredients = [];
var sorts = ko.observableArray([]);
...
The issue is that when you bind this:
<td data-bind="text: $data.name"></td>
$data.name is not observable, it's a simple property on an object, created here:
sorts[i]['name'] = names[i];
Knockout will quite happily bind properties like this, and display them, but any updates to them are not visible to knockout. Instead, as well as your observableArray, you also need to make any individual properties you want the ability to update observable as well:
sorts[i]['name'] = ko.observable(names[i]);
Then when you update it, knockout will see the change. Note however that you can't simply just assign to the property, as you'll just overwrite the knockout observable and it will be lost, instead you need to call the observable with the update:
self.sorts[0]["name"]("test");

How can i iterate over elastic search results using angular?

In my dashboard application am using elastic search to retrieve data, which retrieves data fine. Now i need to iterate over the data and get the result in the required way.
Here is my code,
routerApp.controller('SearchCtrl', function($scope, ejsResource) {
var ejs = ejsResource('http://192.168.1.200:9200');
var oQuery = ejs.QueryStringQuery().defaultField('Agent');
var client = ejs.Request()
.indices('nondomain_callrelatedinfo')
.types('callrelatedinfos');
$scope.search = function() {
$scope.results = client
.query(oQuery.query($scope.queryTerm || '*'))
.doSearch();
console.log($scope.results);
};
});
I have added a console.log in the results and it reuturns something like this,
Data:
when i iterate over hits, it says cannot read property of "hits" undefined
$scope.dataRetrieved= $scope.results.value.hits;
for (var i = 0; i < $scope.dataRetrieved.length; i++) {
console.log($scope.dataRetrieved[i]);
};
};
You should use the Promise object that the doSearch() returns :
client
.query(oQuery.query($scope.queryTerm || '*'))
.doSearch().then(function (resp) {
var hits = resp.hits.hits;
// do whatever
}, function (err) {
console.trace(err.message);
});
Knowing angular, if you want to bind the result to $scope.result maybe a $timeout(function() {}) could also help.
https://docs.angularjs.org/api/ng/function/angular.forEach
just nest your forEach:
angular.forEach(results, function(result, key) {
angular.forEach(result, function(value, key) {
this.push(key + ': ' + value);
}, log);
}, log);
$scope.results.hits.hits;
$scope.dataRetrieved= $scope.results.hits.hits;
for (var i = 0; i < $scope.dataRetrieved.length; i++) {
console.log($scope.dataRetrieved[i]);
};
};
the first hits is a object the second is your array. thats why the first hits has no property length

'undefined' elements in the array

I have the following JavaScript I am using to save selected check boxes:
function SubmitCheckBoxes()
{
alert("test");
var selectedIDs = [];
var x = 0;
a = document.getElementsByTagName("input");
for (i = 0; i < a.length; i++) {
if (a[i].type == "checkbox" ) {
if (a[i].checked) {
alert(a[i].value);
selectedIDs[x] = a[i].value;
x++;
}
}
}
$.post('./Courses/SaveAndRedirect', selectedIDs, function (data) { });
}
However when I look at the form data being submitted all its says is undefined: undefined for each element in the array.
Not sure what the problem is here.
It is the data attribute in the jquery post method that is wrong. It can't take an array as a parameter. Here is the documentation
data map or string that is sent to the server with the request.
Try using a object literal instead:
$.post('./Courses/SaveAndRedirect', {selectedIDs: selectedIDs}, function (data) { });
I would also try writing selectedIDs[x] = a[i].value; differently:
selectedIDs.push(a[i].value);
I think the problem may be that your post variables are associated with just a numeric instance rather than a field name
You can try something like this:
var selectedIDs = [];
$('input[type="checkbox"]:checked').forEach(function(i, e){
selectedIDs.push($(e).val());
});
$.post('./Courses/SaveAndRedirect', selectedIDs, function (data) { });

SlickGrid cannot delete added rows, but only existing ones. What am I doing wrong?

Here is my code for adding a row:
grid.onAddNewRow.subscribe(function (e, args) {
var item = args.item;
id=id+1;
item["id"] = "id_"+id;
grid.invalidateRow(data.length);
data.push(item);
dataView.beginUpdate();
dataView.endUpdate();
grid.updateRowCount();
grid.render();
});
And here is my code for deleting a row:
if ((event.which == 46)) {
$(".slick-cell.selected").parent().each(function() {
var item = dataView.getItem($(this).attr("row"));
var rowid = item.id;
dataView.deleteItem(rowid);
grid.invalidate();
grid.render();
});
}
This works for already existing rows but not for the added ones. for some reasons THE item variable is undefined for new rows. What am I doing wrong?
Edited
Thanks, Tin! So I`ve got a solution :
grid.onAddNewRow.subscribe(function (e, args) {
var item = args.item;
id=id+1;
item["id"] = "id_"+id;
data.push(item);
dataView.beginUpdate();
dataView.setItems(data);
dataView.endUpdate();
});
.
if ((event.which == 46)){
var rows = grid.getSelectedRows();
for (var i = 0, l = rows.length; i < l; i++) {
var item = dataView.getItem(rows[i]);
var rowid = item.id;
dataView.deleteItem(rowid);
}
}
You seem to be doing a lot of things in your code that don't quite make sense to me:
In your onAddNewRow handler, you are:
Invalidating the row being added. Why? You don't need to do that.
You update the "data" array directly but then do a no-op call on the "dataView". What are you using as a data source - data or dataView?
You don't need to tell the grid to updateRowCount() or render().
In your delete handler:
DO NOT access SlickGrid DOM directly (i.e. $(".slick-cell.selected"))! Use grid.getSelectedRows() instead.
If you are using "dataView" as your data source, you should already have events wired up to listen to DataView changes and update the grid as needed. Calls to grid.invalidate() and grid.render() are not needed.

Categories

Resources