How to connect a button function to an object property? - javascript

This is the simplified code I'm working with. Is there a better way to find the correct bool?
I'm a beginner so I dont understand keyword "this" completely, but is there a way to connect button with a coresponding object maybe with "this" or something simmilar?
arr = [
{
name: "obj1",
bool: false,
},
{
name: "obj2",
bool: false,
}
];
function buildButtons() {
for (var i = 0; i < arr.length; i++) {
let button = document.createElement("button");
button.innerHTML = arr[i].name;
button.onclick = function() {
// is there a better way to find the correct bool?
for (i = 0; i < arr.length; i++) {
if (this.innerHTML === arr[i].name) {
arr[i].bool = true;
}
}
};
window.document.body.appendChild(button);
}
}
buildButtons();

Your best bet is to change the data structure to a more appropriate one. Rather than having to loop through an array to find the correct name, just use the names as keys in a single object, so you can look them up directly. Altered code follows:
obj = { obj1: false, obj2: false}
function buildButtons() {
for (let name in obj) {
let button = document.createElement("button");
button.innerHTML = name;
button.onclick = function() {
obj[name] = true;
}
window.document.body.appendChild(button);
}
}
buildButtons();
Note that I've used let rather than var for the loop variable - this is necessary (in your version too) for the event handler functions to "capture" the correct value of the loop variable. See here for a very thorough explanation of this.

Related

template strings inside if statements

