JS - Change value of all instances of object - javascript

My instances of some object has some value called selected and method select(). When method select() is fired, I want object's selected value to be set true, but selected value of all other instances of this object to be false - how to do it?
In other words - How to change value of all instances of some object?
var Puzzel = function() {
this.selected = false;
};
Puzzel.prototype = {
select: function{
this.selected = true;
//how to set selected = false on every other instance of Puzzel
}
}

If you can rely on getters/setters (see compatibility) then the below will work.
This approach has constant overhead when selecting or checking selection, and constant memory overhead.
var Selectable = function () {
// Define your constructor normally.
function Selectable() {
}
// Use a hidden variable to keep track of the selected item.
// (This will prevent the selected item from being garbage collected as long
// as the ctor is not collectible.)
var selected = null;
// Define a getter/setter property that is true only for the
// item that is selected
Object.defineProperty(Selectable.prototype, 'selected', {
'get': function () { return this == selected; },
// The setter makes sure the current value is selected when assigned
// a truthy value, and makes sure the current value is not selected
// when assigned a falsey value, but does minimal work otherwise.
'set': function (newVal) {
selected = newVal ? this : this == selected ? null : selected;
}
});
// Define a select function that changes the current value to be selected.
Selectable.prototype.select = function () { this.selected = true; };
// Export the constructor.
return Selectable;
}();

You would need to keep track of those instances. Here's one way of doing it:
(function() {
var instances = [];
window.MyClass = function() {
instances.push(this);
// rest of constructor function
};
window.MyClass.prototype.select = function() {
for( var i=0, l=instances.length; i<l; i++) instances[i].selected = false;
this.selected = true;
};
})();

Related

Knockout.js: computed observable not updating as expected

