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.
Related
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);
...
};
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);
I want to create a js class that resembles simple music playlist (Array). I want to instantiate this playlist with IDs, each ID being a track ID in my database. I have interface like this:
function Playlist() {
Playlist.prototype.current = 0;
Playlist.prototype.prev = function() {
if (this.current-1 < 0) {
return null;
}
return this[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.length) { // length is index + 1
return null;
}
return this[++this.current];
};
Playlist.prototype.seek = function(id) {
for (i in this) {
if (this[i] == id) {
this.current = parseInt(i);
return i;
}
}
return false;
};
Playlist.prototype.getCurrent() {
return this.current;
};
};
The code above DOES NOT do what I want, because I imagine it as class that has it's method defined, that can be instantiated like this:
var newPlaylist = Playlist(2,3,5,10/* those are my ids */);
And currently the only way I've found is something like:
Playlist.prototype = new Array(2, 3, 5, 10/* those are my ids */);
Which does not make any sense since it can be instantiated as different objects. Any ideas are very welcome!
Best way - nested array;
function Playlist() {
this.current = 0;
this.list = Array.prototype.slice.call(arguments);;
};
Playlist.prototype.prev = function() {
if (this.current-1 < 0) {
return null;
}
return this.list[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.list.length) { // length is index + 1
return null;
}
return this.list[++this.current];
};
Playlist.prototype.getCurrent = function() {
return this.current;
};
var newPlaylist = new Playlist(2,3,5,10/* those are my ids */);
But you can't use list[i] to get element by index, but you just need add at() method to your class that provide similar functionality
PlayList.prototype.at(i) {
return this.list[i];
}
Since you cannot subclass Array, you should build wrapper objects with your Playlist constructor:
Playlist = (function() {
function Playlist(list) {
this.list = list || [];
}
Playlist.prototype.current = 0;
Playlist.prototype.prev = function() {
if (this.current <= 0)
return null;
return this.list[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.length)
return null;
return this.list[++this.current];
};
Playlist.prototype.seek = function(id) {
return this.list.indexOf(id);
};
return Playlist;
})();
Usage:
var newPlaylist = new Playlist([2,3,5,10]);
What is the best way to have js return undefined rather than throw an error when a parent property does not exist?
Example
a = {}
b = a.x.y.z
// Error: Cannot read property 'value' of undefined
// Target result: b = undefined
You have to check for the existence of each property:
var b;
if (a.x && a.x.y && a.x.y.z) {
b = a.x.y.z
}
Or, simliar to another poster's "safeGet" function:
var get = function (obj, ns) {
var y = ns.split('.');
for(var i = 0; i < y.length; i += 1) {
if (obj[y[i]]) {
obj = obj[y[i]];
} else {
return;
}
}
return obj;
};
Use:
var b = get(a, 'x.y.z');
try {
a = {}
b = a.x.y.z
}
catch (e) {
b = void 0;
}
I would go for slightly verbose:
var b = ((a.x || {}).y || {}).z
you could write a safeGet helper function, something like:
edited for drilldown as suggested in comments by arcyqwerty
var getter = function (collection, key) {
if (collection.hasOwnProperty(key)) {
return collection[key];
} else {
return undefined;
}
};
var drillDown = function (keys, currentIndex, collection) {
var max = keys.length - 1;
var key = keys[currentIndex];
if (typeof collection === 'undefined') {
return undefined;
}
if (currentIndex === max) {
return getter(collection, key);
} else {
return drillDown(keys, currentIndex + 1,
getter(collection, key));
}
};
var safeGet = function (collection, key) {
if (key.indexOf(".") !== -1) {
return drillDown(key.split("."), 0, collection);
} else {
return getter(collection, key);
}
};
a = { x: 1 };
b = safeGet(a, 'x.y.z');
http://jsfiddle.net/YqdWH/2/
I build a prototype that handle pages, I successfully add (push), but can get the data, I failed:
var foundImageIndex = Pages.indexFirst(function (item) { if (item.PageID == PageID) return true; });
Here the javascript page handler:
var Pages = new Array();
PageContainer = function () //constructor for the proxy
{
// this._baseURL = url;
};
PageContainer.prototype =
{
AddPage: function (data) {
if (data == null) return;
Pages.push({ PageID: data.PageID, SegmentID: data.SegmentID });
},
GetPage: function (PageID) {
alert('getPage('+PageID+')=' + JSON.stringify(Pages));
var foundImageIndex = Pages.indexFirst(function (item) { if (item.PageID == PageID) return true; });
var dt = { PageID: Pages[foundImageIndex].PageID, SegmentID: Pages[foundImageIndex].SegmentID };
return dt;
}
};
I call from other js as following:
var gPageContainer = new PageContainer();
for (var i = 0; i < SegStruct.SegmentsCount; i++) {
var segRClass = //get from webservice
gPageContainer.AddPage({ PageID: i, SegmentID: segRClass.SegmentID });
}
I trying to call: gPageContainer.GetPage(1); but it failed in GetPage: function (PageID) it returns -1 in:
var foundImageIndex = Pages.indexFirst(function (item) { if (item.PageID == PageID) return true; });
foundImageIndex always -1
why?
Simply add the following before the constructor:
if (typeof Array.prototype.indexFirst == 'undefined') {
Array.prototype.indexFirst = function (validator) {
for (var i = 0; i <= this.length - 1; i++) {
if (validator(this[i])) {
return i;
}
}
return -1;
};
}