how to write object-oriented jQuery plugins? - javascript

So I have a bit of experience writing normal plugins to do whatever, but I want to move towards an object-based event-driven system that can be more dynamic and customizable for the end user. For the sake of my question I have written up a small plugin that simply highlights text on the $(selector).hover() event.
Here is the JS/jQuery:
(function($) {
var objs = [];
var defaults = {
color: "blue",
normal: "black",
onHover: function() {},
offHover: function() {}
};
var Text = function (settings, self) {
this.self = $(self);
this.color = settings.color;
this.normal = settings.normal;
this.show = function () { this.self.css( "color", this.color); }
this.noShow = function () { this.self.css( "color", this.normal);}
this.onHover = settings.onHover;
this.offHover = settings.offHover;
};
$.fn.myPlugin = function(opts) {
this.each(function() {
var settings = $.extend({}, defaults, opts);
$(this).data('index', objs.push(new Text(settings, this)) -1);
// I feel like this should be handled differently, maybe
// attach an event to the inside of the object?
});
this.hover(
function(e) {
objs[$(e.currentTarget).data('index')].show();
objs[$(e.currentTarget).data('index')].onHover();
}, function(e) {
objs[$(e.currentTarget).data('index')].noShow();
objs[$(e.currentTarget).data('index')].offHover();
});
};
}(jQuery));
Basically, this line...
(this).data('index', objs.push(new Text(settings, this)) -1);
...could be handled much differently and more efficiently. The problem is I need a global array that holds all objects generated by the plugin. So if I call the plugin twice on two separate 'p' tags, then there should be two objects in that array, so on so forth. Right now, that aspect is 'working' but I need to store a reference to what index that object is at by attaching an 'index' data type to the DOM element. This feels like a very wrong way to have an object oriented approach. So how can I, on an event, trigger the function...
myObject.show();
...where myObject is a reference to the element in the array that I want to highlight.
I hope my question is clear, it is a weird issue to describe I feel, but also a very powerful concept if it can be applied the way I am thinking of it. Let me know if anything is unclear and I would be happy to clarify.

In doing a bit more reading and trying to understand how object oriented programming works in respect to javascript, jquery and the DOM, I stumbled upon my own answer. Here is what the code looks like for anyone that may have been as confused as I was going into plugin development:
(function($) {
var defaults = {
color: "blue",
normal: "black",
onHover: function() {},
offHover: function() {}
};
var Text = function(opts, self) {
var settings = $.extend({}, defaults, opts);
this.self = $(self);
this.color = settings.color;
this.normal = settings.normal;
this.onHover = settings.onHover;
this.offHover = settings.offHover;
this.show = function () { this.self.css( "color", this.color); };
this.noShow = function () { this.self.css( "color", this.normal); };
};
$.fn.myPlugin = function(opts) {
this.each(function() {
this.text = new Text(opts, this);
});
this.hover(
function() {
this.text.show();
this.text.onHover.call();
}, function() {
this.text.noShow();
this.text.offHover.call();
});
};
}(jQuery));
The issue I was dealing with was an appropriate understanding of name space and closure, as well as what things you can and cannot do with DOM elements. I am not sure if this is the common way or not, but it is working very well for my uses and might work for yours.

Related

JQuery Extended Object Issue

