I am trying to understand how to work jQuery and other libraries. I would like to know how to create a selector with this format:
$("#selector").get();
By the moment, I am trying the next, but i don't know how to run internal functions (get(), set()):
var $ = (function() {
var jQuery = {
get: function() {
console.log("get() function!!");
return this;
},
set: function() {
console.log("set() function!!");
return this;
}
};
return function(el) {
return document.querySelector(el);
}
})();
I have read something about modular pattern design in JavaScript, but I don't understand all.
The way to make chainable functions, is to first and foremost create instances with the new keyword.
This can be done "automatically" by making sure the this value of the called function is an instance of itself, if not explicitly call it with new.
Then it's just a matter of returning the instance and using prototyped methods.
var $ = function(selector) {
if (! (this instanceof $) ) {
return new $(selector);
}
this.el = document.querySelectorAll(selector);
return this;
}
$.prototype.css = function(prop, val) {
this.el.forEach(function(element) {
element.style[prop] = val;
});
return this;
}
$('#test').css('color', 'red').css('font-size', '30px')
<div id="test">test</div>
const $ = function(selector) {
if (!(this instanceof $)) {
return new $(selector);
};
this.el = document.querySelectorAll(selector);
};
$.prototype.css = function(obj) {
this.el.forEach(function(element) {
element.style[Object.keys(obj)[0]] = Object.values(obj);
});
};
$.prototype.click = function(callback) {
this.el.forEach(function(element) {
element.addEventListener('click', callback, false);
});
};
jQuery this or $(selector) is array like [div, div] not object {el: [div, div]}, so its not using this.el to modified the elements, here simplified version
if (window.$ === undefined) window.$ = (function () {
var $, fun = {}, emptyArray = [];
function Z(dom, selector) {
var i, len = dom ? dom.length : 0;
for (i = 0; i < len; i++) this[i] = dom[i];
this.length = len;
this.selector = selector || '';
}
fun.Z = function (dom, selector) {return new Z(dom, selector);};
fun.init = function (selector, context) {
if (!selector) return fun.Z();
var dom = document.querySelectorAll(selector);
return fun.Z(dom, selector);
};
Z.prototype = {
splice: emptyArray.splice,
forEach: emptyArray.forEach,
html: function (str) {
return this.forEach(function (el) {
el.innerHTML = str;
});
},
css: function(obj, value){
if(typeof obj == 'object'){ // like: .css({background: 'red'})
for(var k in obj){
return this.forEach(function (el) {
el.style[k] = obj[k];
});
}
}
else{ // called: .css('background', 'red')
return this.forEach(function (el) {
el.style[obj] = value;
});
}
}
};
$ = function (sel, ctx) {return fun.init(sel, ctx); };
return $;
})();
<div class="test"> AAAA </div>
<div class="test"> BBBB </div>
<button onclick="$('.test').css({background: 'red'})">red</button>
<button onclick="$('.test').css('background', 'blue')">blue</button>
<br />
<button onclick="console.log($('.test'))">log to console</button>
Related
I'm creating javascript library like jQuery, I have successfully adding prototype html() but if I call it with $(selector) it return object like {'el' : [array]} and if change in function to return this.el; it return array but I can't call .html().
How it can return [array] instead without breaking prototype?
window.$ = function(selector) {
if (!(this instanceof $)) {
return new $(selector);
}
this.el = [];
this.html = function(str) {
this.el.forEach(function(el) {
el.innerHTML = str;
});
};
(function(self) {
for (var eList = document.querySelectorAll(selector), i = eList.length - 1; i > -1; i--) {
self.el[i] = eList[i];
}
})(this);
return this;
};
$('#test').html('BBBBB')
console.log($('#test')[0])
<div id="test">AAAAAAA</div>
window.$ = function(selector) {
var x = [];
x = document.querySelectorAll(selector);
console.log(x);
return x;
};
NodeList.prototype.html = function(str) {
console.log(this);
this.forEach(function(el){
el.innerHTML = str;
});
}
console.log($('#test').html('BBBBB'))
<div id="test"></div>
I extended the html method inside the NodeList array. tell me if it can fit you.
finally it solved by looking Zepto source code, the magic to convert object to array is you have to create $.prototype.splice, it copied from the Array.splice
// only load if no jQuery on the page
if (window.$ === undefined) window.$ = (function () {
var $, zepto = {}, emptyArray = [];
function Z(dom, selector) {
var i, len = dom ? dom.length : 0;
for (i = 0; i < len; i++) this[i] = dom[i];
this.length = len;
this.selector = selector || '';
}
Z.prototype = {
splice: emptyArray.splice,
forEach: emptyArray.forEach,
html: function (str) {
this.forEach(function (el) {
el.innerHTML = str;
});
}
};
zepto.Z = function (dom, selector) {return new Z(dom, selector);};
zepto.init = function (selector, context) {
if (!selector) return zepto.Z();
var dom = document.querySelectorAll(selector);
return zepto.Z(dom, selector);
};
$ = function (sel, ctx) {return zepto.init(sel, ctx); };
return $;
})();
$('.test').html('DDDDD');
console.log($('.test'));
<div class="test"> AAAA </div>
<div class="test"> BBBB </div>
<div class="test"> CCCC </div>
I am trying to write own custom plain Javascript plugin.
Here is my sample plugin code:
(function() {
var pMethods = {
append: function(text) {
var node = this.node;
node.innerHTML += text;
},
click: function(fn) {
if (this.node instanceof Array) {
this.node.forEach(function(e) {
e.addEventListener('click', function() {
fn();
});
}, this);
} else {
this.node.addEventListener('click', function(e) {
fn(e);
});
}
}
};
myPlugin = function(selector) {
this.node = document.querySelectorAll(selector);
if (this.node.length === 1) {
this.node = this.node[0];
}
return this.node;
};
myPlugin.prototype = pMethods;
this.r = function(selector) {
return new myPlugin(selector);
};
}());
which has just two function append and click.
Here is my HTML:
<div class="close"></div>
Now I am trying to add click event on close div as follow:
r('.close').click(function() {
alert('Hi')
});
but it is not working as expected and I don't know what I'm missing here.
Your code did not work because you were explicitly checking if your element collection is an Array. Any element collection returned will be a NodeList which is an array like object, but not an array.
if (this.node instanceof Array)
should be
if (this.node instanceof NodeList)
Or you could use Array.prototype.slice to convert the NodeList to an Array
this.node = Array.prototype.slice.call(
document.querySelectorAll(selector)
)
Here are a couple of optimisations.
(function() {
var pMethods = {
append: function(text) {
// iterate over the collection
this.nodes.forEach(function(node) {
node.innerHTML += text;
})
// return this for chaining
return this
},
click: function(fn) {
// iterate over the collection
this.nodes.forEach(function(e) {
e.addEventListener('click', fn);
});
// return this for chaining
return this
},
find: function(selector) {
return new myPlugin(
// flat map over each of the nodes in the collection
this.nodes.reduce(function(nodes, node) {
return [].concat.apply(nodes, node.querySelectorAll(selector))
}, [])
)
}
};
myPlugin = function(nodes) {
// changed constructor to recievea array of elemnets only
// it's private so won't affect anything else
this.nodes = nodes
};
myPlugin.prototype = pMethods;
this.r = function(selector) {
var nodes = null
// handle creating the object with normal elements
if (selector instanceof HTMLElement) {
nodes = [selector]
}
else {
nodes = [].slice.call(
document.querySelectorAll(selector)
);
}
return new myPlugin(nodes);
};
}());
r('.close')
.click(function(e) {
console.log('alerts suck! ' + e.target.textContent)
r(e.target).find('.child').append(' appended child!')
})
.append(' append works!')
<div class="close">
close
<div class="child">this is the child</div>
</div>
your constructor function (e.g. myPlugin = function(selector) {) should return this instead of this.node
I've got a class that is basically a native Javascript Array, but it raises events when items are added or removed.
hb.extend( {
Classes: {
Collection: hbClass.inherit({
init: function (arr) {
// get the functions we want to retain
var _on = this.on,
_trigger = this.trigger,
_push = this.push,
_remove = this.remove,
_reset = this.reset,
_from = this.fromArray,
_watch = this.watch;
// Set the object up as an Array
this.__proto__ = Array.prototype;
// get the Array functions we want to use
this.arrPush = this.push;
// reapply the old functions
this.push = _push;
this.remove = _remove;
this.reset = _reset;
this.fromArray = _from;
this.on = _on;
this.trigger = _trigger;
this.watch = _watch;
if (arr && (arr.length && typeof arr !== "string")) this.fromArray(arr, true);
},
fromArray: function (arr, stopEvent) {
this.reset();
for (var i = 0, len = arr.length; i < len; i++) {
this.arrPush(arr[i]);
}
if (!stopEvent) this.trigger('change', this);
},
push: function () {
this.arrPush.apply(this, arguments);
this.trigger('add', this);
this.trigger('change', this);
return this;
},
remove: function (from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
this.arrPush.apply(this, rest);
this.trigger('remove', this);
this.trigger('change', this);
return this;
},
reset: function () {
this.length = 0;
this.trigger('change', this);
this.trigger('remove', this);
}
})
}
});
There may be better ways to do it, but it works for me.......except in IE.
In IE at the line this.arrPush.appy(this, arguments); under the push method, it hits a Stack Overflow error.
Specifically:
SCRIPT28: Out of stack space
But this does NOT occur in Firefox or Chrome.
Anyone have any advice?
EDIT
Trigger code:
this.hbClass.prototype.trigger = function(type, data, context) {
var listeners, handlers, i, n, handler, scope;
if (!(listeners = this.listeners)) {
return;
}
if (!(handlers = listeners[type])){
return;
}
for (i = 0, n = handlers.length; i < n; i++){
handler = handlers[i];
if (handler.method.call(
handler.context, this, type, data
)===false) {
return false;
}
}
return true;
}
The issue is probably this line:
this.__proto__ = Array.prototype;
as __proto__ is not supported in some versions of IE. It has been codified in the ES6 specification, but that isn't implemented in some versions of IE. I don't understand exactly how your code works, but the safe way to set a prototype is like this:
Working demo: http://jsfiddle.net/jfriend00/ff99G/
function myClass() {
// add new methods to this instance in the constructor
this.fromArray = function() {};
};
// become an array and get all its methods
myClass.prototype = Array.prototype;
var x = new myClass();
Here's an example of the kind of thing you're doing using .prototype that works in IE:
function log(msg) {
var result = document.getElementById("result");
var div = document.createElement("div");
div.innerHTML = msg;
result.appendChild(div);
}
function myClass() {
var _push = this.push;
this.count = function() {
return this.length;
}
this.trigger = function(type, name) {
var str = type;
if (name) {
str += ", " + name;
}
log(str);
}
this.push = function() {
var retVal = _push.apply(this, arguments);
this.trigger("change", "push");
return retVal;
}
};
// become an array and get all its methods
myClass.prototype = Array.prototype;
var x = new myClass();
x.push("foo");
x.push("whatever");
log(x.count());
I have this code:
(function() {
var base = function (elem) {
var elements = document.querySelectorAll(elem);
return {
elems: elements[0],
on: function (evt, func) {
if(this.elems) this.elems.addEventListener(evt, func, false);
return this;
}
};
};
window.base = window._ = base;
})();
And I can do this:
_('form').on('submit', uploadImage);
But if i do:
_('form').appendChild(input);
i get an error: Object #<Object> has no method 'appendChild'
So how can i use _('element') with native functions and still make it work with the methods in my object?
Give your object an .appendChild function that calls the .appendChild on the element.
(function() {
var base = function (elem) {
var elements = document.querySelectorAll(elem);
return {
elems: elements[0],
on: function (evt, func) {
if(this.elems) this.elems.addEventListener(evt, func, false);
return this;
},
appendChild: function(el) {
this.elems.appendChild(el);
return this;
};
};
window.base = window._ = base;
})();
Side note. If you're only interested in the first element returned from querySelectorAll, you can use querySelector instead.
return {
elems: document.querySelector(elem),
on: function (evt, func) {
// ...
I think you can do this with prototype (not recommended):
// Prototype.js style
var Base = function (selector) {
return document.querySelector(selector);
};
Element.prototype.on = function (e, f) {
this.addEventListener(e, f, false);
return this;
};
elp = Base('#result');
elp.on('click', function () {
console.log(this);
});
elp instanceof Element; // true
elp.innerHTML; // text
Or with an object wrapper:
// jQuery style
var Base = function (selector) {
this[0] = document.querySelector(selector);
return this;
};
Base.prototype.on = function (e, f) {
this[0].addEventListener(e, f, false);
return this;
};
elj = new Base('#result'); // internal new called in jQuery
elj.on('click', function () {
console.log(this);
});
elj instanceof Base; // true
elj[0] instanceof Element; //true
elj[0].innerHTML; // text
var Stuff = (function() {
return {
getId: function (id) {
return document.getElementById(id);
},
attr: function (ele, attr, newVal) {
var newVal = newVal || null;
if (newVal) {
ele.setAttribute(attr, newVal);
} else {
var attrs = ele.attributes,
attrslen = attrs.length,
result = ele.getAttribute(attr) || ele[attr] || null;
if (!result) {
for (var i = 0; i < attrslen; i++)
if (attr[i].nodeName === attr) result = attr[i].nodeValue;
}
return result;
}
}
}
})();
With this html:
<div id="foo" data-stuff="XYZ">Test Div</div>
The current implementation:
(function ($) {
console.log(
$.attr($.getId('foo'), 'data-stuff') // XYZ
);
})(Stuff);
How do I rewrite my library above to make it chain like the following?
(function ($) {
console.log(
$.getId('foo').attr('data-stuff') // XYZ
);
})(Stuff);
Building specifically from your code, you could do this:
Example: http://jsfiddle.net/patrick_dw/MbZ33/
var Stuff = (function() {
return {
elem:null,
getId: function (id) {
this.elem = document.getElementById(id);
return this;
},
attr: function (attr, newVal) {
var newVal = newVal || null;
var ele = this.elem;
if (newVal) {
ele.setAttribute(attr, newVal);
} else {
var attrs = ele.attributes,
attrslen = attrs.length,
result = ele.getAttribute(attr) || ele[attr] || null;
if (!result) {
for (var i = 0; i < attrslen; i++)
if (attr[i].nodeName === attr) result = attr[i].nodeValue;
}
return result;
}
}
}
})();
This adds a elem property which stores the result of the getId. The getId returns this, which is the object referenced by Stuff containing all the methods. As such, you can directly call attr from the returned object.
I would imagine you would want to include a return value for attr of this for when the attribute is being set, so that chaining can continue.