Building Classes in javascript for beginner - javascript

I'm well versed with javascript and jQuery. I've just never built a class before. Maybe a class is not even what I'm looking for.
I have a function I call to launch an overlay and it's used a lot and contains some parameters.
function launchOverlay(method, content, width, closeBtn) {
$("body").append('<div id="overlay-backdrop" style="display:none"></div>');
$("#overlay-backdrop").css({
width: $(document).width(),
height:$(document).height()
}).fadeIn();
$("#overlay-backdrop").append('<div id="overlay-canvas-area"><div class="inner-canvas-area"></div><div>');
if(typeof closeBtn == 'undefined'){
$("#overlay-canvas-area").append('<div class="close-btn"><a class="close" onClick="closeOverlay()">Close</a></div>');
}
if (method == "load"){
$("#overlay-canvas-area .inner-canvas-area").load(content);
}if(method == "append"){
$("#overlay-canvas-area .inner-canvas-area").append(content);
}
var canvasAreaWidth = width+($("#overlay-canvas-area").width());
var canvasAreaHeight = $("#overlay-canvas-area").height();
$("#overlay-canvas-area").animate({
top:((($(document).height())-(canvasAreaHeight))/2),
left: ((($(document).width())-(canvasAreaWidth))/2)
},700);
}
I find myself modifying this constantly to fit my needs and then going back to old instances and modifying the function call. I would also like to pass json as settings with the function.
first question is, are classes what I'm looking for?
if so, where is a good place to learn?
if not, what should I do to improve functionality?

