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 ...
Related
I have a node js web app that is using handlebars. Users are asking me to let them register their own handlebars helpers.
I'm quite hesitant about letting them do it... but I'll give it a go if there is a secure way of doing it so.
var Handlebars = require("handlebars");
var fs = require("fs");
var content = fs.readFileSync("template.html", "utf8");
//This helper will be posted by the user
var userHandlebarsHelpers = "Handlebars.registerHelper('foo', function(value) { return 'Foo' + value; });"
//eval(userHandlebarsHelpers); This I do not like! Eval is evil
//Compile handlebars with user submitted Helpers
var template = Handlebars.compile(content);
var handleBarContent = template({ foo: bar });
//Save compiled template and some extra code.
Thank you in advance!
Because helpers are just Javascript code, the only way you could safely run arbitrary Javascript from the outside world on your server is if you either ran it an isolated sandbox process or you somehow sanitized the code before you ran it.
The former can be done with isolated VMs and external control over the process, but that makes it quite a pain to have helper code in some external process as you now have to develop ways to even call it and pass data back and forth.
Sanitizing Javascript to be safe from running exploits on your server is a pretty much impossible task when your API set is as large as node.js. The browser has a very tightly controlled set of things that Javascript can do to keep the underlying system safe from what browser Javascript can do. node.js has none of those safeguards. You could put code in one of these helpers to erase the entire hard drive of the server or install multiple viruses or pretty much whatever evil exploit you wanted to code. So, running arbitrary Javascript will simply not be safe.
Depending upon the exact problems that need to be solved, one can something develop a data driven approach where, instead of code, the user provides some higher level set of instructions (map this to that, substitute this with that, replace this with that, display from this set of data, etc...) that is not actually Javascript, but rather some non-executable meta data. That is much more feasible to make safe because you control all the code that acts on this meta data so you just have to make sure that the code that processes the meta data isn't capable of being tricked into doing something evil.
Following #jfriend00 input and after some serious testing I found a way to do it using nodejs vm module.
Users will input their helpers with this format:
[[HBHELPER 'customHelper' value]]
value.replace(/[0-9]/g, "");
[[/HBHELPER]]
[[HBHELPER 'modulus' index mod result block]]
if(parseInt(index) % mod === parseInt(result))
block.fn(this);
[[/HBHELPER]]
//This will throw an error when executed "Script execution timed out."
[[HBHELPER 'infiniteLoop' value]]
while(1){}
[[/HBHELPER]]
I translate that block into this and execute it:
Handlebars.registerHelper('customHelper', function(value) {
//All the code is executed inside the VM
return vm.runInNewContext('value.replace(/[0-9]/g, "");', {
value: value
}, {
timeout: 1000
});
});
Handlebars.registerHelper('modulus', function(index, mod, result, block) {
return vm.runInNewContext('if(parseInt(index) % mod === parseInt(result)) block.fn(this);', {
index: index,
mod: mod,
result: result,
block: block
}, {
timeout: 1000
});
});
Handlebars.registerHelper('infiniteLoop', function(value) {
//Error
return vm.runInNewContext('while(1){}', {
value: value
}, {
timeout: 1000
});
});
I made multiple tests so far, trying to delete files, require modules, infinite loops. Everything is going perfectly, all those operations failed.
Running the handlebar helper callback function in a VM is what made this work for me, because my main problem using VM's and running the whole code inside was adding those helpers to my global Handlebars object.
I'll update if I found a way to exploit it.
Currently we have an extensive web-based office application written in PHP, javascript to manage various processes (billing, ordering, user management, reporting, subscription management, article managementm, warehousing etc.)
The application uses iframes to load pages that perform various functions within the main application interface. From the main page tabs are created so you can access all open pages if needed. Each page has their own javascript include-files, css etc to perform complex tasks. All iframed-pages (around 600) are on the same domain/host.
Since many people describe using iframes as 'evil', I was examining on how I could convert the application into using divs only.
The quesions I have are:
1) in a situation where there are no iframes anymore, do I need to include every used javascript files on the main start page? Or is it possible to dynamically load javascript files into the memory depending on which page you access? (inluding new function declarations etc.)
2) Can these include-files be removed completely out of the memory when someone closes a page? (in this case a div). This to avoid possible conflicts between function names & variables of different files and also not to burden the browser's memory usage.
3) Or perhaps iframes are not that 'evil' afterall, and this is a correct way for using iframes?
thanks for the advise
chees
Patrick
have you tried any code or just asking?
you can use jquery (see here)
$.getScript("ajax/test.js", function(data, textStatus, jqxhr) {
console.log(data); //data returned
console.log(textStatus); //success
console.log(jqxhr.status); //200
console.log('Load was performed.');
});
As long as your javascript is modular, you can unload it whenever you want.
In case you don't know, in Javascript functions are objects. And with objects you can do this:
var a = {b:10, c:20, d:30}; //Some memory is allocated for object a
a = null; //Object a will be deleted, and no longer need any memory
How can you use that to handle functions? Here's easiest explanation:
var myFunction = function(a,b,c) {
alert(a+b+c);
}; //Here we have a function stored in memory
myFunction = null; //And now we don't
You can make whole libraries of functions this way and then remove them with one operation. Example:
var MyMath = {};
MyMath.abs = function(a) {
return (a<0)?-a:a;
};
MyMath.min = function(a,b) {
return a>b?b:a;
}; //MyMath uses some memory.
alert(MyMath.min(5,10));
MyMath = null; //Now it does not
So, wrap all of your libraries into objects, and unload those objects whenever you need.
Added:
When your browser sees some new Javascript code - it executes it all at once. It also does save it in memory, but never uses it again (basically it is stored the same way hello is stored in <div>hello</div>). Therefore the best solution would be:
1) Add <script> tags dynamically to load desired Javascript files on demand. It will look something like:
if (MyMathLibrary == null || MyMathLibrary == undefined) {
createScriptElementForMathLibrary();
}
alert(MyMathLibrary.calculateEnthropyOfTheUniverse());
2) Whenever you no longer need the library, you just do MyMathLibrary = null. You can also delete it from DOM at this point, it won't do any harm, as I said, it is no more than invisible div at this point.
But honestly, are you sure this is worth the bother? People nowadays write 3D shooters purely in Javascript and computers come with 4-16 GB of memory. Also, it's common practice to first make a working program and bother with optimizations only if it lags. Otherwise it is likely that you will spend a month of work for something no user will ever notice.
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.
So I just need a sanity check on the way in which I layout my code for an application. I'm always keen to learn better approaches.
I basically use an Object Literal to organise my code meaning that I have one single global variable. Then for each section of the application I create a separate object - something like:
var MYAPP = {
init : function() {
//site wide common js
},
sections : {
homepage : function() {
//homepage js
},
anotherpage : function() {
//another page js
tools.usefultool();
}
},
tools : {
usefultool : function() {
//useful reuseable method
}
}
};
My question is while this helps code organisation, I'm wondering about objects being initialised but never used. For example - if I'm on a site's homepage I'll just call MYAPP.sections.homepage() . I don't actually need any of the other objects so I'm wondering - does this structure have a performance implication? Is there a better way? The structure closely follows the the great Rebecca Murphy article "Using Object to Organise Your Code" (http://blog.rebeccamurphey.com/2009/10/15/using-objects-to-organize-your-code).
Thanks!
Yes, there's always a performance hit in unused code as the parser has to actually interpret the code even if it's not executed. But any performance hit here is so minute that you're never going to notice it. The only real hit in unused code like this is in the bandwidth required to download it. If you have a 100kb file downloaded that you never use then you're wasting the time to download that file.
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.