I am trying to loop through an array of objects, which depending on their type property, will create a different class and append it to an array. The problem is that the output is always just a list of duplicates of the last class created.
// Create Elements from Content
// The id's are created by UUIDV4 and are all different.
self._elements = new Array
let e;
self.content.page_data.forEach(cont => {
switch (cont.type) {
case 'paragraph':
e = new Paragraph()
console.log(e.element.id)
self._elements.push(e)
break;
case 'title':
console.log('title')
return
}
})
console.log(self._elements)
After troubleshooting I've found that the problem isn't e, as each instance is different, however once it is pushed / added to the array, the problem occurs. The problem only occurs when instances of Paragraph() are created. As other items in the array, like text, will remain the same while still duplicating the last class.
Please can someone explain what I'm missing here?
EDIT - Class for Paragraph
class Paragraph {
constructor(value = '') {
self._element = template_paragraph.cloneNode(true).content.children[0];
const quil = self._element.children[0].children[1].children[0];
self.quill = new Quill(quil, {
modules: {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline'],
[{ list: 'ordered' }, { list: 'bullet' }]
]
},
placeholder: 'Compose an epic...',
theme: 'snow' // or 'bubble'
})
self._element.id = uuidv4()
}
get element() {
return self._element
}
set_content(content) {
// Set quill value
if (!content) return
//self.quill.setContents(content)
}
}
The quill interacts with my html clone as intended. I hope this will help.
The keyword is this in JavaScript. Not self (that's a Python thing.) Since self is not a keyword in JavaScript, some people use it by convention as a normal variable name by manually assigning var self = this; somewhere. But really, I think you just want to say this and use it the normal way.
Replace self with this in your code and you should be good to go.
Allow me to demonstrate a counterexample to your claim. You code seems to work correctly and the problem is elsewhere, most likely your Paragraph class.
By just changing the supporting framework (consisting of self and its content, page_data etc.) and the Paragraph class) I can demonstrate that your code (which I have used verbatim) works correctly, in that each element of self._elements is indeed different (most notably has a different id).
// [[[ MOCKED FRAMEWORK TO DEMONSTRATE THAT YOUR CODE WORKS
let self = { content: { page_data: [
{type:'title'},
{type:'paragraph'},
{type:'paragraph'},
] } };
let nextUnusedId = 101;
let Paragraph = function () { this.element = { id: nextUnusedId++ } }
// ]]]
// Create Elements from Content
// The id's are created by UUIDV4 and are all different.
self._elements = new Array
let e;
self.content.page_data.forEach(cont => {
switch (cont.type) {
case 'paragraph':
e = new Paragraph()
console.log(e.element.id)
self._elements.push(e)
break;
case 'title':
console.log('title')
return
}
})
console.log(self._elements)
Try just using a new variable declared inside the scope of the loop
// Create Elements from Content
// The id's are created by UUIDV4 and are all different.
self._elements = new Array
self.content.page_data.forEach(cont => {
switch (cont.type) {
case 'paragraph':
var e = new Paragraph()
console.log(e.element.id)
self._elements.push(e)
break;
case 'title':
console.log('title')
return
}
})
console.log(self._elements)
From your problem description, it sounds as if there is a single "reference" variable of 'e' and you are just creating an array of that reference over and over again, and whatever the loop last iterated over is what all those references point to
Related
I want to loop a function changing the variable each time it runs until all variables are used. Currently, I have this function duplicated 20+ times changing out the "a_week" for each one.
var a_week = "yes";
var b_week = "no";
var c_week = "yes";
function onload() {
if a_week = "yes" {
document.getElementById("dot").classList.add('open');
}
else if a_week = "no" {
document.getElementById("dot").classList.add('closed');
}
}
I would make an array of your weeks, make your onload function take an argument and then loop through them.
const weeks = [true, false, false, true];
function onload(week) {
if(week) {
document.getElementById("dot").classList.add('open');
} else {
document.getElementById("dot").classList.add('closed');
}
}
weeks.forEach(onload);
You can look at the docs for forEach at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach.
For each distinct week, you may not be intending to change the same HTML element each time. The above code would just keep adding classes to the same #dot element. If you intended to target multiple elements, maybe save an array of elements or their selectors. Or combine that with the data in an object like my old answer has. You didn't share your HTML structure so I'm not sure what the best way to structure the DOM manipulation is.
Also onload is not a great name for a function since it may be assumed to be connected to the browser's onload event.
Answer for old question:
Seems like you should make whatever sa_g_week represents an object and then loop through an array of them (or make an map/object out of them if they need to be referenced by key). I'm not sure what your context is but I'm going to make up some field names and assume you're working with locations.
const locations = [
{
isOpen247: true,
isPermanentlyClosed: false,
openTime: new Date(),
closeTime: new Date(),
},
{
isOpen247: true,
isPermanentlyClosed: true,
openTime: new Date(),
closeTime: new Date(),
},
];
You could then loop through the array and do what you need to:
locations.forEach(location => {
if(location.isOpen247) {
// do stuff
} else {
// do other stuff
}
});
Using Mithril, a Javascript framework, I am trying to add new elements after the initial body has been created and rendered.
Here is my problem in it's most basic form:
let divArray = [];
let newDivButton = m('button', { onclick: ()=> {
divArray.push(m('div', "NEW DIV PUSHED"));
}}, "Add new div");
divArray.push(newDivButton);
divArray.push(m('div', "NEW DIV PUSHED"));
let mainDiv = {
view: () => {
return divArray;
}
}
m.mount(document.body, mainDiv);
The code above will create a button and one line of text saying NEW DIV PUSHED. The button adds one new text element exactly like the 1st one. The problem here is that those additional elements are simply not rendered even if the view function is called. I stepped in the code and clearly see that my divArray is being populated even if they are not rendered.
One thing I noticed is that the initial text element (the one that is rendered) has it's dom property populated by actual div object. All the subsequent text elements in my array have their dom property set to undefined. I don't know how to fix this but I am convinced it is related to my problem.
Mithril has an optimization built into the render lifecycle - it won't re-render a DOM tree if the tree is identical to the last tree. Since divArray === divArray is always true the nodes are never re-rendering.
The simple, but non-ideal solution is to slice your array so you're always returning a new array from mainDiv#view and therefore, Mithril will always re-render the top-level array:
let mainDiv = {
view: () => {
return divArray.slice();
}
};
The more correct way to do this is to map over the data, creating vnodes at the view layer, rather than keeping a list of vnodes statically in your module scope:
let data = ["Data Available Here"];
let mainDiv = {
view: () => {
return [
m(
'button',
{ onclick: () => data.push("Data Pushed Here") },
"Add new div"
);
].concat(
data.map(datum => m('div', datum))
);
}
};
JS-Interpreter is a somewhat well-known JavaScript Interpreter. It has security advantages in that it can completely isolate your code from document and allows you to detect attacks such as infinite loops and memory bombs. This allows you to run externally defined code safely.
I have an object, say o like this:
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
I'd like to be able to run the code in process through JS-Interpreter:
for (let i = 0; i < o.process.length; i++)
interpretWithinContext(o, o.process[i]);
Where interpretWithinContext will create an interpreter using the first argument as the context, i.e. o becomes this, and the second argument is the line of code to run. After running the above code, I would expect o to be:
{
hidden: false,
regex: /^[a-z]+$/i,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: '^[a-z]+$'
}
That is, hidden and regex are now set.
Does anyone know if this is possible in JS-Interpreter?
I’ve spent a while messing around with the JS-Interpreter now, trying to figure out from the source how to place an object into the interpreter’s scope that can be both read and modified.
Unfortunately, the way this library is built, all the useful internal things are minified so we cannot really utilize the internal things and just put an object inside. Attempts to add a proxy object also failed failed since the object just wasn’t used in a “normal” way.
So my original approach to this was to just fall back to providing simple utility functions to access the outside object. This is fully supported by the library and probably the safest way of interacting with it. It does require you to change the process code though, in order to use those functions. But as a benefit, it does provide a very clean interface to communicate with “the outside world”. You can find the solution for this in the following hidden snippet:
function createInterpreter (dataObj) {
function initialize (intp, scope) {
intp.setProperty(scope, 'get', intp.createNativeFunction(function (prop) {
return intp.nativeToPseudo(dataObj[prop]);
}), intp.READONLY_DESCRIPTOR);
intp.setProperty(scope, 'set', intp.createNativeFunction(function (prop, value) {
dataObj[prop] = intp.pseudoToNative(value);
}), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"set('hidden', !get('visible'));",
"set('regex', new RegExp(get('validate'), 'i'));"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
However, after posting above solution, I just couldn’t stop thinking about this, so I dug deeper. As I learned, the methods getProperty and setProperty are not just used to set up the initial sandbox scope, but also as the code is being interpreted. So we can use this to create a proxy-like behavior for our object.
My solution here is based on code I found in an issue comment about doing this by modifying the Interpreter type. Unfortunately, the code is written in CoffeeScript and also based on some older versions, so we cannot use it exactly as it is. There’s also still the problem of the internals being minified, which we’ll get to in a moment.
The overall idea is to introduce a “connected object” into the scope which we will handle as a special case inside the getProperty and setProperty to map to our actual object.
But for that, we need to overwrite those two methods which is a problem because they are minified and received different internal names. Fortunately, the end of the source contains the following:
// Preserve top-level API functions from being pruned/renamed by JS compilers.
// …
Interpreter.prototype['getProperty'] = Interpreter.prototype.getProperty;
Interpreter.prototype['setProperty'] = Interpreter.prototype.setProperty;
So even if a minifier mangles the names on the right, it won’t touch the ones on the left. So that’s how the author made particular functions available for public use. But we want to overwrite them, so we cannot just overwrite the friendly names, we also need to replace the minified copies! But since we have a way to access the functions, we can also search for any other copy of them with a mangled name.
So that’s what I’m doing in my solution at the beginning in patchInterpreter: Define the new methods we’ll overwrite the existing ones with. Then, look for all the names (mangled or not) that refer to those functions, and replace them all with the new definition.
In the end, after patching the Interpreter, we just need to add a connected object into the scope. We cannot use the name this since that’s already used, but we can just choose something else, for example o:
function patchInterpreter (Interpreter) {
const originalGetProperty = Interpreter.prototype.getProperty;
const originalSetProperty = Interpreter.prototype.setProperty;
function newGetProperty(obj, name) {
if (obj == null || !obj._connected) {
return originalGetProperty.call(this, obj, name);
}
const value = obj._connected[name];
if (typeof value === 'object') {
// if the value is an object itself, create another connected object
return this.createConnectedObject(value);
}
return value;
}
function newSetProperty(obj, name, value, opt_descriptor) {
if (obj == null || !obj._connected) {
return originalSetProperty.call(this, obj, name, value, opt_descriptor);
}
obj._connected[name] = this.pseudoToNative(value);
}
let getKeys = [];
let setKeys = [];
for (const key of Object.keys(Interpreter.prototype)) {
if (Interpreter.prototype[key] === originalGetProperty) {
getKeys.push(key);
}
if (Interpreter.prototype[key] === originalSetProperty) {
setKeys.push(key);
}
}
for (const key of getKeys) {
Interpreter.prototype[key] = newGetProperty;
}
for (const key of setKeys) {
Interpreter.prototype[key] = newSetProperty;
}
Interpreter.prototype.createConnectedObject = function (obj) {
const connectedObject = this.createObject(this.OBJECT);
connectedObject._connected = obj;
return connectedObject;
};
}
patchInterpreter(Interpreter);
// actual application code
function createInterpreter (dataObj) {
function initialize (intp, scope) {
// add a connected object for `dataObj`
intp.setProperty(scope, 'o', intp.createConnectedObject(dataObj), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"o.hidden = !o.visible;",
"o.regex = new RegExp(o.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
And that’s it! Note that while that new implementation does already work with nested objects, it may not work with every type. So you should probably be careful what kind of objects you pass into the sandbox. It’s probably a good idea to create separate and explicitly safe objects with only basic or primitive types.
Have not tried JS-Interpreter. You can use new Function() and Function.prototype.call() to achieve requirement
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
for (let i = 0; i < o.process.length; i++)
console.log(new Function(`return ${o.process[i]}`).call(o));
Hi may be interpretWithinContext look like something like that ?
let interpretWithinContext = (function(o, p){
//in dunno for what you use p because all is on object o
o.hidden = (o.hidden === null) ? false : o.hidden;
o.regex = (o.regex === null) ? '/^[a-z]+$/i' : o.regex;
console.log(o);
return o;
});
https://codepen.io/anon/pen/oGwyra?editors=1111
I am creating a form and I am trying to find a simple, elegant way of handling to see if all inputs exist.
Form = Ember.Object.extend({
// section 1
name: null,
age: null,
isABoolean: null,
// section 2
job: null,
numberOfSiblings: null,
isComplete: Ember.computed.and('_isSection1Complete', '_isSection2Complete'),
_isSection1Complete: function() {
var isPresent = Ember.isPresent;
return isPresent(this.get('name')) && isPresent(this.get('age')) && isPresent(this.get('isABoolean'));
}.property('name', 'age', 'isABoolean'),
_isSection2Complete: function() {
var isPresent = Ember.isPresent;
return isPresent(this.get('job')) && isPresent(this.get('numberOfSiblings'));
}.property('job', 'numberOfSiblings')
});
However, this doesn't seem to scale. My actual application will have many sections (over 20 sections).
I am looking into trying to create a re-usable computed property that fits my needs. Take for example the code of what I am going for:
Form = Ember.Object.extend({
// properties...
isComplete: Ember.computed.and('_isSection1Complete', '_isSection2Complete'),
_isSection1Complete: Ember.computed.allPresent('name', 'age', 'isABoolean'),
_isSection2Complete: Ember.computed.allPresent('job', 'numberOfSiblings')
});
I feel that this is a common case, but I'm failing to find the correct computed properties on how to execute this, so I would like to make my own.
Two questions:
Where's the best place to define the custom computed property? Can I just attach a function to Ember.computed?
Is there an easier way to solve this? I feel like I'm overlooking something simple.
As for Question #1,
You can define a custom computed helper in the App namespace. In this example, I created a new computed helper called allPresent that checks each property passed in against Ember.isPresent.
App.computed = {
allPresent: function (propertyNames) {
// copy the array
var computedArgs = propertyNames.slice(0);
computedArgs.push(function () {
return propertyNames.map(function (propertyName) {
// get the value for each property name
return this.get(propertyName);
}, this).every(Ember.isPresent);
});
return Ember.computed.apply(Ember.computed, computedArgs);
}
};
It can be used like this, per your example code:
_isSection2Complete: App.computed.allPresent(['job', 'numberOfSiblings'])
I adapted this from the approach here: http://robots.thoughtbot.com/custom-ember-computed-properties
As for Question #2, I can't think of a simpler solution.
I had to make a minor adjustment to Evan's solution, but this works perfectly for anyone else that needs it:
App.computed = {
allPresent: function () {
var propertyNames = Array.prototype.slice.call(arguments, 0);
var computedArgs = propertyNames.slice(0); // copy the array
computedArgs.push(function () {
return propertyNames.map(function (propertyName) {
// get the value for each property name
return this.get(propertyName);
}, this).every(Ember.isPresent);
});
return Ember.computed.apply(Ember.computed, computedArgs);
}
};
This can now be used as such:
_isSection2Complete: App.computed.allPresent('job', 'numberOfSiblings')
I have a class called "Game" and inside it, there's a property called "inventory" which invokes the "Inventory(id)" function where all the inventory functions are created and housed to clear clutter.
I've created a new object
var Game = new Game(body, mainElement, inventoryItems, statusElement);
(it should be self explanitory, the body is selecting the body tag, main element is my main area, status is a status element, and of course inventoryItems is the <ul> I'm using to append my items into.) - you can check the codepen for a better understanding.
The main code you need to help me
function Game(body, mainElement, inventoryItems, statusElement) {
this.body = body;
this.main = mainElement;
this.inventory = Inventory(inventoryItems);
this.status = statusElement;
}
function Inventory(y) {
this.element = y;
this.create = function(itemName, equippable, sellable) {
this.element.append(function(){
var item = '';
if (itemName.length > 0) {
item += "<li>" + itemName;
if (sellable) {
item += "<label>Sell<input type='checkbox' name='sell' onclick='javacript: confirmSell(this);' /></label>";
}
if (equippable) {
item += "<label>Equip<input type='checkbox' name='equip' onclick='javacript: equip(this);' /></label>";
}
}
return item;
}, this.element);
}
}
var Game = new Game(body, mainElement, inventoryItems, statusElement);
Game.inventory(create("Bronze Knife", true, true));
Game.inventory(create("Ramen Noodles", false, true));
Game.inventory(create("Boots w/da Fur", true, true));
Now, I get funny errors when I try calling the inventory(create(string, bool, bool));
It creates the first item, so at least I know "something" is going "somewhat" correctly, or I could be entirely wrong and deserve to just shut my computer down.
In chrome I'm told the Bronze Knife inventory.create is telling me undefined is not a function.
Any help is GREATLY appreciated
EDIT: link
Game.inventory is object (reference to Inventory object (add new before Inventory)) which contain method create, so you can call this method like this
.....
this.inventory = new Inventory(inventoryItems);
.....
var Game = new Game(body, mainElement, inventoryItems, statusElement);
Game.inventory.create("Bronze Knife", true, true);
Game.inventory.create("Ramen Noodles", false, true);
Game.inventory.create("Boots w/da Fur", true, true);
Demo: http://codepen.io/tarasyuk/pen/yyJGJK
Game is a function, and you're defining a variable named Game.
Try to rename var Game = new Game(...); to var game = new Game(...);
Use new when creating an inventory. Use dot notation to access inventory's methods, as they are properties of the inventory object.
// Relevant changes shown:
function Game(body, mainElement, inventoryItems, statusElement) {
this.inventory = new Inventory(inventoryItems);
}
Game.inventory.create("Bronze Knife", true, true);
In the future, you may want to try running your code through a linter like JSLint. Just turn off the "messy white space" option or add /*jslint white: true */ to the top of your file, and it should give you useful feedback on how to solve this on your own.
I ran your code through JSLint and the first thing I noticed was "'Inventory' was used before it was defined." So I moved Inventory above Game. I ran JSLint again and it reported "Missing 'new'." Finally, I looked down through the report and found "'create' was used before it was defined.". Those two points of data could have hinted you in the right direction.