scope of "this" changes when using grep in jquery widget - javascript

I have two groups of JSON data, one containing the data I want to filter down, and a second group representing the criteria for the filter.
The filter structure is pretty basic. It contains an Id of the element it's filtering on and it's value.
The other structure contains multiple fields, including the Id that relates back to the filter structure.
Both of these are stored in the global part of the widget. Normally, I would use this.filterData or this.jsonObjects to access them. However, if I try to filter using either grep, or the javascript array.filter function, then "this" changes, so I can't access the data anymore. Is there a way around this?
applyFilters: function() {
//var returnedData = $.grep(this.options.jsonObjects, this.grepFunction);
var returnedData = this.options.jsonObjects.filter(this.filterMatch);
filteredData = returnedData;
this.options.onFilterApply.call(this);
},
filterMatch: function(element) {
for(var key in this.filterData) {
if(this.filterData.hasOwnProperty(key)) {
for(var a = 0; a < this.filterData[key].values.length; a++) {
if(element[this.filterData[key].id]==this.filterData[key].values[a]) {
return true;
}
}
}
}
return false;
}
Hope this makes sense. During the applyFilters function, "this" represents the widget itself and it works fine. But as soon as it enters the filterMatch function, "this" becomes the window, so this.filterData is undefined. How can I access the filterData inside that function, or ultimately, what is the best way of filtering down a list of JSON objects?

You can save your scope in a variable before entering the filterMatch function.
Something like :
var that = this;
applyFilters: function() {
//var returnedData = $.grep(this.options.jsonObjects, this.grepFunction);
var returnedData = this.options.jsonObjects.filter(this.filterMatch);
filteredData = returnedData;
this.options.onFilterApply.call(this);
},
filterMatch: function(element) {
for(var key in that.filterData) {
if(that.filterData.hasOwnProperty(key)) {
for(var a = 0; a < that.filterData[key].values.length; a++) {
if(element[that.filterData[key].id]==that.filterData[key].values[a]) {
return true;
}
}
}
}
return false;
}
You can bind your this to the function scope also.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Related

How to call a object method without object instance?

