How to add/insert an item into an ObservableArray at a certain position with Knockout.js - javascript

All the knockout examples I have found seem to add a new item to the end of an ObservableArray using something like:
viewModel.SomeItems.push(someNewItem);
This of course places the item at the end of the array.
How to I add an item to the ObservableArray at a certain position?
eg. something like:
viewModel.SomeItems.push(someNewItem, indexToInsertItAt);

You should be able to use the native JavaScript splice method -
viewModel.SomeItems.splice(2,0,someNewItem);
Docs here - https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice
Example here (not Knockout specific) - How to insert an item into an array at a specific index?
From the Knockout docs -
For functions that modify the contents of the array, such as push and
splice, KO’s methods automatically trigger the dependency tracking
mechanism so that all registered listeners are notified of the change,
and your UI is automatically updated.

For knockout use
viewModel.SomeItems.unshift(someNewItem);
See also: http://knockoutjs.com/documentation/observableArrays.html

I made this extension function which worked nicely for me. Splice wasn't working for me if I was adding to the end of a sparse array.
ko.observableArray.fn.setAt = function(index, value) {
this.valueWillMutate();
this()[index] = value;
this.valueHasMutated();
}
This even works with:
var a = ko.observableArray(['a', 'b', 'c']);
a.setAt(42, 'the answer');

Related

Is My Approach to the Todo App Delete Function Wrong?

I am learning React and just created a simple todo app using only React. My todo app has the standard structure of having a text input and an "ADD" button next to it. The user would type their todo in the input and every time they click on the "ADD" button next to it, a new ordered list of their inputs would appear underneath the input and "ADD" button.
The user can also delete a todo entry by clicking on the entries individually, like this:
To accomplish this behaviour of deleting entries, I used this delete function:
delete(elem) {
for (var i = 0; i < this.state.listArray.length; i++) {
if (this.state.listArray[i] === elem) {
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
break;
}
}
}
My todo app works exactly the way that I want it to work, but as I look at other people's more conventional approach to this delete function, they either just simply use the splice method or the filter method.
For the splice method approach, they apparently just simply "remove" the unwanted entry from the listArray when the user clicks the particular entry. This does not work for me as using this method results in all my entries getting deleted except for the entry that I clicked on, which is the one that I want to delete.
On the other hand, the filter method approach apparently works by comparing the elem, which is the data passed from a child component, with each element in the listArray, and if the element in the for loop does not equal to the elem, then it would be passed onto a new array. This new array would be the one to not be deleted. This approach works better than the simple splice approach, however, one problem that I had encountered with this approach is that if I have more than one entry of the same value, for example, "Feed the dog". I only want one of the "Feed the dog" entries to be deleted, but it deletes both of them.
I thought of an approach to tackle this problem, eventually coming up with the current version of my code, which uses the splice method, but the splice method is used before I set it in the state. As evident here:
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
My question can be broken down into three subquestions:
Considering that React states should be immutable, is the first line of the code above mutating my state? Is this approach not okay?
I thought that all React states were only possible to be changed inside a "setState" function, but my first line of code from above is not inside a setState function, yet it changed the state of listArray. How is this possible?
If my approach is mutating the state and is not ideal, how would you go about making the delete function so that it only deletes one entry and not more than one if there are multiple similar entries?
Yes, splice affects the array it acts on so don't use in this way. Instead you need to create a new array of the correct elements:
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== i);
});
If you want to remove only the first instance, maybe couple with a findIndex (although indexOf would work in your example as well) first:
delete(elem) {
const idxToFilter = this.state.listArray.findIndex(el => el === elem);
if (idxToFilter < 0) {
return;
}
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== idxToFilter);
});
}
This creates a new array without modifying the old which will cause anything that reacts to listArray changing to be notified since the reference has changed.

knockout js, add new item to an array

