I have a website with multiple pages, each of them with custom event handlers attached to certain elements. Each page has an id in the html markup (#page1, #page2, ...).
In my javascript file for the website I have seperated the functions for each site in self executing modules, simplified like so:
//module 1 for stuff that happens on #page1
(function() {
// stuff to happen, events, delegations, preparations, etc
})();
I thought I could execute certain page related modules only if the #id is found in the current document like:
if( $("#page1").length ) {
// call module 1
};
... because a lot of event delegation happens in some modules.
Is this a common/good approach to speed up things? Or is it better to include the modules in seperated js files only on the subsites where they are needed? Or any other method/ideas?
Put all the code in a single js file so it is cached. Put an id on the body or similar element, then put each module of code in a separate function. Then have an object to hold module references linked to body element ids, then in the onload:
var modules = {
id1: function() { /* stuff for page 1 */},
id2: function() { /* stuff for page 2 */},
...
}
var id = document.body.id
if (id && id in modules) {
modules[id]();
}
Then you just give the body the id of the function that should run for that page.
Related
I am new to Javascript/backboneJS/RequireJS. In order to render my front end I have made one controller, one model and one view. Also, I have one dropdown in my html file.
So what I have done till now is in my html file at the END I have
require(['common'],function()
{
require(['jquery','fastclick','nprogress','charts','underscore','spinner'],function()
{
require(['firstDashboardController']);
});
});
So I am loading "firstDashboardController" and this controller loads all the modules accordingly and displays data in front end. So everything works fine.
Now I have a dropdown in the front end. When I select the dropdown, as per the id selected I want to retrieve the data. So I need to call "firstDashboardController" again so that everything gets rendered as per the new id that I have got.
So what am I suppose to do? Like do I need to UN-REQUIRE my "firstDashboardController" and then REQUIRE it again passing the new id. Because the controller is already loaded via Require beacuse I loaded it in my HTML file as mentioned above. But I need to load it again as per the new id selected it via dropdown. So how to go about it? Pleas help me. if any code snippet is required I can put that.
Code Snippet:
My Controller:
define(['backbone', 'firstSubViewModel','dropdownViewModel', 'dropdownModel'],
function(Backbone, firstSubViewModel, dropdownViewModel, dropdownModel) {
var ch = new dashboardModel.chart({});
if (localStorage.getItem('p_kt') && localStorage.getItem('p_acid') && localStorage.getItem('p_seid')) {
var data = {
tokenProp: localStorage.getItem('p_kt'),
accountIdProp: localStorage.getItem('p_acid'),
sessionIdProp: localStorage.getItem('p_seid')
};
$.when(
ch.fetch(data) // GETTING DATA FROM MODEL VIA AJAX CALL in my model.JS
).then(function() {
// Main Graph on Dashboard
new firstSubViewModel({
el: '#chartAnchor1',
model: ch
});
});});
I somehow need to call ch.fetch() again.
You aren't properly defining your controller. You currently have it as sort of a one-time setup method instead of something you can re-run later. Let's go step by step.
myLife.js:
define([], function() {
return "a complex series of failures";
});
By returning a value from define's callback, this defines that anytime I require "myLife", then it will provide "a complex series of failures" in the callback function. This is what Backbone and other AMD modules do to appear inside your code blocks. However, it only runs the contents once; and saves the result. So, this won't work:
incrementer.js:
var x = 0;
define([], function() {
x = x + 1;
return x;
});
(Trying to require incrementer would always give you "1".)
What you want to do is return a function - one you can re-run anytime.
incrementerV2.js:
define([], function() {
var x = 0;
return function() {
x = x + 1;
return x;
};
});
In any file, you can then have this:
require(['incrementerV2'], function(myIncr) {
myIncr(); // 1
myIncr(); // 2
});
...And, for the record, I would recommend having only one require statement in any given file whenever possible. You can add dependencies in that first-argument array if load order is important.
More commonly, people will have one module contain a self-defined object that has multiple functions in it, rather than the function I gave above. Returning and then using just one function is valid as well, depending on the use case. Any variable type works, but just remember it will always be the one and only same variable anytime you later need it.
Load Order
When the system retrieves myLife.js or incrementer.js above, there's an intermediate step before it actually runs the definition function we've defined. It will look at the first argument, the array of named dependencies, and figure out if there are still dependencies needed before it can run the function given. Example:
a.js: require(['b', 'c'], function(b, c) {
b.js: define(['c'], function(c) {
c.js: define([], function() {
a.js is requested first, but not run because it needs B and C. B loads next, but is ignored because C is not loaded. C runs, and then its return value is passed into A and B. This system is internally very smart, and should never request the same file twice or have conflicts if one file loads before another. You can use it much like imports in Java.
Also, let's say you only added 'c' in a.js so that b.js wouldn't crash, because it needs it loaded first - in that case, just take it out of A and it should work the same.
a.js: require(['b'], function(b) {
Just like A did, B will automatically load all its dependencies before it executes anything. A simple principle is to only refer a dependency if it's actually directly referenced in the file (or defines necessary global variables)
I have a 300 line javascript file that sets up jQuery event handlers and other needed functions for a partial view that's used by multiple views within a ASP.NET MVC application. The event handlers handle 99% of everything identically regardless of which view is using the partial. This question is about that 1% difference.
Since JavaScript doesn't have interfaces is it safe to define a function to be called by one or more of the event handlers that processes the things that are different in a separate file that is loaded depending on which view is used? If not, what would be the best way to handle this situation? In other languages I'd use interfaces and/or abstract classes in this situation.
Example:
shared file
$(document).ready(function() {
//shared variables here for methods
$(document).on('click', '.selectable-table tbody tr', function() {
//do shared actions
mySpecificFunction();
//finish shared actions (if necessary)
});
});
Definition1.js
function mySpecificFunction() {
//do stuff
}
Definition2.js
function mySpecificFunction() {
//do other stuff
}
The views would load the appropriate scripts as such:
<script src="definitionX.js"></script>
<script src="sharedScript.js"></script>
The "signature" (term being used generously because javascript) of mySpecificFunction() would be the same for each definition, but something in my gut is telling me that this is bad practice. Is there a better/correct way to do this or a design pattern for this purpose?
I think you can use OOP approach here and you don't need the abstract classes or interfaces for that, instead you can use objects (which are more flexible than in other languages).
For example, you can have a base View prototype with shared code and then load specific view1.js, view2.js where the base prototype will be extended with specific code:
$(document).ready(function() {
// view is a view instance coming from the specific view.js
view.init();
});
// sharedScript.js, view prototype
var View = {
init: function() {
$(document).on('click', '.selectable-table tbody tr', function() {
// do shared actions
// ...
// do specific actions
this.mySpecificFunction();
});
},
mySpecificFunction: function() {
//do specific things, can be left empty in the "prototype" object
return;
}
};
// view1.js
var view = Object.create(View);
view.mySpecificFunction = function() {
alert('view 1');
}
// view2.js
var view = Object.create(View);
view.mySpecificFunction = function() {
alert('view 2');
}
And the views would load shared and specific scripts:
<script src="sharedScript.js"></script>
<script src="view1.js"></script>
This is just a rough idea which can be improved, for example, you may want to concatenate and compress all your js code into the single file for production. In this case the global view variable coming from view1.js, view2.js, etc would become a problem.
An improvement can be some kind of "router" which will detect what view should be instantiated:
$(document).ready(function() {
router.when('/', function() {
view = HomePageView();
}).when('/about', function() {
view = AboutPageView();
});
view.init();
});
The approach outlined above will work but it's not the best approach in terms of maintainability. Adding one file or another via a script tag to import the specific function
doesn't necessarily make it clear to another developer that you have actually changed the behaviour of the event handlers in the shared code.
A simple alternative could be that within each view you would wrap the partial view within a containing element that has an identifying css class to differentiate between the behaviour required at that point.
Then assign event handlers individually for those different css classes:
$(document).ready(function() {
//shared variables here for methods
$(document).on('click', 'div.type1 .selectable-table tbody tr', function() {
//do shared actions
mySharedActions();
mySpecificFunction1();
//finish shared actions (if necessary)
});
$(document).on('click', 'div.type2 .selectable-table tbody tr', function() {
//do shared actions
mySharedActions()
mySpecificFunction2();
//finish shared actions (if necessary)
});
});
This would allow you to keep all your specific functions together in one place and makes the changing behaviour predicated by the css class explicit
for future developers to see.
I just started on Polymer. There seems to be two events indicating content is ready:
// Listen for template bound event to know when bindings
// have resolved and content has been stamped to the page
app.addEventListener('dom-change', function() {
console.log('Our app is ready to rock!');
});
// See https://github.com/Polymer/polymer/issues/1381
window.addEventListener('WebComponentsReady', function() {
// imports are loaded and elements have been registered
});
I wonder if it is necessary to wrap them together and put the code inside, to make sure that the document is fully loaded before doing any script, for example:
app.addEventListener('dom-change', function() {
window.addEventListener('WebComponentsReady', function() {
// scripts go here
});
});
However, I don't know what is the correct way to do so in all browsers. If WebComponentsReady happens before dom-change, the inside script never execute.
Heck, this might not even be necessary because the polymer-starter-kit doesn't wrap them together. In that case, which types of script should go inside dom-change event and which types of script should go inside WebComponentsReady event?
Use the native ready callback as described here.
<script>
(function() {
Polymer({
is: 'example-element',
properties: {...},
ready: function() {
// access a local DOM element by ID using this.$
this.$.header.textContent = 'Hello!';
}
});
})();
</script>
I'm building a small web app with a few other people. I want to allow the other developers to define a couple of functions that are always called when the document is ready.
Our app inserts the following script into the HTML body of every page:
<script type="text/javascript">
(function(){
window.Utils = {
funcs: {}
};
$(document).ready(function(){
alert('Calling funcs...');
var startFunc = Utils.funcs['start'];
if (startFunc != undefined){
startFunc();
}
var finishFunc = Utils.funcs['finish'];
if (finishFunc != undefined){
finishFunc();
}
});
})();
</script>
Then, in a separate .js file, a developer should be able to do the following:
Utils.funcs['start'] = function(){
alert('Starting...');
};
Utils.funcs['finish'] = function(){
alert('Finishing...');
};
But this doesn't work. The functions are never called?
jsFiddle link: http://jsfiddle.net/XvQtF/
jsFiddle's (very surprising) default is to put your code in a window load handler. (You can see this on the left at the top, the second drop-down box says "onload".) That happens very late in the loading process, long after ready has fired. So the functions aren't added until after you've tried to run them.
If the other developers put their functions in Utils.funcs in script elements after your element defining Utils but without waiting for a load event, it's fine: Updated Fiddle
For what it's worth, though, I would lean toward using a pub/sub solution rather than a single function. If you want to have more than one start function, for instance, your current structure doesn't allow it.
jQuery has Deferred and Promise now, which can be used for this. Here's a simple example of that: Live Copy | Live Source
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<meta charset=utf-8 />
<title>Pub/Sub with Deferred and Promise</title>
</head>
<body>
<script>
(function($) {
// Deferred objects for our app states
var start = new $.Deferred(),
finish = new $.Deferred();
window.Utils = {
start: start.promise(), // Only expose the Promise of each
finish: finish.promise() // " " " " " "
};
$(function() {
alert("Calling funcs...");
// Start the app
start.resolve();
// Finish the app (or whatever)
finish.resolve();
});
})(jQuery);
</script>
<script>
// Module 1
Utils.start.then(function() {
alert("Module 1 Started!");
});
Utils.finish.then(function() {
alert("Module 1 Finished!");
});
</script>
<script>
// Module 2
Utils.start.then(function() {
alert("Module 2 Started!");
});
Utils.finish.then(function() {
alert("Module 2 Finished!");
});
</script>
</body>
</html>
Set up Utils in a separate script to be loaded as the very first one. Also, do this unconditionally (not in any callback etc):
/* load as first script, sets up a global container-object for later use */
var Utils = {
funcs: {}
}
Note that it is ok to define a global variable in the global scope.
/* other script */
(function(){
function myFunc() { /*...*/ };
// now store a reference in Utils
Utils.funcs.start = myFunc;
})();
As mentioned in an other answer: Be aware of the loading and calling order of your various scripts / code:
$(document).ready is essentially the "DOMContentLoaded"-event with most browsers (but earlier versions of IE). "DOMContentLoaded" fires, when all inline-resources originally found in the head-section have been loaded and the DOM-structure of the body is present.
Since this does not include any injected content, it is probably granted, that the event is fired before any modular library (which are loading modules by injecting script tags) is fully loaded and present. (Given that these scripts are loading concurrently with images and other inline-stuff using just a handful of network-slots/sockets provided by the browser, they are probably one of the last things to be ready in your whole loading-process.)
Instead of just using
startFunc() and
finishFunc()
try using this
startFunc.apply(this, null) and
finishFunc.apply(this, null)
this will invoke the functions.
also make sure that
Utils.funcs['start']
Utils.funcs['finish']
is getting initialized before they are called.
Sorry I couldn't be anymore specific with the title.
I'm building a web-site (personal), which displays different content to the user depending on the query string that is used in the url.
e.g. page=home.html would display home.html
The websites Javascript is wrapped inside an object, with each value containing different data, some pseudo code:
(function(){
var wrapper = {
init: function(){
//Runs on document ready
this.foo();
this.nav.render();
},
foo: function(){
//Some functionality goes here for the website, e.g. Display something from an API
},
nav: {
//Functionality to handle the navigation, has different properties
config: {
//Contains the config for nav, e.g. page names + locations
dir: '/directory/to/content/',
pages: {
page_name: wrapper.nav.config.dir + 'page_value'
}
},
render: function(){
//some code
},
routes: function(){
//some code}
}
}
};
$(function(){
wrapper.init();
});
})();
My problem is that I'm trying to prepend the dir value to each of the page values (inside the object where the pages are defined), expecting to get the output of (in this pseudo code case) of directory/to/content/page_value, but instead dir is undefined when I'm trying to access it, I've tried the following to achieve what I want:
wrapper.nav.config.dir + 'page_value'
I've been playing around with the last 30 minutes trying to find out what I'm doing wrong, and even thought about hard-coding the URL in for each page.
The reasoning for wanting to do this is that my local development server and web host have different directory structures, so I don't want to re-write the URL's each time I want to develop + publish. As for why everything is wrapped inside an object, I thought it would be easier to maintain this way.
Hopefully the answer is simple and it's just an amateur mistake / lack of understanding.
The issue is that you can't refer to a variable that is being defined in that very definition.
So, inside the definition of wrapper, you can't refer to wrapper. And, inside the definition of config, you can't refer to config either and so on.
The usual design pattern for solving this is to initialize as much as you can in the declaration of your data structure and then do the rest in .init() when you can freely access all of it.
Change the first two lines to:
var wrapper = null;
(function(){
wrapper = {
Otherwise, the wrapper is a local variable to your anonymous function.
The problem is that you're still busy defining the wrapper when you ask for its value, which is why it's still undefined.
The code below fails too:
var x = {
y:"1",
z:x.y
}
Why not:
//...
init: function(){
//Runs on document ready
this.foo();
var config = this.nav.config;
for (var page in config.pages) {
config.pages[page] = config.dir + config.pages[page];
}
},
//...