I have a method loadSet which creates elements with datas from the localstorage, and this should be run on page load i am calling it via
ReminderSet.prototype.loadSet(); // works fine
My question is, is there any other way to call a method that don't need a reference to an object instance? like person1.loadSet(); or should i abandon this and make it as a regular function?
ReminderSet.prototype.loadSet = function() {
var keys = Object.keys(localStorage),
i = 0,
key,
array;
for (; key = keys[i]; i++) {
const setId = localStorage.getItem(key);
array = JSON.parse(setId); //parse and store key values
let array_index = 0;
//Re-create the reminders and set their properties//
$reminderSection.append($('<div/>').addClass('set').attr('id', key) //Set the ID
.append($('<div/>').addClass('set-title').append($('<h1>').attr('contenteditable', 'true').text(array[array_index].set_title)), //Index is always at 0//
$('<div/>').addClass('create-reminder-control').append($('<button>').addClass('add-new-reminder').text("+ add new"), $('<input>').addClass('create-reminder-value').attr({ type: "text", placeholder: "get something done" })), $('<div/>').addClass('reminder-lists'), $('<div/>').addClass('save-control').append($('<button>').addClass('save-reminder-button').text('Save'))))
//Get our key values //
for (; array_index < array.length; array_index++) {
/*Select the element id */
$("#" + key).children('.reminder-lists').append($('<div/>').addClass('a-reminder').attr('contenteditable', 'true').text(array[array_index].description).append($('<div/>').addClass('delete-reminder').text('x'))) //Get the reminders
} //end
}
};
If loadSet doesn't need or use an instance, it doesn't make any sense for it to be on ReminderSet.prototype. Either make it a standalone function:
function loadSet() {
// ...
}
// Call it like so: loadSet();
...or a property on ReminderSet itself:
ReminderSet.loadSet = function() {
// ...
};
// Call it like so: ReminderSet.loadSet();
Only put functions on the object that a constructor's prototype property refers to if they need to use this (the instance).
You can set the function directly as a property of the other ReminderSet:
ReminderSet.loadSet = function() {//etc.}
Then you can simply call: ReminderSet.loadSet()

Why Javascript object didn't change?

Can someone explain me this strange js behavior ?
All of this is in AngularJS.
I have helper function in my main app.js to simply return element from an array by its id:
var MyLib = MyLib || {};
MyLib.helpers = {
find: function(needle, stack) {
for (var i = 0; i < stack.length; i++) {
if(stack[i]._id === needle)
return stack[i];
}
return false;
}
}
Then I have factory and function to handle database change:
// categories are grabbed from db
var categories = [some array of objects];
// change is object returned from database that has all info about object as well as new object itself
function handleChange(change) {
var _category = MyLib.helpers.find(change.id, categories);
// if deleted, that part is ok
if(change.deleted) {
var idx = categories.indexOf(_category);
if(idx !== -1) {
categories.splice(idx, 1);
}
} else {
// if updated that part is weird
if(_category) {
_category = change.doc;
}
// if newly added that part is ok
else {
categories.push( angular.copy(change.doc) );
}
}
}
Why when I try to update element grabbed from categories array doesn't update in categories array ?
// categories ARE NOT updated after this
_category = change.doc;
and only when I refer to categories by index like this:
// categories ARE updated after this although _category is returned from this array by index (find function)
var idx = categories.indexOf(_category);
categories[idx] = change.doc;
I don't understand this...
You are overwriting the variable with a new value and any reference to prior value is gone.
Instead of overwriting the original object value with a new object you could update the existing object using angular.extend()
angular.extend(_category, change.doc);
I didn't analyze everything, but you should always have dot notation.
_category pass by value, and will not change when 'MyLib.hel ...' is changed
var _category = MyLib.helpers.find(change.id, categories);
something.category pass by reference, and will be changed when 'MyLib.hel ...' is changed
var something.category = MyLib.helpers.find(change.id, categories);

How can I insert an argument like forEach, reduce and the like does?

I'm trying to reinvent the wheel, sort of.. Just messing around trying to remake some jquery functions.. I've come this far
var ye = function (ele) {
if (ele[0] == "#")
{
return document.getElementById(ele.slice(1));
}
else if (ele[0] == ".")
{
// returns an array, use index
return document.getElementsByClassName(ele.slice(1));
}
else
{
// also returns an array
return document.getElementsByTagName(ele);
}
}
but how can I use this element as a parameter in a function in the 'ye' prototype. For example, if I wanted to make fontsize how could I get the dom element like here:
ye.prototype.fontSize = function (ele)
{
ele.style.fontSize = "30px";
}
Just to add a bit to make the title relevant.. forEach inserts three arguments into the callback function, just like I want ye to insert ele into the fontSize function.
Just messing around trying to remake some jquery functions...
...but how can I use this element as a parameter in a function in the 'ye' prototype..
Here is a very crude and simple way to start...
Create a function with a property called elems which is an array and will store the selected DOM elements.
Like this:
var oye = function() { this.elems = []; };
On its prototype, you can create your custom functions which you want to expose. e.g. the function fontSize (as in your question), iterate over the elems array property that we created earlier changing the font size of each DOM element stored in. this points to the instance which is calling this function which we will ensure to be of type oye later on. To enable chaining, we simply return itself via this.
Like this:
oye.prototype.fontSize = function(size) {
this.elems.forEach(function(elem) {
elem.style.fontSize = size;
});
return this;
};
Now create the selector function called ye. This serves the purpose of selecting the DOM elements, storing them in the elems array property of a new instance of oye class, and return the instance. We call the slice of the array prototype to convert the nodeList to an array.
Like this:
var ye = function(elem) {
var newOye = new oye;
newOye.elems = [].slice.call(document.querySelectorAll(elem));
return newOye;
};
Now start using it in your code. Just like jQuery, you can use ye to select and then call your custom functions.
Like this:
ye("#elem1").fontSize('30px');
Just like jQuery, you can also chain multiple custom functions as shown in the complete working example below:
ye("P").fontSize('24px').dim(0.4);
Next step: Remember this is just a very crude example. You can now proceed to club the step 1 and 2 into a single call using the init pattern returning the new object from the selector function itseld. Learn more about Javascript and best practices.
Here is a sample working demo:
var oye = function() { this.elems = []; };
oye.prototype.fontSize = function(size) {
this.elems.forEach(function(elem) {
elem.style.fontSize = size;
});
return this;
};
oye.prototype.dim = function(value) {
return this.elems.forEach(function(elem) {
elem.style.opacity = value;
});
return this;
};
var ye = function(elem) {
var newOye = new oye;
newOye.elems = [].slice.call(document.querySelectorAll(elem));
return newOye;
};
ye("#elem1").fontSize('30px');
ye(".elem2").fontSize('20px');
ye("P").fontSize('24px').dim(0.4);
<div>This is normal text.</div>
<div id="elem1">size changed via id.</div>
<div class="elem2">size changed via class.</div>
<div class="elem2">size changed via class.</div>
<p>size changed and dimmed via tag name</p>
<p>size changed and dimmed via tag name</p>
Regarding your question, I may think you're new to JavaScript, or not familiar with its basic concepts. I'm not sure reinventing the wheel is a good thing in such conditions.
Since you've cited jQuery, you can have a look at its source code to understand how it works under the hood:
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core.js#L17-L23
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core.js#L38-L81
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core/init.js#L19-L114
Having that said, I would have done something like this:
var ye = function ( ele ) {
return new ye.prototype.init(ele);
};
ye.prototype.init = function( ele ) {
this._elements = [].slice.call(document.querySelectorAll(ele));
return this;
};
ye.prototype.forEach = function( fn ) {
this._elements.forEach(fn);
return this;
};
ye.prototype.fontSize = function( fontSizeValue ) {
this.forEach(function (ele) {
ele.style.fontSize = fontSizeValue;
});
return this;
};
The associated usage is as follow:
var myCollection = ye('.someClassName');
myCollection.forEach(function ( item, index ) {
console.log(item.style.fontSize);
});
myCollection.fontSize('45px');
myCollection.forEach(function ( item, index ) {
console.log(item.style.fontSize);
});
Use ye function calling before setting style, something like:
ye.prototype.fontSize = function(ele) {
ye(ele).style.fontSize = '30px';
}
returned object should be richer, like that:
var baseObject = {
// Will be used for the element:
element: null,
width: function(){ return this.element.getwidth(); /* or anything similar*/ }
// ... Further methods
}
and then in your ye function:
var ye = function (ele) {
var yeElem = clone(baseObject); // See comment below!!
if (ele[0] == "#") { yeElem.element = document.getElementById(ele.slice(1)); }
else if (ele[0] == "."){ /*...*/ }
else { /*...*/ }
return yeElem;
}
This way the new element has built in methods.
As for the clone() method used, it doesn't exist but you have to use some clone method.
I recommend Loadsh's _.cloneDeep() (here).

Returning an array containing named properties of each object

Just getting stumped and not sure why my code isn't working. The instructions are to take an array of objects and a property name, and return an array containing the named property of each object.
so something likepluck([{a:1}, {a:2}], 'a') // -> [1,2] where pluck is the function I want to create.
So far, I have:
function pluck(objs, name) {
var pushedArray=[];
for (i=0;i<objs.length;i++){
var totalpushedArray = pushedArray.push(name[i]);
}
}
but the code itself isn't working as far as I can tell. There are additional guidelines to leave undefined if the object doesnt have the property but I figure that I will get to that after I solve this first.
You forgot to add a return statement and you're not referencing the object property. See below.
function pluck(objs, name) {
var pushedArray = [];
for (var i = 0; i < objs.length; i++) {
pushedArray.push(objs[i][name]);
}
return pushedArray;
};
If you want a more "functional" solution, you can use map.
function pluck(objs, name) {
return objs.map(function(obj) {
return (obj.hasOwnProperty(name) ? obj[name] : null);
});
};

Access grandchild of a variable (parent.child.grandchild) without dots and one pair of brackets

I'm building a canvas-related class with a kind of conversion table. The conversion table can be edited by the user. (Isn't really relevant, but maybe you want to know why):
cLayout = function(option) {
//obtaining the canvas (el) here
this.setup = function(option) {
t=this.table;
for (var p in t)
{
el[t[p][0]] = option[p]||t[p][1]
}
}
this.setup(option)
}
cLayout.prototype.table = {
width:[['style']['width'],"100%"],
height:['style'['height'],"100%"],
bg:[['style']['backgroundColor'],""],
position:[['style']['position'],"absolute"],
left:['style'['left'],"0px"],
top:['style'['left'],"0px"]
}
Example:
var b = new cLayout({left:'10%',width:'90%'})
Real question:
Normally, I'd use el['style']['width'] to set el.style.width.
But I want to use el[something] without the second pair of brackets: I want the property name to be completely variable (I also want to be able to set el['innerHTML']). So, is there a way to get a grandchild by using a[b], without using a[b][c]?
P.S. Of course, I don't want to use eval.
No it is not possible. If you have nested objects, you cannot just skip a level.
You could write a helper function though, which takes a string like "child.grandchild" and sets the corresponding property:
function setProp(obj, prop, val) {
var parts = prop.split('.');
while(parts.length > 1) {
obj = obj[parts.shift()];
}
obj[parts.shift()] = val;
}
(You should also test whether a certain property is available.)
Then your code could look like:
var cLayout = function(option) {
//obtaining the canvas (el) here
this.setup = function(option) {
for(var p in this.table) {
setProp(el, this.table[p][0], option[p]||t[p][1]);
}
}
this.setup(option)
}
cLayout.prototype.table = {
width:['style.width',"100%"],
height:['style.height',"100%"],
//...
}

Categories

Resources