is it correct syntax to use string literals inside if statements. please guide. how can i use categ dynamically in the function based on different values so that i dont have to write the same function for every button clicked.
const filterButtons= document.querySelectorAll(".filter-btn")
let categ;
filterButtons.forEach(function (btn) {
btn.addEventListener("click",function (e) {
if(e.currentTarget.dataset.id=="price") {
categ = "price";
console.log(categ);
}
if(e.currentTarget.dataset.id=="discountPercentage") {
categ = "discountPercentage";
console.log(categ);
}
if(e.currentTarget.dataset.id=="rating") {
categ = "rating";
console.log(categ);
}
let newModData = [...mainProducts];
let i=0;
let j=0;
let fixed = 0
while(j< newModData.length-1) {
while(i< newModData.length-1)
{
if(`newModData[i+1].${categ}`< `newModData[fixed].${categ}`)
{
const temp = Object.assign({}, newModData[fixed]);
newModData[fixed] = newModData[i+1];
newModData[i+1] = temp;
}
i++;
}
i=j+1;
fixed++;
j++;
}
displayNew(newModData);
})
is it correct syntax to use string literals inside if statements. please guide. how can i use categ dynamically in the function based on different values so that i dont have to write the same function for every button clicked.
If newModData is an array of objects, then the correct syntax is:
if(newModData[i+1][categ] < newModData[fixed][categ]) {
// Your logic here
}
To access properties of an object dynamically you must use brackets [] instead of dot .:
const prop = "name"; //
const someObj = {
name: "Jane",
age: 20
}
console.log(someObj[prop]); // --> Jane

Problems with scope

I am trying to assign a handler to every child element that I loop over. My problem is that the: target = child.items[i].id; will have the id of the last element that I loop over. So this part:
fn: function() {
isoNS.injectKey(target);
}
will always have the the id (target) of the last child. How can I do this?
I have tried put this in front, like this: isoNS.injectKey(this.target);
var arr=[];
for(var i = 0; i < obj.items.length; ++i) {
target = child.items[i].id;
arr.push({
key: sHolder.charAt(i),
fn: function() {
isoNS.injectKey(target);
},
});
}
So my main problem, is that each different value of: target = child.items[i].id; is overwritten with the latest element each time. I hope I am making myself understood.
In case you are wondering what obj and child is... I left them out to make the code shorter and more understandable. just know that they do have values in them, and are never null
You could do this
var arr = Array.prototype.map.call(obj.items, function(obj, i) {
return {
key: sHolder.charAt(i),
fn: function() {
isoNS.injectKey(child.items[i].id);
}
};
});
the Array map function provides the closure, and a nicer way to build up your arr
I use Array.prototype.map.call because there's no indication if obj.items is a TRUE array ... if it is, then it's a bit simpler
var arr = obj.items.map(function(obj, i) {
return {
key: sHolder.charAt(i),
fn: function() {
isoNS.injectKey(child.items[i].id);
}
};
});
The problem is your function is a closure and it has captured a reference to the target variable and this gets changed by your loop before the call back is invoked. A simple way to get around this is to wrap your function in another closure that captures the value of the target variable.
You can do this like so:
(function(capturedValue){
return function () {
// do something with the capturedValue
};
}(byRefObject));
The first function is part of an Immediately Invoked Function Expression (IIFE). It serves to capture the value of the byRefObject. You can read more about IIFE here.
Your code could look like this:
var arr=[];
for(var i = 0; i < obj.items.length; ++i) {
target = child.items[i].id;
arr.push({
key: sHolder.charAt(i),
fn: (function(target) {
return function() {
isoNS.injectKey(target);
};
})(target)
},
});
}
This has to do with closures:
var arr=[];
function getFunc(t){
return function() {
isoNS.injectKey(t);
}};
for(var i = 0; i < obj.items.length; ++i) {
target = child.items[i].id;
arr.push({
key: sHolder.charAt(i),
fn: getFunc(target),
});
}

Pushing object into array erases instead of adding

I have a function like this :
$scope.saveSearch = function () {
var alreadyExist = false;
for (var i = 0; i < $scope.savedSearch.length; i++) {
if (JSON.stringify($scope.searched) === JSON.stringify($scope.savedSearch[i])) {
alreadyExist = true;
break;
}
}
if (!alreadyExist) {
$scope.savedSearch.push($scope.searched);
localStorage.setItem("savedSearch", JSON.stringify($scope.savedSearch));
}
};
Before that : $scope.savedSearch = [];
$scope.searched = {
IS: "",
area: "",
block: "",
type: "",
level: ""
};
The values in $scope.searched object are initialized and then modified by the user.
My problem is :
$scope.savedSearch always contains only the last pushed object. Instead of adding the object to the array, it just replaces the current object.
I don't understand why.
You'll want to change your push line to:
$scope.savedSearch.push(angular.copy($scope.searched));
I believe your problem is that objects are passed by reference. Since the object you have in the savedSearch is always pointing to the exact object you're searching, alreadyExist will always be true.
My guess is that the object reference is being stored in your array, not the actual object itself. Because of this, any subsequent calls to push the object to your array will not work because the object reference already exists in the array. It's merely updated.
Try this instead. Use angular.copy() to create a deep copy of the object and push the copy to your array. See if that works.
if (!alreadyExist) {
$scope.savedSearch.push(angular.copy($scope.searched));
localStorage.setItem("savedSearch", JSON.stringify($scope.savedSearch));
}
You are pushing the Object outside of the for so only 1 element get pushed in try move it inside the for and every object which doesnt already exist will be pushed in
$scope.saveSearch = function () {
var alreadyExist = false;
for (var i = 0; i < $scope.savedSearch.length; i++) {
if (JSON.stringify($scope.searched) === JSON.stringify($scope.savedSearch[i])) {
alreadyExist = true;
break;
}
if (!alreadyExist) {
$scope.savedSearch.push($scope.searched);
localStorage.setItem("savedSearch", JSON.stringify($scope.savedSearch));
}
}
};
easier way would be to just
$scope.saveSearch = function () {
var alreadyExist = false;
for (var i = 0; i < $scope.savedSearch.length; i++) {
if (JSON.stringify($scope.searched) != JSON.stringify($scope.savedSearch[i])) {
$scope.savedSearch.push($scope.searched);
localStorage.setItem("savedSearch", JSON.stringify($scope.savedSearch));
}else{
break
}
}
};

Linking property of a custom element to it's attribute

Question & Demo
I've recently started to work with custom elements.
As you know, a HTMLElement has both a markup inside the document, and a JavaScript object. So, with my custom element, I've tried to link the JavaScript object properties with the element's attributes.
So, if any of those is updated, the other would be updated as well. But this isn't happening and I swear I've tried everything, maybe is something stupid I'm missing but for me, how this code is behaving is a freaking mistery.
After reading the code explanation below and seen the demo, you should be able to understand my question:
Why are the custom element attributes updating correctly, but not it's properties?
I've setup a JSFiddle to illustrate my problem, and I will be going over how the code is supposed to work in this post.
HTML
<e-button color="red" width="250px">RED BUTTON</e-button>
Well it rarely gets any simpler than that. I create a custom object called "e-button", with color=red and width=250px.
JavaScript
var eButtonProto = Object.create(HTMLElement.prototype);
eButtonProto.createdCallback = function() {
this.__htmlToJsProp(); //Gets all the HTML attributes and makes them accessible via JS.
this.__processAttr(); //Makes decision upon predefined attributes.
}
eButtonProto.__htmlToJsProp = function() {
var attr = this.attributes;
for (var i = 0; i < attr.length; i++) {
var current = attr[i];
var name = current.name;
var value = current.value;
this[name] = value;
Object.defineProperty(this, name, {
get: function() {
return this.getAttribute(name);
},
set: function(val) {
this.setAttribute(name, val);
}
});
}
}
eButtonProto.attributeChangedCallback = function(name, oldVal, val) {
this[name] = val;
this.__processAttr();
}
eButtonProto.__processAttr = function() {
var color = this.color || this.defaults.color;
this.style.backgroundColor = color;
}
eButtonProto.defaults = {
color: "whitesmoke"
}
var eButton = document.registerElement("e-button", {
prototype: eButtonProto
});
window.onload = function() {
redButton = document.querySelector("e-button[color=red]");
console.log("button ATTRIBUTES", redButton.getAttribute("color"), redButton.getAttribute("width"));
console.log("button PROPERTIES", redButton.color, redButton.width);
} < /script>
The really important code snippets here are these, which essentialy should make my idea work, first, the __htmlToJsProp() function:
eButtonProto.__htmlToJsProp = function() {
var attr = this.attributes; //Gets the element's attributes.
for (var i = 0; i < attr.length; i++) {
var current = attr[i]; //Element attribute name,value pair.
var name = current.name; //Attribute name.
var value = current.value; //Attribute value.
Object.defineProperty(this, name, { //Defines the element property from the attribute name, for simplicity I will be using the color attribute as my example.
get: function() {
return this.getAttribute(name); //When accessing element.color you should get element.getAttribute("color")
},
set: function(val) {
this.setAttribute(name, val); //When setting element.color = "red" you should also be doing element.setAttribute("color","red");
}
});
this[name] = value; //Sets element.color = "red"
}
}
and then the attributeChangedCallback function:
eButtonProto.attributeChangedCallback = function(name, oldVal, val) {
this[name] = val; //This would be the other way around, if the attribute is updated via setAttribute, or the browser console, the property is updated (works).
this.__processAttr(); //You can ignore this
}
Conclusions
You see after testing A LOT I found that if you place yourself in the for loop and output the property value, it will give you element.color = "red" and element.width = "250px";
But if you test it outside the for loop, it gives you element.color = "250px" and element.width = "250px" for the properties but the attributes update properly, that is element.getAttribute("color") = "red" and element.getAttribute("width") = "250px".
If you made it this far, well thanks, hopefully you can find a way out of this problem, which I really don't seem to be able to solve, happy coding :)
Your issue seems to be within the for loop, the getters and setters are called later, so the value of i isn't what you think it is, the loop completes and sets i to the latest iterated value.
You'll solve it with a closure
eButtonProto.__htmlToJsProp = function () {
var attr = this.attributes;
for (var i = 0; i < attr.length; i++) {
(function(current, self) {
var name = current.name;
var value = current.value;
Object.defineProperty(self, name, {
get: function () {
return this.getAttribute(name);
},
set: function (val) {
this.setAttribute(name, val);
}
});
self[name] = value;
})(attr[i], this);
}
}
FIDDLE

Javascript setters/getters

var author = {
firstname: 'Martin',
lastname: 'Hansen'
}
function settersGetters(propStr) {
for (var i = 0; i < propStr.length; i++) {
author['_'+ propStr[i]] = null;
author.__defineGetter__(propStr[i],
function() {
return author['_'+ propStr[i]];
});
author.__defineSetter__(propStr[i],
function(val) {
author['_'+ propStr[i]] = val;
});
};
}
The above code would hopefully generate setters/getters for any supplied properties (in an array) for the object author.
But when I call the below code Both firstname and lastname is olsen.. What am I doing wrong?
settersGetters(['firstname', 'lastname']);
author.firstname = 'per';
author.lastname = 'olsen';
console.log(author.firstname);
console.log(author.lastname);
I suspect this is a closure issue, which several helpful people explained to me here.
Try wrapping the i reference inside a function, and read up on closures. Despite all the help, I confess that I still don't really understand them.
The definition is made in a closure, so all the setters are using the last value of i.
Use this instead:
function setterGetter(property)
{
author['_'+ property] = null;
author.__defineGetter__(property,
function() {
return author['_'+ property];
});
author.__defineSetter__(property,
function(val) {
author['_'+ property] = val;
});
}
function settersGetters(propStr) {
for (var i = 0; i < propStr.length; i++) {
setterGetter(propStr[i]);
};
}

Categories

Resources