Edit: Added code for function populateDropdown and function isSystemCorrect (see bottom)
Edit 2 I have narrowed it down a bit and the problem seems to arise in the arrayFilter function in the computed observable. This returns an empty array, no matter what I try. I have checked that self.testsuites() looks ok right before filtering, but the filtering still fails.
I have a problem with my computed observable, filteredTestsuites.
As you can see from the screendump, the testsuites observable is populated correctly, but the computed observable remains empty. I have also tried choosing another option than "Payment" from the dropdown menu, to see if this will trigger the observable, it did not.
I would think the computed observable would be updated every time self.testsuites() or self.dropdownSelected() was changed, but it doesnt seem to trigger on neither of them.
What am I doing wrong here?
I simply want to make the computed observable filter the testsuites after the chosen dropdown option, every time either of them change.
function ViewModel() {
var self = this;
// The item currently selected from a dropdown menu
self.dropdownSelected = ko.observable("Payment");
// This will contain all testsuites from all dropdown options
self.testsuites = ko.mapping.fromJS('');
// This will contain only testsuites from the chosen dropdown option
self.filteredTestsuites = ko.computed(function () {
return ko.utils.arrayFilter(self.testsuites(), function (testsuite) {
return (isSystemCorrect(testsuite.System(), self.dropdownSelected()));
});
}, self);
// Function for populating the testsuites observableArray
self.cacheTestsuites = function (data) {
self.testsuites(ko.mapping.fromJS(data));
};
self.populateDropdown = function(testsuiteArray) {
for (var i = 0, len = testsuiteArray().length; i < len; ++i) {
var firstNodeInSystem = testsuiteArray()[i].System().split("/")[0];
var allreadyExists = ko.utils.arrayFirst(self.dropdownOptions(), function(option) {
return (option.Name === firstNodeInSystem);
});
if (!allreadyExists) {
self.dropdownOptions.push({ Name: firstNodeInSystem });
}
}
};
}
$(document).ready(function () {
$.getJSON("/api/TestSuites", function (data) {
vm.cacheTestsuites(data);
vm.populateDropdown(vm.testsuites());
ko.applyBindings(vm);
});
}
Function isSystemCorrect:
function isSystemCorrect(system, partialSystem) {
// Check if partialSystem is contained within system. Must be at beginning of system and go
// on to the end or until a "/" character.
return ((system.indexOf(partialSystem) == 0) && (((system[partialSystem.length] == "/")) || (system[partialSystem.length] == null)));
}
As suggested in a comment - rewrite the cacheTestsuites method:
self.testsuites = ko.observableArray();
self.filteredTestsuites = ko.computed(function () {
return ko.utils.arrayFilter(self.testsuites(), function (testsuite) {
return (isSystemCorrect(testsuite.System(), self.dropdownSelected()));
});
});
self.cacheTestsuites = function (data) {
var a = ko.mapping.fromJS(data);
self.testsuites(a());
};
The only thing different here is the unwrapping of the observableArray from the mapping function.

How to define a custom binding who use previous value to determine class in Knockout?

I need to bind a table with knockout, and I would like the table cell to get a different css class if the new value is higher or lower of the previous.
I have in mind different possibilities, such as storing the previous value in the bindingContext and have a function which returns the right class, but is it possible to add a custom binding handler which receives the previous value and the new value?
Although Jeff's and Sławomir's answers would work, I found an alternative that doesn't need any change to the view model nor relies on altering the DOM element object.
function subscribeToPreviousValue(observable, fn) {
observable.subscribe(fn, this, 'beforeChange');
}
ko.bindingHandlers['bindingWithPrevValue'] = {
init: function (element, valueAccessor) {
var observable = valueAccessor();
var current = observable();
console.log('initial value is', current);
subscribeToPreviousValue(observable, function (previous) {
console.log('value changed from', previous, 'to', current);
});
}
};
Naturally, that will only work if the bound property is an observable.
I looked into knockout source and I suppose that you can't access previous value inside update method of bindingHandler but you can store it inside element
ko.bindingHandlers['bindingWithPrevValue'] = {
update: function (element, valueAccessor) {
var prevValue = $(element).data('prevValue');
var currentValue = valueAccessor();
$(element).data('prevValue', currentValue());
// compare prevValue with currentValue and do what you want
}
};
What you could do is create an extender to extend the observables that you wish to track the previous values of. You could then inspect the previous value to do as you wish.
Just pass in the name of the property that will hold the previous value.
ko.extenders.previousValue = function (target, propertyName) {
var previousValue = ko.observable(null);
target[propertyName] = ko.computed(previousValue);
target.subscribe(function (oldValue) {
previousValue(oldValue);
}, target, 'beforeChange');
return target;
};
Then to use it:
function ViewModel() {
this.value = ko.observable('foo').extend({ previousValue: 'previousValue' });
}
var vm = new ViewModel();
console.log(vm.value()); // 'foo'
console.log(vm.value.previousValue()); // null
vm.value('bar');
console.log(vm.value()); // 'bar'
console.log(vm.value.previousValue()); // 'foo'
In your case, you could probably use something like this:
function TableCell(value) {
this.value = ko.observable(value).extend({ previousValue: 'previousValue' });
this.cssClass = ko.computed(function () {
// I'm assuming numbers
var current = Number(this.value()),
previous = Number(this.value.previousValue());
if (current < previous)
return 'lower';
else if (current > previous)
return 'higher';
}, this);
}

Returning a private variable in JavaScript

I don't know why console.log(Set.current_index) shows 0 instead of 3.
var Set = (function() {
var set = [];
var index = 0;
function contains(set, e) {
for (var i = 0; i < set.length; i++) {
if (set[i] === e) {
return true;
}
}
return false;
}
var add = function(e) {
if (!contains(set, e)) {
set[index++] = e;
}
}
var show = function() {
for (var i = 0; i < set.length; i++) {
console.log(set[i]);
}
}
return {
add: add,
show: show,
current_index: index
};
})();​
Set.add(20);
Set.add(30);
Set.add(40);
Set.show();
console.log(Set.current_index);
As written current_index just gets the initial value of index - it doesn't mirror any changes to that value because that variable is of primitive type.
If you have a 'reference type' (i.e. an object or array) then changes to its contents become visible in any other variable that references the same object. That doesn't happen with primitive types, they're copied "by value" into the new variables, and changes to the original variable don't affect the copy.
You need to make current_index into a function that returns the current value of index, or write it as a getter which allows you to treat .index as a read-only property by invisibly calling a function to return the current value.
For an example of the latter method (which requires ES5, or shims to replicate the functionality) see http://jsfiddle.net/alnitak/WAwUg/, which replaces your current return block with this:
var interface = {
add: add,
show: show
};
Object.defineProperty(interface, 'index', {
get: function() {
return index;
},
enumerable: true
});
return interface;
Javascript always passes by value except when a variable refers to an object. So your initialization of current_index just gets the initial value of index rather than permanently pointing to the variable, so after that initialization, the two variables are on their separate ways therefore incrementing index doesn't increment current_index.

Simple jQuery issue - how to check if $(this) is the last $(this) clicked?

I am essentially adding radio-buttons functionality to buttons with the class "tagbutton". There is a flaw with my code, because _this is never _last...
taguid = "";
_last = "";
$('.tagbutton').live('click', function() {
_this = $(this);
if(_last) {
//There was a last object
if(_last == _this) { // The last object was the current object
alert('deactiveate for this obj');
} else { // The last object was not the current object
alert('deactivate last, activate this');
}
} else {
alert('first object activated');
var taguid = $(this).prev().attr('data-uid');
alert(taguid);
_last = $(this);
}
});
It's because the objects' references aren't the same. A simpler way might be to activate the clicked one, then deactivate the last one. It'll have the same effect:
var taguid = "";
var _last;
$('.tagbutton').live('click', function() {
if(_last) {
// There was a last object
// Activate this
// Deactivate last
} else {
alert('first object activated');
var taguid = $(this).prev().attr('data-uid');
alert(taguid);
_last = $(this);
}
});
The jQuery function $() returns a jQuery object, and when you call it a second time you get back a different object.
However, within your event handler the keyword this refers to the actual DOM element, so if you save that directly then your comparison should work:
_this = this;
// and then later
_last = this;

Was variable x ever true?

In Javascript, is there a good way to check if a variable was ever a true, (or any value), in the entire session? The best way I can think of right now is to perform a regular check like this, recording the truthiness in another variable:
if (variable){
variablewasevertrue = true;
}
Then when I want to know if the original variable was ever true, I check if the new variablewasevertrue is true or undefined. There's nothing more graceful like if (variable was ever true){ is there? That doesn't seem very Javascript-y.
No there is no if (variable was ever true) facility in the language. Variables store values, not history.
Intercepting values as they're assigned and checking is the only way to do it. If the variable is really a property (e.g. a global variable is a property of the global object) you can intercept changes easily using setters.
So to have a history keeping global variable you could do
var hasEverBeenTruthy = false;
(function () {
var value;
Object.defineProperty(window, "myGlobal", {
"get": function () { return value; },
"set": function (newval) {
if (newval) { hasEverBeenTruthy = true; }
value = newval;
}
});
})();
This will work on modern browsers, and there are __defineSetter__ variants on many older browsers.
Variables store value, not a history of a memory location. If you want to do something like this, I would suggest you use an Object of some sort:
var x = {
value: false,
history: [],
set: function(val){
history.push(this.value);
this.value = val;
},
wasEver: function(val){
return this.history.indexOf(val) >= 0;
}
};
Then you can use the object like so:
x.set(false);
x.value; //returns false
x.set(true);
x.value; //returns true
x.wasEver(true); // returns true
x.wasEver(false); //returns true
x.wasEver("hello"); //returns false
This gives each object it's own history (as in, it can check multiple values, not just one - as with the getter/setter stuff mentioned in other answers), and is guaranteed to work in any scope, as all functionality is contained within the defined object.
No, except that you could use a getter and setter like this, which delegates the setting of a variable so that you can check whether it is to set at one time:
var value,
wasevertrue = false;
window.__defineSetter__('test', function(v) {
value = v;
wasevertrue = wasevertrue || (v === true);
});
window.__defineGetter__('test', function() {
return value;
});
Now,
test = false; // wasevertrue === false
test = true; // wasevertrue === true
test = false; // wasevertrue === true
Better yet would be putting this in a closure because you can now just set value = true as a workaround to the setter.
no - there is no state tracking on variables. it is only whatever its current value is. beyond that its your own custom implementation using property-like methods for state tracking.
Have another variable called "wasevertrue = false." Anywhere you set "variable" immediately follow it with a check that sees if variable == true. If it is, set wasevertrue = true.
You can't use a single scalar variable to track history, but you could use an array. It's not ideal, but it's an alternative:
function setVar(arr, value) {
arr.unshift(value);
return arr;
}
function getVar(arr) {
return arr[0];
}
function checkHist(arr, histValue) {
var ii;
for (ii = 0; ii < arr.length; ii++) {
if (arr[ii] === histValue) {
return true;
}
}
return false;
}
var myVar = [];
myVar = setVar(myVar, true);
myVar = setVar(myVar, false);
alert(checkHist(myVar, true)); // alerts "true"

Categories

Resources