To keep organized, I'd like to keep all the javascript for my site in a single file:
scripts.js
However, some of my scripts are only used on on some pages, other scripts are only used on other pages.
In my document-ready function it looks like this:
function home_page() {
// image rotator with "global" variables I only need on the home page
}
$('#form')... // jQuery form validation on another page
The problem with this, is that I am getting javascript to execute on pages it's not even needed. I know there is a better way to organize this but I'm not sure where to start...
One thing you could do would be to use classes on the <html> or <body> tag to establish the type of each page. The JavaScript code could then use fairly cheap .is() tests before deciding to apply groups of behaviors.
if ($('body').is('.catalog-page')) {
// ... apply behaviors needed only by "catalog" pages ...
}
Even in IE6 and 7, making even a few dozen tests like that won't cause performance problems.
I usually do something like this, or some variation (a little pseudo code below) :
var site = {
home: {
init: function() {
var self=this; //for some reference later, used quite often
$('somebutton').on('click', do_some_other_function);
var externalFile=self.myAjax('http://google.com');
},
myAjax: function(url) {
return $.getJSON(url);
}
},
about: {
init: function() {
var self=this;
$('aboutElement').fadeIn(300, function() {
self.popup('This is all about me!');
});
},
popup: function(msg) {
alert(msg);
}
}
};
$(function() {
switch($('body').attr('class')) {
case 'home':
site.home.init();
break;
case 'about':
site.about.init();
break;
default:
site.error.init(); //or just home etc. depends on the site
}
});
I ususally have an init() function that goes something like this:
function init() {
if($('#someElement').length>1) {
runSomeInitFunction()
}
... more of the same for other elements ...
}
Basically just check to see if the element exists on the page, if it does, run its own initialization function, if not, skip it.
The whole JS codes is cached by the browser after the first page load anyway, so there's no point in fragmenting your JS file down into page-specific pieces. That just makes it a maintenance nightmare.
You could use for each page object literals to get different scopes.
var home = {
other: function() {
},
init: function() {
}
};
var about = {
sendButton: function(e) {
},
other: function() {
},
init: function() {
}
}
var pagesToLoad = [home, about];
pagesToLoad.foreach(function(page) {
page.init();
});
Related
I'm an intermediate front-end JS developer and I'm trying the Module Pattern outlined by Chris Coyyer here.
But when I store a jQuery selector in the settings, I'm unable to use it to trigger a click event. See the below code with my comments... Any help is greatly appreciated!
var s,
TestWidget = {
settings: {
testButton: $("#testing")
},
init: function() {
s = this.settings;
this.bindUIActions();
},
bindUIActions: function() {
console.log(s.testButton); // This works: [context: document, selector: "#testing", constructor: function, init: function, selector: ""…]
//This doesn't work - why?????
s.testButton.click(function() {
//Why isn't this triggered?
alert('testButton clicked');
});
/*This works, obviously:
$('#testing').click(function() {
alert('testButton clicked');
});
*/
}
};
$(document).ready(function() {
TestWidget.init();
});
The problem is that you initialize $("#testing") before the DOM is ready, so this jQuery object is empty.
A simple solution is to put all your code in the ready callback.
Another one would be to replace
settings: {
testButton: $("#testing")
},
init: function() {
s = this.settings;
this.bindUIActions();
},
with
settings: {
},
init: function() {
s = this.settings;
s.testButton = $("#testing");
this.bindUIActions();
},
But it's hard to get why you use so much code for such a simple thing. You might be overusing the pattern here and it's not really clean as you have two global variables s and TestWidget when one would already be a lot.
Here's a slight variation of your code which would be, in my opinion, cleaner, while still using modules (IIFE variant) :
TestWidget = (function(){
var settings = {};
return {
init: function() {
settings.testButton = $("#testing");
this.bindUIActions();
},
bindUIActions: function() {
console.log(settings.testButton);
settings.testButton.click(function() {
alert('testButton clicked');
});
}
}
})();
$(document).ready(function() {
TestWidget.init();
});
settings is kept in the closure and doesn't leak in the global namespace. Note that even this version doesn't make sense if you don't do more with the module.
Let's say I have the following code:
$(function () {
$(".buy-it-now.ribbon").click(function () {
$(".bid-to-beat.ribbon.active").removeClass("active");
$(".bid-to-beat.ribbon").addClass("inactive");
$(".buy-it-now.ribbon.inactive").removeClass("inactive");
$(".buy-it-now.ribbon").addClass("active");
$(".bid-now").hide();
$(".buy-now").show();
$(".add-to-cart").hide();
})
$(".bid-to-beat.ribbon").click(function () {
$(".buy-it-now.ribbon.active").removeClass("active");
$(".buy-it-now.ribbon").addClass("inactive");
$(".bid-to-beat.ribbon").removeClass("inactive");
$(".bid-to-beat.ribbon").addClass("active");
$(".buy-now").hide();
$(".bid-now").show();
$(".add-to-cart").show();
});
});
It is a simple function that allows for multiple UI related things to happen on the front-end of a site I am working on. I am fairly (very) new to jQuery and JavaScript in general and am learning about refactoring and making my code more condensed now. The way I currently write code is sort of line per thought I have. So my question is how would an experienced developer write this same code? Or rather, how could I refactor this code?
Try the following:
$(function () {
var $handlers = $('.buy-it-now.ribbon, .bid-to-beat.ribbon');
$handlers.click(function() {
$handlers.toggleClass("active inactive");
var $elements = $(".bid-now, .add-to-cart"),
$buyElement = $(".buy-now");
if($(this).is('.buy-it-now.ribbon')) {
$elements.hide();
$buyElement.show();
} else {
$elements.show();
$buyElement.hide();
}
});
});
This question would be better suited for codereview, but yes it can be condensed a little using method chaining.
$(function () {
$(".buy-it-now.ribbon").click(function () {
$(".bid-to-beat.ribbon").removeClass("active").addClass("inactive");
$(".buy-it-now.ribbon").removeClass("inactive").addClass("active");
$(".bid-now").hide();
$(".buy-now").show();
$(".add-to-cart").hide();
})
$(".bid-to-beat.ribbon").click(function () {
$(".buy-it-now.ribbon").removeClass("active").addClass("inactive");
$(".bid-to-beat.ribbon").removeClass("inactive").addClass("active");
$(".buy-now").hide();
$(".bid-now").show();
$(".add-to-cart").show();
});
});
You could condense it further by pre selecting the elements and caching them in variables before the click events as long as no elements are added or removed during the life of the page.
As your code it is you can combine some of the selectors into a single line. And also because your elements looks to be static you can cache them into a variable and use them later as it reduces the number of times a element is looked up in the DOM reducing the accessing time..
Also you can limit the scope of these variables or selectors by encasing them in an object or a closure..
Maybe something in these lines..
$(function () {
cart.init();
});
var cart = {
elems : {
$buyRibbon : null,
$bidRibbon : null,
$bidNow: null,
$buyNow: null,
$addToCart: null
},
events : {
},
init : function() {
this.elems.$buyRibbon = $(".buy-it-now.ribbon");
this.elems.$bidRibbon = $(".bid-to-beat.ribbon");
this.elems.$bidNow = $(".bid-now") ;
this.elems.$buyNow = $(".buy-now") ;
this.elems.$addToCart = $(".add-to-cart") ;
this.events.buyClick();
this.events.bidClick();
}
};
cart.events.buyClick = function() {
cart.elems.$buyRibbon.on('click', function(){
cart.elems.$bidRibbon.removeClass('active').addClass('inactive');
cart.elems.$buyRibbon.removeClass('inactive').addClass('active');
cart.elems.$bidNow.hide();
cart.elems.$buyNow.show();
cart.elems.$addToCart.hide();
});
}
cart.events.bidClick = function() {
cart.elems.$bidRibbon.on('click', function(){
cart.elems.$buyRibbon.removeClass('active').addClass('inactive');
cart.elems.$bidRibbon.removeClass('inactive').addClass('active');
cart.elems.$bidNow.show();
cart.elems.$buyNow.hide();
cart.elems.$addToCart.show();
});
}
So basically in here your whole cart is a object ..And the cart has different properties which are related to this.. You follow the principles of object oriented programming here..
Using closures I heard gives you better design limiting the scope of your code..
Might I suggest something like this:
$(function () {
var buyNowButton = $('buy-it-now.ribbon'),
bidToBeatButton = $('.bid-to-beat.ribbon'),
buyNowEls = $('.buy-now'),
bidToBeatEls = $('.bid-now,.add-to-cart');
var toggleButtons = function(showBuyNow){
buyNowButton.toggleClass('active', showBuyNow);
bidToBeatButton.toggleClass('active', !showBuyNow);
buyNowEls.toggle(showBuyNow);
bidToBeatEls.toggle(!showBuyNow);
}
buyNowButton.click(function(){ toggleButtons(true) });
bidToBeatButton.click(function(){ toggleButtons(false) });
});
You could save a some lines by removing the selectors at the start and just do the selection in place, if the saved space would be more important than the minor performance hit. Then it would look like this:
$(function () {
var toggleButtons = function(showBuyNow){
$('buy-it-now.ribbon').toggleClass('active', showBuyNow);
$('.bid-to-beat.ribbon').toggleClass('active', !showBuyNow);
$('.buy-now').toggle(showBuyNow);
$('.bid-now,.add-to-cart').toggle(!showBuyNow);
}
$('buy-it-now.ribbon').click(function(){ toggleButtons(true) });
$('.bid-to-beat.ribbon').click(function(){ toggleButtons(false) });
});
The first version selects the elements once and holds them in memory; the second selects them each time the button is clicked. Both solve the problem I believe would occur with the selected answer where clicking the same button twice would cause the .active and .inactive classes to get out of sync with the shown/hidden elements.
I am using the cakephp framework and I created 2 separate javascript files and placed them into my webroot/js folder. The first javascript file contains modal dialog variables that contain the settings for the dialog boxes. The second javascript file contains other click event handlers that post data to an action and then open up the dialog.
The problem I am having is that the second file calls a variable from the first file using
$variablename and I get an error saying varaibleName is not defined.
Some code is below to show you what I mean.
From the first file:
var $editSel = $("#editSel_dialog").dialog(
{
autoOpen: false,
height: 530,
width: 800,
resizable: true,
modal: true,
buttons:
{
"Cancel": function()
{
$(this).dialog("close");
}
}
});
From the second file:
$('.neweditSel_dialog').live('click', function()
{
$.ajaxSetup({ async: false });
var selected = [];
$("#[id*=LocalClocks]").each(function()
{
if(false != $(this).is(':checked'))
{
var string = $(this).attr('id').replace('LocalClocks', '');
string = string.substring(10);
selected.push(string);
}
});
if(0 === selected.length)
{
$selError.dialog('open');
$selError.text('No Local Clocks Were Selected')
}
else
{
$.post('/LocalClocks/editSelected', { "data[Session][selected]": selected }, function(data)
{
});
$editSel.load($(this).attr('href'), function ()
{
$editSel.dialog('open');
});
}
return false;
});
This was working when I was using jquery-1.4.2.min.js, but I am using jquery1.7 now.
I also ended up putting the first file with all the variables inside of $(document).ready(function(){}); I tried putting the second file inside of a document.ready() function but that made no difference.
Any help would be great.
Thanks
You are dealing with an issue in scope. In javascript:
function foo() {
var greet = "hi";
}
function bar() {
console.log(greet); // will throw error
}
However:
var greet;
function foo() {
greet = "hi";
}
function bar() {
console.log(greet); // will log "hi"
}
You must define your variable in a common parent of both functions that need to access it. Unfortunately, since you do not use any modeling convention or framework, that is the window object (why are global variables bad?).
So, you must define var $whateveryouneed before and outside of both $(document).readys.
Also, keep the declaration and definition seperate. Your definition instantiates a jQuery object, so you must encapsulate it inside a $(document).ready() (use $(function() {}) instead):
var $editSel;
$(function () {
$editSel = $("#editSel_dialog").dialog(
{
autoOpen: false,
height: 530,
width: 800,
resizable: true,
modal: true,
buttons:
{
"Cancel": function()
{
$(this).dialog("close");
}
}
});
});
I don't think you can guarantee the order in which handlers will be fired, which means that the document ready may be fired in different order than you expect. Is the variable you are trying to access in the second file a global variable? Try to think about your variables scope as I would have thought this is the issue.
You cannot guarantee that one file will be loaded before the other. And you cannot guarantee that document.ready in one file will fire before the other.
Therefore, I suggest you wrap your code in functions and call them in a single document.ready handler in the order you need.
For example:
function initVariables(){
window.$editSel = ... // your code from the first file here
}
function initHandlers(){
// your code from the second file here
}
And then:
$(document).ready(function() {
initVariables();
initHandlers();
});
You'll notice that I used the global window object to expose your variable. It would be even better if you used a common namespace for them.
I'm using object literals on my project. I'm targeting selecting with jquery. It works fine the first time but when the part I'm targeting is reloaded with AJAX I can't target those element anymore. But I look into firebug they're there...
I'm even doing console.log() to test if my code works and it works but it just doesn't want to pick those. So in order for it to work, I have to refresh the entire browser.
Do you know what's the deal with AJAX dom reload and selectors.
I think it's something to do with the DOM reloading and redrawing itself or something along those lines...
Here is my code:
Module.editWishlistTitle = {
wishListContent: $('.mod-wish-list-content'),
title: $('.title').find('h2'),
titleTextField: $('#wishlist-title-field'),
titleInnerContainer: $('.title-inner'),
editTitleForm: $('.edit-title-form'),
submitCancelContainer: $('.submit-cancel'),
notIE9: $.browser.msie && $.browser.version < 9,
edit: function () {
var fieldTxt = this.titleTextField.val(),
editForm = this.editTitleForm,
titleParent = this.titleInnerContainer,
fieldCurrentTitle = this.title.text();
this.titleTextField.val(fieldCurrentTitle);
this.submitCancelContainer.removeClass('hidden');
if (!this.notIE9) {
editForm.css('opacity', 0).animate({ opacity: 1 }).removeClass('hidden');
titleParent.addClass('hidden').animate({ opacity: 0 });
console.log(editForm);
} else {
editForm.removeClass('hidden');
titleParent.addClass('hidden');
}
}
init: function () {
var self = this;
console.log(this.editTitleForm);
//edit
this.wishListContent.delegate('.edit-title a', 'click', function (e) {
self.edit();
e.preventDefault();
});
};
If you are replacing an element on the page, you are destroying the original reference to the element. You need to redo the reference to point to the new element.
Create a new method in your code that (re)initializes the references you need. Instead of adding them in the odject, set them in the method.
Basic idea:
Module.editWishlistTitle = {
wishListContent: $('.mod-wish-list-content'),
title: $('.title').find('h2'),
//titleTextField: $('#wishlist-title-field'),
...
...
initReferences : function(){
this.titleTextField = $('#wishlist-title-field');
},
...
...
init: function () {
this.initReferences();
...
...
And when your Ajax call comes back you just need to call initReferences again.
After DOM ready, if you inject any data / class / id will not be available in DOM, so better you use live or delegate to get your new data access.
http://api.jquery.com/delegate/
Best to use delegate, that will take care your new data loaded after dom ready, that way you can avoid to refresh /reload your page.
I can't create a new element in the page. I check the page and domain when the page is onload, that's work, but I don't know how can I create a new element in the correct window page.
window.addEventListener("load", function() { myExtension.init(); }, false);
var myExtension = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", myExtension.onPageLoad, true);
},
onPageLoad: function(aEvent) {
var unsafeWin = aEvent.target.defaultView;
if (unsafeWin.wrappedJSObject) unsafeWin = unsafeWin.wrappedJSObject;
var locationis = new XPCNativeWrapper(unsafeWin, "location").location;
var hostis = locationis.host;
//alert(hostis);
if(hostis=='domain.com')
{
var pathnameis=locationis.pathname;
if(pathnameis=='/index.php')
{
$("#left .box:eq(0)").after('<div id="organic-tabs" class="box"></div>'); // this code somewhy doesn't working, but if I copy to FireBug it't work.
}
}
}
}
My question is: How can I use Javascript and jQuery from firefox addon when I want to manipulate html in the correct window content? What is need from here
$("#left .box:eq(0)").after('<div id="organic-tabs" class="box"></div>');
for working.
This code has a bunch of issues. For one, appcontent is not the browser, gBrowser is. So it should be:
init: function() {
gBrowser.addEventListener("DOMContentLoaded", myExtension.onPageLoad, true);
},
Then, using wrappedJSObject is absolutely unnecessary (and also not safe the way you do it).
var wnd = aEvent.target.defaultView;
var locationis = wnd.location;
Finally, you are trying to select an element in the browser document (the document that your script is running in), not in the document loaded into the tab. You need to give jQuery an explicit context to work on:
$("#left .box:eq(0)", wnd.document)
But you shouldn't use jQuery like that, it defines a number of global variables that might conflict with other extensions. Instead you should call jQuery.noConflict() and create an alias for jQuery within myExtension:
var myExtension = {
$: jQuery.noConflict(true),
....
myExtension.$("#left .box:eq(0)", wnd.document)
Here is a template you can use that incorporates your sample code. I also added an additional statement so you could see another use of jQuery. Important points:
You must load jQuery before you can use it. You should myplace the jQuery library file you want to use in Chrome, for example, in the chrome/content directory.
Use window.content.document as the context for every jQuery
operation on the contents of the Web page
Use this as the context of a successful search result to help you
insert code in the correct spot.
window.addEventListener('load', myExtension.init, false);
var myExtension = {
jq : null,
init : function() {
var app;
// Load jQuery
var loader = Components.classes["#mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://myExtension/content/jquery-1.5.2.min.js");
myExtension.jq = jQuery.noConflict();
// Launch extension
if ((app = document.getElementById("appcontent"))) {
app.addEventListener("DOMContentLoaded", myExtension.run, true);
}
},
run : function() {
// make sure this is the correct Web page to change
var href = event.originalTarget.location.href;
if (href && href.match(/http:\/\/(www\.)?domain\.com\/(index\.php)/i)) {
changeScreen();
}
},
changeScreen : function() {
// make changes to the screen
// note the "window.content.document) in the first jQuery selection
myExtension.jq("#left .box:eq(0)", window.content.document).after('');
// note the use of "this" to use the search results as the context
myExtension.jq("#right", window.content.document).each(function() {
myExtension.jq("tr td", this).append('MATCH!');
});
}
}