So this follows on from my previous question:
knockout js, add additional elements to an array
Basically I have an app where a user fills in certain data, clicks next and this is added to an array. However, what I'd like to do is add some items into the array before the user even begins using the app (these items I get from a database). The idea being that at the start they can view each item in the array and then choose and an item and edit this item. I've got a feeling I'm missing something blindingly obvious but I cannot seem to figure it out
Knockout observable arrays have equivalent functions to native JavaScript arrays. See: http://knockoutjs.com/documentation/observableArrays.html
So you need just to use arr.pop(item) or arr.push(item).
In case you need to replace all items and want to avoid multiple events to raise, use observableArray.valueWillMutate() and valueHasMutated() functions. See sample where I do swap the entire array:
ko.observableArray.fn.replaceWith = function (valuesToPush) {
// NOTE: base on - ko.observableArray.fn.pushAll
var underlyingArray = this();
var oldItemcount = underlyingArray.length;
this.valueWillMutate();
// adding new items to obs. array
ko.utils.arrayPushAll(underlyingArray, valuesToPush);
// removing old items (using KO observablearray fnc.)
if (oldItemcount > 0)
this.removeAll(underlyingArray.slice(0, oldItemcount));
this.valueHasMutated();
return this; //optional
};

Is there a way to map a value in an object to the index of an array in javascript?

