Should new jQuery functions be placed within the jQuery namespace? - javascript

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)

Related

Native JS alternative for Java's next() and hasNext() methods

So recently I built a search and replace program with Java, now I am working on translating/rebuilding that program with JavaScript. However, I am having trouble finding JS method alternatives for next() and hasNext(). I am new to JS so I don't know what JS methods would work similarly to the Java methods I am used to.
This is my program, I commented through it to show exactly what I am doing with the previously mentioned methods. Basic set up, 2 text areas, one for the search box (search criteria, box 2), and one for the main document (the field of search, box 1). It basically boils down to a cross-reference. It will highlight all the similarities between the documents.
function search() {
//define an array to store the search criteria.
var array = [];
// define a counter.
var n = 0;
// define a constant for the first box, the search field.
const box1 = document.getElementById("box1");
// define a constant for the second box, the search criteria.
const box2 = document.getElementById("box2");
// loop through the search criteria, storing each word as a seperate element in the array.
// this uses non js terms, this is where I need the help.
while (box2.hasNext()) {
array[n] = box2.next();
n = n + 1;
}
// resets the counter.
n = 0;
// loops through each search item, finding and replacing each item with itself, surrounded by mark tags.
while (n <= array.length) {
box1.replace(array[n], "<mark>" + array[n] + "</mark>");
}
}
</script>
There is bound to be other issues, bugs and syntax, feel free to point them out but lets try and keep the focus on the methodology (i.e. method alternatives for next() and hasNext()).
Thanks.
-EDIT- I'd prefer to use native alternative (no jquery) becuase I know even less about jquery than I do js.

Understanding and correcting the structure of Javascript code

