I'm getting into writing some more complex javascript applications, and I'm running into the limitations of my own knowledge-- please forgive any naming errors or obvious noob stuff, I'm not a js pro!
I have about 4 or 5 scripts I've put in their own files, just to keep things a little easier to maintain. So maybe there's one script that deals with building page elements (like complex forms), another that just handles data, creating generic ajax request objects, defining parsers and error functions for the data returned, and another that is purely display-oriented.
I've set global variables in the page that then get populated by various scripts that get loaded at run time. For example, I define var myapp = { }; in the main HTML page, and then in the scripts various function populate this "namespace" like:
myapp.myfunction = function(){
// do stuff
}
The problem is that despite all the scripts including a $(document).ready(function() block that wraps all function definitions, when a function is called from one script that refers to another (that is, if my data.js file calls a function myapp.myDisplayFunction that is in the display.js file, I sometimes get an Object has no method 'myDisplayFunction'
Other than slamming all functions into one massive script, how do you deal with this problem? Is there a best practice that I'm missing? or is this just a question of specifying a different order that the scripts are called in?
Thanks
When you are not sure if method you are about to call exists (is already loaded) you can do a check:
if (myapp) //my app namespace is defined
{
if (myapp.myFunction) //myFunction is defined
{
myapp.myFunction();
}
else
alert('You have to load myFile.js first!');
}
Just check for the function before using:
if(typeof(myapp.myDisplayFunction) !== undefined) {
// do your stuff
} else {
// wait for a while
}
And check whether you have async attribute set while loading the .js files.
Related
I have written this piece of JS and CSS loading code and I would like some advice on it. Anything some of the Javascript Gurus could possibly point out would be much appreciated. The code works, but I have not done extensive testing, because I am concerned about replacing functions in this manner.
A single javascript file containing JQuery as well as the below code will be included on all the pages. We write all the components in house and keep them very modular separated into their own folder with the corresponding JS and CSS. You can imagine starting to use for instance a dropown, dialog and a datepicker on one page would require us to add 6 includes and this quite frankly is annoying, because I want the dependencies to resolve automatically and using JSP includes could possibly make multiple calls to the same resources.
Below is the src to load a single datepicker lazily
;(function($){
//All Lazily loaded components go here
$.fn.datepicker = function(settings){
console.log("This should only be displayed once");
loadCSS("/res/component/datepicker/datepicker.css");
var elem = this;
return loadJS("/res/component/datepicker/datepicker.js",
function(){return elem.datepicker(settings)});//After Load Completion the $.fn.datepicker is replaced
//by the proper working implementation, execute it and return it so we maintain the chain
};
}(jQuery));
function loadCSS(absoluteUrl){
if(loadCSS[absoluteUrl])
return;//Css already loaded
$('<link>')
.appendTo('head')
.attr({type : 'text/css', rel : 'stylesheet'})
.attr('href', absoluteUrl);//Appending entire element doesn't load in IE, but setting the href in this manner does
loadCSS[absoluteUrl] = true;//Memoize
}
function loadJS(absoluteUrl, onComplete){
if(loadJS[absoluteUrl])
return;//Script already loaded
loadJS[absoluteUrl] = true;//Memoize
var result;
jQuery.ajax({
async : false,//Synchronized because we need to maintain the JQuery chain
type :'GET',
url : absoluteUrl,
dataType :'script',
success : function(){
result = onComplete();
}
});
return result;
}
Have you looked in to Require JS, it will send async requests for only the modules you need for a given module.
In addition, because dependencies are scoped to the callback function, namespaces clashing is less of an issue
Typically you would have:
require(["jquery", "foo", "bar"], function($, foo, bar){...});
which allows your code to remain modularized both server side, and client side, in separate locations.
Of course, you need to set up require on your server with a config (described in the webpage), and wrap your resources in define blocks:
define("foo", ["jquery"], function($){...});
The downside is performance on pages that require many modules. In this situation you benefit more from having all resources in combined files, but note that query strings will cause the browser not to cache files in any case.. which is also another performance consideration.
Hope that helps
ps. In terms of CSS lazy loading, you could always use javascript to inject link tags into the head adhoc, and provide some javascript interface functions that your other code can call in order to request a CSS dependency dynamically.
So, as a sort of exercise for myself, I'm writing a little async script loader utility (think require.js, head.js, yepnope.js), and have run across a little bit of a conundrum. First, the basic syntax is like this:
using("Models/SomeModel", function() {
//callback when all dependencies loaded
});
Now, I want to know, when this call is made, what file I'm in. I could do it with an ajax call, so that I can mark a flag after the content loads, but before I eval it to mark that all using calls are going to be for a specific file, then unset the flag immediately after the eval (I know eval is evil, but in this case it's javascript in the first place, not json, so it's not AS evil). I'm pretty sure this would get what I need, however I would prefer to do this with a script tag for a few reasons:
It's semantically more correct
Easier to find scripts for debugging (unique file names are much easier to look through than anonymous script blocks and debugger statements)
Cross-domain requests. I know I could try to use XDomainRequest, but most servers aren't going to be set up for that, and I want the ability to reference external scripts on CDN's.
I tried something that almost got me what I needed. I keep a list of every time using is called. When one of the scripts loads, I take any of those using references and incorporate them into the correct object for the file that just loaded, and clear the global list. This actually seems to work alright in Firefox and Chrome, but fails in IE because the load events seem to go off at weird times (a jQuery reference swallowed a reference to another type and ended up showing it as a dependency). I thought I could latch on to the "interactive" readystate, but it doesn't appear to ever happen.
So now I come asking if anybody here has any thoughts on this. If y'all want, I can post the code, but it's still very messy and probably hard to read.
Edit: Additional usages
//aliasing and multiple dependencies
using.alias("ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js", "jQuery");
using(["jQuery", "Models/SomeModel"], function() {
//should run after both jQuery and SomeModel have been loaded and run
});
//css and conditionals (using some non-existant variables here)
using.css({ src: "IEFix", conditionally: browser === "MSIE" && version < 9 });
//should include the IEFix.css file if the browser is IE8 or below
and to expound more on my response below, consider this to be file A (and consider the jquery alias from before to be there still):
using(["jQuery", "B"], function() {
console.log("This should be last (after both jQuery and B have loaded)");
console.log(typeof($));
});
Then this would be B:
using("C", function() {
console.log("This should be second");
});
And finally, C:
console.log("This should be first");
The output should be:
This should be first
This should be second
This should be last (after both jQuery and B have loaded)
[Object Object]
Commendable that you are taking on such an educational project.
However, you won't be able to pull it off quite the way you want to do it.
The good news is:
No need to know what file you are in
No need to mess with eval.
You actually have everything you need right there: A function reference. A callback, if you will.
A rough P-code for your using function would be:
function using(modules, callback) {
var loadedModules = []
// This will be an ajax call to load things, several different ways to do it..
loadedModules[0] = loadModule(modules[0]);
loadedModules[1] = loadModule(modules[1]);
// Great, now we have all the modules
// null = value for `this`
callback.apply(null, loadedModules);
}
I'm slowly getting a better understanding of JavaScript but I'm stuck on how best to tackle this particular organization/execution scenario.
I come from a C# background and am used to working with namespaces so I've been reading up on how to achieve this with JavaScript. I've taken what was already starting to become a large JavaScript file and split it out into more logical parts.
I've decided on a single file per page for page specific JavaScript with anything common to two or more pages, like reusable utility functions, in another namespace and file.
This makes sense to me at the moment and seems to be a popular choice, at least during the development process. I'm going to use a bundling tool to combine these disparate files for deployment to production anyway so anything that makes development more logical and easier to find code the better.
As a result of my inexperience in dealing with lots of custom JavaScript I had a function defined in the common JavaScript file like this:
common.js
$(document).ready(function () {
var historyUrl = '/history/GetHistory/';
$.getJSON(historyUrl, null, function (data) {
$.each(data, function (index, d) {
$('#history-list').append('<li>' + d.Text + '</li>');
});
});
});
This is obviously far from ideal as it is specific to a single page in the application but was being executed on every page request which is utterly pointless and insanely inefficient if not outright stupid. So that led me to start reading up on namespaces first.
After a bit of a read I have now moved this to a page specific file and re-written it like this:
Moved from common.js to historyPage.js
(function(historyPage, $, undefined) {
historyPage.GetHistory = function () {
var historyUrl = '/history/GetHistory/';
$.getJSON(historyUrl, null, function (data) {
$.each(data, function (index, d) {
$('#history-list').append('<li>' + d.Text + '</li>');
});
});
};
}( window.historyPage = window.historyPage || {}, jQuery ));
I found this pattern on the jQuery Enterprise page. I'm not going to pretend to fully understand it yet but it seems to be a very popular and the most flexible way of organizing and executing JavaScript with various different scopes whist keeping things out of the global scope.
However what I'm now struggling with is how to properly make use of this pattern from an execution point of view. I'm also trying to keep any JavaScript out of my HTML Razor views and work in an unobtrusive way.
So how would I now call the historyPage.GetHistory function only when it should actually execute ie: only when a user navigates to the History page on the web site and the results of the function are required?
From looking at the code, it would seem that the easiest test would be to check if the page you are on contains an element with an id of history-list. Something like this:
var $histList = $('#history-list');
if($histList.length > 0){
// EXECUTE THE CODE
}
Though if it really only ever needs to run on one given page, maybe it's just not a good candidate for a shared javascript file.
Using the code I have detailed above in the question I have gotten it working by doing the following:
In _Layout.cshtml
#if (IsSectionDefined("History"))
{
<script type="text/javascript">
$(document).ready(function () {
#RenderSection("History", required: false)
});
</script>
}
In History.cshtml
#section History
{
historyPage.GetHistory();
}
The code is executing as required only when the user requests the History page on the web site. Although the comment from #Dagg Nabbit above has thrown me a curve ball in that I thought I was on the right track ... Hmm ...
I'm working on a web application that includes different JavaScript files, depending on where I am in the app. For instance, I have a display.js for each page, each of which has an "init()" function that is called as soon as the page is loaded.
This works well for the webapp, but in my QUnit tests, where all script files are included from a single index.html, functions of the same names override each other.
How are such problems best handled? One test index.html file per page creates lots of boilerplate code and makes it non-trivial to execute all test cases. That's why I decided to name each and every function distinctively, e.g. "initFrontPage()" instead of "init()". This, however, makes the application code a bit weird: Not only do I have to include the right file, I also have to call the right functions in it. Is there a better way?
The solution is to use namespaces:
In foo/display.js:
window.foo = {};
foo.init = function () { ... };
In bar/display.js:
window.bar = {};
bar.init = function () { ... };
Then, in the page that uses bar/display.js's init method:
(function (display) {
display.init();
}(bar));
It would be a good idea to wrap your display.js code in an IIFE as well.
We are attempting to only make available certain functions to be run based on what request address is.
I was wondering how we could do this:
if(condition1)
{
$(document).ready(function() {
...
...
// condition1's function
});
}
else if(condition2)
{
$(document).ready(function() {
...
...
// condition2's function
});
else if...
I was wondering what a good pattern would work for this? since we have all of our functions in one file.
It depends on what your conditions are like...
If they're all of a similar format you could do something like
array = [
["page1", page1func],
["page2", page2func],
...
]
for(i=0; i<array.length; ++i)
{
item = array[i];
if(pageName == item[0]) $(document).ready(item[1]);
}
I like Nick's answer the best, but I might take a hash table approach, assuming the 'request address' is a known fixed value:
var request_addresses = {
'request_address_1': requestAddress1Func,
'request_address_2': requestAddress2Func
};
$(document).ready(request_addresses[the_request_address]);
Of course, request_addresses could look like this as well:
var request_addresses = {
'request_address_1': function () {
/* $(document).ready() tasks for request_address_1 */
},
'request_address_2': function () {
/* $(document).ready() tasks for request_address_2 */
}
};
I don't see any problem with that. But this might be better:
$(document).ready(function() {
if (condition1)
// condition1's function
else if (condition2)
// condition2's function
...
});
It would probably be cleaner to do the site URL checking on the server (if you can?) and include different .js files depending on the condition, e.g.
** Using ASP.NET MVC
<html>
<head>
<%
if(Request.Url.Host == "domain.com")
{ %><script type="text/javascript" src="/somejsfile1.js"></script><% }
else
{ %><script type="text/javascript" src="/somejsfile2.js"></script><% }
%>
</head>
</html>
This way, each js file would be stand-alone, and also your HTML wouldn't include lines of JS it doesn't need (i.e. code meant for "other" sites)
Maybe you could give more detail as to what exactly you are doing, but from what I can tell why wouldn't you just make a different JS file containing the necessary functions for each page instead of trying to dump all of them into one file.
I would just leave all of the functions in one file if that's the way they already are. That will save you time in rework, and save the user time with reduced latency costs and browser caching. Just don't let that file get too large. Debugging and modifying will become horrendous.
If you keep them all in one file, Add a script onn each page that calls the one(s) you want.
function funcForPage1() {...}
function funcForPage2() {...}
Then, on page1
$(funcForPage1);
etc.
Instead of doing what you're planning, consider grouping the functions in some logical manner and namespace the groups.
You'd have an object that holds objects that holds functions and call like this:
serial = myApp.common.getSerialNumber(year,month);
model = myApp.common.getModelNumber(year);
or
myApp.effects.blinkText(textId);
If you wanted to hide a function or functions per page, I suppose you could null them out by function or group after the load. But hopefully having things organized would satisfy your desire to clean up the global namespace.
I can't think of a particularly elegant way to achieve this using only JavaScript. If that's all that's available to you, then I'd at least recommend you use a switch statement or (preferably) a hash table implementation to reference your functions.
If I had to do something like this, given my development environment is fully under my control, I'd break up the JavaScript into individual files and then, having determined the request, I would use server side code to build a custom bundled JavaScript file and serve that. You can create cache copies of these files on the server and send client side caching headers too.
This article, which covers this technique as part of a series may be of interest to you.