Prepending that a solution only needs to work in the latest versions of Chrome, Firefox, and Safari as a bonus.
-
I am trying to use an associative array for a large data set with knockout. My first try made it a true associative array:
[1: {Object}, 3: {Object},...,n:{Object}]
but knockout was not happy with looping over that. So I tried a cheating way, hoping that:
[undefined, {Object}, undefined, {Object},...,{Object}]
where the location in the array is the PK ID from the database table. This array is about 3.2k items large, and would be iterated over around every 10 seconds, hence the need for speed. I tried doing this with a splice, e.g.
$.each(data, function (index, item) {
self.myArray.splice(item.PKID, 0, new Object(item));
}
but splice does not create indices, so since my first PKID is 1, it is still inserted at myArray[0] regardless. If my first PK was 500, it would start at 0 still.
My second thought is to initialize the array with var myArray = new Array(maxSize) but that seems heavy handed. I would love to be able to use some sort of map function to do this, but I'm not really sure how to make the key value translate into an index value in javascript.
My third thought was to keep two arrays, one for easy look up and the other to store the actual values. So it combines the first two solutions, almost, by finding the index of the object in the first example and doing a lookup with that in the second example. This seems to be how many people manage associative arrays in knockout, but with the array size and the fact that it's a live updating app with a growing data set seems memory intensive and not easily manageable when new information is added.
Also, maybe I'm hitting the mark wrong here? We're putting these into the DOM via knockout and managing with a library called isotope, and as I mentioned it updates about every 10 seconds. That's why I need the fast look up but knockout doesn't want to play with my hash table attempts.
--
clarity edits:
so on initial load the whole array is loaded up (which is where the new Array(maxLength) would go, then every 10 seconds anything that has changed is loaded back. That is the information I'm trying to quickly update.
--
knockout code:
<!-- ko foreach: {data: myArray(), afterRender: setInitialTileColor } -->
<div class="tile" data-bind="attr: {id: 'tileID' + $data.PKID()}">
<div class="content">
</div>
</div>
<!-- /ko -->
Then on updates the hope is:
$.each(data.Updated, function (index, item) {
var obj = myModel.myArray()[item.PKID];
//do updates here - need to check what kind of change, how long it's been since a change, etc
}
Here is a solution how to populate array items with correct indexes, so it doesn't start from the first one (0 (zero) I meant)
just use in loop
arr[obj.PKID] = obj;
and if your framework is smart (to use forEach but not for) it will start from your index (like 500 in case below)
http://jsfiddle.net/0axo9Lgp/
var data = [], new_data = [];
// Generate sample array of objects with index field
for (var i = 500; i < 3700; i++) {
data.push({
PKID: i,
value: '1'
});
}
data.forEach(function(item) {
new_data[item.PKID] = item;
});
console.log(new_data);
console.log(new_data.length); // 3700 but real length is 3200 other items are undefined
It's not an easy problem to solve. I'm assuming you've tried (or can't try) the obvious stuff like reducing the number of items per page and possibly using a different framework like React or Mithril.
There are a couple of basic optimizations I can suggest.
Don't use the framework's each. It's either slower than or same as the native Array method forEach, either way it's slower than a basic for loop.
Don't loop over the array over and over again looking for every item whose data has been updated. When you send your response of data updates, send along an array of the PKIds of the updated item. Then, do a single loop:
.
var indexes = []
var updated = JSON.parse(response).updated; // example array of updated pkids.
for(var i=0;i<allElements.length;i++){
if(updated.indexOf(allElements[i].pkid)>-1)
indexes.push(i);
}
So, basically the above assumes you have a simple array of objects, where each object has a property called pkid that stores its ID. When you get a response, you loop over this array once, storing the indexes of all items that match a pk-id in the array of updated pk-ids.
Then you only have to loop over the indexes array and use its elements as indexes on the allElements array to apply the direct updates.
If your indexes are integers in a reasonable range, you can just use an array. It does not have to be completely populated, you can use the if binding to filter out unused entries.
Applying updates is just a matter of indexing the array.
http://jsfiddle.net/0axo9Lgp/2/
You may want to consider using the publish-subscribe pattern. Have each item subscribe to its unique ID. When an item needs updating it will get the event and update itself. This library may be helpful for this. It doesn't depend upon browser events, just arrays so it should be fairly fast.

Working with Data Attributes - Build a List & See if One Exists

I have 2 questions based on the graphic below:
How can I tell if one of the 'data-conversationmessageuserid' data attributes with a specific value exists - say 1000000003? I believe data selectors is what I need and have tried the following but its not working yet:
if($('#conversationsInBoxMessagesWrapperDIV')['data-conversationmessageuserid=1000000003']) {
// do something
}
How could I get all the 'data-conversationmessageuserid' data attributes into an array and the loop through them? I'm still playing with this code but its far from publishable. Trying to use .map
.map(function()
thankyou so much
Try:
if($('#conversationsInBoxMessagesWrapperDIV [data-conversationmessageuserid=1000000003]').length)
or
$('#conversationsInBoxMessagesWrapperDIV').find('[data-conversationmessageuserid=1000000003]') //Or children only to look at one level.
To get all the data values you could do:
var conversationmessageuserids = $('#conversationsInBoxMessagesWrapperDIV').children().map(function(){
return $(this).data('conversationmessageuserid');
}).get();
jQuery supports data attributes: http://api.jquery.com/data/
So you could do if($('#conversationsInBoxMessagesWrapperDIV').data('conversationmessageuserid') === 1000000003)

How Do I Make an Array of Objects a Reactive Data Source?

I have an array of objects. Say
var
sidelist = [
{
name:"asdf",
id:1234,
types:[...]
}
];
Every object is turned into a box on the page using this construct
Template.global.side = function(){
var obj = [], m;
m = 1;
for (var i in sides){
obj.push({
index : m,
object : sides[i]
});
}
return obj;
}
The HTML:
{{#each side}}
<div class="span{{this.index}}" id={{this.object.id}}>
<div class="side-head">{{this.object.name}}</div>
</template>
There is a function that creates and pushes a new object into the array. How do I make the row of boxes reactively update on the page when the array they depend on changes?
So when I add a new object a new box should appear.
If you want to use Dependencies, it can look like this:
var sidelist = ...;
var sidelist_dep = new Deps.Dependency;
Template.global.side = function(){
sidelist_dep.depend();
// Do your stuff here;
return ...;
};
// Important: call this every time you change sidelist,
// AFTER the change is made.
sidelist_dep.changed();
See: http://docs.meteor.com/#deps
In almost all cases, you should put the objects in a Meteor Collection instead of an array that is part of a reactive object. There are many reasons for this, including the following
Adding, removing, searching, and updating will all be faster
The reactivity will be on the element level instead of the array
Meteor won't re-render the whole set of objects when something is added or deleted - just the change
You can define a sort order on the collection, making it much more flexible than a fixed sequence
Take a look at Andrew Wilcox's isolate-value smart package:
https://atmosphere.meteor.com/package/isolate-value
The README contains the exact example of selectively rerendering relevant templates when values are added/removed from an array stored in a Session varaible.

Categories