I've been programming in JavaScript for a couple of years, but I've never understood how different techniques work in JavaScript, only that it works..
Now, after learning properly how prototypes and constructors work, I took a look at jQuery to learn something before I set out to make my own (not publicly accessible) plugin for my own website.
The problem is just that I don't understand how it works. Here is a almost working sample:
http://jsfiddle.net/3zWvR/1/
(function() {
test = function(selector) {
return new test.prototype.init(selector);
}
test.prototype = {
init: function(selector) {
alert("init ran");
if (!arguments[0]) {
return this;
}
}
}
// As I understand the jQuery code, the next line should really be
// test.prototype = {
test.prototype.init.prototype = {
send: function() {
alert("send ran");
}
}
window.ob = test;
})()
ob().send();
I've commented a line in there that shows what I think really should be there if I do it like jQuery. But I'm not able to replicate it so that you could do ob.method() either...
How is the jQuery "framework" or skeleton built and how does it work?
Well, your question is very interesting, and it's something that has been on my head since I started to look at the source code of jQuery. Your code should work as you wanted to if you add this:
test.prototype.init.prototype = test.prototype;
test.send = test.prototype.send = function(){
alert("send ran");
};
instead of this:
test.prototype.init.prototype = {
send: function() {
alert("send ran");
}
};
You are probably saying "I already know that, you are just adding the method send() to the test object and to the prototype of itself", but doing it this way, you are doing exactly what you want: make that ob, ob() and all the variables created with it, like var somevar = ob() have the method send().
If you take a look at the jQuery source code, they use the method extend() to extend the jQuery object. Looking at some of the .extend() calls, you will see that if one method/property is added only to the jQuery.fn object (that is a shorthand to the prototype), the jQuery object doesn't have that method/property. You can see this if you type in a console jQuery.off, it will return undefined, but the method off exists in the jQuery.fn object (type jQuery.fn.off in a console and you will see it).
If you think in jQuery, when you write a plugin, you start by doing jQuery.fn.plugin =, so you add your plugin to the objects created by jQuery, but your plugin is not accessible directly from jQuery.
Think of jQuery as a constructor that also has a lot of utilities. The object returned when you do $(selector) have all the methods in jQuery.fn but not all of the jQuery object, only the ones that they have added into the jQuery.fn.
Related
Right now I have about 3 seperate javascript "classes". I call them like this;
ajax(parameters).success().error()
getElement('selector').height(400).width(400).remove();
customAlert({object with arguments});
This not only feels like random function calling, but will be likely to give me some naming issues.
Then I thought: How does jQuery do it?
Well, I have no idea. I've tried googling the subject but so far I haven't found any results of how to make this happen. Only results of how to add prototypes and such...
The basic idea of my classes is like this:
var getElement = function(selector){
if(!(this instanceof getElement))
{
return new getElement(selector);
}
//Do element getting
return this;
};
getElement.prototype = {
name: function,
name: function,
//etc.
};
Now, this is kind of working perfectly fine, but I'd like to "prefix" and scope my functions within a wrapper class. I want to call my functions like this:
wrap.getElement(selector);
wrap.ajax(parameters).success().error();
wrap.customAlert({object with arguments});
However, whenever I try it, I bump into at least one kind of error or issue, like;
losing the this scope within my classes
Being unable to add prototype functions to my classes
The entire wrapper class reinstantiating with every function call
being unable to create new object() because the scope isn't right anymore
Also, if at all possible I would like to not re-initialize the wrapper class every time. (seems wildly inefficient, right?)
So I'd want 1 instance of the wrapper class, while the classes within it get their new instances and just do their thing. Is this even possible or am I just dreaming here?
This is the way I've tried it so far;
//It would probably be easier to use an object here, but I want it to
//default to wrap.getElement(selector) when it's just wrap(selector),
//without re-instantiating the old class
var wrap = function(selector){
//Check if docready and such here
}
wrap.prototype.getElement = function(){
// the getElement class here
}
//This is where it starts going wrong. I can't seem to re-add the prototypes
//back to the getElement class this way.
wrap.getElement.prototype = {
name:function,
name:function,
//etc
}
//I can call wrap().getElement(); now, but not the prototypes of
getElement().
//Also, I still have wrap() while I'd want wrap.getElement.
Then I "solved" the issue of having to do wrap() by putting it into a variable first.
var test = new wrap();
test.getElement(selector); // works and only inits once!
//However, I did have to remove the 'new instance' from my getElement()
I have also tried it this way, but this just gave me errors on top of errors of which I didn't really know why;
(function(wrap) {
console.log("init")
this.getElement = function() {
return "test";
};
})(wrap);
// Gives me "wrap is undefined" etc.
And last but not least, I have tried it this way;
var wrap = new function() {
return this;
};
wrap.getElement = function(){};
//This works perfectly fine
wrap.getElement.prototype.css = function(){};
//This will cause "getElement.css is not a function"
So yeah, I'm kind of stuck here. There are many ways to get past this in ES6, I've found. I am however not willing to move to ES6 yet (Because I don't use anything that still needs an interpreter). So it has to be ES5.
The easiest way to wrap your modules in a namespace and keep the classes intact is to use the "Revealing module pattern".
It uses an immediately invoked function expression (IIFE) to set up all the functions with a private scope and then returns just the functions in a new object (which works as a namespace for your module)
var wrap = (function() {
function getElement() {...}
function moreFunctions() {...}
function etcFuncitons() {...}
return {
getElement: getElement,
moreFunctions: moreFunctions,
etcFuncitons: etcFuncitons
};
})();
To answer your second question
I would like to be able to call wrap() by itself as well. I want it to forward automatically to getElement(). Is this hard to do? Is this possible with this construction? Because this looks very easy to maintain and I'd love to keep it like your answer. - Right now it will reply wrap() is not a function
I haven't tested this but you should be able to attach the functions directly to a returned wrapper function. This should avoid the issue of shared this by adding them to the prototype
var wrap = (function() {
function getElement() {...}
function moreFunctions() {...}
function etcFuncitons() {...}
function wrap(selector) {
return new getElement(selector);
}
wrap.getElement = getElement;
wrap.moreFunctions = moreFunctions;
wrap.etcFuncitons = etcFuncitons;
return wrap;
};
})();
This works because everything is an object in javascript, even functions haha
I have this really old unmaintained plugin which in a nutshell is structured like this:
The plugin itself work fine, but I need to access some of the prototype methods.
var $el = $.find('myEl');
$el.searchlight(foo, bar);
$el.clearResults() //this throws an exception
When I call a prototype method I get an exception that the method doesn't exist. Did I get something completely wrong with prototype or am I just using it the wrong way.(I can hack the library if that's required since it's unmaintained for 7 years now).
An additional question would be how I can make the initializer return the element itself, would I just have to add 'return this.each' as last statement in $.fn.searchlight?
To solve this you could store the instance of the plugin within a data attribute on the element. You can then call methods on that function as required, something like this:
// in plugin:
$.fn.searchLight = function(url, options) {
return this.each(function() {
$(this).data('searchlight', new SearchLight(this, url, options));
});
};
// in the calling code, instantiate
var $el = $('.searchLightElement').searchLight(foo, bar);
// then use the methods of the plugin
$el.data('searchlight').clearResults();
Working example
I have a question regarding the structure of a jQuery plugin that I found.
For better understanding, here is a simplified example of the plugins structure:
// Regular constructor function
function MyPlugin() {
this.myValue = "My Value";
}
// Methods on the prototype
MyPlugin.prototype.showValue = function() {
alert($.myplug.getValue());
}
MyPlugin.prototype.getValue = function() {
return this.myValue;
}
// jQuery plugin
$.fn.myplug = function() {
// Why is is possible to access $.myplug here although it's not created yet?
return this.each(function() {
$(this).html($.myplug.getValue());
});
};
// Create new MyPlug instance
$.myplug = new MyPlugin();
// Calling the jQuery plugin on a DOM element
$('div').myplug();
For the most part, I get what is happening. The actual plugin logic seems to be written as a normal JavaScript "class".
This is followed by a jQuery plugin definition – I think, actually, some new method is added to jQuery's prototype. This is where things get tricky to me:
How is is possible to access the class instance inside the plugin, although the class is instantiated after the plugin definition? Is there a mechanism at work similar to variable hoisting?
In case you want to try something, here is a Fiddle of the example: http://jsfiddle.net/kq8ykkga/
$(this).html($.myplug.getValue()); isn't evaluated until you call $('selector').myplug(), executing the function body.
I'm trying to follow this article to write a simple jQuery plugin: http://brolik.com/blog/how-to-create-a-jquery-plugin/
I seem to always get the following error in my console:
HTML
<div id="prod-part">TODO write content</div>
Javascript
(function($){
$.blogPost = function(el, options) {
var base = this;
base.$el = $(el);
base.el = el;
base.$el.data('blogPost', base);
base.init = function(){
console.log("hello");
};
};
})(jQuery);
$(function () {
$('#prod-part').blogPost();
});
Here is a simple jsfiddle which still creates the issue. I'm not sure If I am calling on the plug-in incorrectly or if the plugin is coded incorrectly. I've tried jQuery versions 1.7.2 and 1.11.0 and still come out with the same results. Any suggestions would be greatly appreciated.
http://jsfiddle.net/45oLp31m/1/
Background:
The jQuery function (jQuery() or $()) acts as a factory to return a new instance of jQuery collection when you pass in a selector. So $('#foo') returns an instance of jQuery collection. In order to call methods off of that instance, like $('#foo').somePlugin(), those methods have to be defined on the instance. The primary way we get methods onto instances is to add them to the constructor's prototype.
Solution
So the solution to your specific error is that jQuery plugins are defined on the jQuery collection constructor's prototype. This prototype is aliased at jQuery.fn (and jQuery is aliased as $, so $.fn is also ok). Adding methods to the prototype is as simple as $.fn.somePlugin = function () {}.
Thus your plugin needs to be defined like:
$.fn.blogPost = function(el, options) {
More
As I said, this is for the specific error you quoted. I assume at this point that you haven't made it much further in your tutorial, so I won't go into the other issues in your code (like the fact that your plugin does not return the collection for chaining).
Instead of
$.blogPost = function(el, options) {...}
Try
$.fn.blogPost = function(el, options) {...}
Functions in the $.fn namespace are available as methods on jQuery collections, which is what you want.
$(selector).blogPost();
Such methods a generally known as "plugins".
In other (rarer) circumstances, you might want to extend the jQuery namespace itself, in which case your $.foo = function() {...} would be applicable. Such functions are known as "static methods".
Trying to use an svg onClick to call a prototype function.
Usually to call a prototype function I would just do this.(functionName) but when I put it into the .setAttribute(onclick, "this.(functionName)") it does not recognise the prototype function. Has anyone had any experience in this?
In case the above wasn't clear heres the basic jist of it...
function myobject(svgShape) {
this.svgshape.setAttribute(onclick, 'this.doSomething()');
}
myobject.prototype.doSomething = function() {
alert("works");
}
Three things that may help:
1) First off, I think you're missing this line from the top of your myobject function:
this.svgshape = svgshape;
I'm assuming that was just an error posting the question and have inserted that below.
2) Normally when you're using Prototype (or any modern library), you don't use strings for callbacks, you use functions. Also, you normally assign handlers using the library's wrapper for addEventListener / attachEvent (observe, in Prototype's case) rather than the old DOM0 attribute thing. So:
function myobject(svgShape) {
this.svgshape = svgshape;
$(this.svgshape).observe('click', this.doSomething); // STILL WRONG, see below
}
myobject.prototype.doSomething = function() {
alert("works");
}
3) But JavaScript doesn't have methods (it doesn't really need them), it just has functions, so the above won't ensure that this (the context of the call) is set correctly. With Prototype you'd use bind to set the context:
function myobject(svgShape) {
this.svgshape = svgshape;
$(this.svgshape).observe('click', this.doSomething.bind(this));
}
myobject.prototype.doSomething = function() {
alert("works");
}
(Or you can use your own closure to do it. The advantage of bind is that the closure is in a very well-controlled environment and so doesn't close over things you don't want kept around.)
Now, I've never done any SVG programming with Prototype, so if observe doesn't work for some reason, you might try directly assigning to the onclick reflected property:
function myobject(svgShape) {
this.svgshape = svgshape;
this.svgshape.onclick = this.doSomething.bind(this);
}
myobject.prototype.doSomething = function() {
alert("works");
}
I'm still using bind there so that this has the correct value.
These posts from my anemic little blog offer more discussion of the above:
Mythical methods
You must remember this
Closures are not complicated