I'd like to ensure that all objects in an array are of type Item. If I do so, a splice does no longer work later.
This is how I do this:
computedItems: {
get()
{
//return this.modelValue;
return this.modelValue?.map((item) => new Item(item));
},
set(newValue)
{
this.$emit("update:modelValue", newValue);
}
}
This works fine but it seems to break reactivity, as:
removeItem(item) {
let key = this.computedItems.findIndex((i) => {
return item === i;
});
this.computedItems.splice(key, 1);
}
does not work (no error, list is just not being updated).
If I do
computedItems: {
get()
{
return this.modelValue;
},
set(newValue)
{
this.$emit("update:modelValue", newValue);
}
}
the splice does work as expected (but the items are not mapped to the specific object).
My questions:
How can I solve that?
Why is that the case? Is it a bad idea to map within the computed setter?
I'm not sure why it happens and cannot explain it very well.
Array and Object is keeping in memories. So it’s a pointer to memories.
The pointer was not changing and nothing is reactive. (Headache)
Can you try this code:
removeItem(item) {
const newArray = [...this.computedItems];
let key = newArray.findIndex(i => item === i);
newArray.splice(key, 1);
this.computedItems = newArray;
}
Related
I am working through a code challenge and I need to return a string into a variable if the guess passed in attemptAnswer(guess) matches the answer property's value. My test is failing currently saying that the variable response is undefined.
Is this some sort of binding issue?
...curious how this problem could be resolved.
Thank you!
class Sphinx {
constructor() {
this.name = null;
this.riddles = [];
}
collectRiddle(riddle) {
this.riddles.push(riddle);
if (this.riddles.length > 3) { this.riddles.shift() };
}
attemptAnswer(guess) {
this.riddles.forEach( (element, index) => {
if (guess === element.answer) {
this.riddles.splice(index, 1);
return "That wasn't that hard, I bet you don't get the next one."
};
})
}
}
//test
const response = sphinx.attemptAnswer('short');
assert.equal(response, 'That wasn\'t that hard, I bet you don\'t get the next one');
When you return in attemptAnswer() you're actually retuning to the inner forEach callback function you defined: (element, index) => {..., not the outer attemptAnswer() method.
Instead of immediately returning within your forEach loop, you can set a variable outside this loop called result, and then return the result once your forEach loop is complete.
Also, currently, you're not creating a new instance of Sphinx, which means you don't have an object which can call the attemptAnswer() method. To fix this add new Sphinx() to create a new Sphinx object.
See example below:
class Sphinx {
constructor() {
this.name = null;
this.riddles = [{"answer":"short"}];
}
collectRiddle(riddle) {
this.riddles.push(riddle);
if (this.riddles.length > 3) {
this.riddles.shift()
};
}
attemptAnswer(guess) {
let res = "";
this.riddles.forEach((element, index) => {
if (guess === element.answer && !res) {
// no need for splice as it will skip an entry
res = "That wasn't that hard, I bet you don't get the next one.";
};
})
return res;
}
}
const response = new Sphinx();
response.collectRiddle({"answer":"short"});
console.log(response.attemptAnswer('short'));
you're never calling collectRiddle so this.riddles is always [] and the forEach block is never entered, therefore, not returning anything, so, the return value is undefined
you should have a variable called found right before the loop, if you find a match, set it to truethen return the string depending on the found variable :
note : the string inside the function is different from the one you're comparing it to (it has backslashes and ends with a dot) so the test will always be falsy
class Sphinx {
constructor() {
this.name = null;
this.riddles = [];
}
collectRiddle(riddle) {
this.riddles.push(riddle);
if (this.riddles.length > 3) {
this.riddles.shift()
};
}
attemptAnswer(guess) {
var found = false;
this.riddles.forEach((element, index) => {
if (guess === element.answer) {
found = true;
}
})
return found ? "Woohoo" : "That wasn't that hard, I bet you don't get the next one."
}
}
//test
const s = new Sphinx();
const response = s.attemptAnswer('short');
console.log(response === `That wasn't that hard, I bet you don't get the next one.`);
I assume you already did const sphynx = new Sphynx().
attemptAnswer() doesn't return anything, in Javascript, if you don't return anything, you basically return undefined. So it is normal that response is undefined.
In your case, I would use for-loop, instead of forEach.
attemptAnswer(guess) {
for (let i = 0; i < this.riddles.length; i++) {
if (guess === this.riddles[i].answer) {
this.riddles.splice(index, 1);
return "That wasn't that hard, I bet you don't get the next one.";
}
}
return "Not found";
}
Using .splice() inside forEach is not recommended
using forEach, will go through all the items inside the array, even if you already found your answer.
I have this piece of code running on the client that filters a list of events:
if (res)
{
eventList.filter(function(event) {
const out = res.find(function(visibility) { return visibility.ID == event.id; }) == undefined;
return out;
});
alert(eventList);
}
displayEvents(eventList);
The problem is that even when out is false the element is not filtered out.
Just for debug I tried to return false in any case and the resulting array still had all the initial elements:
eventList.filter(function(event) {
return out;
});
What am I doing wrong here??
EDIT:
res is an array of JSON objects (containg only ID field) returned by the server, while eventList is a list of Facebook events, passed to this callback function from a Facebook API request
Array.prototype.filter does not change array inplace, it returns new array made of items that satisfies the provided predicate. It should look like this
var result = eventList.filter(function(event) {
return res.find(function(visibility) { return visibility.ID == event.id; }) === undefined;
});
You don't need to declare and assign variable and then return it from function, you can simply return expression
I have an observableArray that in its subscribe callback I need to filter the new array passed.
For example:
myArray.subscribe(function(elements) {
ko.utils.arrayFilter(elements, function(element) {
return element.x > 10
})
})
This, of course, doesn't work since arrayFilter nor the native filter() method doesn't change the original array. The problem is that i can't do this:
myArray.subscribe(function(elements) {
var newArray = ko.utils.arrayFilter(elements, function(element) {
return element.x > 10
})
myArray(newArray)
})
because this would be an infinite loop. How would I filter the array inside the subscription function?
The best solution is the one suggested by nemesv: Don't alter the array itself, but instead create a new computed observable that encapsulates the filter behavior.
var filteredArray = ko.computed(function () {
return ko.utils.arrayFilter(myArray(), function(element) {
return element.x > 10;
});
});
The simplest solution, if you're feeling lazy, would be to replace
myArray(newArray)
with
if (newArray.length !== elements.length) {
myArray(newArray);
}
as suggested in the comment by James Thorpe. The flaw here is that every subscriber might run twice, including the filter operation itself.
Your problem implies that you really want to encapsulate your data and not to expose it directly over the view-model.
Try something like this:
var arr = ko.observableArray();
this.AddItem = function(element) {
if (element.x > 10)
arr.push(element);
};
this.GetItems = function() {
return arr();
};
Is it possible to create an array that will only allow objects of a certain to be stored in it? Is there a method that adds an element to the array I can override?
Yes you can, just override the push array of the array (let's say all you want to store are numbers than do the following:
var myArr = [];
myArr.push = function(){
for(var arg of arguments) {
if(arg.constructor == Number) Array.prototype.push.call(this, arg);
}
}
Simply change Number to whatever constructor you want to match. Also I would probably add and else statement or something, to throw an error if that's what you want.
UPDATE:
Using Object.observe (currently only available in chrome):
var myArr = [];
Array.observe(myArr, function(changes) {
for(var change of changes) {
if(change.type == "update") {
if(myArr[change.name].constructor !== Number) myArr.splice(change.name, 1);
} else if(change.type == 'splice') {
if(change.addedCount > 0) {
if(myArr[change.index].constructor !== Number) myArr.splice(change.index, 1);
}
}
}
});
Now in ES6 there are proxies which you should be able to do the following:
var myArr = new Proxy([], {
set(obj, prop, value) {
if(value.constructor !== Number) {
obj.splice(prop, 1);
}
//I belive thats it, there's probably more to it, yet because I don't use firefox or IE Technical preview I can't really tell you.
}
});
Not directly. But you can hide the array in a closure and only provide your custom API to access it:
var myArray = (function() {
var array = [];
return {
set: function(index, value) {
/* Check if value is allowed */
array[index] = value;
},
get: function(index) {
return array[index];
}
};
})();
Use it like
myArray.set(123, 'abc');
myArray.get(123); // 'abc' (assuming it was allowed)
So yes I can subscribe to an observable array:
vm.myArray = ko.observableArray();
vm.myArray.subscribe(function(newVal){...});
The problem is the newVal passed to the function is the entire array. Is there anyway I can get only the delta part? Say the added or removed element?
As of KnockoutJS 3.0, there's an arrayChange subscription option on ko.observableArray.
var myArray = ko.observableArray(["Alpha", "Beta", "Gamma"]);
myArray.subscribe(function(changes) {
// For this example, we'll just print out the change info
console.log(changes);
}, null, "arrayChange");
myArray.push("newitem!");
In the above callback, the changes argument will be an array of change objects like this:
[
{
index: 3,
status: 'added',
value: 'newitem!'
}
]
For your specific problem, you want to be notified of new or removed items. To implement that using Knockout 3, it'd look like this:
myArray.subscribe(function(changes) {
changes.forEach(function(change) {
if (change.status === 'added' || change.status === 'deleted') {
console.log("Added or removed! The added/removed element is:", change.value);
}
});
}, null, "arrayChange");
Since I couldn't find any info on this elsewhere, I'll add a reply for how to use this with TypeScript.
The key here was to use the KnockoutArrayChange interface as TEvent for subscribe. If you don't do that, it'll try to use the other (non-generic) subscribe and will complain about status, index, and value not existing.
class ZoneDefinition {
Name: KnockoutObservable<String>;
}
class DefinitionContainer
{
ZoneDefinitions: KnockoutObservableArray<ZoneDefinition>;
constructor(zoneDefinitions?: ZoneDefinition[]){
this.ZoneDefinitions = ko.observableArray(zoneDefinitions);
// you'll get an error if you don't use the generic version of subscribe
// and you need to use the KnockoutArrayChange<T> interface as T
this.ZoneDefinitions.subscribe<KnockoutArrayChange<ZoneDefinition>[]>(function (changes) {
changes.forEach(function (change) {
if (change.status === 'added') {
// do something with the added value
// can use change.value to get the added item
// or change.index to get the index of where it was added
} else if (change.status === 'deleted') {
// do something with the deleted value
// can use change.value to get the deleted item
// or change.index to get the index of where it was before deletion
}
});
}, null, "arrayChange");
}
In order to only detect push() and remove() events, and not moving items, I put a wrapper around these observable array functions.
var trackPush = function(array) {
var push = array.push;
return function() {
console.log(arguments[0]);
push.apply(this,arguments);
}
}
var list = ko.observableArray();
list.push = trackPush(list);
The original push function is stored in a closure, then is overlayed with a wrapper that allows me do do anything I want with the pushed item before, or after, it is pushed onto the array.
Similar pattern for remove().
I am using a similar but different approach, keep track whether an element has been instrumented in the element itself:
myArray.subscribe(function(array){
$.each(array, function(id, el) {
if (!el.instrumented) {
el.instrumented = true;
el.displayName = ko.computed(function(){
var fn = $.trim(el.firstName()), ln = $.trim(el.lastName());
if (fn || ln) {
return fn ? (fn + (ln ? " " + ln : "")) : ln;
} else {
return el.email();
}
})
}
});
})
But it is really tedious and the pattern repeated across my code
None that I know of. Wanna know what I do? I use a previous variable to hold the value, something called selectedItem
vm.selectedItem = ko.observable({});
function addToArray(item) { vm.selectedItem(item); vm.myArray.push(item); }
So that way, when something happens to my observable array, I know which item was added.
vm.myArray.subscribe(function(newArray) { var addedItem = vm.selectedItem(item); ... }
This is really verbose, and assuming your array holds many kinds of data, you would need to have some sort of flags that helps you know what to do with your saved variables...
vm.myArray.subscribe(function(newArray) {
if ( wasUpdated )
// do something with selectedItem
else
// do whatever you whenever your array is updated
}
An important thing to notice is that you might know which item was added if you know whether push or unshift was used. Just browse the last item of the array or the first one and voila.
Try vm.myArray().arrayChanged.subscribe(function(eventArgs))
That has the added value when an item is added, and the removed value when an item is removed.