Making Vue.js detect changes to array using $set() - javascript

I'm having trouble updating an array that is displayed as a list. I'm trying to make Vue detect the changes using $set() (as explained in the documentation), but I can't make it work.
Here's my code:
this.choices = this.currentScene.choices;
for (i = 0; i < this.choices.length; i++) {
choice = this.currentScene.choices[i];
choice.parsedText = this.parseText(choice.text);
this.choices.$set(i, choice);
}
Vue still doesn't update the view. What am I doing wrong? Thanks in advance!
Edit: Yes, "this" refers to the Vue instance.

It would definitely be useful to have a JSfiddle of your code, but I'm going to take a crack anyways.
I'm not sure you need to use that function to update the array, since as the documentation points out, its only when you need to change the index of the item.
JavaScript has a built in function called .map that takes a callback function and returns a new array with the callback applied to each item.
For example, you could translate your function to this, assuming that .parseText is a method on the Vue class.
var self = this; // so that we can access the Vue class inside map
this.choices = this.currentScene.choices.map(function(choice) {
choice.parsedText = self.parseText(choice.text);
return choice;
});
And Vue should pick up those changes.

You could use a computed property for this, so you never have to manually update the array. Anytime choices changes you would see the change reflected in this.parsedChoices:
computed: {
parsedChoices: function(){
return this.currentScene.choices.map(function(choice) {
choice.parsedText = this.parseText(choice.text);
return choice;
}.bind(this)); // bind Vue class as value of `this` inside func
}
}

Related

Knockout Utility Functions - ko.utils.arrayFilter - data binding

I found this tutorial for a knockout utilty that filters through an array and creates a new filtered version of the array.
http://www.knockmeout.net/2011/04/utility-functions-in-knockoutjs.html
From there, I understand that the this.filter in "this.filter().toLowerCase();" is the ko.observable bound to the input box in the view.
I tried to integrate this into my code.
I am aware I need more changes. The method "ko.utils.stringStartsWith" is not supported any longer
I am getting the error "Uncaught TypeError: this.kofilter is not a function"
I am not sure what that means, is there something wrong with the data binding?
This is my JS code
this.filteredItems = ko.computed(function() {
console.log(this)
var filter = this.kofilter().toLowerCase();
if (!filter) {
return self.venueList();
} else {
return ko.utils.arrayFilter(this.venueList(), function(venues) {
return ko.utils.stringStartsWith(venues.name().toLowerCase(), filter) ;
});
}
}, this.venueList);
};
And this is the HTML
<br><input placeholder = "Search..." data-bind="value: kofilter, valueUpdate: 'afterkeydown'">
I'm willing to bet it's yet again a problem with this. For instance I don't think you want to pass this.venueList to the computed as the context object, but rather just this.
If you have a self variable, stick to that, and don't use this. Otherwise make sure you pass the correct this argument to all the function calls.
I found the issue, I was missing self.kofilter = ko.observable('');

Firebase - Object vs Array and Returning a .key()

I'm using firebase + angularfire for a project that involves multiple users editing data at the same time (which would make an object ideal) as well as the need to quickly sort and filter the data on the fly (ideal for an array).
So, would it be better for me to:
1) use FirebaseObject and then use an angular toArray filter for sorting
2) use FirebaseArray and just make sure to use $add to ensure use of unique IDs (even storing the push ID as a property of the pushed object itself).
Currently, we are using the second option, which leads me to my second question:
When I use $push, how can I return the ref.key() back to the controller?
Modifying the example from the API guide:
var list = $firebaseArray(ref);
var addItem = function(itemDataObject) {
list.$add(itemDataObject).then(function(ref) {
var id = ref.key();
console.log("added record with id " + id);
list.$indexFor(id); // returns location in the array
});
};
How can I get that id variable to be returned when the addItem function is called? Even if I declare a variable outside the list.$add function and set the variable within the function, I get an undefined result.
One way would be to fall back to using Firebase's regular JavaScript SDK.
var list = $firebaseArray(ref);
var addItem = function(itemDataObject) {
var newItemRef = ref.push(itemDataObject);
var id = newItemRef.key();
console.log("added (or still adding) record with id " + id);
return id;
};
But if you need the key() of the new ref for anything more than displaying it, you'll need to wait until it's available.
In that case, the easiest approach is to return the promise:
var list = $firebaseArray(ref);
var addItem = function(itemDataObject) {
return list.$add(itemDataObject);
};
What happens next, depends on the calling code and what you want it to do.
Since you want to return the result of an asynchronous operation, you may benefit from reading my answer here and the links in it: Asynchronous access to an array in Firebase
One possible issue seems like having variable declaration of id even though that variable is declared globally.
For example:
var id;
function setIDVal1(){
var id=10;
}
function setIDVal2(){
id=101;
}
function getIDVal(){
alert(id);
}
Check this fiddle for the local variable issue & check this fiddle for an example to get the ID.

