We're trying to create our own Javascript library to replace jQuery and reduce overhead. We're wanting to call functions from the global scope using the this keyword, but the script is breaking in our foreach loop. How do we callback an object inside or custom "each" function using our $(this).getAtt('data-src') function rather than a.getAttribute('data-src')
this is defaulting to the window's object. Here's a minimized version of our library
var $=(function(){
'use strict';
var c = function(w){
if(!w)return;
if(w==='document'){this.elems=[document];}
else if(w==='window'){this.elems=[window];}
else {this.elems=document.querySelectorAll(w);}
};
c.prototype.each = function(callback){
if(!callback || typeof callback !== 'function')return;
for(var i = 0, length = this.elems.length; i < length; i++){callback(this.elems[i], i);}return this;
};
c.prototype.setAtt=function(n,v){this.each(function(item){item.setAttribute(n,v);});return this;};
c.prototype.getAtt=function(n){return this.elems[0].getAttribute(n);};
var init=function(w){return new c(w);};return init;
})();
function loadImgs(){
$("img[data-src]").each(function(a,b){
console.log(a.getAttribute('data-src'));
console.log($(this).getAtt('data-src'));
});
}
And our minimized HTML :
<a onclick="loadImgs();">lazyload</a><br>
<img src="spacer.gif" alt=""/></div><img class="lazyLoad" data-src="replacer.jpg" alt="">
Pass the calling context you want (the element) to the getAttribute method by using .call().
You also need the c constructor to set the elems property to the argument if the argument is an element:
} else if (w instanceof HTMLElement) {
this.elems = [w];
}
for (var i = 0, length = this.elems.length; i < length; i++) {
callback.call(this.elems[i], this.elems[i], i);
// ^^ "this" in callback
// ^^ first argument to callback
// ^^ second argument to callback
}
var $ = (function() {
'use strict';
var c = function(w) {
if (!w) return;
if (w === 'document') {
this.elems = [document];
} else if (w === 'window') {
this.elems = [window];
} else if (w instanceof HTMLElement) {
this.elems = [w];
} else {
this.elems = document.querySelectorAll(w);
}
};
c.prototype.each = function(callback) {
if (!callback || typeof callback !== 'function') return;
for (var i = 0, length = this.elems.length; i < length; i++) {
callback.call(this.elems[i], this.elems[i], i);
}
return this;
};
c.prototype.setAtt = function(n, v) {
this.each(function(item) {
item.setAttribute(n, v);
});
return this;
};
c.prototype.getAtt = function(n) {
return this.elems[0].getAttribute(n);
};
var init = function(w) {
return new c(w);
};
return init;
})();
function loadImgs() {
$("img[data-src]").each(function(a, b) {
console.log(a.getAttribute('data-src'));
console.log($(this).getAtt('data-src'));
});
}
<a onclick="loadImgs();">lazyload</a><br>
<img src="spacer.gif" alt="" /></div><img class="lazyLoad" data-src="replacer.jpg" alt="">
Related
TasksI need to modify the displaySortedTaskList function so that it runs if there are 3 arguments passed, and throws an error object with a message if there aren't 3 arguments passed. My attempt:
"use strict";
var sortTaskList = function(tasks) {
var isArray = Array.isArray(tasks);
if (isArray) {
tasks.sort();
}
return isArray;
};
var displaySortedTaskList = function(tasks, div, handler) {
if(arguments.length = Function.length){
var html = "";
var isArray = sortTaskList(tasks);
if (isArray) {
//create and load html string from sorted array
for (var i in tasks) {
html = html.concat("<p>");
html = html.concat("<a href='#' id='", i, "'>Delete</a>");
html = html.concat(tasks[i]);
html = html.concat("</p>");
}
div.innerHTML = html;
// get links, loop and add onclick event handler
var links = div.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
links[i].onclick = handler;
}
}
} else {document.getElementById("message").innerHTML = "The displaySortedTaskList function of the tasklist library requires three arguments"}
};
var deleteTask = function(tasks, i) {
var isArray = sortTaskList(tasks);
if (isArray) { tasks.splice(i, 1); }
};
var capitalizeTask = function(task) {
var first = task.substring(0,1);
return first.toUpperCase() + task.substring(1);
};
You might use rest parameters and check whether the length of the array is 3:
var displaySortedTaskList = function(...args) {
if (args.length !== 3) {
document.getElementById("message").textContent = "The displaySortedTaskList function of the tasklist library requires three arguments";
return;
// or `throw new Error('not enough args')` ?
}
const [tasks, div, handler] = args;
// rest of your code
(note that you should assign to .textContent when inserting text - .innerHTML is appropriate when inserting HTML markup, which is not the case here)
Live snippet:
var displaySortedTaskList = function(...args) {
if (args.length !== 3) {
return console.log('error');
}
console.log('rest of the code');
}
displaySortedTaskList('foo', 'bar');
displaySortedTaskList('foo', 'bar', 'baz');
displaySortedTaskList('foo', 'bar', 'baz', 'buzz');
I want to invoke function X everytime any other function is invoked. I want to keep this as generic as possible.
having these two functions
function x(){ console.log("invoke BEFORE"); }
function someFunction(something){ console.log(something); }
When someFunction is invoked
someFunction("testoutput");
I want the console to output this:
>> invoke BEFORE
>> testoutput
I also want this behaviour to apply to any function of a certain object.
For example:
var myFunctions = {
first:function(){/* do something */},
second:function(){/* do something else*/}
}
myFunctions.before(function(){/do something before/});
Anyone know a solution?
EDIT:
I have come up with a solution like this:
Object.prototype.before = function(x){
for(var key in this){
if(typeof this[key] === "function")
this[key] = (function(x, f) {
var g = f;
return (function() {
x();
return g.apply(this, arguments);
});
}(x, this[key]));
}
}
var test = { func: function(){console.log("test")}};
test.before(function(){console.log("before")});
test();
results in:
>> before
>> test
YAAAYYY
how do you like this?
This is a bad idea that will make understanding and debugging your program much harder.
You can use what in Python is called "monkey-patching" to achieve this:
(function() {
{
var origSomeFunction = someFunction;
someFunction = (function() {
x();
return origSomeFunction.apply(this, arguments);
});
}();
This works because I changed the (global) name someFunction to refer to a new function that I defined. Within the closure of that function I keep a reference to the original function that you want to pass the call on to.
In my opinion, event binding is more flexible than function wrapping since you can remove x whenever you want. Here is a possible implementation:
// Observable
var Observable = {};
Observable.create = function (options) {
var observable = { events: {} };
var events = options.events || [];
var i, l = events.length;
for (i = 0; i < l; i++) {
observable.events[events[i]] = [];
}
return observable;
};
Observable.one = function (observable, event, handler) {
Observable.on(observable, event, function f () {
Observable.un(observable, event, f);
handler.apply(this, arguments);
});
};
Observable.on = function (observable, event, handler) {
observable.events[event].push(handler);
};
Observable.un = function (observable, event, handler) {
observable.events[event].splice(
observable.events[event].indexOf(handler), 1
);
};
Observable.emit = function (observable, event, params) {
var handlers = observable.events[event];
var i, l = handlers.length;
if (!params) params = {};
params.source = observable;
for (i = 0; i < l; i++) {
handlers[i].call(observable, params);
}
};
Observable.observeMethod = function (observable, name) {
var meth = observable[name];
var before = 'before' + name.toLowerCase();
var after = 'after' + name.toLowerCase();
observable.events[before] = [];
observable.events[after] = [];
observable[name] = function () {
var ret;
Observable.emit(observable, before);
ret = meth.apply(observable, arguments);
Observable.emit(observable, after, { value: ret });
return ret;
};
};
// init
var printer = init({
sayHello: function () {
this.print('Hello World.');
},
sayHi: function (e) {
this.print('Hi ' + e.pseudo + '.');
},
print: function (msg) {
print(msg);
}
});
var clock = init({
tid: null,
events: ['tick'],
stop: function () {
clearTimeout(this.tid);
},
start: function () {
var me = this;
var time = 0;
clearTimeout(this.tid);
(function tick () {
me.tid = setTimeout(tick, 1000);
me.emit('tick', { time: time++ });
})();
}
});
// demo: printer
printer.on('afterprint', printNewline);
printer.on('beforesayhello', printBullet);
printer.sayHello();
printer.sayHello();
printer.un('beforesayhello', printBullet);
printer.sayHello();
// demo: clock
clock.on('tick', function (e) {
if (e.time) printer.print('tick ' + e.time);
if (e.time === 3) this.stop();
});
clock.one('afterstop', clock.start);
clock.start();
// helpers
function init (obj) {
obj = initObservable(obj);
obj.one = function (event, handler) {
Observable.one(this, event, handler);
};
obj.on = function (event, handler) {
Observable.on(this, event, handler);
};
obj.un = function (event, handler) {
Observable.un(this, event, handler);
};
obj.emit = function (event, params) {
Observable.emit(this, event, params);
};
return obj;
}
function initObservable (obj) {
var k, observable;
observable = Observable.create({
events: obj.events
});
for (k in observable) {
obj[k] = observable[k];
}
for (k in obj) {
if (typeof obj[k] === 'function') {
Observable.observeMethod(obj, k);
}
}
return obj;
}
function printBullet () {
print('• ');
}
function printNewline () {
print('<br />');
}
function print (html) {
document.body.innerHTML += html;
}
I am working on a javascript library that will work like this: tex("element").print("hi"). Here is the code:
(function (window) {
var regex = {
Id : /^[#]\w+$/,
Class : /^[.]\w+$/,
Tag : /^\w+$/,
validSelector : /^([#]\w+|[.]\w+|\w+)$/
},
tex = function(selector){
//only some of the functions need to select an element
//EX:
// style: tex(selector).style(style);
//one that would not need a selector is the random number function:
// tex().random(from,to);
if (selector){
if (typeof selector === 'string'){
var valid = regex.validSelector.test(selector);
if( valid ){
if(regex.Id.test(selector)){
this = document.getElementById(selector);
}
if(regex.Class.test(selector)){
this = document.getElementByClass(selector);
}
if(regex.Tag.test(selector)){
this = document.getElementByTagName(selector);
}
}
}else if(typeof selector === 'object'){
this = selector;
}
//this = document.querySelector(selector);
// I could make a selector engine byt I only need basic css selectors.
}
};
tex.prototype = {
dit : function(){
this.innerHTML = 'Hi?!?!?!'
}
};
window.tex = tex;
})(window);
When I try to run the code I get an error that says, "Left side of argument is not a reference" referring to this = document.getElementById(selector);
Does anyone know what is wrong with my code?
Because you can not set this.
To do something that you are after, you just return this.
without using a prototype
var foo = function( selector ) {
this.print = function () {
console.group("in print");
console.log(this.elements[0].innerHTML);
console.groupEnd("in print");
return this;
}
this.printAll = function () {
console.group("in printAll");
for (var i=0; i<this.elements.length; i++) {
console.log(this.elements[i].innerHTML);
}
console.groupEnd("in printAll");
return this;
}
this.elements = document.querySelectorAll( selector );
return this;
}
console.group("id");
foo("#foofoo").print();
console.groupEnd("id");
console.group("class");
foo(".bar").printAll().print();
console.groupEnd("class");
JSFiddle
Basic example with prototype
(function () {
var basic = function (selector) {
this.elements = document.querySelectorAll(selector);
return this;
}
basic.prototype.print = function () {
console.group("in print");
console.log(this.elements[0].innerHTML);
console.groupEnd("in print");
return this;
}
basic.prototype.printAll = function () {
console.group("in printAll");
for (var i = 0; i < this.elements.length; i++) {
console.log(this.elements[i].innerHTML);
}
console.groupEnd("in printAll");
return this;
}
var foo = function (selector) {
return new basic(selector);
}
window.foo = foo;
})();
console.group("id");
foo("#foofoo").print();
console.groupEnd("id");
console.group("class");
foo(".bar").printAll().print();
console.groupEnd("class");
JSFiddle
This code will pass the last value created by the loop the eventListener function, I need the value at the time the eventListener was created to be attached.
window.onload = function() {
var el = document.getElementsByClassName('modify');
for (var i = 0; i < el.length; i++) {
var getId=el[i].id.split("_");
document.getElementById("modify_y_"+getId[2]).addEventListener('mouseover', function() {
document.getElementById("modify_x_"+getId[2]).style.borderBottom = '#e6665 solid 3px';
}, false);
}
}
You can use the bind prototype which exists on all functions in modern browsers
window.onload = function() {
var el = document.getElementsByClassName('modify');
for (var i = 0; i < el.length; i++) {
var getId=el[i].id.split("_");
document.getElementById("modify_y_"+getId[2]).addEventListener('mouseover', function(theid) {
document.getElementById("modify_x_"+getId[2]).style.borderBottom = '#e6665 solid 3px';
}.bind(null,getId[2]), false);
}
}
If you need to support older browsers that doesn't have bind nativly built in, you can use this poly-fill taken from MDN where you will also find documentation on the bind prototype function
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
You do that by using a builder function:
window.onload = function () {
var el = document.getElementsByClassName('modify');
for (var i = 0; i < el.length; i++) {
var getId = el[i].id.split("_");
document.getElementById("modify_y_" + getId[2]).addEventListener('mouseover', makeHandler(getId[2]), false);
}
function makeHandler(theId) {
return function () {
document.getElementById("modify_x_" + theId).style.borderBottom = '#e6665 solid 3px';
};
}
};
The function returned by makeHandler closes over the theId argument, which doesn't change.
From async.memoize(), what does the last else block after the comment in this function do?
https://github.com/caolan/async/blob/master/lib/async.js#L671
async.memoize = function (fn, hasher) {
var memo = {};
var queues = {};
hasher = hasher || function (x) {
return x;
};
var memoized = function () {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
var key = hasher.apply(null, args);
if (key in memo) {
callback.apply(null, memo[key]);
}
else if (key in queues) {
queues[key].push(callback);
}
else {
// what does this else block do?
queues[key] = [callback];
fn.apply(null, args.concat([function () {
memo[key] = arguments;
var q = queues[key];
delete queues[key];
for (var i = 0, l = q.length; i < l; i++) {
q[i].apply(null, arguments);
}
}]));
}
};
memoized.unmemoized = fn;
return memoized;
};
If the key is not found in either the memo or the queues objects (the first two parts of the if statement), then it calls the callback and assigns the return value from the callback to queues[key] as a one element array.
Then, it calls whatever functions where in the queue[key] array.