My Closure Won't Work - javascript

I'm trying to prevent polluting the global space with a bunch of variables, and since I can't use let yet, I have to make closures. So I have this basic webpage with a bootstrap accordion, each card hiding different examples. In one I have a form that asks in a <select> what your position is. OnChange it will grab the eventData object, call spillObject() (the closure method), and populate another accordion card with its contents. It works, but problem is, I can't seem to make it work as a simple closure. Nothing seems to happen, and since you can't debug a closure, other than making it spit out console.logs() everywhere, I can't find out what's wrong with it.
Here's the code:
$(function() {
$("#position").change( /*(*/ function(eventData) {
var div = $('#explodedObject');
div.html('');
var result = spillObject('#explodedObject', eventData, '');
/*};*/
div.append(result);
} /*)()*/ );
var spillObject = (function(dataParent, obj, heirarchy) {
var heirArr = heirarchy == '' ? [] : heirarchy.split('_');
heirArr.push(1);
var teir = heirArr.length - 1;
var id = "#collapse" + heirArr.join('');
var headerID = 'header' + heirArr.join('');
var card = document.createElement('div');
card.classList.add('card');
var cardHeader = document.createElement('div');
cardHeader.classList.add('card-header');
cardHeader.id = headerID;
var h5 = document.createElement('h5');
h5.classList.add('mb-0');
var button = document.createElement('button');
button.classList.add('btn', 'btn-link');
button.setAttribute('data-toggle', 'collapse');
button.setAttribute('data-target', id);
var cardBody = document.createElement('div');
cardBody.classList.add('card-body');
var collapse = document.createElement('div');
collapse.id = id.substr(1, id.length - 1);
collapse.classList.add('collapse');
collapse.setAttribute('data-parent', dataParent);
var dl = document.createElement('dl');
dl.id = '#' + heirArr.join('');
var dt;
var dd;
var x;
return function() {
for (x in obj) {
dt = document.createElement('dt');
dd = document.createElement('dd');
dt.innerHTML = x;
if (typeof obj[x] == 'object' && heirArr.length < 3) {
heirArr[teir]++;
innerObj = spillObject(dl.id, obj[x], heirArr.join('_'));
dd.appendChild(innerObj);
} else {
dd.innerHTML = obj[x];
}
dl.append(dt);
dl.append(dd);
}
heirArr.pop();
heirArr[heirArr.length - 1] = parseInt(heirArr[heirArr.length - 1]);
heirArr[heirArr.length - 1]--;
collapse.appendChild(cardBody);
button.innerHTML = 'card ' + heirArr.join('.');
h5.appendChild(button);
cardHeader.appendChild(h5);
card.appendChild(cardHeader);
card.appendChild(collapse);
cardBody.appendChild(dl);
return card;
};
});
})();
More basically, I'm following this template:
var method = (function(param){
var var1 = 'default value';
return function(){
var1 = 'something else';
};
})();
Should I have used this one instead, and if so would it hide the variables?
var method2 = function(param) {
return function() {
var var1 = 'default value';
};
};

It's because the result of spillObject is a function, you must then also call that function. Here's a simplified version of what you are doing.
var spillObject = (function(dataParent, obj, heirarchy) {
console.log('first function')
return function() {
console.log('second function')
};
});
var result = spillObject()
console.log(result)
result()

Related

How do I get elements that were created using the DOM instead of HTML?