How to easily determine what's added/removed in a $scope.$watch?

I am tasked with wrapping a JQuery-style UI component within an Angular directive. I have completed the task but it looks rather ugly. The underlying component's API only has add and remove methods for controlling a list of items. The only thing I can think to do is watch the two-way bound variable passed into my directive with scope.$watchCollection and then iterate over the old and new arguments to determine what has been added or removed, then invoke the add/remove API calls on the underlying component. This works, but I'm curious if there is a better way to accomplish this. The code is hideous and confusing.
Here is a demo minimal recreation of the component in it's natural habitat:
http://codepen.io/Chevex/pen/ofGeB?editors=101
You can see the component is instantiated on an element that has the items in the list within the markup. The component then returns an API to add/remove individual items. Please note that I have no control over the underlying component in our actual application and that the real component does far more than my demo component (which could easily be re-written with an ng-repeat).
Here is a demo minimal recreation of my directive wrapper:
http://codepen.io/Chevex/pen/LDgCF?editors=101
This works great and simulates a two-way bound variable with a jquery-style component that only has add/remove methods for individual items. I want to know if there is a better way to determine new/removed items within $watchCollection.
// Watch items for changes.
scope.$watchCollection('items', function (newItemList, oldItemList) {
// Iterate over the newItemList and determine what items are new.
if (newItemList) {
newItemList.forEach(function (newItem) {
var addItem = true;
if (oldItemList) {
oldItemList.forEach(function (oldItem) {
if (newItem === oldItem) {
addItem = false;
}
});
}
if (addItem) {
listApi.addItem(newItem);
}
});
}
// Iterate over the oldItemList and determine what was removed.
if (oldItemList) {
oldItemList.forEach(function (oldItem, oldItemIndex) {
var removeItem = true;
if (newItemList) {
newItemList.forEach(function (newItem) {
if (oldItem === newItem) {
removeItem = false;
}
});
}
if (removeItem) {
listApi.removeItem(oldItemIndex);
}
});
}
});
All of that just to determine what should be added or removed using the component API whenever the collection changes. Is this the best way? I ask because I've been finding myself writing similar logic repeatedly and wondered if there may be a more proper and/or simpler way to do it.
You could use something like this:
Array.prototype.diff = function(arr) {
return this.filter(function(el) { return arr.indexOf(el) < 0; });
};
var addedItems = newItemList.diff(oldItemList);
var removedItems = oldItemList.diff(newItemList);

How to temporary stop calculating computed when executing multiple array push/remove operations

