I have a Node object inside my Angular controller. Each Node has a next property which points to the next item:
$scope.Stack = function () {
this.top = null;
this.rear = null;
this.size = 0;
this.max_size = 15;
};
$scope.Node = function (data) {
this.data = data;
this.next = null;
this.previous = null;
};
$scope.Stack.prototype.pushUp = function (data) {
for (i = 0; i < data.items.length; i++) {
if (data.items[i]) {
var node = new $scope.Node(data.items[i]);
if (node) {
node.previous = this.top;
if (this.top) {
this.top.next = node;
}
this.top = node;
// if first push, the set the rear
if (this.size == 0) {
this.rear = node;
}
this.size += 1;
}
}
}
};
Creating object:
$scope.Timeline = new $scope.Stack();
My question: is there a way to iterate over linked data structures like this using ng-repeat/Angular?
According to the AngularJS docs,
variable in expression – where variable is the user defined loop variable and expression is a scope expression giving the collection to enumerate.
therefore ngRepeat can only be used to iterate over a Javascript "Collection". Collections include Arrays, Maps, Sets, and WeakMaps. So the answer to your question is No, you cannot iterate over a linked structure.
I am trying to create a javascript object like
var allUserExpiry={};
allUserExpiry[aData.userId][aData.courseId][aData.uscId] = aData;
But I am getting an error like allUserExpiry[aData.userId] undefined.
Is there a way, whereby I can set multi-level JS-Object keys? or is it important that I should go by doing allUserExpiry[aData.userId]={}, then allUserExpiry[aData.userId][aData.courseId]={} ?
Please let me know if there are any utility functions available for the same.
No, there is no way to set "multilevel keys". You need to initialize each object before trying to add properties to it.
var allUserExpiry = {};
allUserExpiry[aData.userId] = {}
allUserExpiry[aData.userId][aData.courseId] = {}
allUserExpiry[aData.userId][aData.courseId][aData.uscId] = aData;
Using Computed property names from ES6, it is possible to do:
var allUserExpiry = {
[aData.userId] = {
[aData.courseId]: {
[aData.uscId]: aData
}
}
};
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names
Simply use loadash,
let object = {};
let property = "a.b.c";
let value = 1;
_.set(object, property, value); // sets property based on path
let value = _.get(object, property, default); // gets property based on path
Or you can do it:
function setByPath(obj, path, value) {
var parts = path.split('.');
var o = obj;
if (parts.length > 1) {
for (var i = 0; i < parts.length - 1; i++) {
if (!o[parts[i]])
o[parts[i]] = {};
o = o[parts[i]];
}
}
o[parts[parts.length - 1]] = value;
}
And use:
setByPath(obj, 'path.path2.path', someValue);
This approach has many weak places, but for fun... :)
Why not just do this?
var allUserExpiry={};
allUserExpiry[aData.userId] = {aData.courseId: {aData.uscId: aData}};
I have a pretty hacky but short way of doing it in IE9+ as well as real browsers.
Given var path = 'aaa.bbb.ccc.ddd.eee'; where path is what your intending to make into an object and var result = {}; will will create the object {aaa: {bbb: {ccc: {ddd: {eee: {}}}}}
result = {}
path.split('.').reduce(function(prev, e) {
var newObj = {};
prev[e] = newObj;
return newObj;
}, result);
will store the object in result.
How it works:
split('.') converts the input into ['aaa', 'bbb', 'ccc', 'ddd', 'eee']
reduce(function (...) {...}, result) runs through the array created by split, and for each entry will pass along a returned value to the next one. In our case we pass the new object through after adding the new object to the old one. This creates a chain of objects. reduce returns the last object you return inside of it, so we have to defined result beforehand.
This relies on using references so it won't be immediately clear how it works if you're expecting your code to be maintained by anyone else and should probably be avoided to be honest, but it works at least.
You can also use the following to create the initial structure:
var x = function(obj, keys) {
if (!obj) return;
var i, t;
for (i = 0; i < keys.length; i++) {
if (!t) {
t = obj[keys[i]] = {};
} else {
t[keys[i]] = {};
t = t[keys[i]];
}
}
};
var a = {};
x(a, ['A', 'B', 'C', 'D', 'E', 'F']);
Another approach without strings or array as argument.
function fillObject() {
var o = arguments[0];
for(var i = 1; i < arguments.length-1; i++) {
if(!o.hasOwnProperty(arguments[i])) {
o[arguments[i]] = {};
}
if(i < arguments.length-2) {
o = o[arguments[i]];
}else {
o[arguments[i]] = arguments[i+1]
}
}
}
var myObj = {"foo":{}};
fillObject(myObj,"back","to","the","future",2);
console.log(JSON.stringify(myObj));
// {"foo":{},"back":{"to":{"the":{"future":2}}}}
But I wouldn't use it :-) It's just for fun.
Because I don't like too much intelligent algorithm. (If it was in this category)
Using lodash you can do this easily (node exists and empty check for that node)..
var lodash = require('lodash-contrib');
function invalidateRequest(obj, param) {
var valid = true;
param.forEach(function(val) {
if(!lodash.hasPath(obj, val)) {
valid = false;
} else {
if(lodash.getPath(obj, val) == null || lodash.getPath(obj, val) == undefined || lodash.getPath(obj, val) == '') {
valid = false;
}
}
});
return valid;
}
Usage:
leaveDetails = {
"startDay": 1414998000000,
"endDay": 1415084400000,
"test": { "test1" : 1234 }
};
var validate;
validate = invalidateRequest(leaveDetails, ['startDay', 'endDay', 'test.test1']);
it will return boolean.
Another solution using reduce function (thanks Brian K).
Here we created a get/set to general proposes. The first function return the value in any level. The key is splited considering the separator. the function return the value refered from last index in the key's array
The second function will set the new value considering the last index of the splited key
the code:
function getObjectMultiLevelValue(_array,key,separator){
key = key.split(separator || '.');
var _value = JSON.parse(JSON.stringify(_array));
for(var ki in key){
_value = _value[key[ki]];
}
return _value;
}
function setObjectMultiLevelValue(_array,key,value,forcemode,separator){
key.split(separator || '.').reduce(function(prev, currKey, currIndex,keysArr) {
var newObj = {};
if(prev[currKey] && !forcemode){
newObj = prev[currKey];
}
if(keysArr[keysArr.length-1] == currKey){
newObj = value;
prev[currKey] = newObj;
}
prev[currKey] = newObj;
return newObj;
}, _array);
return _array;
}
//testing the function
//creating an array
var _someArray = {a:'a',b:'b',c:{c1:'c1',c2:{c21:'nothing here...'}}};
//a multilevel key to test
var _key = 'a,a1,a21';
//any value
var _value = 'new foo in a21 key forcing replace old path';
//here the new value will be inserted even if the path exists (forcemode=true). Using comma separator
setObjectMultiLevelValue(_someArray,_key,_value,true,',');
console.log('_someArray:');
console.log(JSON.stringify(_someArray));
//inserting another value in another key... using default separator
_key = 'c.c2.c21';
_value = 'new foo in c21 key';
setObjectMultiLevelValue(_someArray,_key,_value);
console.log('_someArray:');
console.log(JSON.stringify(_someArray));
//recovering the saved value with different separators
_key = 'a,a1,a21';
console.log(getObjectMultiLevelValue(_someArray,_key,','));
_key = 'c.c2.c21';
console.log(getObjectMultiLevelValue(_someArray,_key));
Let assume our object is
const data = {
//some other data
userInfo: {},
};
First, define a new property of that object
data.userInfo.vehicle = {};
then simply
data.userInfo.vehicle.vehicleType = state.userInfo.vehicleType;
In my program, I have declared an object myObject like this :
function myObject()
{
this.start=start;
function start(callbackFunction)
{
// Do something with callbackFunction
}
}
In my main() method, I create objects and I want to start nested callback like this :
var myObject1 = new myObject();
var myObject2 = new myObject();
var list = [];
list.push(myObject1);
list.push(myObject2);
var result = function() {};
var obj;
for (var i=list.length-1; i>=0; i--) {
obj = list[i];
result = function() { obj.start(result);}
}
result(); // I want to do myObject1.start(myObject2.start)); e.g. a nested callback
I don't understand why it doesn't work.
How can I correct my code ?
The result variable is redefined after each iteration.
Your need to set your function like so :
var myObject1 = new myObject();
var myObject2 = new myObject();
var list = [];
list.push(myObject1);
list.push(myObject2);
var result= function() {};
for (var i=list.length-1; i>=0; i--) {
var obj = list[i];
result = obj.start.bind(obj, result);
}
result();
Using the bind method will force the state of the variable to be saved at each iteration.
The problem is that you are not using closures properly.
In your for loop you declare a function that uses a variable from the outer scope (result). When the for loop ends, the result variable will contain the last function defined, instead of the one defined at step i, as you would expect.
One solution as you so very well hinted in a comment is recursivity:
function myObject(name)
{
this.name = name;
this.start= function(callbackFunction) {
console.log(this.name);
// Do something with callbackFunction
callbackFunction();
};
}
var myObject1 = new myObject(1);
var myObject2 = new myObject(2);
var list = [];
list.push(myObject1);
list.push(myObject2);
var runner = function(list, currentIndex) { // recursive function
if (currentIndex < 0) return function(){ console.log('INITIAL');};
return function(){
list[currentIndex].start(runner(list, currentIndex-1));
};
};
runner(list, list.length-1)();
DEMO: http://jsbin.com/ukeBUweG/2/edit
One last note, the solution above tries to stay true to your initial code. It is not
obj1.start(obj2.start(function(){}))
, but
function(){ obj1.start(function(){ obj2.start(function(){}) })}();
I'm getting the next error in function getOlder():
TypeError: Cannot read property 'age' of undefined
What's the problem and how to fix it?
function person(name, age) {
this.name=name;
this.age=age
}
// Returns the older person in a group of persons.
var getOlder = function(people) {
if (people.length === 0) {
return new person();
}
var older = people[0]; // The first one is the older for now.
var value;
for (var _ in people) {
value = people[_];
if (value.age > older.age) {
older = value;
}
}
return older;
};
// Declare some persons.
var paul = new person("Paul", 23);
var jim = new person("Jim", 24);
var sam = new person("Sam", 84);
var rob = new person("Rob", 54);
var karl = new person("Karl", 19);
var older = getOlder(paul, jim);
if (older.name !== "Jim") {
console.log("Fail");
}
Look at the signature of your function:
var getOlder = function(people) {
You did not create your function such that it accepts two person objects. You apparently only accept an array, as you're using people.length and people[0] inside the function. So you also have to pass an array:
var older = getOlder([paul, jim]);
var getOlder = function() {
var people = arguments;
if (people.length === 0) {
return new person();
}
var older = people[0]; // The first one is the older for now.
var value;
for (var _ in people) {
value = people[_];
if (value.age > older.age) {
older = value;
}
}
return older;
};
By Accepting no arguments in the function definition and relying on the variable arguments feature of JS you can get away with calling getOlder(paul, jim).
arguments is a property of every function which is basically an array of variable arguments provided to it while calling .
Say i have this function that dynamically creates my namespace for me when I just pass it a string, (I'm pretty sure basically what YUI JS library does):
MyObj.namespace('fn.method.name');
would result in
MyObj.fn.method.name = {}
being created - all three levels being equivalent to an empty object.
Now, what I want to do, though, is make the last level, in this case name, set to a function, but without having to redeclare the newly created object.
So instead of doing this:
function fnName() { /* some code here */ }
MyObj.namespace('fn.method.name');
MyObj.fn.method.name = new fnName();
i want to call something like:
MyObj.add('fn.method.name', fnName);
And internally, the add method would programmatically instantiate the passed in function:
MyObj.fn.method.name = new fnName()
In the way I have it implemented, I can create the namespace object and set it to an empty object, however, when I try to instantiate a passed in function and associate that namespace with the passed in function, it never gets added to the namespace. Instead, an empty object is always returned. Any ideas?
edit: Here is the namespace method. this is attached to the base object as a JSON object, so please ignore the formatting:
namespace: function (ns) {
var _ns = ns.split('.'),
i = 0, nsLen = _ns.length,
root = this;
if (_ns[0] === gNS) {
_ns.shift();
nsLen = _ns.length;
}
for (i = 0; i < nsLen; i++) {
// create a property if it doesn't exist
var newNs = _ns[i];
if (typeof root[newNs] === "undefined") {
root[newNs] = {};
}
root = root[newNs];
}
return root;
}
edit2 - removed the passed in fn argument
Were you looking for something like this:
var root = {};
function create(ns, fn) {
var nsArray = ns.split(/\./);
var currentNode = root;
while(nsArray.length > 1) {
var newNS = nsArray.shift();
if(typeof currentNode[newNS] === "undefined") {
currentNode[newNS] = {};
}
currentNode = currentNode[newNS];
}
if(fn) {
currentNode[nsArray.shift()] = fn;
}
else {
currentNode[nsArray.shift()] = {};
}
}
Then:
create("a.b.c");
console.log(root.a);
console.log(root.a.b);
console.log(root.a.b.c);
Gives:
Object { b={...}}
Object { c={...}}
Object {}
And:
create("d.e.f", function() { console.log("o hai"); });
console.log(root.d);
console.log(root.d.e);
console.log(root.d.e.f);
Gives:
Object { e={...}}
Object {}
function()
Calling the function you defined:
root.d.e.f();
Gives:
o hai
Well you haven't given the namespace function but your add function could look something like this:
MyObj.add = function (namespace, value) {
var names = namespace.split('.'), current = this, name;
while (names.length > 1) {
name = names.shift();
current[name] = {};
current = current[name];
}
current[names[0]] = value;
};
This code assigns the value given to the last part of the namespace. You could modify it to current[names[0] = new value(); if you want the object constructed by the passed in function (and you are assuming the constructor function takes no arguments).
function ns() {
var root = window;
for (var i = 0; i < arguments.length; i++) {
var arr = arguments[i].split(/\./);
for (var j = 0; j < arr.length; j++) {
var item = arr[j];
if (typeof item !== 'string') {
root = item;
}
else {
if (!root[item]) {
root[item] = {};
}
root = root[item];
}
}
root = window;
}
}
then you can create using
ns('fn.method.name');
or
ns('fn.method.name','fn.method.secondName');
and call using
fn.method.name
this function creates your namespace on 'window' so alternatively you can use
window.fn.method.name