I tried to use document.getElementsByClassName, but when I did that it gave me a collection of elements like this:
HTMLCollection(2)
0: button#1.remove
1: button#2.remove
length: 2
2: <unreadable>
__proto__: HTMLCollection
When I tried to iterate through it there was nothing in the HTML Collection. I also tried to find the length of the HTML Collection, but it said it was 0. Also these buttons were created using JavaScript. The buttons created using HTML I am able to iterate through.
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('submit-note').addEventListener('click', add, false);
document.getElementById('clear').addEventListener('click', clear, false);
document.getElementById('copy').addEventListener('click', copy, false);
updateNotes();
chrome.storage.onChanged.addListener(updateNotes);
document.addEventListener('keydown', function(event) {
if (event.keyCode == 13) {
add();
}
});
var elements = document.getElementsByClassName('remove');
console.log(elements);
console.log(elements.length);
for (item of elements) {
console.log(item);
}
})
function updateNotes() {
document.getElementById("note-container").innerHTML = "";
chrome.storage.local.get(['notes'], function (result) {
var curNotes = result.notes;
console.log(curNotes)
if (curNotes) {
for (var i = 0; i < curNotes.length; i++) {
var parsedDate = new Date(JSON.parse(curNotes[i].date))
var hour = parsedDate.getHours().toString() + ":" + parsedDate.getMinutes() + "am";
if (parsedDate.getHours() > 12) {
hour = parsedDate.getHours() - 12;
hour = hour.toString() + ":" + parsedDate.getMinutes() + "pm";
}
var greatNote = document.createElement("div")
var fullNote = document.createElement("div")
var deleteContianer = document.createElement("div");
var para = document.createElement("p");
var node = document.createTextNode(curNotes[i].note);
para.appendChild(node);
var date = document.createElement("p");
var dateNode = document.createTextNode(`${parsedDate.getMonth() + 1}/${parsedDate.getDate()}/${parsedDate.getFullYear()} - ${hour}`);
date.appendChild(dateNode);
fullNote.appendChild(para);
fullNote.appendChild(date);
var remove = document.createElement("button")
remove.innerHTML = "D";
remove.id = curNotes[i].id.toString();
deleteContianer.appendChild(remove);
var element = document.getElementById("note-container");
greatNote.appendChild(fullNote);
greatNote.appendChild(deleteContianer);
element.appendChild(greatNote);
para.classList.add('note-text');
date.classList.add('date-text');
fullNote.classList.add('full-note');
remove.classList.add('remove');
greatNote.classList.add('great-note')
}
}
})
}
That was my first comment:
When you call updateNotes() It is calling that asynchronous function and it is its callback that creates the .remove class elements you look for. So when calling console.log(elements.length), a couple lines after... The async callback has not executed yet.
What I forgot to tell is to add new Promise() somewhere.
So you have to add await in front of updateNotes() because that is where you need to wait. But it has to be a promise, else it is considered an already resolved promise... And won't wait. See await documentation.
So here is what I would try (I skipped a couple lines for clarity):
async function functionName (){
// ...
await updateNotes();
// ...
}
async function updateNotes() {
return new Promise( resolve =>{
document.getElementById("note-container").innerHTML = "";
await chrome.storage.local.get(['notes'], function (result) {
// ...
}
})
}

How to make the same button swap text each time it is clicked?

If I have a paragraph
<p id=peed></p>
and I have a button which on click runs this function:
<script>
var x = 'welcome'
function onButtonClick() {
document.getElementById('peep').innerHTML = [x];
}
</script>
This swaps the peep paragraph with variable x.
How, using if statements, would I make the same button when clicked a second time reverse this an replace x with the paragraph peep again?
Function like that?
<script>
function myFunction()
{
var tmp = document.getElementById("peep").innerHTML;
if(tmp === 'x') {
document.getElementById("peep").innerHTML = 'y';
}
else {
document.getElementById("peep").innerHTML = 'x';
}
}
</script>
With ES6, you could use the destructuring assignment for swapping the variable and the actual text.
var a = 5, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 5
Implementation
var x = 'welcome';
function peep() {
[document.getElementById("peep").innerHTML, x] = [x, document.getElementById("peep").innerHTML];
}
<button onclick="peep()">peep</button>
<div id="peep">some text</div>
You first need to get the original content:
var originalPeep = document.getElementById("peep").innerHTML;
You need to have a variable to store whether it has been replaced or not:
var replaced = false;
Now in your function, you need to check the value of replaced and replace it accordingly (and reset our replaced marker):
function myFunction() {
if (replaced) {
document.getElementById("peep").innerHTML = originalPeep;
replaced = false;
} else {
document.getElementById("peep").innerHTML = x;
replaced = true;
}
}
So in total that becomes:
var originalPeep = document.getElementById("peep").innerHTML;
var replaced = false;
function myFunction() {
if (replaced) {
document.getElementById("peep").innerHTML = originalPeep;
replaced = false;
} else {
document.getElementById("peep").innerHTML = x;
replaced = true;
}
}
Swapping values is tricky: you need to store the current value in a temporary variable, replace the current value with the new one, then assign the temporary value to the variable holding new one.
document.querySelector("button").onclick = myFunction;
var x = 'welcome';
function myFunction()
{
var peep = document.getElementById("peep");
var tmp = peep.innerHTML;
peep.innerHTML = x;
x = tmp;
}
<div id="peep">peep</div>
<button>swap</button>
var x = 'welcome'
var peeepText = '';
function myFunction() {
if (!peeepText) {
peeepText = document.getElementById("peep").innerHTML;
document.getElementById('peep').innerHTML = [x];
} else {
document.getElementById('peep').innerHTML = peepText;
peepText = '';
}
}
You could try doing this with an array an count as well.
var x = [];
x[0] = 'welcome';
x[1] = 'googbye';
var count = 1;
function myFunction()
{
document.getElementById('peep').innerHTML = x[count%2];
count++;
}

Javascript Callback in for Loop

My problem is that I'm having a Function A which calls at one point another function, let's call it Function B (getChildContent) and needs the return value of Function B in order to proceed. I know that it's because of Javascripts Asynchronous Nature, and i tried to solve it with a callback. But i can't get it work properly.
FunctionA(){
//some Code.....
else {
for(i in clustertitles) {
if(S(text).contains(clustertitles[i])) {
var parent = {};
parent.ClusterName = clustertitles[i];
parent.Functions = [];
var str = '== ' + clustertitles[i] + ' ==\n* ';
str = S(text).between(str,'.').s;
var caps = parseFunctions(str);
for(y in caps) {
//var content = getChildContent(caps[y]);
getChildContent(caps[y], function(content) { //Function call
var child = {};
child.FunctionName = caps[y];
child.Content = [];
child.Content.push(content);
parent.Functions.push(child);
console.log(content);
});
}}}
}
function getChildContent (capname, callback) {
t = capname.replace(' ', '_');
bot.page(t).complete(function (title, text, date) {
var str = S(text).between('== Kurzbeschreibung ==\n* ', '.').s;
if(str === undefined || str === null || str === '') {
throw new Error('Undefined, Null or Empty!');
}
else {
var content = {};
str = parseTitles(str);
content.Owner = str[0];
content.Aim = str[1];
content.What = str[2];
content.Who = str[3];
content.Steps = str[4];
content.Page = 'some URL';
callback(content);
}
});
}
So in Function A I'm trying to call getChildContent from a for-Loop and pass the current string from caps-array. For each String in caps-array getChildContent() makes a http request over a node.js module and retrieves a string. With this string i'm building an object (content) which is needed in Function A to continue. However the 'console.log(content)' in Function A just prints out the object which is created with the last string in caps-array, but for many times. E.G. if caps-array has 5 entries, i get 5 times the object which is created with the last entry of caps-array.
How can i manage the loop/callback to get every time the right object on my console?
Your loop should call another function that preserves the value of y, something like this:
FunctionA(){
//some Code.....
else {
for(i in clustertitles) {
if(S(text).contains(clustertitles[i])) {
var parent = {};
parent.ClusterName = clustertitles[i];
parent.Functions = [];
var str = '== ' + clustertitles[i] + ' ==\n* ';
str = S(text).between(str,'.').s;
var caps = parseFunctions(str);
for(y in caps) {
yourNewFunction (y, caps, parent);
}}}
}
function yourNewFunction (y, caps, parent) {
getChildContent(caps[y], function(content) { //Function call
var child = {};
child.FunctionName = caps[y];
child.Content = [];
child.Content.push(content);
parent.Functions.push(child);
console.log(content);
});
}
function getChildContent (capname, callback) {
t = capname.replace(' ', '_');
bot.page(t).complete(function (title, text, date) {
var str = S(text).between('== Kurzbeschreibung ==\n* ', '.').s;
if(str === undefined || str === null || str === '') {
throw new Error('Undefined, Null or Empty!');
}
else {
var content = {};
str = parseTitles(str);
content.Owner = str[0];
content.Aim = str[1];
content.What = str[2];
content.Who = str[3];
content.Steps = str[4];
content.Page = 'some URL';
callback(content);
}
});
}
There are 2 ways to do so.
Put the loop inside a function, execute your callback after the loop is done. (Problematic if you are doing async call inside the loop.
function doLoopdiloopStuff() {
for() {
}
callback();
}
The other way, the way i prefer looks like this:
for(var i = 0; i < stuff || function(){ /* here's the callback */ }(), false; i++) {
/* do your loop-di-loop */
}
In another example:
for (var index = 0; index < caps.length || function(){ callbackFunction(); /* This is the callback you are calling */ return false;}(); index++) {
var element = caps[index];
// here comes the code of what you want to do with a single element
}

How can I refresh or load JSON to my viewModel on Knockout JS with complex models

I fork the code from here:
http://kindohm.github.io/knockout-query-builder/
The code works nice on the client side.
But when I try to save the viewModel as JSON and then retrieve the data from the server the UI never refresh at all.
This is the original viewModel:
window.QueryBuilder = (function(exports, ko){
var Group = exports.Group;
function ViewModel() {
var self = this;
self.group = ko.observable(new Group());
// the text() function is just an example to show output
self.text = ko.computed(function(){
return self.group().text();
});
}
exports.ViewModel = ViewModel;
return exports;
})(window.QueryBuilder || {}, window.ko);
I be added the next method to the viewModel
self.Save = function () {
console.log(ko.toJSON(self));
}
Added this button to the view
<input type="submit" value="Save" data-bind="click: Save"/>
This is the Group viewModel:
window.QueryBuilder = (function(exports, ko){
var Condition = exports.Condition;
function Group(data){
var self = this;
self.templateName = data.templateName;
self.children = ko.observableArray(data.children);
self.logicalOperators = ko.observableArray(data.logicalOperators);
self.selectedLogicalOperator = ko.observable(data.selectedLogicalOperator);
// give the group a single default condition
self.children.push(new Condition());
self.addCondition = function(){
self.children.push(new Condition());
};
self.addGroup = function(){
self.children.push(new Group());
};
self.removeChild = function(child){
self.children.remove(child);
};
// the text() function is just an example to show output
self.text = ko.computed(function(){
var result = '(';
var op = '';
for (var i = 0; i < self.children().length; i++){
var child = self.children()[i];
console.log(child);
result += op + child.text();
op = ' ' + self.selectedLogicalOperator() + ' ';
}
return result += ')';
});
}
exports.Group = Group;
return exports;
})(window.QueryBuilder || {}, window.ko);
So when I press the "save" button the console show the JSON from this viewModel, everything fine here.
This is the JSON returned:
{"group":{"templateName":"group-template","children":[{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"}],"logicalOperators":["AND","OR"],"selectedLogicalOperator":"AND","text":"(Points = 0 AND Points = 0 AND Points = 0)"},"text":"(Points = 0 AND Points = 0 AND Points = 0)"}
I make a simple hack to avoid the connection to the server, so I take that json copy and paste on the load event and send to the constructor of the viewModel:
var vm;
window.addEventListener('load', function(){
var json = {"group":{"templateName":"group-template","children":[{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"}],"logicalOperators":["AND","OR"],"selectedLogicalOperator":"AND","text":"(Points = 0 AND Points = 0 AND Points = 0)"},"text":"(Points = 0 AND Points = 0 AND Points = 0)"};
vm = new QueryBuilder.ViewModel(json);
ko.applyBindings(vm);
}, true);
Then I modify the viewModel to recibe the json parameter
window.QueryBuilder = (function(exports, ko){
var Group = exports.Group;
function ViewModel(json) {
var self = this;
self.group = ko.observable(json.group);
// the text() function is just an example to show output
self.text = ko.computed(function(){
return self.group().text();
});
self.Save = function () {
console.log(ko.toJSON(self));
}
}
exports.ViewModel = ViewModel;
return exports;
})(window.QueryBuilder || {}, window.ko);
When I refresh the index.html the view is never loaded correctly and show this error on the JS console:
TypeError: self.group(...).text is not a function
return self.group().text();
Someone knows where is my mistake?
The last problem I had was related to the text() function on the child.
I fix this with the use of try/catch. So when the viewModel are new it have the text() function, but when this is loadad the text() does not exist, so I take the value directly from the "text" field.
try {
result += op + child.text();
}
catch(err) {
result += op + child.text;
}
The problem was on the Group class and Condition class.
This is the current and working code:
window.QueryBuilder = (function(exports, ko){
var Condition = exports.Condition;
function Group(data){
var self = this;
self.templateName = data.templateName;
self.children = ko.observableArray(data.children);
self.logicalOperators = ko.observableArray(data.logicalOperators);
self.selectedLogicalOperator = ko.observable(data.selectedLogicalOperator);
// give the group a single default condition
self.children.push(new Condition(data.children[0]));
self.addCondition = function(){
self.children.push(new Condition());
};
self.addGroup = function(){
self.children.push(new Group());
};
self.removeChild = function(child){
self.children.remove(child);
};
// the text() function is just an example to show output
self.text = ko.computed(function(){
var result = '(';
var op = '';
for (var i = 0; i < self.children().length; i++){
var child = self.children()[i];
try {
result += op + child.text();
}
catch(err) {
result += op + child.text;
}
op = ' ' + self.selectedLogicalOperator() + ' ';
}
return result += ')';
});
}
exports.Group = Group;
return exports;
})(window.QueryBuilder || {}, window.ko);
window.QueryBuilder = (function(exports, ko){
function Condition(data){
var self = this;
self.templateName = data.templateName;
self.fields = ko.observableArray(data.fields);
self.selectedField = ko.observable(data.selectedField);
self.comparisons = ko.observableArray(data.comparisons);
self.selectedComparison = ko.observable(data.selectedComparison);
self.value = ko.observable(data.value);
// the text() function is just an example to show output
self.text = ko.computed(function(){
return self.selectedField() +
' ' +
self.selectedComparison() +
' ' +
self.value();
});
}
exports.Condition = Condition;
return exports;
})(window.QueryBuilder || {}, window.ko);
Instead of self.group = ko.observable(json.group);, you should take a similar approach as you did on load self.group = ko.observable(new Group());, but this time pass the json.group data in Group
self.group = ko.observable(new Group(json.group));
I don't see where Group is defined, but you should make sure that it is able to handle and convert the JSON you now pass in, into observables.

Javascript and module pattern

i think i did not understand javascript module pattern.
I just create this module:
var mycompany = {};
mycompany.mymodule = (function() {
var my = {};
var count = 0;
my.init = function(value) {
_setCount(value);
}
// private functions
var _setCount = function(newValue) {
count = newValue;
}
var _getCount = function() {
return count;
}
my.incrementCount = function() {
_setCount(_getCount() + 1);
}
my.degreeseCount = function() {
_setCount(_getCount() - 1);
}
my.status = function() {
return count;
}
return my;
})();
var a = mycompany.mymodule;
var b = mycompany.mymodule;
console.debug(a, 'A at beginning');
console.debug(a, 'B at beginning');
a.init(5);
b.init(2);
console.log('A: ' + a.status()); // return 2 (wtf!)
console.log('B: ' + b.status()); // return 2`
Where is the mistake?
I thought that my code would have returned to me not 2 value, but 5.
What's the reason?
a and b are the exact same objects.
var a = mycompany.mymodule;
var b = mycompany.mymodule;
What you want to do is create two different objects which have the same prototype. Something similar to this:
mycompany.mymodule = (function () {
var my = function () {};
my.prototype.init = function (value) {
_setCount(value);
};
my.prototype.incrementCount = ...
// ...
return my;
}());
a = new mycompany.mymodule();
b = new mycompany.mymodule();
a.init(5);
b.init(2);
For more info, research "javascript prototypal inheritance"
In JavaScript, objects are passed by reference, not copied.
To explain further, here is a simplified version of your code:
var pkg = (function () {
var x = {};
return x;
}());
var a = pkg;
var b = pkg;
You do not create two separate objects but only reference the object pointed at by pkg from both a and b. a and b are exactly the same.
a === b // true
This means that calling a method on a you are ultimately doing the same to b (it points to the same object—x.)
You don't want to use the module pattern for this. You want the usual constructor+prototype.
function Pkg() {
this.count = 0;
};
Pkg.prototype.init = function (count) { this.count = count; };
var a = new Pkg();
var b = new Pkg();
a === b // false
a.init(2);
a.count === 2 // true
b.count === 2 // false
Here is a good read about module pattern.

Categories

Resources