I have computed which based on observableArray property headers. Also i have methods for adding and removing multiple headers:
function ViewModel() {
var self = this;
self.headers = ko.observableArray();
self.newHeaders = ko.computed(function() {
var countOfNew = 0;
ko.arrayForEach(self.headers(), function(header) {
if (!header.id) {
countOfNew++;
}
});
return countOfNew;
});
self.addHeaders = function(headers) {
ko.arrayForEach(headers, function(header) {
self.headers.push(header);
}
};
self.removeHeaders = function(headers) {
ko.arrayForEach(headers, function(header) {
self.headers.remove(header);
}
};
}
When i call addHeaders or removeHeaders, newHeaders called for each item in headers array. There is any solution how temporary stop calculating computed field? (someting like as ko.valueWillMutate, ko.valueHasMutated which used for subscribers).
From Knockout.js Performance Gotcha #2 - Manipulating observableArrays
A better pattern is to get a reference to our underlying array, push
to it, then call .valueHasMutated(). Now, our subscribers will only
receive one notification indicating that the array has changed.
I forked Ryan Niemeyer jsfiddle example here .. Open the console and check how many times the message 'calling computed object' is logged!.
Keep in mind that an observable array is still just an observable. You can get the array's value var arr = oarr(), do whatever you want with it, and put it back in oarr(arr).
Knockout 3.1.0 includes the rateLimit extender that can improve performance in this scenario quickly and easily. Simply apply the extender to your observable array:
self.headers = ko.observableArray().extend({rateLimit: 0});
(Version 3.1.0 is currently [3/3/2014] available as a beta release.)
If you're only interested in delaying the computed observable (and not everything that's dependent on the array), you can apply the rateLimit extender to the computed observable instead. If you're using Knockout 3.0 or earlier, you can use the throttle extender on the computed to achieve a similar result:
self.newHeaders = ko.computed(function() {
...
}).extend({throttle: 1});

How to force knockoutjs to update UI (reevaluate bindings)

(I know there are other questions here asking the same thing; I've tried them and they don't apply here)
I have a collection being displayed by a Knockout JS foreach. For each item, the visible binding is set by call a method, based on something external to the item itself. When the externality changes, I need the UI to be redrawn.
A striped down version can be seen in this Fiddle: http://jsfiddle.net/JamesCurran/2us8m/2/
It starts with a list of four folder names, and displays the ones starting with 'S'.
<ul data-bind="foreach: folders">
<li data-bind="text: $data,
visible:$root.ShowFolder($data)"></li>
</ul>
<button data-bind="click:ToA">A Folders</button>
Clicking the button should display the ones starting with 'A' instead.
self.folders = ko.observableArray(['Active', 'Archive', 'Sent', 'Spam']);
self.letter = 'S';
// Behaviours
self.ShowFolder = function (folder)
{
return folder[0] === self.letter;
}
self.ToA = function ()
{
self.letter = 'A';
}
UPDATE:
After Loic showed me how easily this example could be fixed, I reviewed the differences between this example and my actual code. I'm using an empty object as a dictionary to toggle if an item is selected self.Selected()[item.Id] = !self.Selected()[item.Id];
The object being changed is already an observable. I assumed that Knockout didn't realize that the list is dependent on the external observable, but it does. What Knockout was missing was that the observable was in fact changing. So, the solution was simply:
self.Selected()[item.Id] = !self.Selected()[item.Id];
self.Selected.notifySubscribers();
Here's what I came up with:
What you have to understand is that Knockout is only "answering" to data changes in observables. If an observable changes, it will trigger every object that uses it. By making your self.letter an observable. You can simply change it's value and uses it somewhere like self.letter() and it will automagically redraw when needed.
http://jsfiddle.net/2us8m/3/
function WebmailViewModel() {
// Data
var self = this;
self.folders = ko.observableArray(['Active', 'Archive', 'Sent', 'Spam']);
self.letter = ko.observable('S');
// Behaviours
self.ShowFolder = function (folder)
{
return folder[0] === self.letter();
}
self.ToA = function ()
{
self.letter('A');
}
};
ko.applyBindings(new WebmailViewModel());
In case you have complex bindings, like storing an object inside an observable. If you want to modify that object you have multiple possible choices.
self.Selected()[item.Id] = !self.Selected()[item.Id];
You could change it to this by making everything "observables" but if my memory is right, it can become complicated.
self.Selected()[item.Id](!self.Selected()[item.Id]());
I remember I had one similar issue where I had dependency problem where I had to update a country, region, city. I ended up storing it as list inside an observable to prevent update on individual element change. I had something like this.
var path = PathToCity();
path[0] = 'all';
path[1] = 'all';
PathtoCity(path);
By doing this, the change would be atomic and there will be only one update. I haven't played a lot with knockout for a while. I'm not sure but I do believe that the last time I worked with knockout, it was able to "optimize" and prevent to redraw the whole thing. But be careful because if it is not able to guess that you didn't change many thing, it could redraw the whole observable tree (which could end up pretty bad in term of performance)
In your example, we could use the same behaviour with my modified example:
http://jsfiddle.net/2us8m/4/

Categories