Javascript: Internal array is not reset to outer objects - javascript

How do I grant access to inner properties of objects in the right way? This is what does break my application:
I have an object that handles an array (simplified here):
function ListManager() {
var list = [],
add = function (element) {
list.push(element);
},
clear = function () {
list = [];
};
return {
add: add,
clear: clear,
list : list
};
};
But I get this when using it:
var manager = new ListManager();
manager.add("something");
manager.clear();
console.log(manager.list.length); // <= outputs "1"!
Stepping through the code shows, that within the clear method, list becomes a new array. But from outside the ListManager the list ist not cleared.
What am I doing wrong?

This is because clear sets the value of var list, not the .list on the object returned from ListManager(). You can use this instead:
function ListManager() {
var list = [],
add = function (element) {
this.list.push(element);
},
clear = function () {
this.list = [];
};
return {
add: add,
clear: clear,
list : list
};
}

Using your current structure, you could do:
function ListManager() {
var list = [],
add = function (element) {
list.push(element);
},
clear = function () {
list = [];
};
getList=function(){
return list;
}
return {
add: add,
clear: clear,
list : list,
getList: getList
};
};
var manager = new ListManager();
manager.add("something");
console.log(manager.getList()); // ["something"]
manager.clear();
console.log(manager.getList()); // []

function ListManager() {
var list = [],
add = function (element) {
this.list.push(element);
},
clear = function () {
this.list = [];
};
return {
add: add,
clear: clear,
list : list
};
};
var manager = new ListManager();
manager.add("something");
manager.clear();
console.log(manager.list.length); // <= now outputs "0"!

As has already been explained, your issue is that when you do list = [], you are changing the local variable list, but you aren't changing this.list as they are two separate variables. They initially refer to the same array so if you modified the array rather than assigning a new one to just one of the variables, they would both see the change.
Personally, I think you're using the wrong design pattern for creating this object that just makes things more complicated and makes it more likely you will create problems like you did. That design pattern can be useful if you want to maintain private instance variables that are not accessible to the outside world, but it creates a more complicated definition and maintenance if everything is intended to be public.
One of my programming goals is to use the simplest, cleanest way of expressing the desired functionality.
So that end, since everything in this object is intended to be public and accessible from outside the object, this is a whole lot simpler and not subject to any of the types of problems you just had:
function ListManager() {
this.list = [];
this.add = function(element) {
this.list.push(element);
}
this.clear = function() {
this.list = [];
}
}
Or, perhaps even use the prototype:
function ListManager() {
this.list = [];
}
ListManager.prototype = {
add: function(element) {
this.list.push(element);
},
clear: function() {
this.list = [];
}
};

Related

javascript class with event methods

