I am trying to construct a hierarchy (tree structure) using JavaScript. For that, I wrote a Node class that represents a node in the tree. When I retrieve the data from the database, it's all being retrieved properly (i.e: the root node has the ParentId as null, it has 3 children that point to it as the parent, and the descendant nodes are set up properly as well...). But when I try to map them to my JavaScript model, the Children property of the root node is ending up being undefined. I do not know how that could be posible even though during runtime, when I output the contents of the Children property in the console I can see the children nodes being added to it. Here's my code:
var Node = function (obj) {
var self = this;
var isDefined = obj != undefined;
self.hasChildren = function () {
return self.Children.length > 0;
};
self.hasParent = function () {
var p = self.ParentId;
return !(p == null || p == undefined || p == 0);
};
self.addChildren = function (objArray) {
if (!$.isArray(self.Children)) {
self.Children = [];
}
for (var i = 0; i < objArray.length; i++) {
self.addChild(objArray[i]);
}
};
self.addChild = function (obj) {
if (obj instanceof Node) {
self.Children.push(obj);
} else {
var n = new Node(obj);
self.Children.push(n);
}
};
self.removeChild = function (n) {
var index = self.Children.indexOf(n);
if (index > -1) {
self.Children.splice(index, 1);
}
};
self.Id = isDefined ? obj.Id : null;
self.ParentId = isDefined ? obj.ParentId : null;
self.Name = isDefined ? obj.Name : '';
self.Children = isDefined ? self.addChildren(obj.Children) : [];
self.TypeId = isDefined ? obj.TypeId : null;
};
The way I thought about the addChildren method, is that I would pass the raw JSON object coming from the server into the constructor of the Node object and then in case it has any children (which essentially have the same properties as the parent), addChildren will be called which will in turn create a new Node for each element. Eventually, the tree will be built recursively.
So where did I go wrong? Why does the Children property end up being undefined?
self.Children = isDefined ? self.addChildren(obj.Children) : [];
You are setting self.Children equal to the return of self.addChildren(). That function has no return.
Here is a couple things I would recommend
function Node(obj) {
// clean constructor moving function definitions to prototype
var self = this;
// ensure that we at least have an object passed in
obj = obj || {};
// default values at the top
self.Id = null;
self.ParentId = null;
self.Name = '';
self.Children = [];
self.TypeId = null;
// fold in data with $.extend, no need to specify each key manually
// third object is to overwrite any added Children as those need to be handled seperately
$.extend(self, obj, { Children : [] });
// if we have children, add them using the addChildren method
if (typeof obj.Children !== undefined && $.isArray(obj.Children)) {
self.addChildren(obj.Children);
}
}
// using prototype to reduce memory footprint
Node.prototype.hasChildren = function () {
return this.Children.length > 0;
};
Node.prototype.hasParent = function () {
var p = this.ParentId;
return !(p == null || p == undefined || p == 0);
};
Node.prototype.addChildren = function (objArray) {
for (var i = 0; i < objArray.length; i++) {
this.addChild(objArray[i]);
}
};
Node.prototype.addChild = function (obj) {
if (obj instanceof Node) {
this.Children.push(obj);
} else {
var n = new Node(obj);
this.Children.push(n);
}
};
Node.prototype.removeChild = function (n) {
var index = this.Children.indexOf(n);
if (index > -1) {
this.Children.splice(index, 1);
}
};
Then I can use this like so:
test = new Node({ Id : "Something", Children : [{ Id : "Interior", Children : [] }] })
Using prototype you reduce the memory footprint and don't create a function reference to each interior function for each Node you create. Each Node one still will reference it's internal data via a this variable.
Related
TasksI need to modify the displaySortedTaskList function so that it runs if there are 3 arguments passed, and throws an error object with a message if there aren't 3 arguments passed. My attempt:
"use strict";
var sortTaskList = function(tasks) {
var isArray = Array.isArray(tasks);
if (isArray) {
tasks.sort();
}
return isArray;
};
var displaySortedTaskList = function(tasks, div, handler) {
if(arguments.length = Function.length){
var html = "";
var isArray = sortTaskList(tasks);
if (isArray) {
//create and load html string from sorted array
for (var i in tasks) {
html = html.concat("<p>");
html = html.concat("<a href='#' id='", i, "'>Delete</a>");
html = html.concat(tasks[i]);
html = html.concat("</p>");
}
div.innerHTML = html;
// get links, loop and add onclick event handler
var links = div.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
links[i].onclick = handler;
}
}
} else {document.getElementById("message").innerHTML = "The displaySortedTaskList function of the tasklist library requires three arguments"}
};
var deleteTask = function(tasks, i) {
var isArray = sortTaskList(tasks);
if (isArray) { tasks.splice(i, 1); }
};
var capitalizeTask = function(task) {
var first = task.substring(0,1);
return first.toUpperCase() + task.substring(1);
};
You might use rest parameters and check whether the length of the array is 3:
var displaySortedTaskList = function(...args) {
if (args.length !== 3) {
document.getElementById("message").textContent = "The displaySortedTaskList function of the tasklist library requires three arguments";
return;
// or `throw new Error('not enough args')` ?
}
const [tasks, div, handler] = args;
// rest of your code
(note that you should assign to .textContent when inserting text - .innerHTML is appropriate when inserting HTML markup, which is not the case here)
Live snippet:
var displaySortedTaskList = function(...args) {
if (args.length !== 3) {
return console.log('error');
}
console.log('rest of the code');
}
displaySortedTaskList('foo', 'bar');
displaySortedTaskList('foo', 'bar', 'baz');
displaySortedTaskList('foo', 'bar', 'baz', 'buzz');
I'm trying to create a JavaScript function that creates an object using strings for structure and fills it from DOM data.
For example, the following strings could look like this:
some.example.here = "hello"
some.example.there = "hi"
other.example = "heyo"
Which should create this object:
{
some: {
example: {
here: "hello",
there: "hi"
},
other: {
example: "heyo
}
}
The data as said comes from DOM and is being load at the code segment labeled "read data into object". The data loads fine and the object structure is being setup fine as well, but the data is not being put into the data field.
Here's the code for the function:
function getDataFromElement(element) {
obj = {};
$(element)
.find("[data-value]")
.each(function() {
// create object node
valueObj = {};
currentValueObj = valueObj;
$.each($(this).attr("data-value").split("."), function(i, objpath) {
currentValueObj[objpath] = {};
currentValueObj = currentValueObj[objpath];
});
// read data into object
if($(this).is("[data-putvalue]") && $(this).attr("data-putvalue") != "html") {
currentValueObj = $(this).attr($(this).attr("data-putvalue"));
} else {
currentValueObj = $(this).html();
}
console.log(currentValueObj);
// combine with previous gathered data
obj = $.extend(true, {}, obj, valueObj);
});
return obj;
}
Does anyone know what to do?
I would do it like this:
var createObject = function(model, name, value) {
var nameParts = name.split("."),
currentObject = model;
for (var i in nameParts) {
var part = nameParts[i];
if (i == nameParts.length-1) {
currentObject[part] = value;
break;
}
if (typeof currentObject[part] == "undefined") {
currentObject[part] = {};
}
currentObject = currentObject[part];
}
};
And then use it like that:
var model = {};
createObject(model, "some.example.here", "hello");
createObject(model, "some.example.there", "hi");
createObject(model, "other.example", "heyo");
Probably this can suit you (adapted from another project of mine, adapt and use as needed):
NOTE the element's name is taken as key and value as the value
function fields2model( $elements, dataModel )
{
$elements.each(function( ){
var $el = $(this),
name = $el.attr('name'),
key, k, i, o, val
;
key = name;
val = $el.val() || '';
k = key.split('.'); o = dataModel;
while ( k.length )
{
i = k.shift( );
if ( k.length )
{
if ( !o.hasOwnProperty( i ) ) o[ i ] = /^\d+$/.test( k[0] ) ? [ ] : { };
o = o[ i ];
}
else
{
o[ i ] = val;
}
}
});
}
Example use:
<input name="some.example.here" value="hello" />
<input name="some.example.there" value="hi" />
var model = {};
fields2model($('input,textarea,select'), model);
The example elements above will give the below model:
model = {
some: {
example: {
here: "hello",
there: "hi"
}
};
Some functional implementation:
const value = 'hello';
'some.example.here'.split('.').reverse().reduce((reduction, segment, index) => {
const result = {};
if (index === 0) {
result[segment] = value;
} else {
result[segment] = reduction;
}
return result;
}, {})
#theFreedomBanana +1
Works for me
const magicFunction = (string, value) =>
string
.split('.')
.reverse()
.reduce((acc, cur, index) => ({ [cur]: index === 0 ? value : acc }), {});
I'm testing the observable pattern in javascript. My callbacks in the array never seem to execute. What is wrong with my syntax?
<script type="text/javascript">
var Book = function (value) {
var onChanging = [];
this.name = function () {
for (var i = 0; i < onChanging.length; i++) {
onChanging[i]();
}
return value;
}
this.addTest = function (fn) {
onChanging.push(fn);
}
}
var b = new Book(13);
b.addTest(function () { console.log("executing"); return true; });
b.name = 15;
</script>
From your code above it looks like you need to call your function name instead of assigning a value something like:
var b = new Book(13);
b.addTest(function () { console.log("executing"); return true; });
b.name(); //<-- Before b.name = 15
Setting b.name = 15 doesn't execute the function, it just overwrites the value of b.name.
You could use getters and setters to react to a changing value. See John Resig's blog post or the MDN reference
I edited your code to use them:
var Book = function (value) {
this.onChanging = [];
this._name = "";
}
Book.prototype = {
addTest: function (fn) {
this.onChanging.push(fn);
},
get name() {
return this._name;
},
set name(val) {
for (var i = 0; i < this.onChanging.length; i++) {
this.onChanging[i](val);
}
this._name = val;
}
};
var b = new Book(13);
b.addTest(function (val) {
console.log("executing", val);
return true;
});
b.name = 15;
b.name = 17;
working demo.
You can also make a more generic solution that can work for all your properties without having to define the getters and setters, a lot of frameworks use this approach.
Book = function () {
this._events = [];
this._rawdata = {};
}
Book.prototype = {
bind: function (fn) {
this._events.push(fn);
},
// pass the property, and it returns its value, pass the value and it sets it!
attr: function (property, val) {
if (typeof val === "undefined") return this._rawdata[property];
this._rawdata[property] = val;
for (var i = 0; i < this._events.length; i++)
// we pass out the val and the property
this._events[i](val, property);
}
};
b = new Book();
b.bind(function (val) {
console.log("executing", val);
return true;
});
b.attr("name","The Hobbit");
b.attr("SKU" ,1700109393901);
console.log(b.attr("name")); // --> The Hobbit
http://jsfiddle.net/wv4ch6as/
Of course you would want to change the binder so that you can bind onto properties not one bind for all properties, but I think this gets the idea.
I am trying to populate my input fields based on the retrieved JSON object. The field names in my form would be something like:
fullName
account.enabled
country.XXX.XXXX
The function should return something like below for the above fields
aData["fullName"]
aData["account"]["enabled"]
aData["country"]["XXX"]["XXXX"]
How should I write my a function that returns a matching JSON entry for a given HTML field's name ?
you could use the attached method that will recursively look for a given path in a JSON object and will fallback to default value (def) if there is no match.
var get = function (model, path, def) {
path = path || '';
model = model || {};
def = typeof def === 'undefined' ? '' : def;
var parts = path.split('.');
if (parts.length > 1 && typeof model[parts[0]] === 'object') {
return get(model[parts[0]], parts.splice(1).join('.'), def);
} else {
return model[parts[0]] || def;
}
}
and you can call it like that :
get(aData, 'country.XXX.XXXX', ''); //traverse the json object to get the given key
Iterate over the form elements, grab their names, split on '.', then access the JSON Object?
Something like:
var getDataValueForField = function (fieldName, data) {
var namespaces = fieldName.split('.');
var value = "";
var step = data;
for (var i = 0; i < namespaces.length; i++) {
if (data.hasOwnProperty(namespaces[i])) {
step = step[namespaces[i]];
value = step;
} else {
return (""); // safe value
}
}
return (value);
};
var populateFormFields = function (formId, data) {
var fields = document.querySelectorAll('#' + formId + ' input');
for (var i = 0; i < fields.length; i++) {
fields[i].value = getDataValueForField(fields[i].name, data);
}
};
populateFormFields('myForm', fetchedFromSomeWhere());
I am trying to simulate a namespace feature in Javascript.
var com = {};
com.domain = {};
com.domain.system = {};
com.domain.net = {};
com.domain.net.ip = {};
com.domain.net.ip.tcp = {};
com.domain.net.ip.udp = {};
com.domain.net.ip.ssl = {};
com.domain.util = {};
com.domain.util.timer = {};
com.domain.plugins = {};
com.domain.session = {};
com.domain.io = {};
com.domain.algorithm = {};
com.domain.debug = {};
This is the namespaces declaration. Later I will add functions to these namespaces.
This is my selector function:
For a convenient way to use namespaces, I add a function named $. This function will walk all namespaces in com. If the selected name exists, return the object.
function $ (selector) {
function digger (namespace, selector) {
for (var prop in namespace) {
if (typeof namespace[prop] == "array" || typeof namespace[prop] == "object") {
if (prop == selector) {
return namespace[prop];
}
var dig = digger(namespace[prop], selector);
if (dig != null) {
return dig;
}
} else {
if (prop == selector) {
return namespace[prop];
}
}
}
}
return digger (com, selector);
}
After that, I add a timer to namespace com.doamin.util.
com.domain.util.timer = function () {
this._handle = new InnerObj.SystemTimer(io);
return this;
};
com.domain.util.timer.prototype.expiresFromNow = function (seconds, cbHandler) {
this._handle.ExpiresFromNow (seconds, cbHandler);
};
com.domain.util.timer.prototype.wait = function (seconds, cbHandler) {
this._handle.Wait (seconds, cbHandler);
};
com.domain.util.timer.prototype.expiresAt = function (seconds, cbHandler) {
this._handle.Wait (seconds, cbHandler);
};
com.domain.util.timer.prototype.cancel = function () {
this._handle.Cancel ();
};
Usage:
1. var timer = new com.domain.util.timer (); OK
timer.expiresAt (1, {}); OK
2. var func = $("timer"); OK
var timer = new func (); OK
timer.expiresAt (1, {}); OK
But but but but but
var timer = new $("timer") (); NG
Can anyone tell me why the last new function is not working?
Try var timer = new ($("timer"))();.
Your question is not clear but I guess since $("timer") returns a function, you want a new instance of the result of $("timer") and not a new instance of $().