Converting a javascript library to chain methods - javascript

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.

Related

How to create selector like jQuery with additional functions?

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>

`this` reference gets lost in prototype chain

This is quite extensive, but fun and detailed.
I have defined two "classes" as follow, using the standard JavaScript ways of prototypes:
Source
function Source() {
this._sourceGuid = "";
this._periodGuid = "";
this._eventName = "";
this._eventKind = 0;
if (arguments.length > 0) {
if (arguments[0].__proto__ === this.__proto__) {
this.sourceGuid(arguments[0].sourceGuid());
this.periodGuid(arguments[0].periodGuid());
this.eventName(arguments[0].eventName());
this.eventKind(arguments[0].eventKind());
} else {
this.sourceGuid(arguments[0].sourceGuid);
this.periodGuid(arguments[0].periodGuid);
this.eventName(arguments[0].eventName);
this.eventKind(arguments[0].eventKind);
}
}
};
Source.prototype.sourceGuid = function (value) {
if (arguments.length > 0) {
this._sourceGuid = value
} else {
return this._sourceGuid;
}
};
Source.prototype.periodGuid = function (value) {
if (arguments.length > 0) {
this._periodGuid = value
} else {
return this._periodGuid;
}
};
Source.prototype.eventName = function (value) {
if (arguments.length > 0) {
this._eventName = value
} else {
return this._eventName;
}
};
Source.prototype.eventKind = function (value) {
if (arguments.length > 0) {
this._eventKind = value
} else {
return this._eventKind;
}
};
Source.prototype.toJSON = function() {
return {
sourceGuid: this.sourceGuid(),
periodGuid: this.periodGuid(),
eventName: this.eventName(),
eventKind: this._eventKind()
};
};
AnalogCamerasSource extends Source
function AnalogCamerasSource() {
this.__proto__.apply(this, arguments);
this._serverGuid = "";
this._cameraId = 0;
this._waitingTime = 0;
if (arguments.length > 0) {
if (arguments[0].__proto__ === this.__proto__) {
this.serverGuid(arguments[0].serverGuid());
this.cameraId(arguments[0].cameraId());
this.waitingTime(arguments[0].waitingTime());
} else {
this.serverGuid(arguments[0].serverGuid);
this.cameraId(arguments[0].cameraId);
this.waitingTime(arguments[0].waitingTime);
}
}
};
AnalogCamerasSource.prototype = Source;
AnalogCamerasSource.prototype.serverGuid = function (value) {
if (arguments.length > 0) {
this._serverGuid = value
} else {
return this._serverGuid;
}
};
AnalogCamerasSource.prototype.cameraId = function (value) {
if (arguments.length > 0) {
this._cameraId = value
} else {
return this._cameraId;
}
};
AnalogCamerasSource.prototype.waitingTime = function (value) {
if (arguments.length > 0) {
this._waitingTime = value
} else {
return this._waitingTime;
}
};
AnalogCamerasSource.prototype.toJSON = function() {
var json = this.__proto__.toJSON();
json.serverGuid = this.serverGuid();
json.cameraId = this.cameraId();
json.waitingTime = this.waitingTime();
return json;
};
Now I need an instance of AnalogCamerasSource, and I am trying to create it as simply as that:
var data = {"sourceGuid":"{05A00E05-F30D-497D-A272-156F135E1486}","periodGuid":"{8A071454-B473-4937-9C54-4899F866D7FA}","eventName":"signal-lost","eventKind":3,"serverGuid":"{9976B57D-486B-4BCA-8432-78D7C8EDB52B}","cameraId":4,"waitingTime":5};
var c = new AnalogCamerasSource(data);
Now, the line this.__proto__.apply(this, arguments); is responsible for calling the parent's constructor, and it seems it should do it fine, but at the moment it should call a parent's function in the parent's constructor, the following error is thrown:
TypeError: this.sourceGuid is not a function
at Function.Source (http://localhost:8081/js/app/events/model/0-Source.js:14:12)
at AnalogCamerasSource (http://localhost:8081/js/app/events/model/AnalogCamerasSource.js:3:33)
Now, here's a picture from Chrome's debugger showing the properties are there but way down the prototype chain, under this.__proto__.prototype when called like this.__proto__.apply(this, arguments) from AnalogCamerasSource.
So, why this.sourceGuid is not a function? How should I call the parent's constructor to have this chain working correctly?
Here's a fiddle: https://jsfiddle.net/98bp8pvh/
Refactored your code (avoid using __proto__ in your code)
function Source() {
var args = arguments[0] || {};
this._sourceGuid = args.sourceGuid || '';
this._periodGuid = args.periodGuid || '';
this._eventName = args.eventName || '';
this._eventKind = args.eventKind || 0;
};
Source.prototype.sourceGuid = function(value) {
if (arguments.length > 0) {
this._sourceGuid = value || '';
} else {
return this._sourceGuid;
}
};
Source.prototype.periodGuid = function(value) {
if (arguments.length > 0) {
this._periodGuid = value || '';
} else {
return this._periodGuid;
}
};
Source.prototype.eventName = function(value) {
if (arguments.length > 0) {
this._eventName = value || '';
} else {
return this._eventName;
}
};
Source.prototype.eventKind = function(value) {
if (arguments.length > 0) {
this._eventKind = value || 0;
} else {
return this._eventKind;
}
};
Source.prototype.toJSON = function() {
return {
sourceGuid: this.sourceGuid(),
periodGuid: this.periodGuid(),
eventName: this.eventName(),
eventKind: this.eventKind()
};
};
function AnalogCamerasSource() {
var args = arguments[0] || {};
this._serverGuid = args.serverGuid || '';
this._cameraId = args.cameraId || 0;
this._waitingTime = args.waitingTime || 0;
this.sourceGuid(args.sourceGuid);
this.periodGuid(args.periodGuid);
this.eventName(args.eventName);
this.eventKind(args.eventKind);
};
AnalogCamerasSource.prototype = Object.create(Source.prototype);
AnalogCamerasSource.prototype.serverGuid = function(value) {
if (arguments.length > 0) {
this._serverGuid = value || '';
} else {
return this._serverGuid;
}
};
AnalogCamerasSource.prototype.cameraId = function(value) {
if (arguments.length > 0) {
this._cameraId = value || 0;
} else {
return this._cameraId;
}
};
AnalogCamerasSource.prototype.waitingTime = function(value) {
if (arguments.length > 0) {
this._waitingTime = value || 0;
} else {
return this._waitingTime;
}
};
AnalogCamerasSource.prototype.toJSON = function() {
var json = Source.prototype.toJSON.call(this);
json.serverGuid = this.serverGuid();
json.cameraId = this.cameraId();
json.waitingTime = this.waitingTime();
return json;
};
var data = {
"sourceGuid": "{05A00E05-F30D-497D-A272-156F135E1486}",
"periodGuid": "{8A071454-B473-4937-9C54-4899F866D7FA}",
"eventName": "signal-lost",
"eventKind": 3,
"serverGuid": "{9976B57D-486B-4BCA-8432-78D7C8EDB52B}",
"cameraId": 4,
"waitingTime": 5
};
var c = new AnalogCamerasSource(data);
console.log(c);
Keep it simple, do your inheritance like this:
AnalogCamerasSource.prototype = Source.prototype;
...and just apply the constructor function of the parent class in the constructor of the subclass:
function AnalogCamerasSource() {
Source.apply(this, arguments);
...
}
There usually isn't any need to reference __proto__ directly.
Try changing AnalogCamerasSource.prototype = Source; to AnalogCamerasSource.prototype = new Source; and this.__proto__.apply(this, arguments); to Source.apply(this, arguments);.
The problem is that with your approach AnalogCameraSource.prototype gets set to a function (constructor for Source object), rather than to an object. This function itself does not have the methods defined on its prototype. If you set AnalogCameraSource.prototype to a new Source object (created with new Source), then AnalogCameraSource.prototype is an object, which is how things ought to be. This puts all methods defined on the Source prototype in the prototype chain of AnalogCameraSource, and the Source constructor is able to run without a hitch.
I solved my problem with this workaround:
I changed the prototype definition:
AnalogCamerasSource.prototype = new Source();
Then I added the following function to AnalogCamerasSource:
AnalogCamerasSource.prototype.super = function() {
Source.apply(this, arguments);
};
And then in the constructor I am calling it as:
function AnalogCamerasSource() {
this.super.apply(this, arguments);
...
};

Javascript object not recognizing function

I have a Javascript class defined as below:
function Node(init, parent) {
this.value = init;
this.children = [];
this.updateChildren(this.value);
}
Node.prototype.updateChildren = function(value) {
this.children.push(value);
};
When I run it.. i receive the error,
this.updateChildren() is not defined.
Any clue what I am missing here ?
Full code here :
'use strict';
var millionNumbers = [];
for (var i = 2; i< 1000000; i++) {
if (!millionNumbers[i]) {
millionNumbers[i] = new Node(i, null);
}
}
function Node(init, parent) {
this.value = init;
this.children = [];
if (parent) {
this.parent = parent;
}
var newValue;
if (isEven(this.value)) {
newValue = this.value/2;
} else {
newValue = (this.value * 3) + 1;
}
//whether newValue is 1 or something else, we still have add it to the children list
this.updateChildren(newValue);
if (millionNumbers[newValue]) {
var chainedNode = millionNumbers[newValue];
this.children.concat(chainedNode.children);
}
if (newValue === 1) {
this.endChain();
} else {
new Node(newValue, this);
}
}
Node.prototype.updateChildren = function(value) {
this.children.push(value);
if(this.parent) {
this.parent.updateChildren(value);
}
};
Node.prototype.endChain = function() {
if (!millionNumbers[this.value]) {
millionNumbers[this.value] = this;
this.parent = null;
}
};
function isEven(value) {
if (value % 2 === 0) {
return true;
} else {
return false;
}
}
The for loop instantiates Node objects before Node.prototype.updateChildren is set, so within the Node constructor, this.updateChildren is still undefined.
To fix the problem, just move the for loop to the end of the file.
Also: Best of luck with the Collatz conjecture!
think you are calling these classes without defining them first.
You can try this way:
function updateChildren(value) {
this.children.push(value);
};
function Node(init, parent) {
this.value = init;
this.children = [];
this.updateChildren(value);
}
Node.prototype.updateChildren(value);

How this and $(this) end up being the same when extending jQuery?

I'm trying to work with a plugin which extends jQuery like so:
$.extend({
StatelessDeferred: function () {
var doneList = $.Callbacks("memory"),
promise = {
done: doneList.add,
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function (obj) {
var i,
keys = ['done', 'promise'];
if (obj === undefined) {
obj = promise;
} else {
for (i = 0; i < keys.length; i += 1) {
obj[keys[i]] = promise[keys[i]];
}
}
return obj;
}
},
deferred = promise.promise({});
deferred.resolveWith = doneList.fireWith;
return deferred;
}
});
Problem is (and I'm not even sure it's caused here), after the callback loads, inside a done callback, both this and $(this) are the same, so I end up for example with: this === $(this) === $(document).
I'm not really sure I understand what's being extended. The plugin works fine with it except for the false assignment.
Question:
Could the above extension be causing this === $(this) === $(document)?
EDIT:
Full plugin (120lines):
"use strict";
(function (window, $) {
$.extend({
StatelessDeferred: function () {
var doneList = $.Callbacks("memory"),
promise = {
done: doneList.add,
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function (obj) {
var i,
keys = ['done', 'promise'];
if (obj === undefined) {
obj = promise;
} else {
for (i = 0; i < keys.length; i += 1) {
obj[keys[i]] = promise[keys[i]];
}
}
return obj;
}
},
deferred = promise.promise({});
deferred.resolveWith = doneList.fireWith;
// All done!
return deferred;
}
});
var routes = [],
current_priority = 0,
methods = {
add: function (pattern, priority) {
var i = 0,
inserted = false,
length = routes.length,
dfr = $.StatelessDeferred(),
context = $(this),
escapepattern,
matchingpattern;
if (priority === undefined) {
priority = 0;
}
if (pattern !== undefined) {
// http://simonwillison.net/2006/Jan/20/escape/
escapepattern = pattern.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
matchingpattern = escapepattern
.replace(/<int:\w+>/g, "(\\d+)")
.replace(/<path:\w+>/g, "(.+)")
.replace(/<\w+>/g, "([^/]+)");
while (!inserted) {
if ((i === length) || (priority >= routes[i][2])) {
routes.splice(i, 0, [new RegExp('^' + matchingpattern + '$'), dfr, priority, context]);
inserted = true;
} else {
i += 1;
}
}
}
return dfr.promise();
},
go: function (path, min_priority) {
var dfr = $.Deferred(),
context = $(this),
result;
if (min_priority === undefined) {
min_priority = 0;
}
setTimeout(function () {
var i = 0,
found = false,
slice_index = -1,
slice_priority = -1;
for (i = 0; i < routes.length; i += 1) {
if (slice_priority !== routes[i][2]) {
slice_priority = routes[i][2];
slice_index = i;
}
if (routes[i][2] < min_priority) {
break;
} else if (routes[i][0].test(path)) {
result = routes[i][0].exec(path);
dfr = routes[i][1];
context = routes[i][3];
current_priority = routes[i][2];
found = true;
break;
}
}
if (i === routes.length) {
slice_index = i;
}
if (slice_index > -1) {
routes = routes.slice(slice_index);
}
if (found) {
dfr.resolveWith(
context,
result.slice(1)
);
} else {
dfr.rejectWith(context);
}
});
return dfr.promise();
},
};
$.routereset = function () {
routes = [];
current_priority = 0;
};
$.routepriority = function () {
return current_priority;
};
$.fn.route = function (method) {
var result;
if (methods.hasOwnProperty(method)) {
result = methods[method].apply(
this,
Array.prototype.slice.call(arguments, 1)
);
} else {
$.error('Method ' + method +
' does not exist on jQuery.route');
}
return result;
};
}(window, jQuery));
So I can use this as a router and set a route like so:
$(".element").add("route", "/foo/bar/<path:params>", 2).done(function(params){
// do something, for example
console.log(this);
console.log($(this));
console.log("which will be the same = $('.element'));
});
Hope it's more clear now.
Thanks for having a look.
From the documentation:
If only one argument is supplied to $.extend(), this means the target argument was omitted. In this case, the jQuery object itself is assumed to be the target.
Most cases, jQuery is attached to your document with : $(document).ready()
I think what's happening is jQuery object is wrapped onto the document. Then you merged it with $.extend(myObject). This returns a single object that is both jQuery object and myObject.

How to put and get the value by the key using object in JavaScript?

How to use the functionality of dictionary in JavaScript?
Look at this question the specified way is working, but I am setting the function instance as a key like this:
Scale = function ()
{
this.Collections = {};
this.IndexTracker = {};
this.UpdateIndex = function ()
{
var index = 0;
for ( var i = 0; i < this.Collections.length; i++ )
{
this.SetIndex( this.Collections[i], index++ );
}
}
this.SetIndex = function ( obj, value )
{
this.IndexTracker[obj] = value;
}
this.GetIndex = function ( obj, value )
{
return this.IndexTracker[obj];
    }
}
this.Collections will hold the some function instance.
The problem here is the function instance is overwritten by the next function instance in this.Collections. The the length of the Collections always is 1. How to solve this?
This is an example:
var Scale = function () {
var _Collections = {},
_IndexTracker = {},
ret = function () {
function UpdateIndex() {
var index = 0,i,l;
for (i = 0,l=_Collections.length; i < l; i++) {
this.SetIndex(_Collections[i], index++);
}
}
function SetIndex(obj, value) {
_IndexTracker[obj] = value;
}
function GetIndex(obj, value) {
return _IndexTracker[obj];
}
return {
UpdateIndex : UpdateIndex,
SetIndex : SetIndex,
GetIndex : GetIndex
};
};
return ret;
}();

Categories