so I've been on here for awhile, and I'm still considered an entry level programmer based on my general knowledge of structure and basic concepts. I have a function below that was given to me in an answer for a different question I asked. I can understand most of what it is doing, but I need help understanding the rest of what it does. I'm asking this because I would really like to understand further advanced concepts of javascript, and jQuery.
So what I've done below is placed the function, and I'll comment in what I know about what the function is doing at where, and then I'll place question marks where I"m confused.
function validate(){
//array of objeccts used to defined the class selector for each element iterated
//with what validation function is be assigned to that specific selector
var fields = [
{
selector: $('.not-empty'),
validations: [ isNotEmpty]
},
{
selector: $('.email'),
validations: [ isNotEmpty, isEmail]
},
{
selector: $('.number'),
validations: [ isNotEmpty, isNumber]
},
{
selector: $('.number-noreq'),
validations: [isNumberNotRequired]
},
{
selector: $('.checked'),
validations: [isChecked]
}
];
//remove any classes of 'has-error' from each element traversed before validation begins
$('.form-control').closest('.form-group').removeClass('has-error');
//defining variables
var i = 0, k = 0, z = 0, j = fields.length, item, selector, fn, info;
//for loop to traverse the fields array of objects
for(; i < j; i++){
item = fields[i];
//traversing each field.validation
for(k = 0; k < item.validations.length; k++){
fn = item.validations[k]; //setting fn as a function found in validation
//traversing each selector in item
for( z = 0; z < item.selector.length; z++){
selector = $(item.selector[z]); //setting the selector
//attempting to set info to the closest form or input group found by the selector
info = selector.closest('.form-group, .input-group');
if(info) //if info contains data
//?????????????????????????????????????? no idea what's going on below other
//other than it's running the validation function that was passed, but why
//is it written like this and what is it doing?
info[fn(selector.val()) ? 'removeClass' : 'addClass']('has-error');
}
}
}
}
So that is the basic question I have for this code (where all the question marks are). If someone can clearly answer what is going on, why you write the code like that, what the purpose of it is, and is it benefcial or not, would be fantastic. if you need more clarification I would be happy to provide it. I just want to be able to explain the code to somebody and know what I am talking about instead of trying to have to bs my through it. I think it was Einstein who said, "If you can't explain something accurately and to the point, then you truly do not understand it" or something like that!
Thank you in advance!
EDIT: here are the functions that 'validations' traverse through
//validation functions
function isNotEmpty(value){
return value && $.trim(value).length > 0;
}
function isEmail(value){
return /^([^#\s\t\n]+\#[\w\d]+\.[\w]{2,3}(\.[\w]{2})?)$/.test(value);
}
function isNumber(value){
return /^\d+$/.test(value);
}
function isNumberNotRequired(value){
return /^\d+$/.test(value) || value.length < 1;
}
function isChecked(value){
var r = false;
var name = $(value).attr('name');
$('input[name="'+name+'"').each(function(){
if($(this).is(':checked')){
r = true;
}
});
return r;
}
SECOND EDIT/UPDATE: We have determined that there is a severe error in the code that allows it not to keep track of the validation and take into account previous validations for input groups, and other related sections. How does this corrected. I'm testing items on jsfiddle at the moment I will return when I have restuls!
This line:
info[fn(selector.val()) ? 'removeClass' : 'addClass']('has-error');
is equivalent to this:
var result = fn(selector.val());
if (result)
info.removeClass("has-error");
else
info.addClass("has-error");
How is that? Well, your code calls the function plucked from the list of validation routines stored in that data structure, passing the value of the field to be tested. The result of that function call is used as a true/false test in the ? : expression. If the result is true, the ? : resolves to the string "removeClass"; if false, to "addClass".
Now, what is info? It's a jQuery object that refers to the closest piece of the DOM that (presumably) is where an error message would be displayed, or where some other indicator would be shown based on some CSS rule. The [ ] operator will take whichever of those two strings the ? : resolves to and use that as a property accessor. The net effect, therefore, is to reference either info.removeClass or info.addClass. Those are both references to jQuery methods, so one or the other will be called. In either case, the code wants to operate on the class name "has-error", because it wants to either add it (when the validation fails) or remove it (when the validation succeeds).
That said, the code has a serious defect: if, for a given field, there is in fact a list of validation functions, the code will run all of them (which is fine). However, for each validation function, it sets or clears that "has-error" class without regard to prior validation results. That might work, if you're really careful with the ordering of the validation functions, but that's an awfully fragile way of doing things. I think it would be much more robust if it made each test and kept track of whether any test failed, and then after that process is complete for a given field it'd only then set or clear the "has-error" class.
Fixing the code isn't too hard. Currently it iterates the the validation functions outside the iteration over the selected fields, which (I think) is backwards. However, as long as it checks the state of the error indicator element(s), it should be OK.
First, at the top, the code removes "has-error" from .form-group elements but not from .input-group elements. That's clearly incorrect, so:
$('.form-control').closest('.form-group, .input-group').removeClass('has-error');
Then, in the loop:
for( z = 0; z < item.selector.length; z++){
selector = $(item.selector[z]); //setting the selector
//attempting to set info to the closest form or input group found by the selector
info = selector.closest('.form-group, .input-group');
if (info.length && !fn(selector.val())) // if info contains data and field is invalid
info.addClass('has-error');
}
Since all the "has-error" flags are cleared at the outset, all we need to do is add the class to classes that are invalid. If you wanted to have a positive "is-ok" class, then you'd add that to everything at the top and remove it when you find an error.
As you should have known, foo.bar are foo["bar"] are identical in JavaScript (if you did not know, learn it, now).
This line
info[fn(selector.val()) ? 'removeClass' : 'addClass']('has-error');
means
var methodName;
if (fn(selector.val())) { methodName = 'removeClass'; } else { methodName = 'addClass'; }
info[methodName]('has-error')
so, in yet another words,
if (fn(selector.val())) {
info.removeClass('has-error');
} else {
info.addClass('has-error');
}
So it is actually switching class has-error on/off. Just it's pretty densely written.

Building Classes in javascript for beginner

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.

Stopping an draggable object from dragging

I'm currently working on a drag and drop plugin. I want to add a feature so the user can limit the amount of times the drggable object can be dragged and dropped. I will call this feature, dragLimitation.
This is what I have so far:
var limit = 0;
$(document).ready(function() {
$(oj).mouseup(function() {
if (o.dragLimitation !== false) {
if (limit > (o.dragLimitation-1)) {
//Code to Stop Drag Here
} else {
limit++;
$('#back').html(limit);
}
}
});
});
About the Code: There are couple of things I want to get clear to you guys so I can get an answer.
The var, oj in: $(oj).mouseup(function() { is just referring to this. In this case this would be: $('#drag').jDrag();, which is just to get my plugin running.
#drag = this
One thing I want to point out is o.dragLimitation. This is to get the amount of times the drag and drop item/object can be dropped(mouseup).
Example:
$('#drag').jDrag({
dragLimitation: 20
});
This would make #drag be able to be dragged and dropped 20 times.
I got a lot of the code but I just don't know how to stop the element from dragging. I don't to break the code either using:
$('body').append('<span>');
So the users can still use the rest of the page.
Here is where the code for the stop dragging should be:
if (limit > (o.dragLimitation - 1)) {
//Code to Stop Drag Here
}
I really hope someone can help me with the details I gave.
Thanks for any help.
(function($) {
$.extend($.fn, {
jDrag: function() {
var dragCount = this.data("jDrag.dragCount"),
limitation = this.data("jDrag.limitation");
if(typeof dragCount !== "number" || !isFinite(dragCount))
{
/*
* Drag count isn't a valid number.
* Give it a 0 value, and save it to the target.
*/
dragCount = 0;
this.data("jDrag.dragCount", dragCount);
}
if(typeof limiation !== "number" || !isFinite(limitation))
{
/*
* Limitation isn't a valid number.
* Load default limitation from plugin defaults.
*/
limitation = $.data("jDrag.defaults").limitation;
}
if(dragCount <= limitation)
{
/*
* Drag limitation isn't yet exceeded, increment count
* and save it to the target.
*/
this.data("jDrag.dragCount", ++dragCount);
/* Continue code here. */
}
}
});
})(jQuery);
Refer to jQuery.data method for internally storing data for your plugins. As you can probably see, the code above simply loads the amount of times the target has been dragged, and the limitation placed on the target. If the dragCount isn't a valid number, it gives it a dragCount of zero (, later changed to 1). If the limitation isn't a valid number, it loads the plugin's default limitation stored in the plugin's data.
Notice that we used this.data, but we later used $.data. When using this.data, this information is stored on the specific element. Using $.data will load data for the plugin itself. Since we use this.data to store information on the specific element, you can easily store different drag limitations on different elements, instead of making them all share the same limitations. If you follow this outline, you shouldn't have any problems.
Note: I didn't test this script. It is just a basic idea.
Update: Added comments to code for better understanding.
You don't necessarily need any code to stop the drag/drop functionality, you should just wrap the code for the drag/drop functionality in an if that checks if the limitation has been met. If it has then the dragging/dropping code wont execute.

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!

Categories

Resources