My goal is to make a class with some chained functions, but I'm stuck and hoping for some help. This is what I got:
robin = new batman("myiv");
var batman = (function() {
var me = this;
function batman(id){
me._id=id;
document.getElementById(id).addEventListener('mousemove', me.mouseMoving.bind(me),true);
}
this.mouseMoving = function(){
document.getElementById(me._id).style.background="orange";
}
return batman;
}
And this pseudo code is what I am aiming to get. Basically, pass in the ID of an element in my HTML and chain functions to it such as onclick etc, and whatever code inside there, runs. as in example, changing background colors.
Is it possible?
superman("mydiv"){
.onmouseover(){
document.getElementById(the_id).style.background="#ffffff";
},
.onmouseout(){
document.getElementById(the_id).style.background="#000000";
},
etc...
}
edit: updated with missing code: "return batman;"
You can do method chaining by returning the current object using this keyword
var YourClass = function () {
this.items = [];
this.push = function (item) {
if (arguments) {
this.items.push(item);
}
return this;
}
this.count = function () {
return this.items.length;
}
}
var obj = new YourClass();
obj.push(1).push(1);
console.log(obj.count())
Working sample
https://stackblitz.com/edit/method-chaining-example?file=index.js

Javascript: move objects from one array to another: Best approach?

I have two arrays, called 'objects' and 'appliedObjects'. I'm trying to come up with an elegant way in Javascript and/or Angular to move objects from one array to another.
Initially I did something like this:
$scope.remove = function () {
angular.forEach($scope.appliedObjects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.objects.push(element);
$scope.appliedObjects.splice(index, 1);
}
});
}
$scope.add= function () {
angular.forEach($scope.objects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.appliedObjects.push(element);
$scope.objects.splice(index, 1);
}
});
}
But then I realized that when the value was removed from the looping array, and it would not add or remove every other item, since it went by index.
Then I tried using a temporary array to hold the list of items to be added or removed, and I started getting strange referential issues.
I'm starting to spin a bit on what the best solution to this problem would be...any help and/or guidance would much appreciated.
function moveElements(source, target, moveCheck) {
for (var i = 0; i < source.length; i++) {
var element = source[i];
if (moveCheck(element)) {
source.splice(i, 1);
target.push(element);
i--;
}
}
}
function selectionMoveCheck(element) {
if (element.selected) {
element.selected = false;
return true;
}
}
$scope.remove = function () {
moveElements($scope.appliedObjects, $scope.objects, selectionMoveCheck);
}
$scope.add = function () {
moveElements($scope.objects, $scope.appliedObjects, selectionMoveCheck);
}
When a construct does too much automatically (like forEach, or even a for-loop, in this case), use a more primitive construct that allows you to say what should happen clearly, without need to work around the construct. Using a while loop, you can express what needs to happen without resorting to backing up or otherwise applying workarounds:
function moveSelected(src, dest) {
var i = 0;
while ( i < src.length ) {
var item = src[i];
if (item.selected) {
src.splice(i,1);
dest.push(item);
}
else i++;
}
}
You are altering the array while iterating on it, you will always miss some elements.
One way of doing it would be to use a third array to store the references of the objects that need to be removed from the array:
// "$scope.add" case
var objectsToRemove = [];
$scope.objects.forEach(function (value) {
if (value.selected) {
value.selected = false;
$scope.appliedObjects.push(value);
objectsToRemove.push(value);
}
});
objectsToRemove.forEach(function (value) {
$scope.objects.splice($scope.objects.indexOf(value), 1);
});
If you wish to move simply whole array you could do:
appliedObjects = objects;
objects = []
Of course it won't work if they were parameters of a function!
Otherwise I cannot see other way than copying in the loop, e.g.
while (objects.length) {
appliedObjects.push(objects[0]);
objects.splice(0,1);
}
or if you like short code :) :
while (objects.length) appliedObjects.push(objects.splice(0,1));
check fiddle http://jsfiddle.net/060ywajm/
Now this maybe is not a fair answer, but if you notice you are doing alot of complicated object/array manipulations, you should really check out lodash or underscore library. then you could solve this with on liner:
//lodash remove function
appliedObjects.push.apply( appliedObjects, _.remove(objects, { 'selected': true}));
//or if you want to insert in the beginning of the list:
appliedObjects.splice(0, 0, _.remove(objects, { 'selected': true}));
This is a first pass at what I think will work for you. I'm in the process of making a test page so that I can test the accuracy of the work and will update the tweaked result, which hopefully there will not be.
EDIT: I ran it and it seems to do what you are wanting if I understand the problem correctly. There were a couple of syntax errors that I edited out.
Here's the plunk with the condensed, cleaned code http://plnkr.co/edit/K7XuMu?p=preview
HTML
<button ng-click="transferArrays(objects, appliedObjects)">Add</button>
<button ng-click="transferArrays(appliedObjects, objects)">Remove</button>
JS
$scope.transferArrays = function (arrayFrom, arrayTo) {
var selectedElements;
selectedElements = [];
angular.forEach(arrayFrom, function(element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function(element) {
arrayTo.push(arrayFrom.splice(
arrayFrom.map(function(x) {
return x.uniqueId;
})
.indexOf(element.uniqueId), 1));
});
};
Old code
$scope.remove = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.appliedObjects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.objects.push($scope.appliedObjects.splice(
$scope.appliedObjects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
$scope.add = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.objects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.appliedObjects.push($scope.objects.splice(
$scope.objects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
You can use this oneliner as many times as many items you need to move from arr1 to arr2 just prepare check func
arr2.push(arr1.splice(arr1.findIndex(arr1El => check(arr1El)),1)[0])
You can use this to concat 2 arrays:
let array3 = [...array1, ...array2];

passing array to function - dont get updated

Sorry for the question but I am new to JavaScript
i have defined an object
define(['sharedServices/pubsub', 'sharedServices/topics'], function (pubsub,topics) {
'use strict';
function Incident() {
var that = this;
this._dtasks = [];
this.handlePropertyGet = function(enstate, ename) {
if (!this.entityAspect || !this.entityAspect.entityManager || !this.entityAspect.entityManager.isReady) {
enstate = [];
} else {
enstate = this.entityAspect.entityManager.executeQueryLocally(new breeze.EntityQuery(ename).where('IncidentID', 'eq', this.IncidentID));
}
return enstate;
};
Object.defineProperty(this, 'DTasks', {
get: function () {
return this.handlePropertyGet(this._dtasks, "DTasks");
},
set: function (value) { //used only when loading incidents from the server
that.handlePropertySet('DTask', value);
},
enumerable: true
});
}
return {
Incident: Incident
};
});
when I am calling the property DTasks the inner member _dtask is equal to [], even when i enter the get property and i see that the enstate is filled with the objects when the handlePropertyGet is finished and returned to the get scope the _dtasks remains empty, doesn't it supposed to pass as reference?
this._dtasks "points" to an array. If you pass it as a parameter to this.handlePropertyGet, you make enstate refer to the same array.
If you change the array (as in enstate.push("bar")), the change affects this._dtasks too: you are actually changing neither of them, just the array they both point to.
However, the lines
enstate = []
and
enstate = this.entityAspect.entityManager.executeQueryLocally(new breeze.EntityQuery(ename).where('IncidentID', 'eq', this.IncidentID));
don't modify the array you already have. Instead, they create new arrays and change enstate so that it points to them. this._dtasks, however, remains unchanged.
An easy way to fix it would be to change the code inside the getter to
return this.handlePropertyGet("_dtasks", "DTasks");
and handlePropertyGet to
this.handlePropertyGet = function(enstate, ename) {
if (!this.entityAspect || !this.entityAspect.entityManager || !this.entityAspect.entityManager.isReady) {
this[enstate] = [];
} else {
this[enstate] = this.entityAspect.entityManager.executeQueryLocally(new breeze.EntityQuery(ename).where('IncidentID', 'eq', this.IncidentID));
}
return this[enstate];
};
That way, you would be changing the value of this._dtasks directly.
As an alternative, you could achieve the same result by changing enstate = [] to enstate.length = 0 (which clears the array instead of changing the variable. See https://stackoverflow.com/a/1232046/3191224) and enstate = this.entityAspect.[...] to
var newContent = enstate = this.entityAspect.entityManager.executeQueryLocally(new breeze.EntityQuery(ename).where('IncidentID', 'eq', this.IncidentID));
enstate.length = 0;
Array.prototype.push.apply(enstate, newContent);
which clears the array and then pushes all the elements from the other array, effectively replacing the whole content without changing enstate itself.
My guess is that you are trying to do something like this
Javascript
function Incident() {
var reset = false;
this._dtasks = [];
this.handlePropertyGet = function (ename) {
if (reset) {
this._dtasks = [];
} else {
this._dtasks = [1, 2, 3];
}
return this._dtasks;
};
Object.defineProperty(this, 'DTasks', {
get: function () {
return this.handlePropertyGet("DTasks");
},
enumerable: true
});
}
var x = new Incident();
console.log(x.DTasks);
Output
[1, 2, 3]
On jsFiddle
So you can then use this simplified example with the ideas given by #user3191224

Pointers and array class in javascript [duplicate]

This question already has an answer here:
Double-Queue Code needs to be reduced
(1 answer)
Closed 9 years ago.
Is there any way for me to shorten this code by using pointers?
I need to make a class that has mostly the same function as a given array class unshift,shift,push and pop but with different names.
var makeDeque = function()
{
var a= [], r=new Array(a);
length = r.length=0;
pushHead=function(v)
{
r.unshift(v);
}
popHead=function()
{
return r.shift();
}
pushTail=function(v)
{
r.push(v);
}
popTail=function()
{
return r.pop();
}
isEmpty=function()
{
return r.length===0;
}
return this;
};
(function() {
var dq = makeDeque();
dq.pushTail(4);
dq.pushHead(3);
dq.pushHead(2);
dq.pushHead("one");
dq.pushTail("five");
print("length " + dq.length + "last item: " + dq.popTail());
while (!dq.isEmpty())
print(dq.popHead());
})();
Output should be
length 5last item: five
one
2
3
4
Thanks!
Maybe I'm oversimplifying, but why not just add the extra methods you need to the Array prototype and call it directly?
I need to make a class that has mostly the same function as a given array class unshift,shift,push and pop but with different names.
I suppose you could add these "new" methods to Array.prototype.
Like this perhaps?
var makeDeque = (function (ap) {
var Deque = {
length: 0,
pushHead: ap.unshift,
popHead: ap.shift,
pushTail: ap.push,
popTail: ap.pop,
isEmpty: function () {
return !this.length;
}
};
return function () {
return Object.create(Deque);
};
})(Array.prototype);
DEMO
If it's still too long, you can always directly augment Array.prototype like others already mentionned. We agree that it's all experimental here and the only goal is to save characters.
!function (ap) {
ap.pushHead = ap.unshift;
ap.popHead = ap.shift;
ap.pushTail = ap.push;
ap.popTail = ap.pop;
ap.isEmpty = function () {
return !this.length;
};
}(Array.prototype);
function makeDeque() {
return [];
}
This can be compressed to 174 chars:
function makeDeque(){return[]}!function(e){e.pushHead=e.unshift;e.popHead=e.shift;e.pushTail=e.push;e.popTail=e.pop;e.isEmpty=function(){return!this.length}}(Array.prototype)
DEMO
Not sure why you need this, but my suggestions per best practice are:
Don't override the Array.prototype. The reason for this is because other libraries might try to do the same, and if you include these libraries into yours, there will be conflicts.
This code is not needed. var a= [], r=new Array(a);. You only need ...a = [];.
Ensure you are creating a real class. In your code, makeDeque is not doing what you want. It is returning this which when a function is not called with the new keyword will be the same as the window object (or undefined if you are using what is called as "strict mode"). In other words, you have made a lot of globals (which are usually a no-no, as they can conflict with other code too).
When you build a class, it is good to add to the prototype of your custom class. This is because the methods will only be built into memory one time and will be shared by all such objects.
So I would first refactor into something like this:
var makeDeque = (function() { // We don't need this wrapper in this case, as we don't have static properties, but I've kept it here since we do want to encapsulate variables in my example below this one (and sometimes you do need static properties).
function makeDeque () {
if (!(this instanceof makeDeque)) { // This block allows you to call makeDeque without using the "new" keyword (we will do it for the person using makeDeque)
return new makeDeque();
}
this.r = [];
this.length = 0;
}
makeDeque.prototype.setLength = function () {
return this.length = this.r.length;
};
makeDeque.prototype.pushHead=function(v) {
this.r.unshift(v);
this.setLength();
};
makeDeque.prototype.popHead=function() {
return this.r.shift();
this.setLength();
};
makeDeque.prototype.pushTail=function(v){
this.r.push(v);
this.setLength();
};
makeDeque.prototype.popTail=function() {
return this.r.pop();
this.setLength();
};
makeDeque.prototype.isEmpty=function() {
return this.r.length === 0;
};
return makeDeque;
}());
Now you could shorten this as follows, but I wouldn't recommend doing this, since, as it was well said by Donald Knuth, "premature optimization is the root of all evil". If you try to shorten your code, it may make it inflexible.
var makeDeque = (function() {
function makeDeque () {
if (!(this instanceof makeDeque)) {
return new makeDeque();
}
this.r = [];
this.length = 0;
}
makeDeque.prototype.setLength = function () {
return this.length = this.r.length;
};
for (var i=0, methodArray = [
['pushHead', 'unshift'], ['popHead', 'shift'], ['pushTail', 'push'], ['popTail', 'pop']
]; i < methodArray.length; i++) {
makeDeque.prototype[methodArray[i][0]] = (function (i) { // We need to make a function and immediately pass in 'i' here because otherwise, the 'i' inside this function will end up being set to the value of 'i' after it ends this loop as opposed to the 'i' which varies with each loop. This is a common "gotcha" of JavaScript
return function () {
var ret = this.r[methodArray[i][1]].apply(this.r, arguments);
this.setLength();
return ret;
};
}(i));
}
makeDeque.prototype.isEmpty=function() {
return this.r.length === 0;
};
return makeDeque;
}());
If you need to get the length by a length property, as opposed to a method like setLength() which sets it manually after each update, either of the above code samples could be shortened by avoiding the setLength() method, but you'd need to use the Object.defineProperty which does not work (or does not work fully) in older browsers like IE < 9.

Access grandchild of a variable (parent.child.grandchild) without dots and one pair of brackets

I'm building a canvas-related class with a kind of conversion table. The conversion table can be edited by the user. (Isn't really relevant, but maybe you want to know why):
cLayout = function(option) {
//obtaining the canvas (el) here
this.setup = function(option) {
t=this.table;
for (var p in t)
{
el[t[p][0]] = option[p]||t[p][1]
}
}
this.setup(option)
}
cLayout.prototype.table = {
width:[['style']['width'],"100%"],
height:['style'['height'],"100%"],
bg:[['style']['backgroundColor'],""],
position:[['style']['position'],"absolute"],
left:['style'['left'],"0px"],
top:['style'['left'],"0px"]
}
Example:
var b = new cLayout({left:'10%',width:'90%'})
Real question:
Normally, I'd use el['style']['width'] to set el.style.width.
But I want to use el[something] without the second pair of brackets: I want the property name to be completely variable (I also want to be able to set el['innerHTML']). So, is there a way to get a grandchild by using a[b], without using a[b][c]?
P.S. Of course, I don't want to use eval.
No it is not possible. If you have nested objects, you cannot just skip a level.
You could write a helper function though, which takes a string like "child.grandchild" and sets the corresponding property:
function setProp(obj, prop, val) {
var parts = prop.split('.');
while(parts.length > 1) {
obj = obj[parts.shift()];
}
obj[parts.shift()] = val;
}
(You should also test whether a certain property is available.)
Then your code could look like:
var cLayout = function(option) {
//obtaining the canvas (el) here
this.setup = function(option) {
for(var p in this.table) {
setProp(el, this.table[p][0], option[p]||t[p][1]);
}
}
this.setup(option)
}
cLayout.prototype.table = {
width:['style.width',"100%"],
height:['style.height',"100%"],
//...
}

Categories

Resources