I have the "error: Cannot read property 'shorten' of undefined" error when running my test. I want my loop to run the shorten function to check if the string is longer then 20 characters, and if so limit it to that.
function ListView(list) {
this.list = list;
this.converted;
}
ListView.prototype.convert = function() {
var output = [];
this.list.notelist.forEach(function(element) {
this.shorten(element);
output += "<li><div>" + element.text + "</div></li>";
});
this.converted = "<ul>" + output + "</ul>";
};
ListView.prototype.shorten = function(string) {
if (string.length > 20) {
return string.substring(0, 20);
}
return string;
};
list is from another constructor but i mocked it with;
var mockList = { notelist: [{ text: "hello" }, { text: "goodbye" }] };
There are serveral problems with your code:
You encountered the what is this problem which is a very common problem with beginners, have a look at this link. In the anonymous function body of function (element) { .. it doesn't obtain the context of your custom type, thus this is a reference to your browser window.
Your shorten method is called with a different usage in its semantics. You did not take what it returns, but element is not modified at all with the method.
So, let's try to correct the code for what you attempt to do:
<script>
function ListView(list) {
this.list = list;
this.converted;
}
ListView.prototype.convert = function () {
var output = [];
var that = this;
this.list.notelist.forEach(function (element) {
that.shorten(element);
output += "<li><div>" + element.text + "</div></li>";
});
this.converted = "<ul>" + output + "</ul>";
};
ListView.prototype.shorten = function (el) {
var string = el.text;
if (string.length > 20) {
el.text = string.substring(0, 20);
}
};
var mockList = { notelist: [{ text: "hello" }, { text: "goodbye0123456789012345" }] };
var listview1 = new ListView(mockList);
listview1.convert();
alert(listview1.converted);
</script>
goodbye0123456789012345 is modified intentionally for the test, in the result it will be shorten as goodbye0123456789012.
You lost the this binding in the forEach
Try:
ListView.prototype.convert = function() {
var output = [];
this.list.notelist.forEach(function(element) {
this.shorten(element);
output += "<li><div>" + element.text + "</div></li>";
}.bind(this));
this.converted = "<ul>" + output + "</ul>";
};
or
this.list.notelist.forEach((element) => {
this.shorten(element);
output += "<li><div>" + element.text + "</div></li>";
});
Similar to: Passing scope to forEach
Try by binding this
this.list.notelist.forEach(function(element) {
this.shorten(element);
output += "<li><div>" + element.text + "</div></li>";
}.bind(this));
You can use an ES6 arrow function in this case to ensure that this is maintained within your function.
this.list.notelist.forEach((element) => {
this.shorten(element);
output += "<li><div>" + element.text + "</div></li>";
});
Remember to check the availability of arrow functions on browsers. If you want a more widely supported solution use the bind function like others have mentioned.
forEach accepts a second argument which is the thisArg, by default it is undefined.
Try this:
this.list.notelist.forEach(function(element) {
this.shorten(element);
output += "<li><div>" + element.text + "</div></li>";
}, this); // <-- note the `this` argument passed here
Related
I'm unsure on how to build this list (which is a string) and then returning as one complete string.
I've worked past my last issue but I think this one is realy bugging me. buildItem() should iterate through item, and then recursively build a list while getting the totalCost from another callback. I know it works asynchronously...
buildItem(data, function(html){
$('#nestable ol').append(html);
});
Should append the 'final' html string that's created from being appended throughout the file.
function buildItem(item, callback) {
getTotalCost(item, function(totalCost) {
var html = "<li class='dd-item' data-id='" + item.id + "' data-email='" + item.email + "' data-title='" + item.corporateTitle + "' data-name='" + item.firstName + " " + item.lastName + "' id='" + item.id + "'>";
if (item.children && item.children.length > 0) {
html += "<ol class='dd-list'>";
$.each(item.children, function (index, sub) {
buildItem(item, function(subHtml){
html += subHtml;
})
})
html += "</ol>";
}
html += "</li>";
callback(html);
});
}
I know that
buildItem(item, function(subHtml){
html += subHtml;
})
shouldn't work since javascript is asynchronous. I'm just not sure on how to return from a recursive function? If I were to do something like
buildItem(item, function(subHtml){
callback(subHtml);
})
You'll get duplicate values because you'll have the starting value and it's children, but since you're also calling it back you'll get the children outside of the starting value. So it'll look like
1
a
b
c
d
e
a
b
c
d
e
So what's the best way to approach a solution? I was thinking of making another function, hypothetically a buildChild(sub) that returned html, but the same issue with asynchronous is going to come up where the return will be undefined. I've read some of the threads where you can handle asynchronous values with callbacks, but I'm not sure on how to do it with recursion here.
getTotalCost is another callback function that shouldn't mean much, I removed the line by accident but I just need the totalCost from a database.
function getTotalCost(item, callback) {
$.ajax({
dataType: "json",
url: "/retrieveData.do?item=" + item.email,
success: function(data) {
var totalCost = 0;
for (var i = 0; i < data.length; i++) {
totalCost += parseFloat(data[i].cost);
}
callback(totalCost);
}
});
}
You can simplify this with promises and async functions:
async function getTotalCost(item) {
const data = await Promise.resolve($.ajax({
dataType: "json",
url: "/retrieveData.do?item=" + item.email
}));
return data.reduce((acc, next) => acc + next.cost, 0);
}
async function buildItem(item) {
const totalCost = await getTotalCost(item);
let html = `<li class="dd-item" data-id="${item.id}" data-email="${item.email}" data-title="${item.corporateTitle}" data-name="${item.firstName} ${item.lastName}" id="${item.id}">`;
if (item.children && item.children.length > 0) {
html += '<ol class="dd-list">';
for (const childItem of item.children) {
html += await buildItem(childItem);
}
html += "</ol>";
}
html += "</li>";
return html;
}
Unfortunately, async functions aren't supported by all browsers yet, so you'll have to use Babel to transpile your code.
I also added some new ES6 features: arrow functions, const and template literals.
You can mix slow ajax requests with logic and recursion if you execute your code via synchronous executor nsynjs.
Step 1. Write your logic as if it was synchronous, and place it into function:
function process(item) {
function getTotalCost(item) {
var data = jQueryGetJSON(nsynjsCtx, "/retrieveData.do?item=" + item.email).data;
var totalCost = 0;
for (var i = 0; i < data.length; i++) {
totalCost += parseFloat(data[i].cost);
}
return totalCost;
};
function buildItem(item) {
const totalCost = getTotalCost(item);
var html = "<li class='dd-item' data-id='" + item.id + "' data-email='" + item.email + "' data-title='" + item.corporateTitle + "' data-name='" + item.firstName + " " + item.lastName + "' id='" + item.id + "'>";
if (item.children && item.children.length > 0) {
html += '<ol class="dd-list">';
for (var i=0; i<item.children.length; i++)
html += buildItem(item.children[i]);
html += "</ol>";
}
html += "</li>";
return html;
};
return buildItem(item);
};
Step 2: run it via nsynjs:
nsynjs.run(process,{},item,function (itemHTML) {
console.log("all done",itemHTML);
});
Please see more examples here: https://github.com/amaksr/nsynjs/tree/master/examples
I have a function that measures time execution of function, and cleans DOM after each execution:
function measureTimeExecution(domID, testFunc){
console.time("timer");
for(var i = 0; i < 10; i++){
testFunc();
var getDiv = document.getElementById(domID);
}
getDiv.empty();
console.timeEnd("timer");
}
Function that creates new ul
function createList_Task_2(divID){
var createNewUL = document.createElement("ul");
createNewUL.id = "phoneList";
document.getElementById(divID).appendChild(createNewUL);
for(var i = 0; i < phones.length;i++){
var chunk = "<li>" + phones[i].age +"<br>" + phones[i].id +"<br><img src='"
+ phones[i].imageUrl +"'/><br>" + phones[i].name + "<br>" + phones[i].snippet + "</li>";
document.getElementById("phoneList").innerHTML += chunk;
}
}
But iy gives me: Uncaught TypeError: testFunc is not a function;
Example:
measureTimeExecution("div1", createList_Task_3("div1"));
Is it possible to get somehow domID in measureTimeExecution as a argument of testFunc?
the problem is that when you are calling measureTimeExecution you are runing the parameter, instead pass a function again.
look at this code it should work
measureTimeExecution("div1", function () { createList_Task_3("div1"); });
function measureTimeExecution(domID, testFunc)
The function expects the second argument to be a function, but calling it like measureTimeExecution("div1", createList_Task_3("div1"));, it provides the return of createList_Task_3("div1"). Since createList_Task_3 returns nothing, the default return is undefined.
For it to be a function as well as be able to be provided the ID, it should return a function like this:
function createList_Task_2(divID){
return function(){
var createNewUL = document.createElement("ul");
createNewUL.id = "phoneList";
document.getElementById(divID).appendChild(createNewUL);
for(var i = 0; i < phones.length;i++){
var chunk = "<li>" + phones[i].age +"<br>" + phones[i].id +"<br><img src='"
+ phones[i].imageUrl +"'/><br>" + phones[i].name + "<br>" + phones[i].snippet + "</li>";
document.getElementById("phoneList").innerHTML += chunk;
}
}
}
I have a set of controls on my page that I am attempting to iterate through and determine if they have values. Some of these controls are <select> items and in this instance I need to loop through the options associated with the <select> control and find the ones that are selected. I have tried .children() as well as .find("option:selected") and neither of them work (both throw an "undefined" error in javascript). What am I doing wrong?
function getJsonValue(control, json) {
if(control.dataset["fieldname"] !== "undefined"){
if(control.tagName == "SELECT") {
var selected = "{";
var idName = control.dataset["idname"];
control.children.each( function() {
if(this.selected) {
selected += idName + ": " + this.val() + ",";
}
});
selected += "}";
if(selected != '') {
json += control.dataset["fieldname"] + ": ";
json += selected + ",";
}
} else {
if(control.val() != '') {
json += control.dataset["fieldname"] + ": ";
json += control.val() + ",";
}
}
}
};
The error is appearing on line 5 of the above code (control.children.each). I've tried:
control.children.each( function() {
control.childre().each( function() {
control.find("option:selected").each( function() {
None of these options work. For reference, the "control" variable is passed from another function that is found by doing the following:
$("#search-header").find("select").each( function() {
json = getJsonValue(this, json);
});
To further extend on my comment, I can see that you are passing the object this, which is assigned to the variable control in your function. However, jQuery cannot directly operate on this object unless it is converted into a jQuery object, and this can be done simply by wrapping it with the jQuery alias, $(control) (hint, just like how people use $(this) in jQuery, it's the same thing).
Therefore, if you revise your code this way, it should work:
function getJsonValue(control, json) {
if(control.dataset["fieldname"] !== "undefined"){
if(control.tagName == "SELECT") {
var selected = "{";
var idName = control.dataset["idname"];
$(control).children.each( function() {
if(this.selected) {
selected += idName + ": " + this.val() + ",";
}
});
selected += "}";
if(selected != '') {
json += control.dataset["fieldname"] + ": ";
json += selected + ",";
}
} else {
if(control.val() != '') {
json += control.dataset["fieldname"] + ": ";
json += $(control).val() + ",";
}
}
}
};
p/s: On a side note, if you ever want to access the original DOM node from a jQuery object, just use $(control)[0] ;)
Try
$(control).find("option:selected").each( function() {
or
$(control).children.each( function() {
You are passing (this) to you function from the caller. It doesn't have jquery selector.
You are missing Open close bracket on first children
control.children().each( function() {
control.childre().each( function() {
control.find("option:selected").each( function() {
Update
I've come up with a concise solution to this problem, that behaves similar to node's vm module.
var VM = function(o) {
eval((function() {
var src = '';
for (var prop in o) {
if (o.hasOwnProperty(prop)) {
src += 'var ' + prop + '=o[\'' + prop + '\'];';
}
}
return src;
})());
return function() {
return eval(arguments[0]);
}
}
This can then be used as such:
var vm = new VM({ prop1: { prop2: 3 } });
console.assert(3 === vm('prop1.prop2'), 'Property access');
This solution overrides the namespace with only the identifier arguments taken.
Thanks to Ryan Wheale for his idea.
Short version
What is the best way to evaluate custom javascript expression using javascript object as a context?
var context = { prop1: { prop2: 3 } }
console.assert(3 === evaluate('prop1.prop2', context), 'Simple expression')
console.assert(3 === evaluate('(function() {' +
' console.log(prop1.prop2);' +
' return prop1.prop2;' +
'})()', context), 'Complex expression')
It should run on the latest version of node (0.12) and all evergreen browsers at the time of writing (3/6/2015).
Note: Most templating engines support this functionality. For example, Jade.
Long version
I'm currently working on an application engine, and one of its features is that it takes a piece of code and evaluates it with a provided object and returns the result.
For example, engine.evaluate('prop1.prop2', {prop1: {prop2: 3}}) should return 3.
This can be easily accomplished by using:
function(code, obj) {
with (obj) {
return eval(code);
}
};
However, the usage of with is known to be bad practice and will not run in ES5 strict mode.
Before looking at with, I had already written up an alternative solution:
function(code, obj) {
return (function() {
return eval(code);
}).call(obj, code);
}
However, this method requires the usage of this.
As in: engine.evaluate('this.prop1.prop2', {prop1: {prop2: 3}})
The end user should not use any "prefix".
The engine must also be able to evaluate strings like
'prop1.prop2 + 5'
and
'(function() {' +
' console.log(prop1.prop2);' +
' return prop1.prop2;' +
'})()'
and those containing calls to functions from the provided object.
Thus, it cannot rely on splitting the code string into property names alone.
What is the best solution to this problem?
I don't know all of your scenarios, but this should give you a head start:
http://jsfiddle.net/ryanwheale/e8aaa8ny/
var engine = {
evaluate: function(strInput, obj) {
var fnBody = '';
for(var prop in obj) {
fnBody += "var " + prop + "=" + JSON.stringify(obj[prop]) + ";";
}
return (new Function(fnBody + 'return ' + strInput))();
}
};
UPDATE - I got bored: http://jsfiddle.net/ryanwheale/e8aaa8ny/3/
var engine = {
toSourceString: function(obj, recursion) {
var strout = "";
recursion = recursion || 0;
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
strout += recursion ? " " + prop + ": " : "var " + prop + " = ";
switch (typeof obj[prop]) {
case "string":
case "number":
case "boolean":
case "undefined":
strout += JSON.stringify(obj[prop]);
break;
case "function":
// won't work in older browsers
strout += obj[prop].toString();
break;
case "object":
if (!obj[prop])
strout += JSON.stringify(obj[prop]);
else if (obj[prop] instanceof RegExp)
strout += obj[prop].toString();
else if (obj[prop] instanceof Date)
strout += "new Date(" + JSON.stringify(obj[prop]) + ")";
else if (obj[prop] instanceof Array)
strout += "Array.prototype.slice.call({\n "
+ this.toSourceString(obj[prop], recursion + 1)
+ " length: " + obj[prop].length
+ "\n })";
else
strout += "{\n "
+ this.toSourceString(obj[prop], recursion + 1).replace(/\,\s*$/, '')
+ "\n }";
break;
}
strout += recursion ? ",\n " : ";\n ";
}
}
return strout;
},
evaluate: function(strInput, obj) {
var str = this.toSourceString(obj);
return (new Function(str + 'return ' + strInput))();
}
};
UPDATE 3: Once we figured out what you are really asking, the question is clear: you do not do that. Especially in the strict mode.
As an viable alternative to your approach please refer to the documentation on require.js, common.js and other libraries allowing you to load modules in the browser. basically the main difference is that you do not do prop1.prop2 and you do context.prop1.prop2 instead.
If using context.prop1.prop2 is acceptable, see jsfiddle: http://jsfiddle.net/vittore/5rse4jto/
"use strict";
var obj = { prop1 : { prop2: 'a' } }
function evaluate(code, context) {
var f = new Function('ctx', 'return ' + code);
return f(context)
}
alert(evaluate('ctx.prop1.prop2', obj))
alert(evaluate(
'(function() {' +
' console.log(ctx.prop1.prop2);' +
' return ctx.prop1.prop2;' +
'}) ()', obj))
UPDATE: Answer to original question on how to access properties with prop1.prop2
First of all, you can access your variable using dictionary notation, ie:
obj['prop1']['prop2'] === obj.prop1.prop2
Give me several minutes to come up with example of how to do it recursively
UPDATED:This should work (here is gist):
function jpath_(o, props) {
if (props.length == 1)
return o[props[0]];
return jpath_(o[props.shift()], props)
}
function jpath(o, path) {
return jpath_(o, path.split('.'))
}
console.log(jpath(obj, 'prop1.prop2'))
I have an object, X, and some code that creates a div and assigns id = X.ID. After the html is created, I assign the object to the div, like this:
document.getElementById(X.ID).XValue = X;
If I set a break after that statement, I can evaulate document.getElementById(X.ID).XValue and see all the properties of X.
While I was creating the html, I added onmouseup="MOUSE_UP(event)".
var aProp = {};
aProp.ThisValue = "This";
aProp.ThatValue = "That";
aProp.Id = 5;
var html = '<div id="' + aProp.Id + '"';
var func = 'MOUSE_UP';
html += ' onmouseup="' + func + '(event) ">';
html += '</div>';
document.getElementById("test").innerHTML += html;
document.getElementById(aProp.Id).XVALUE = aProp;
function MOUSE_UP(event) {
alert(event.currentTarget.XValue.ThisValue);
}
Now, when I set a break at MOUSE_UP, event.currentTarget is my div (event.currentTarget.id == X.ID), but event.currentTarget.XValue is undefined.
Why is XValue undefined here when it was defined earlier?
Looks like setting innerHTML of #test would wipe out all custom properties from its children. You can check this in the jsFiddle. When you'll run the fiddle as it is, you'll notice NewProp of #1 will become undefined after adding more content with test.innerHTML += ... If you log tabIndex instead of NewProp, you'll get the correct values.
This happens because += operator is just a shortcut for a statement like a = a + b, which can also be written a += b.
Basicly you create a string from the inner HTML of #test, then add another string to it, and finally replace the original innerHTML of #test with this new string. All previous elements in #test are replaced with new ones, which don't have the custom properties set.
When setting id property for an element, also id attribute is added to the HTML, hence they are a part of innerHTML of #test, and are added to the newly created HTML too.
If you use proper DOM manipulation instead of setting innerHTML, you'll get the results you want. The code below uses createElement() and appendChild() methods instead of setting innerHTML.
function myMouseUp(e) {
alert("at MouseUp " + e.currentTarget.NewProp.ThisValue);
}
function buildOneDiv(aProp) {
var html = document.createElement('div');
aProp.ThisValue = 'This is ' + aProp.id;
aProp.ThatValue = 'That is ' + aProp.id;
html.id = aProp.id;
html.addEventListener('mouseup', myMouseUp, false);
html.innerHTML = 'Test ' + aProp.id;
return html;
}
function buildDivs(x) {
var html = buildOneDiv(x);
document.getElementById("test").appendChild(html);
document.getElementById(x.id).NewProp = x;
}
window.onload = function () {
var aProp, i;
for (i = 1; i < 4; i++) {
aProp = {};
aProp.id = i;
buildDivs(aProp);
}
};
A live demo at jsFiddle.
This is not so much an answer as it is a clarification and a work-around.
Given this html
<div id="test"></div>
and this code
function myMouseUp(e) {
alert("at MouseUp " + e.currentTarget.NewProp.ThisValue);
}
function buildOneDiv(aProp) {
aProp.ThisValue = "This";
aProp.ThatValue = "That";
var html = '<div id="' + aProp.id + '"';
var func = 'myMouseUp';
html += ' onmouseup="' + func + '(event) ">';
html += 'Test ' + aProp.id + '</div>';
return html;
}
function buildDivs(x) {
var html = buildOneDiv(x);
document.getElementById("test").innerHTML += html;
document.getElementById( x.id ).NewProp = x;
}
window.onload = function () {
for (var i = 1; i < 4; i++) {
var aProp = {};
aProp.id = i;
buildDivs(aProp);
}
};
The end result is that only the LAST div whose onmouseup is defined will have a legitimate value for NewProp at myMouseUp. For each other div, this property is undefined. This is why I got some comments indicating that "It does work." It works for ONE, which is all I had in my example. (This is the clarification.)
My workaround is to add a global object to be an associative array and change two statements:
var myDivs = {}; // global
Replace
document.getElementById( x.id ).NewProp = x;
in buildDivs with
myDivs[x.id] = x;
and replace
alert("at MouseUp " + e.currentTarget.NewProp.ThisValue);
in myMouseUp with
alert(myDivs[e.currentTarget.id].ThisValue );.
I'd still like to know why the original approach doesn't work.