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
Related
How do you implement delay like the jQuery Library? - I know this question has been asked so many times, but haven't seen anyone implement it using async/await or ES6 stylistics. let me know if you have ideas
//create a jquery like library
class DOM {
constructor(selector){
this.elements = [];
if(!selector){
return;
} else if(selector === 'document' || selector === 'window'){
this.elements = [selector];
} else {
this.elements = Array.from(document.querySelectorAll(selector));
}
}
on(){
console.log('on');
return this;
}
get(){
console.log('get');
return this;
}
delay(ms, callback){
//how to implement this? how to chain result of callback onto next chain?
console.log('delay');
const promise = Promise.resolve();
return promise.then(function(resolve) {
setTimeout(function(){
resolve(this);
}, ms);
});
}
}
const $ = function(selector) {
return new DOM(selector);
}
$('document').on().delay(10000).get()
You probably don't need promises or async/await at all, I think you can create a Proxy object that intercepts subsequent call.
The idea is that, when .delay(duration) is called, it'll return a proxy object instead of the class instance. This proxy object will intercept a method call, set time out for duration, then call the method with the original class instance.
class J {
constructor(selector) {
this.$element = document.querySelector(selector)
}
delay(duration) {
const proxy = new Proxy(this, {
get: (target, prop) => {
const f = target[prop]
return (...args) => {
setTimeout(() => {
return f.apply(target, [...args])
}, duration)
// return the class instance again, so subsequent call & delay still works
return this
}
}
})
return proxy
}
text(content) {
this.$element.textContent = content
return this
}
}
const $ = selector => new J(selector)
$('#test').text('hello').delay(1000).text('world')
<div id="test"></div>
You could maintain a queue of functions still to execute on the selected element(s). That way you can allow multiple delays in the chain and also allow the client to stop the action.
A proxy can be used to "decorate" the methods which make sense to be delayed, so that they can be put in the queue instead of executed whenever a timer is still active.
Here is how that could look:
class DOM {
constructor(selector) {
this.elements = typeof selector === "object" ? [selector]
: selector === 'document' || selector === 'window' ? [document]
: Array.from(document.querySelectorAll(selector));
this.delayed = false;
this.queue = [];
const proxy = new Proxy(this, {
get(obj, prop) {
return !["css","show","hide","delay"].includes(prop) || !obj.delayed ? obj[prop]
: function (...args) {
obj.queue.push(() => proxy[prop](...args));
return this;
}
}
});
return proxy;
}
each(cb) {
this.elements.forEach(cb);
return this;
}
css(name, value) {
return this.each(elem => elem.style[name] = value);
}
show() {
return this.css("display", "");
}
hide() {
return this.css("display", "none");
}
on(eventType, cb) {
return this.each(elem => elem.addEventListener(eventType, cb.bind(elem)));
}
delay(ms) {
this.delayed = true;
setTimeout(() => {
this.delayed = false;
while (this.queue.length && !this.delayed) this.queue.shift()();
}, ms);
return this;
}
stop() {
this.queue.length = 0;
return this;
}
}
const $ = selector => new DOM(selector);
const $span = $('#demo').hide();
for (let i = 0; i < 100; i++) {
$span.delay(500).show()
.delay(500).css("color", "red")
.delay(500).css("color", "blue")
.delay(500).hide();
}
$("#stop").on("click", function () {
$span.stop();
$(this).hide();
});
<div>This is a <span id="demo">colorful </span>demo</div>
<button id="stop">Stop</button>
I have a function that I'd like to apply to several elements of the same class. It's a scroll page function and I need it to only execute once. So I put it in a wrapper. It works but I'd like to be able to just add a class to an element and have it act upon that element. I tried iterating through the elements and using addClass to add a unique class with their respective index added to the end but this did not work. What I have now only acts upon the first element with the "split" class.
//EXECUTES ONLY ONCE
function once(fn, context) {
var result;
return function() {
if(fn) {
result = fn.apply(context || this, arguments);
fn = null;
}
return result;
};
}
// Usage
//var split1 = once(function() {
// fadeInText(".split1");
//});
const handlers = $(".split").toArray()
.map(s => ({ el: $(s), show: once(() => fadeInText(s)) }));
$(window).scroll(function() {
for(const {el, show} of handlers) {
if( $(this).scrollTop() + $(window).height() > el.offset().top)
show();
}
});
//SPLITTEXT
var f = ".split",
fadeInText(f);
function fadeInText(l) {
var el = document.querySelector(l);
var split = el.dataset.split;
var text = new SplitText(el, { type: split });
var tl = new TimelineMax({ paused: false });
var splitEls = text[split];
var wrapEls = function wrapEls(els) {
return els.map(function (el) {
return '<span style="display: inline-block">' + el.innerText + '</span>';
});
};
var wrapped = wrapEls(splitEls);
splitEls.forEach(function (el, i) {
el.style.overflow = 'hidden';
el.innerHTML = wrapped[i];
});
var masks = splitEls.map(function (el) {
return el.querySelector('span');
});
tl.staggerFrom(masks, 1.25, { skewY: 4, y: '200%', ease: Expo.easeOut, delay: 0.9 }, 0.1, 'in');
return l;
}
if you want a pure js application try this:
elements = document.querySelectorAll('.class');
elements.forEach((element, key) => {
//your code
})
$('.class').each(function(){ /* 'this' means the element */})
Inside the function, this is scoped to the element you're on in the iteration.
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>
Simply put
I have a tree structure made of objects.
Is it possible to build that tree and add to each object a reference to their parent ?
I know referencing works with objects, but i'm not sure if it would in that case?
I would like to be able to write something like this
currentLevel = this.getParent();
another exemple would be
this.getChildList().addChild({name: test,parent: this})
Without having copies and creating multiple tree from the first one.
2nd question
How would referencing works with array? Are they considered objects or does it depends on their content?
3nd question
Would saving the tree in the browser's cache, via string-JSON serialisation destroy the references?
You can do this be creating a "TreeNode" class:
var TreeNode = (function(){
//keep track of parent node
TreeNode.prototype.parent = null;
//keep track of children
TreeNode.prototype.children = [];
function TreeNode(parent) {
if(parent !== undefined) {
if(this.setParent(parent)) {
this.parent.addChild(this);
}
}
//...
}
TreeNode.prototype.setParent = function(parent) {
//add some sort of check to make sure it is a `TreeNode`
if(parent instanceof TreeNode) {
this.parent = parent;
return true;
}
return false;
}
TreeNode.prototype.addChild = function(child) {
//add some sort of check to make sure it is a `TreeNode`
if(child instanceof TreeNode) {
this.children.push(child);
child.setParent(this);
}
}
TreeNode.prototype.getParent = function(){
return this.parent;
}
TreeNode.prototype.getChildren = function(){
return this.children;
}
return TreeNode;
})();
And then you can expand from that.
Example Code:
var node_a = new TreeNode();
var node_b = new TreeNode(node_a);
var node_c = new TreeNode(node_a);
console.log(node_a.getParent(), node_c.get_parent()); //null , node_a
console.log(node_a.getChildren()); //[node_b, node_c]
This is just a start, it needs waaaaaaaaaay more expansion :-)
Okay, so there are most likely frameworks out there, but I wrote a quick thing which supports JSON serialisation and the reverse (via it's own methods). I took base inspiration from Neal's answer. Example
var a = new MyTreeNode('a'), // make some nodes
b = new MyTreeNode('b'),
c = new MyTreeNode('c');
a.addChild(b).addChild(c); // a parent of b parent of c
c.getParent() === b; // true
var str = a.toJSON(); // "{"nodeName":"a","childNodes":[{"nodeName":"b","childNodes":[{"nodeName":"c","childNodes":[]}]}]}"
MyTreeNode.parseJSON(str); // MyTreeNode (same structure as before)
Full code
/* MyTreeNode(String nodeName)
Instance Properties
- nodeName, String
- childNodes, Array of MyTreeNodes
- parentNode, MyTreeNode
Instance Methods
- addChild(MyTreeNode node), child MyTreeNode
- removeChild(MyTreeNode node), child MyTreeNode
- getParent, parent MyTreeNode
- getChildList, Array of MyTreeNodes
- serialise, JSON-safe Object
- toJSON, String
Constructor Methods
- deserialise(Object serialised), MyTreeNode
- parseJSON(String JSONString), MyTreeNode
*/
var MyTreeNode = (function () {
function MyTreeNode(nodeName) {
nodeName && (this.nodeName = nodeName);
this.childNodes = [];
}
MyTreeNode.prototype.parentNode = null;
MyTreeNode.prototype.childNodes = [];
MyTreeNode.prototype.nodeName = '';
// getters
MyTreeNode.prototype.getChildList = function () {
return this.childNodes = [];
};
MyTreeNode.prototype.getParent = function () {
return this.parentNode;
};
// add/remove
MyTreeNode.prototype.removeChild = function (node) {
var i = this.childNodes.indexOf(node);
if (node.parentNode !== this || i == -1)
throw new ReferenceError('node is not a child of this');
this.childNodes.splice(i, 1);
node.parentNode = null;
return node;
};
MyTreeNode.prototype.addChild = function (node) {
if (node.parentNode) node.parentNode.removeChild(node);
node.parentNode = this;
this.childNodes.push(node);
return node;
};
// JSON
MyTreeNode.prototype.serialise = function () {
var o = {
nodeName: this.nodeName,
childNodes: []
}, i;
for (i = 0; i < this.childNodes.length; ++i) {
o.childNodes.push(this.childNodes[i].serialise());
}
return o;
};
MyTreeNode.prototype.toJSON = function () {
return JSON.stringify(this.serialise());
};
MyTreeNode.deserialise = function (o) {
var p = new MyTreeNode(o.nodeName), i;
for (i = 0; i < o.childNodes.length; ++i) {
p.addChild(MyTreeNode.deserialise(o.childNodes[i]));
}
return p;
};
MyTreeNode.parseJSON = function (str) {
var o = JSON.parse(str);
return MyTreeNode.deserialise(o);
};
return MyTreeNode;
}());
You could traverse your object and add parent properties to every subobject:
function addParents(obj) {
var name;
for (name in obj) {
if (typeof obj[name] === "object") {
addParents(obj[name]);
obj[name].parent = obj;
}
}
}
var obj = {
g: {
k: [
{
r : 1
},
{
r : 1
}
],
j: {
h: 1
}
}
};
addParents(obj);
console.log(obj.g.parent === obj); //true
console.log(obj.g.k.parent === obj.g); //true
console.log(obj.g.k[1].parent === obj.g.k); //true
console.log(obj.g.j.parent === obj.g); //true
And if you want to add objects later on, you could use something like this:
function addChild(obj, child, name){
obj[name] = child;
child.parent = obj;
}
addChild(obj.g, {t:1}, "xy");
console.log(obj.g.xy.parent === obj.g); //true
FIDDLE
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