I'm quite experienced with jQuery but I'm just getting into object oriented Javascript and want to make sure I'm doing it right.
Here's an example of what I'm doing so far... I'd appreciate tips/advice on how I can improve my approach:
// create a class for collapsable fieldsets
window.CollapsableFieldset = (function() {
function CollapsableFieldset(jQObject) {
// look for all the legend tags within this fieldset
jQObject.find("legend").click(function(event) {
// add or remove the "open" class to the fieldset
return jQObject.toggleClass("open");
});
}
return CollapsableFieldset;
})();
jQuery(document).ready(function() {
return $("fieldset.collapsable").each(function(i, element) {
return new CollapsableFieldset($(element));
});
});
So basically I'm creating a class that defines the behaviour I'm looking for and then in the jQuery block, I'm selecting each object that matches and creating a new instance of the class with that object as an argument.
This is not object-oriented javascript.
Before getting into prototyping, let me make one remark: your use of a closure in CollapsableFieldset is unnecessary and cumbersome. Indeed, you do not have any variables in the (function(){})() pattern. You may rewrite, without any loss, to:
window.CollapsableFieldset = function (jQObject) {
jQObject.find('legend').click(function(event) {
return jQObject.toggleClass('open');
});
}
Now, you create classes in javascript using a function constructor, in which the "this" keyword defines the object that a "new" statement will yield:
function CollapsableFieldset (jQObject) {
// This is the function constructor.
this.field = jQObject;
}
CollapsableFieldset.prototype.findAndToggle = function (tag, cssclass) {
// This defines a method "findAndToggle" on the CollapsableFieldset type.
this.field.find(tag).click(function(event) {
return this.field.toggleClass(cssclass);
});
};
jQuery(document).ready(function() {
return $("fieldset.collapsable").each(function(i, element) {
var colFieldset = new CollapsableFieldset($(element));
return colFieldset.findAndToggle('legend', 'open');
});
});
Please bear in mind that object-oriented javascript is not always the optimal solution.
One option would be to use Jquery UI's widget factory and encapsulate behaviour in widgets.
http://wiki.jqueryui.com/w/page/12138135/Widget-factory
You are using an immediately called function expression for no good reason, your code is functionally equivalent to:
function CollapsableFieldset(jQObject) {
// look for all the legend tags within this fieldset
jQObject.find("legend").click(function(event) {
// add or remove the "open" class to the fieldset
return jQObject.toggleClass("open");
});
}
The "constructor" doesn't return the object created when it is called with new, no advantage it taken of prototype inheritance so no point to using new.
The object returned by the function isn't assigned to anything, so what's the point of returning anything?
> jQuery(document).ready(function() {
> return $("fieldset.collapsable").each(function(i, element) {
> return new CollapsableFieldset($(element));
> });
> });
Where does the object returned by new CollapsableFieldset(...) go?
Incidentally, there is no point to creating global variables using:
window.varName...
when
var varName ...
does it so much better.
Related
This is probably a stupid question, but is there way in Javascript (ES5 preferred) to "extend" a class function similar to how i can i extend a parent' function in PHP ?
Basicly, i have this class hierarchy from System -> Weapon -> Dual and i would like Dual to use the code from System.setState() and then do some more stuff.
Note i use pre ES6 syntax for my hierarchy.
function System(system){
this.setState = function(){
//do stuff
}
}
function Weapon(system){
System.call(this, system);
}
Weapon.prototype = Object.create(System.prototype);
function Dual(system){
Weapon.call(this, system);
this.setState = function(){ // this is the problem
System.prototype.setState(); // error - not defined
//Weapon.protoype.setState() doesnt work either
//do more stuff
}
}
Dual.prototype = Object.create(Weapon.prototype);
Because setState is an instance property of System it does not exist on System.proptotype so you can't call it using System.prototype.setState.call. If you want to call it in this case, just create an object from System like so
function Dual(system){
Weapon.call(this, system);
var parent = new System(system);
this.setState = function() {
parent.setState(); // done
}
}
Instance properties are duplicated on each individual object ( they don't share). Whereas, prototype properties will be shared among children( they are not duplicated on child classes). To make all System 's subclasses share setState function, add it to System 's prototype
function System (arg) { ... }
System.prototype.setState = function () {...}
Now in your child classes, you can do
function Dual(system){
Weapon.call(this, system);
this.setState = function() {
System.prototype.setState.call(this); // done
}
}
First, you should set your instance methods on the prototype:
System.prototype.setState = function() {
// your stuff
}
This will improve performance and allow you to inherit the method without constructing a System instance.
Then, you just need to call System's version of setState on the right object (the instance of Dual) instead of calling it on System.prototype:
Dual.prototype = Object.create(Weapon.prototype, {
'setState': { value: function(){
System.prototype.setState.call(this) // fixed
// other stuff
}}
})
I have a simple requirement, I need add the same code to hundreds of other JavaScript functions, the code can be executed at the end of the function, is there a handy way of doing it, like attach an function to another function dynamically, I think yes, because JavaScript is so powerful and too powerful, any ideas?
Note, I need dynamically assign new code or function to existing functions without change existing function's code, please give a solid solution, I can do it in hacky way, but no hacky way please!
The first method that comes to mind is simply create another function:
function primaryFunction() {
// ...
utilityMethod();
}
function otherPrimaryFunction() {
// ...
utilityMethod();
}
function utilityMethod() { ... }
Now utilityMethod() gets called from the end of each other primary function.
There's also a method which requires more code refactoring but is better in the long term: classes/prototypes.
Essentially, you have one "constructor" function which takes a number of parameters for the "class" and returns an class-like object:
function constructor(someClassField, anotherField) {
this.aField = someClassField;
this.fieldTwo = anotherField;
return this;
}
Now if you call this and pass some parameters, you get a class out:
var myClass = new constructor("1", "2");
myClass.aField == "1";
myClass.fieldTwo == "2";
So: If you define your utility method as above, then you can use this: for every primary function you instantiate a new instance of the constructor, with the final code looking like this:
function constructor(primaryFunction) {
this.function = primaryFunction;
this.call = function() {
this.function();
utilityMethod();
}
this.call();
return this;
}
function utilityMethod() { ... }
var primaryMethod = new constructor(function() { ... });
The creation of primaryMethod now automatically calls the primary function followed by the utility method, before returning the object so you can re-call both if you want to.
I am trying to build a lib and I need to call functions dynamically depending on the variables I have in parameter like this
strategies = min
function dispatchRuleToStrategy(strategies)
{
$.each(strategies, function(index, value) {
strategy = "strategy_" + value;
});
}
function strategy_min()
{
// do something
}
How can I call the function strategy_min() from dispatchRuleToStrategy()?
I've been trying a couple of things none of which are working.
Thanks for your help
Use an Object to create a dictionary of your functions e.g. lib
var lib = {
'strategy_min': strategy_min
};
then you can invoke via the key in this dictionary Object
lib['strategy_min']();
If you've named all your functions and you don't want to re-type the names over and over, you could
var lib = {};
function addToLib(fn) {
lib[fn.name] = fn;
}
// then
addToLib(strategy_min);
// or
[strategy_min].forEach(addToLib);
Put them in an object and use the property name:
var strategy_table = {
min: function() {
// do something
},
max: function() {
// do something else
},
...
};
Then you can access them as strategy_table[value]:
$.each(strategies, function(index, value) {
strategy_table[value]();
});
Others have already suggested to create a wrapper object for the functions, however, if your strategy_min() function is in the global scope, you can access it directly:
window['strategy_' + value]();
window in browsers refers to the global object. The bracket notation is used to access properties whose keys are dynamically generated. This way you are accessing the function, which is a property of the global object, i.e. window, and calling it using the parentheses.
Finally I found the real problem. I was in a jquery document ready which is a closure. I did not knew what closures were before today.
Thanks all for your help
You can use eval() function in the following manner
$.each(strategies, function(index, value) {
strategy = "strategy_" + value;
eval(strategy+"()");
});
I am trying to add a functionality to a web page that uses a jquery library which doesn't seem to have any documentation. (unknown origin) my problem is mainly due to the lack of understanding on jquery plugin model and/or inner workings of javascript.
1. the plugin is initiated as follows
jQuery('div.carousel').scrollGallery({
mask: 'div.mask',
slider: 'div.slideset',
slides: 'div.slide', ............ });
2. the plugin is defined in jquery as follows
;(function($){
function ScrollGallery(options) {
this.options = $.extend({
mask: 'div.mask', ...... }, options);
this.init();
3. in the Object.prototype declaration i see the following function numSlide defined.
ScrollGallery.prototype = {
....................
numSlide: function(c) {
if(this.currentStep != c) {
this.currentStep = c;
this.switchSlide();
}
},
.......... };
Question.
How do i reference numSlide(int) function externally?.
I tried the following methods and it did not work.
myx = jQuery('div.carousel').scrollGallery({ // var myx was added in the global scope
myx.numSlide(1); //error undefined is not a function
i tried adding return this; at the end of myx = jQuery('div.carousel').scrollGallery({ but it still returns the jQuery object.
i also tried
jQuery.scrollGallery().numSlide(2); //error undefined is not a function
jQuery.scrollGallery.numSlide(2); //same error
Do i need to add LIGHT BULB
// jquery plugin
$.fn.scrollGallery = function(opt){
return this.each(function(){
$(this).data('ScrollGallery', new ScrollGallery($.extend(opt,{holder:this})));
});
};
}(jQuery));
ANSWER (I think)
it looks like the ScrollGalary object is stored in a data for the selector. So i believe i can do the following jQuery('selector').data('ScrollGallery').numSlide(2);
I decided to post this anyway in-case if anyone in the future had a similar gullible situation.
One way of doing this will be to initiate ScrollGallery object first and then use it.
var test = new ScrollGallery();
test.numSlide();
if you want to extend jQuery and use the function you can assign it as follows
$.fn.scrollGallery = new ScrollGallery();
and use it
$("window").scrollGallery.numSlide();
I have the 'phone_dlg_manager' constructor function and its private methods show and init_country_code_combobox. The dialog reference is held in the phone_dlg variable. The show method triggers init_country_code_combobox and I have two options:
1) Explicitly pass the variable country_combobox that the init_country_code_combobox methods needs:
function phone_dlg_manager(ctx, open_dlg_button, edit_ctrl, item)
{
var phone_dlg;
show();
function show()
{
phone_dlg = ctx.application.ui.create_dialog(0, "PhoneEditorDlg");
init_country_code_combobox(phone_dlg.country);
read_to_dialog_controls(this._form_item);
phone_dlg.visible = true;
}
function init_country_code_combobox(country_combobox)
{
country_combobox.items.clear();
country_combobox.items.start_adding();
country_combobox.items.finish_adding();
}
}
2) Since phone_dlg is accessible withing init_country_code_combobox through closure, I can access the property that I need without explicitly passing the variable:
function phone_dlg_manager(ctx, open_dlg_button, edit_ctrl, item)
{
var phone_dlg;
show();
function show()
{
phone_dlg = ctx.application.ui.create_dialog(0, "PhoneEditorDlg");
init_country_code_combobox(phone_dlg.country);
read_to_dialog_controls(this._form_item);
phone_dlg.visible = true;
}
function init_country_code_combobox()
{
var country_combobox = phone_dlg.country;
country_combobox.items.clear();
country_combobox.items.start_adding();
country_combobox.items.finish_adding();
}
}
The second option seems easier to understand when reading code, however it makes the init_country_code_combobox function know more than it needs. Which option should I choose?
Thanks
This is mostly a matter of style. Option 1 is a little cleaner, and more extensible, since you can use init_country_code_combobox() to initialize more than just the one dialog. But if this is unlikely to be necessary, option 2 is not unreasonable.