A class (or in javascript a "prototype" is actually the more correct term) is appropriate when there is a lasting object that contains some data and then you want to operate on that data with multiple different methods over time.
The class allows you to neatly specify how the data is stored and what methods operate on the data.
If you just have one operation that produces an output and can take in a variety of different input data, then you won't really benefit from a class. You just need a function that takes a variety of parameters and chooses its operation based on what was passed to it.
In javascript when there are lots of options for a function and they may be variable, then it is sometimes common to pass in an options object that contains properties that direct the operation of the function. The function can then examine which properties are present and what values they have to select how it should behave. The use of the options object can allow much simpler maintenance any time you want to add or modify a parameter rather than continuing to add more and more function arguments. An options object like this can also be passed around more easily rather than passing every single argument individually. You can also create a default state for the options object that contains all the default values for the arguments (what their value should be if they aren't passed). While all of this can be done with multiple traditional function arguments, it can be a lot cleaner to code with an options object.
The use of an options object would look like this:
function doWhatever(mainData, options) {
if (options.foo) {
// do it one way
} else {
// do it the other way
}
}
doWhatever(myData, {foo: true, output: "commas", fee: "whatever"};

For a straight class like implementation I like this: http://ejohn.org/blog/simple-class-instantiation/
But it sounds like you want to create something that in jQuery parlance would be called a 'widget', which gives you a lot of what you are asking for free, plus more:
http://ajpiano.com/widgetfactory/#slide1
http://wiki.jqueryui.com/w/page/12138135/Widget%20factory
http://bililite.com/blog/understanding-jquery-ui-widgets-a-tutorial/

I agree with the others because this function is entirely behavior with no state. You might consider putting the function into a namespace along with related UI helper functions, but that's strictly for better organization and ease of use on multiple pages.
Also, you could improve the readability and performance of this function by storing the jQuery objects returned by append and reusing them for further calls.
function launchOverlay(method, content, width, closeBtn) {
var $overlay = $("body").append('<div id="overlay-backdrop" style="display:none"></div>');
$overlay.css({
width: $(document).width(),
height:$(document).height()
}).fadeIn();
var $canvas = $overlay.append('<div id="overlay-canvas-area"><div class="inner-canvas-area"></div><div>');
if (typeof closeBtn == 'undefined') {
$canvas.append('<div class="close-btn"><a class="close" onClick="closeOverlay()">Close</a></div>');
}
if (method == "load") {
$("#overlay-canvas-area .inner-canvas-area").load(content);
}
else if (method == "append") {
$("#overlay-canvas-area .inner-canvas-area").append(content);
}
var canvasAreaWidth = width + $canvas.width();
var canvasAreaHeight = $canvas.height();
$canvas.animate({
top: (($(document).height() - canvasAreaHeight) / 2),
left: (($(document).width() - canvasAreaWidth) / 2)
}, 700);
}
Note that I've left the var declarations inline to match your style, but you should be aware that their declarations are hoisted to the top of the function. Here it doesn't matter, but it could bite you in more complicated functions later.

Related

ES6 calling method in another class, using modules

I know there is many questions like this asked, but I have been searching for hours and can't find any answers. I have this method, which takes in a parameter, which should be ID of two selects. Using this parameter, I want to determine which select is used and execute the if statement, but to no avail. When I run it, it shows no errors in console in Chrome and it does nothing. Can anyone shed some light on it, this is the method in one export class:
static styleCircle(select) {
if(this.select === ELEMENTS.ELEMENT_COLOR_SELECT) {
var getColor = ELEMENTS.ELEMENT_COLOR_SELECT;
var colorValue = getColor.options[getColor.selectedIndex].value;
ELEMENTS.ELEMENT_STYLE_CIRCLE.style.backgroundColor = colorValue;
} else if(select == ELEMENTS.ELEMENT_BORDER_SELECT) {
var getRadius = ELEMENTS.ELEMENT_BORDER_SELECT;
var radiusValue = getRadius.options[getRadius.selectedIndex].value;
ELEMENTS.ELEMENT_STYLE_CIRCLE.style.borderRadius = radiusValue;
}
}
This is it being called in another class, on two select elements, and the class is imported at the top of the file:
ELEMENTS.ELEMENT_COLOR_SELECT.onchange = Script.styleCircle(this);
ELEMENTS.ELEMENT_BORDER_SELECT.onchange = Script.styleCircle(this);
ELEMENTS is a file with constants, which are just being used to get ID's from the HTML file. I used other methods like this, with onclick events, but none had parameters, and now I'm stuck here. Thanks in advance.
You don't want to call the functions right now but instead you probably want to pass functions. Through that you can access the proper this and pass it to styleCircle:
ELEMENTS.ELEMENT_COLOR_SELECT.onchange = function() {
Script.styleCircle(this);
};
ELEMENTS.ELEMENT_BORDER_SELECT.onchange = function() {
Script.styleCircle(this);
};
Additionally this.select is probably causing you troubles as window.select is undefined.
First step would be to try debugging and ensure select is equivalent to either of those constants. Make sure you have full branching coverage in your debugging. That would mean start by adding an else statement to that if/else if statement - it's possible that your select is not equal to either constant and so neither branch is run.

Form handling and validation in pure JavaScript

My intention is to get your thoughts and criticism about the script below, as regards the algorithm's design, performance and cross-browser compatibility.
I have just started getting into JavaScript having missed out on its awesomeness for quite a while. My background and experience is in developing C/C++/PHP based RESTful backends.
In order to understand the language and the right way of using it, I decided to do something which I am sure has been done many times before. But learning to use a new language and paradigm often entails pain anyway.
This is my attempt to create a normal form processing and validation script/ function.
In order to reduce complexity and keep code simple/clean, I decided to use HTML5 Custom Data Attributes (data-*) to assign metadata for each element in the form:
Data-Required: True or False. If set to true, this parameter makes the form-field required and so it cannot be empty. A value set to false indicates that the field is optional. Default is false.>
Data-Type: Type of validation to be performed. Examples include 'email', 'password', 'numbers' or any other 'regexp'.
A fairy simple example of such a form would be:
<form action="postlistings" id="postlistings" enctype='multipart/form-data' method="post" class="postlistings">
<ul class="login-li">
<li>
<input class="title" name="title" type="title" id="title" data-required="true" data-type="title"></a>
</li>
<li>
<textarea name="body" id="elm1" class="elm1" name="elm1" data-type="body" data-required="true" >
</textarea>
</li>
<li>
<span class="nav-btn-question">Add Listing</span>
</li>
</ul>
</form>
Reminder: This is my first piece of JavaScript code.
The idea is to call Form while passing the form name to retrieve and validate all the field values in one loop for performance. The validation involves two steps as can be guessed from the Data-* attributes described above:
i. Check for required form fields.
In case the values fail to meet step 1 requirement, an error message from configuration is pulled for the specific form value. Thus, for all values that fail to meet this requirement, an array of error messages are collected and passed on to the View.
ii. Perform respective validations.
Validations are only performed if all the values passed step 1. Otherwise, they follow the same steps as indicated in 1 above.
function Form(){
var args = Array.prototype.slice.call(arguments),
formName = args[0],
callback = args.pop(),
userError = [{type: {}, param: {}}],
requiredDataParam = 'required',
typeDataParam = 'type',
form = document.forms[formName],
formLength = form.length || null,
formElement = {id: {}, name: {}, value: {}, required: {}, type: {}};
function getFormElements(){
var num = 0;
var emptyContent = false;
for (var i = 0; i < formLength; i += 1) {
var formField = form[i];
formElement.id[i] = inArray('id', formField) ? formField.id : null;
formElement.name[i] = inArray('name', formField) ? formField.name : null;
formElement.value[i] = inArray('value', formField) ? formField.value : null;
formElement.required[i] = getDataAttribute(formField, requiredDataParam);
formElement.type[i] = getDataAttribute(formField, typeDataParam);
if (formElement.required[i] === true){
if(!formElement.type[i]) {
error('Validation rule not defined!');
}
else if (!formElement.value[i]) {
userError[num++] = {'type': 'required', 'param': form[i]};
emptyContent = true;
}
}
if (emptyContent === false) {
// Perform validations only if no empty but required form values were found.
// This is so that we can collect all the empty
// inputs and their corresponding error messages.
}
}
if (userError) {
// Return empty form errors and their corresponding error messages.
}
return formElement;
};
// Removed the getFormParam function that was not used at all.
return {
getFormElements: getFormElements
}
};
Two outside functions that are used in the JS script above (from JQuery source):
var inArray = function(elem, array){
if (array.indexOf){
return array.indexOf(elem);
}
for (var i = 0, length = array.length; i < length; i++){
if (array[i] === elem){
return i;
}
}
return -1;
}
// This is a cross-platform way to retrieve HTML5 custom attributes.
// Source: JQuery
var getDataAttribute = function(elem, key, data) {
if (data === undefined && elem.nodeType === 1) {
data = elem.getAttribute("data-" + key);
if (typeof data === "string") {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
!CheckType.isNaN ? parseFloat(data) :
CheckType.rbrace.test(data) ? parseJSON(data) :
data;
}
else {
data = undefined;
}
}
return data;
}
An example of Config Error messages can be set as follows:
var errorMsgs = {
ERROR_email: "Please enter a valid email address.",
ERROR_password: "Your password must be at least 6 characters long. Please try another",
ERROR_user_exists: "The requested email address already exists. Please try again."
};
As I post this for your review, please ignore any styling conventions that I might not have followed. My intention is to get your expert reviews on anything I should be doing different or could do better concerning the code itself, and the algorithm.
Besides the styling conventions, all criticism and questions are welcome.
First I'd like to clear up a common misconception. Forgive me if you already understand this clearly; maybe it will be helpful for someone else.
Learning and using jQuery or a similar library does not preclude or conflict with learning the JavaScript language. jQuery is simply a DOM manipulation library which takes away many of the pain points of using the DOM. There's plenty of room to learn and use JavaScript, the language, even if you use a library to abstract away some of the DOM details.
In fact, I would argue that using the DOM directly is likely to teach bad JavaScript coding habits, because the DOM is very much not a "JavaScript-ish" API. It was designed to work identically in JavaScript and Java and potentially other languages, and so it completely fails to make good use of the features of the JavaScript language.
Of course as you said, you're using this as a learning exercise; I just don't want you to fall into the trap that I've seen many people fall into of thinking, "I don't want to learn jQuery, because I want to learn JavaScript instead!" That's a false dichotomy: you have to learn JavaScript in either case, and using jQuery for the DOM doesn't interfere with that at all.
Now some details...
While it's OK to quote property names in an object literal and when you reference the properties, it's customary - and more readable - not to quote them when they are valid JavaScript names. e.g. in your formElement object
formElement = { id: {}, name: {}, value: {}, required: {}, type: {} };
(there was a missing semicolon at the end there too)
and where you use the names you can do:
formElement.id[i] = ...
formElement.name[i] = ...
etc.
Don't run your loops backwards unless the program logic requires it. It doesn't make the code faster except possibly in the case of an extremely tight loop, and it makes it unclear whether you're just prematurely optimizing or actually need the backwards loop.
Speaking of optimization, that loop has several inArray() calls. Since each of those loops through an array, that could be more of a performance impact than the outer loop. I imagine these arrays are probably pretty short? So performance wouldn't matter at all anyway, but this is something to think about in cases where you have longer arrays and objects. In some cases you can use an object with property names and values for a faster lookup - but I didn't look closely enough at what you're doing to suggest anything.
In any case, you're using inArray() wrong! But not your fault, that is a ridiculously named function in jQuery. The name clearly suggests a boolean return value, but the function returns the zero-based array index or -1 if the value is not found. I strongly recommend renaming this function as indexOf() to match the native Array method, or arrayIndex(), or some such.
That same loop has form[i] repeated numerous times. You could do this at the top of the loop:
var field = form[i];
and then use field throughout, e.g. field.id instead of form[i].id. This is generally faster, if it matters (which it probably doesn't here), but more importantly it's easier to read.
Do not use strict boolean comparisons like if( foo === true ) and if( bar === false) unless you really need to - and those cases are rare. The code sends a signal to the reader that there is something going on that's different from the usual boolean test. The only time these particular tests should be used is when you have a variable that may contain a boolean value or may contain some other type of value, and you need to distinguish which is which.
A good example of a case where you should use tests like these is an optional parameter that defaults to true:
// Do stuff unless 'really' is explicitly set to false, e.g.
// stuff(1) will do stuff with 1, but stuff(1,false) won't.
function stuff( value, really ) {
if( really === false ) {
// don't do stuff
}
else {
// do stuff
}
}
That specific example doesn't make a lot of sense, but it should give you the idea.
Similarly, an === true test could be used in a case where need to distinguish an actual boolean true value from some other "truthy" value. Indeed, it looks like this line is a valid case for that:
if (formElement['required'][i] === true){
given that if (formElement['required'][i] comes from the getDataAttribute() function which may return a boolean or other type.
If you are just testing for truthiness, though - and this should be most of the time - simply use if( foo ) or if( ! foo ). Or similarly in a conditional expression: foo ? x : y or !foo ? x : y.
The above was a long-winded way of saying that you should change this:
if (empty_content === false) {
to:
if (!empty_content) {
Your getFormParam() function goes to some work to convert an undefined result to null. There is usually no reason to do this. I don't see any place where that function is called, so I can't advise specifically, but in general you'd be testing for truthiness on something like this, so null and undefined would both be treated as false. Or in cases where you do need to distinguish null/undefined from other values (say, an explicit false), you can easily do it with != null or == null. This is one case where the "looser" comparison performed by == and != is very useful: both null and undefined evaluate the same with these operators.
You asked to ignore coding style, but one little suggestion here: You have a mix of camelCaseNames and names_with_underscores. In JavaScript, camelCaseNames are more idiomatic for function and variable names, with PascalCaseNames for constructor functions. Of course feel free to use underscores where they make more sense, for example if you're writing code that works with database columns in that format you may want your variable names to match the column names.
Hope that helps! Keep up the good work.
Update for your new code
I'm having a bit of trouble following the logic in the code, and I think I know part of the reason. It's a combination of naming conventions and inside-out objects.
First, the name formElement is really confusing. When I see element in JavaScript, I think of either a DOM element (HTMLElement) or an array element. I'm not sure if this formElement represents one or the other or neither.
So I look at the code to figure out what it's doing, and I see it has id:{}, name:{}, ... properties, but the code later treats each of those as an Array and not an Object:
formElement.id[i] = ...
formElement.name[i] = ...
formElement.value[i] = ...
formElement.required[i] = ...
formElement.type[i] = ...
(where i is an integer index)
If that code is right, those should be arrays instead: id:[], name:[], ....
But this is a red flag. When you see yourself creating parallel arrays in JavaScript, you're probably doing it wrong. In most cases you're better off replacing the parallel arrays with a single array of objects. Each of the objects in that array represents a single slice through all your parallel arrays, with a property for each of the previous arrays.
So, this object (where I've made the correction from {} to [] to match its current use):
formElement = { id: [], name: [], value: [], required: [], type: [] };
should be:
formInfo = [];
and then where you have the code that goes:
formElement.id[i] = ...;
formElement.name[i] = ...;
formElement.value[i] = ...;
formElement.required[i] = ...;
formElement.type[i] = ...;
It should be:
var info = {
id: ...,
name: ...,
value: ...,
required: ...,
type: ...
};
formInfo.push( info );
and adjust the rest of the code to suit. For example:
formElement.required[i]
would be:
formInfo[i].required
or even simpler since it's in the same function:
info.required
And note: I'm not saying info and formInfo are great names :-) they are just placeholders so you can think of a better name. The main idea is to create an array of objects instead of a set of parallel arrays.
One last thing and then I'm out of time for now.
That getDataAttribute() function is a complicated little piece of work. You don't need it! It would be simpler would just call the underlying function directly where you need it:
var info = {
...
required: formField.getAttribute('data-required') === 'true',
type: formField.getAttribute('data-type')
};
This also gives you full control of how the attributes are interpreted - as in the === 'true' test above. (This gives you a proper boolean value, so when you test the value later you don't have to use === true on it.)
On a stylistic note, yes, I did hard code the two 'data-xxxx' names right there, and I think that's a better and more clear way to do it.. Don't let your C experience throw you off here. There's no advantage to defining a string "constant" in this particular case, unless it's something that you want to make configurable, which this isn't.
Also, even if you do make a string constant, there's a minor advantage to having the complete 'data-whatever' string instead of just 'whatever'. The reason is that when somebody reads your HTML code, they may see a string in it and search the JS code for that string. But when they search for data-whatever they won't find it if the data- prefix is automagically prepended in the JS code.
Oh, I forgot one last thing. This code:
function Form(){
var args = Array.prototype.slice.call(arguments),
formName = args[0],
callback = args.pop(),
is working way too hard! Just do this instead:
function Form( formName, callback ) {
(and keep the var for the remaining variable declarations of course)
I cannot add comments yet so here is a little tip. I would separate the getFormElements() into smaller private functions. And I would add the errorMsgs to the Form function.
But for a first script in JavaScript, it is very impressive. This is actually the real reason I respond. I think it deserves more upvotes, and I would be very interested in a JS ninja responding to this question.
Good luck!

Javascript - Array of prototype functions

I'm a javascript newbie so I'm writing ugly code so far sometimes due to my lack of experience and how different it is to the languages I'm used to, so the code I'll post below works, but I'm wondering if I'm doing it the right way or perhaps it works but it's a horrible practice or there is a better way.
Basically, I have a little dude that moves within a grid, he receives from the server an action, he can move in 8 directions (int): 0:up, 1: up-right, 2: right... 7: up-left.
the server will send him this 0 <= action <= 7 value, and he has to take the correct action... now, instead of using a switch-case structure. I created a function goUp(), goLeft(), etc, and loaded them in an array, so I have a method like this:
var getActionFunction = actions[action];
actionFunction();
However, what to set all this up is this:
1) create a constructor function:
function LittleDude(container) {
this.element = container; //I will move a div around, i just save it in field here.
}
LittleDude.prototype.goUp() {
//do go up
this.element.animate(etc...);
}
LittleDude.prototype.actions = [LittleDude.prototype.goUp, LittleDude.prototype.goUpLeft, ...];
//In this array I can't use "this.goUp", because this points to the window object, as expected
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction(); //LOOK AT THIS LINE
}
Now if you pay attention, the last line won't work.. because: when i use the index to access the array, it returns a LittleDude.prototype.goUp for instance... so the "this" keyword is undefined..
goUp has a statement "this.element"... but "this" is not defined, so I have to write it like this:
actionFunction.call(this);
so my doAction will look like this:
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction.call(this); //NOW IT WORKS
}
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way. Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.
What you are trying to do is one of the possible ways, but it is possible to make it more simple. Since object property names are not necessary strings, you can use action index directly on prototype. You even don't need doAction function.
LittleDude = function LittleDude(container) {
this.container = container;
}
LittleDude.prototype[0] = LittleDude.prototype.goUp = function goUp() {
console.log('goUp', this.container);
}
LittleDude.prototype[1] = LittleDude.prototype.goUpRight = function goUpRight() {
console.log('goUpRight', this.container);
}
var littleDude = new LittleDude(123),
action = 1;
littleDude[action](); // --> goUpRight 123
littleDude.goUp(); // --> goUp 123
actionFunction.call(this); //NOW IT WORKS
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way.
No, using .call() is perfectly fine for binding the this keyword - that's what it's made for.
Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.
You don't have to define them on the prototype if you don't use them directly :-) Yet, if you do you might not store the functions themselves in the array, but the method names and then call them with bracket notation:
// or make that a local variable somewhere?
LittleDude.prototype.actions = ["goUp", "goUpLeft", …];
LittleDude.prototype.doAction = function(action) {
var methodName = this.actions[action];
this[methodName](); // calls the function in expected context as well
}

Extremely annoying JavaScript array/object error

Basically, I am rewriting part of one of my web applications. I had a script that would collapse some or all panels of the interface at once, and another to code them.
However, my old functions looked really ugly, and were annoying to type and not powerful enough:
function collapse_all()
{
document.getElementById("panel_1").style.display="none"
document.getElementById("panel_2").style.display="none"
document.getElementById("panel_3").style.display="none"
}
function expand_all()
{
document.getElementById("panel_1").style.display=""
document.getElementById("panel_2").style.display=""
document.getElementById("panel_3").style.display=""
}
Now I have this:
function panel() //first variable in argument is collapse or expand, all others are panels to act on
{
var panels = panel.arguments
alert(typeof panel.arguments)
var mode = panels.shift() //here's my problem
if(mode=="collapse") {mode="none"}
if(mode=="expand") {mode=""}
var items = panels.length
for (i = 0;i < items;i++) {document.getElementById(panels[i]).style.display=mode}
}
panel("collapse","panel_1","panel_2","panel_3")
I have a problem though. Firebug tells me panels.shift() is not a function. With some Googling I managed to find out that panel.arguments isn't an array but an object, so I can't use array methods on it. I'm just really confused as to how I could either convert the object into an array or find another workaround, as I know next to nothing about JavaScript objects. Some example code would be highly appreciated.
You can convert the arguments object into an array like this:
var argsArray = Array.prototype.slice.call(arguments);
What this does is use the slice method common to all arrays via Array.prototype to create a genuine Array object from the array-like arguments. call() (a method of all functions) is used to call this slice method with a this value of arguments and no parameters, which has the effect of copying all of the elements of this into a new array. This may seem devious or hacky but it is actually designed into the language: see the note at the bottom of section 15.4.4.10 of the ECMAScript 3rd Edition spec.
Also, within a function you are provided the arguments object as a variable, so you don't need to access it as a property of the function object as you are doing. In your case, just use arguments rather than panel.arguments.
You could keep it much simpler (cleaned up your formatting, semi-colons, etc.):
function panel()
{
var panels = Array.prototype.slice.call(arguments);
var displayMode = (panels[0] == "collapse" ? "none" : "");
for (var i = 1; i < panels.length - 1; i++)
{
document.getElementById(panels[i]).style.display = displayMode;
}
}
Also, if you're rewriting your application, it might be a good time to consider using things like jQuery. You could assign each one of your panels a certain class name, and reduce your code to something like this:
function panel(hide)
{
$('.className').css({ display: (hide ? 'none' : '') });
}
which you could use like so:
panel(true); // or
panel(false);
Or, because now it's so syntactically simple, you might as well just create two separate functions so that your code is straightforward and you know exactly what it's going to do from the function names alone:
function showPanels() {
$('.className').css({ display: '' });
}
function hidePanels() {
$('.className').css({ display: 'none' });
}
And finally, if you don't worry about doing it via CSS, you could really shorten your script to this, which can't be any clearer:
function showPanels() {
$('.className').show();
}
function hidePanels() {
$('.className').hide();
}
Cheers!

Should new jQuery functions be placed within the jQuery namespace?

Context: HTML widgets generated using a Django ModelForm and template, jQuery 1.3.2, JavaScript on IE8, Firefox 3.5 or Safari 4. Procedures: An ordinary JavaScript function with some jQuery inside, or jQuery Enlightenment, Cody Lindley, "Adding new functions to the jQuery namespace," p. 116.
I have a jQuery construct that is repeated several times with different variables and so has been begging to be turned into a function. Basically some of the widgets need to be enabled or disabled based on the values of other widgets. I wrote this function two ways. The first way, with jQuery code inside an ordinary JavaScript function, is as follows:
function enable_or_disable_by_selection(master_id, master_id_value, dependent_ids) {
/*
* The master_id argument is the id attribute string of the master select element in the form
* "#id_select_element_name".
* The master_id_value argument is the selection value, a string, that causes the dependent
* elements to be enabled when it is selected. In all other cases, they are disabled.
* The dependent_ids argument is an array of dependent id attribute strings, such as
* ["#id_element_1", "#id_element_2", "#id_element_3"]
*/
/* ON CHANGE OF master_id SELECTION ELEMENT: */
$(master_id).change(function() {
/* If master_id_value is chosen, enable inputs for elements in dependent_ids: */
if ($(master_id).val() == master_id_value) {
for (var i = 0; i < dependent_ids.length; i++) {
$(dependent_ids[i]).removeAttr("disabled");
}
}
/* Otherwise disable inputs for elements in dependent_ids: */
else {
for (var i = 0; i < dependent_ids.length; i++) {
$(dependent_ids[i]).attr("disabled", true);
}
}
});
}
This works. The second way, recommended by the very able Mr. Lindley, puts my new function in the jQuery namespace. His basic recommended construct may be seen here. This helps me "avoid creating global code that could potentially create conflicts." Here's the code for my function following these recommendations:
(function($){
$.enable_or_disable_by_selection = function(master_id, master_id_value, dependent_ids){
/*
* The master_id argument is the id attribute string of the master select element in the form
* "#id_select_element_name".
* The master_id_value argument is the selection value, a string, that causes the dependent
* elements to be enabled when it is selected. In all other cases, they are disabled.
* The dependent_ids argument is an array of dependent id attribute strings, such as
* ["#id_element_1", "#id_element_2", "#id_element_3"]
*/
/* CHANGE OF master_id SELECTION ELEMENT: */
$(master_id).change(function() {
/* If master_id_value is chosen, enable inputs for elements in dependent_ids: */
if ($(master_id).val() == master_id_value) {
for (var i = 0; i < dependent_ids.length; i++) {
$(dependent_ids[i]).removeAttr("disabled");
}
}
/* Otherwise disable inputs for elements in dependent_ids: */
else {
for (var i = 0; i < dependent_ids.length; i++) {
$(dependent_ids[i]).attr("disabled", true);
}
}
});
};
})(jQuery);
This works too. It's the same logic wrapped up in jQuery.
I know that jQuery makes use of anonymous functions and closures in order to maintain a hermetic namespace. See "Using (function(){})()" by John Resig, here. But, as I study these two snippets, I am having a hard time seeing the risk in the first, simpler version. Also, am I wrong in thinking that this second method is a bit slower? Please help me see the advantages in the second version. I want to know why this is done.
Useful observations not pertinent to the question are always welcome.
1 - The risk on your first version comes with the fact that your function is global, for example, some other random library is added to your page, and it overrides your function, it will have disastrous effects with the rest of the API of your widget.
I think you want to create redistributable widgets, and you must be defensive with your code, keeping as much your of your library code private as possible and you should be very selective and careful when you introduce global objects.
2 - The second example is not more slower, you are simply adding a member to the jQuery object, the time of name resolution of the property is really insignificant.
I would recommend you to read about namespacing and library design:
JavaScript Namespacing
Namespacing your JavaScript
Best Practices in JavaScript Library Design (Video and Slides)

Categories

Resources