I have 3 JQuery plugins which are similar to this
$(function() {
var Plugin1/2/3 = {
fun1:{}
fun2:{}
...
init: function()
{
o = $.extend({}, this);
}
}
$.fn.Plugin1/2/3 = function(){
return this.each(function(){
var obj = Object.create(Plugin1/2/3);
obj.init();
});
}
}
$(document).ready(function() {
$(document).Plugin1();
$(document).Plugin2();
$(document).Plugin3();
});
Everywhere i have used variable o to extend. What happens is when Plugin3 is created all the objects of above two plugins gets overwritten and all information shown are of the 3rd Plugin.
I am looking for a solution to this as well as some good guides on Jquery Advance plugin creation.
Pastebin Link: http://pastebin.com/GJTEEjyt
Result:
Thanks.
That's what happens when you take var for granted!
init: function() {
var o = $.extend({}, this);
}
And re: jQuery plugin, I have been using this for a while now. Take a look.

How do you handle with control binding in jquery

I have a question about jquery and DOM manipulation. How do you handle with DOM controls for e.g.
I have to get value from text input so I could this in ways:
var SomeClass = function() {
var control;
this.setControl = function(c) {
control = c;
}
this.getValue = function() {
return control.val();
}
}
$(document).ready(function() {
var sc = new SomeClass(); // of course control could be passed in contructor as well
sc.setControl($('#CONTROL'));
console.log(sc.getValue());
});
OR
var SomeClass = function() {
var control = $('#CONTROL');
this.getValue = function() {
return control.val();
}
}
$(document).ready(function() {
var sc = new SomeClass();
console.log(sc.getValue());
});
what is your opinion? What is better or maybe this is pile of trash therefore what is the best solution. Plz dont send me to backbone, spine and so on Im interesed in only in jquery.
best!
EDIT:
do you separate logic from UI or you are mixing it?
more complicated example
in js file you have a class that uses text control and in the secound js file also you need values from this input. What you are doing? you just call everytime $('#control') or create a third js file where would be a separated "class" to manipulate this input?
It would make more sense to move the setValue() inside the constructor:
SomeClass = function(c) {
var control = c;
return {
getValue: function() {
return control.val();
}
}
}
var x = new SomeClass($('input'));
alert(x.getValue());
However, I'm not sure how valuable this kind of information hiding will be. Perhaps as some kind of view wrapper.
In many cases you wouldn't need this wrapper, so just:
var $x = $('input'); // keep reference to a bunch of <input> elements.

Javascript / jQuery - anyway to shorthand this or make less obtuse

So, I have this constructor set up with some prototypes methods and because I need the behavior (that this object creates ) to apply to a few diff. elements, I was wondering if there is a better way than doing the following.
var MAINFUNC = function(opts){
this.options = {
item1 : 'somevalue'
},
this.init(opts);
}
MAINFUNC.prototype = {
someFunc1: function(){
// do stuff
},
someFunc2: function(){
// do stuff
},
someFunc3: function(){
// do stuff
},
init: function(data){
$.extend(this.options, data);
this.someFunc1();
}
};
var obj1Create = new MAINFUNC({ someoptions });
var obj2Create = new MAINFUNC({ someoptions });
var obj2Create = new MAINFUNC({ someoptions });
So, its the last three obj instantiations that seem a tad bit obtuse. Perhaps I am incorrect, but I am thinking there is a more refined way of doing this. And yes, each of thos obj*Create does represent a diff element which needs the behavior that is supplied by MAINFUNC.
Thank you.
var MAINFUNC = function(opts)
{
var m = Object.create(MAINFUNC.prototype);
m.options = { ... };
m.init(opts);
return m;
};
var o = [{someoptions}, {someoptions}, {someoptions}].map(MAINFUNC);
// objects are all now in array
A side benefit of this approach is it makes MAINFUNC work whether you use new or not. This makes it much more manageable, especially with collection functions.

Referencing a parent object in callback functions with jQuery

I've a page that is generated dynamically, and that includes certain number (user-dynamically-defined) of advanced scatter plot charts. I intend to create a JavaScript object which defines the scatter plot itself, i.e. which takes some parameters, some data, and some container ID, and which will create the various elements needed to obtain the visualisation: canvas elements, toolbar, etc.. To do so, I started with the following (simplified) class:
(function () {
if (!this.namespace) { this.namespace = {};}
this._instances = { index: 0 };
this.namespace.ScatterPlot = function (containerId, file, options) {
_instances.index ++;
this.id = this.containerId+"-"+_instances.index ;
this.containerId = containerId ;
_instances [this.id] = this;
// ... Do stuffs with file and options ...
// Initialize elements once the DOM is ready
$(this.updateDOM);
}
namespace.ScatterPlot.prototype = {
updateDOM: function() {
$("<canvas>")
.click(clickCallback)
.appendTo("#"+this.containerId);
//(...)
},
clickCallback: function() {
alert("Some click: "+this.id);
}
}
})();
Each object can be created with:
var v1 = new namespace.ScatterPlot("container1", "foo", "foo");
var v2 = new namespace.ScatterPlot("container2", "foo", "foo");
There are two problems here: (1) in updateDOM, 'this' does not make reference to my initial ScatterPlot object, which means that this example will never work, and (2) similarly, the clickCallback will not be able reference the scatterplot with 'this' either.
I'm new to javascript, and I'm still struggeling to understand the logic of OO programming in javascript, so the question is: I'm I taking the wrong direction here ? After some digging, I could roughly achieve what I wanted by passing this to updateDOM:
$(this.updateDOM(this)); // This blows my eyes but does the trick, at least partially
updateDOM: function(that) {
$("<canvas>")
.click(that.clickCallback)
.appendTo("#"+that.containerId);
//(...)
},
clickCallback: function() {
// Not working either... Should pass 'that' to the function too
alert("Some click: "+this.id);
}
But I don't feel this patters to be very elegant... And the problem is not fixed either regarding the click callback.
Thoughts ?
Have a look at MDN's introduction to the this keyword.
The standard ways of dealing with that issue are using a that variable - not as an argument, but in a separate function:
var that = this;
$(function() {
that.updateDOM();
});
// or
$(this.getClickCallback());
...
namespace.ScatterPlot.prototype.getClickCallback = function() {
var that = this;
return function clickCallback(e) {
alert("Some click: "+that.id);
};
};
Alternatively, you can always use .bind() (or $.proxy for older browsers) which do quite what the second example does in a more generic way:
$(this.clickCallback.bind(this));

Adding a function to one jQuery/DOM element

I am authoring a plugin which instantiates a map. The map would then provide a function to move to another place on the earth.
The script makes the map just fine. However I can't "tack" the function on the element, to be used by another plugin in a callback.
Here's the approach I tried; in plugin:
(function($){
$.fn.mapDo(options){
map = new BlahMap(this.get(0));
this.moveTheMap = function(place){
map.moveItToThat(place);
}; // nope.
}
})(jQuery);
Then, in view:
$(map).mapDo();
$(otherElement).otherControl({
callback: function(place){
$(map).moveTheMap(place); // moveTheMap is not there on $(map)!
}
};
The Question
How do I add a function to the map jQuery or DOM element, if possible? If not, how can I provide that kind of functionality?
More importantly, am I going the right way here by separating the things that way? I'm a bit of a neophyte to Javascript, how are these tasks usually done while still keeping the components apart?
While that's the stab I took at it, more generally, I struggled with the concept of outputting things from a jQuery plugin while maintaining chainability. In this case, what I am trying to do is to output a callback from the plugin that will work on the called element later in the execution.
Plugins normally only add one method to the jQuery prototype, and the method calls to the plugin's instances are done with strings.
(function($) {
$.fn.mapDo = function(options) {
var args = [].slice.call(arguments, 1); //Get all the arguments starting from 2nd argument as an array
return this.each(function() {
var $this = $(this),
instance = $this.data("map-instance");
if (!instance) {
$this.data("map-instance", (instance = new BlahMap(this, options)));
}
if (typeof options == "string") {
instance[options].apply(instance, args);
}
});
};
})(jQuery);
$(elem).mapDo( "moveTheMap", place ); //This would also instantiate the plugin if it wasn't instantiated
Here's jsfiddle showing it in action:
http://jsfiddle.net/X8YA8/1/
You could store the map with .data method.
(function($){
$.fn.mapDo = funciont(options) {
this.data('map', new BlahMap(this.get(0)));
return this;
};
$.fn.moveTheMap = function(place) {
var map = this.data('map');
if (map) {
map.moveItToThat(place);
}
return this;
};
})(jQuery);

Categories

Resources