Ok, I've read a lot of information about the new Asset Pipeline for Rails 3.1 and I couldn't find a proper answer to my doubt.
I was loading my .js files according to the view#action I was rendering, on demand. I was doing this to prevent incorrect bindings and to load small .js files.
candidate_opportunities#index
$(".sortable_drag_n_drop").sortable({
update: function(event, ui) {
$.post('/candidate_opportunities/sort', $(this).sortable('serialize'));
},
handle: 'span'
});
candidate_companies#index
$(".sortable_drag_n_drop").sortable({
update: function(event, ui) {
$.post('/candidate_companies/sort', $(this).sortable('serialize'));
},
handle: 'span'
});
$(".sortable_drag_n_drop").disableSelection();
What is the best solution now?
Should I change my bindings and let Sprockets compile all my .js files using //=
require_tree . ?
Or should I try to load my
.js according to my views, so I don't end up with a huge
application.js?
If you are updating this to the pipeline you have several options. You should probably take the way the pipeline should work into account in deciding.
Broadly speaking, the aim of he pipeline is to join all your JS into one file and minfy/compress it. The point in doing this is reduce the number of requests per page, and to allow far-future headers to be set so that the resource is cached at the browser or somewhere in a transparent proxy/cache.
On to the options.
1. The same as you do now.
You could keep doing the same thing as you do know. I presume that you are using the rails helpers to add these view JS files in the main layout file. You could keep doing the same with the pipeline, however you must add all the files you use to the precompile array:
config.assets.precompile += ['candidate_opportunities.js', 'candidate_companies']
The assets must be in assets/javascripts but there is no need to add them to the manifest file as you are adding each on individually.
It is highly recommended that you stick with the Rails defaults for the pipeline and precompile assets for production.
The downside is an extra request on those pages, but this is only an issue if the app is under high load.
2. The Asset Pipeline Way (TM)
To do this with the pipeline you would need to move these files into assets/javascripts and require_tree as you say.
The issue in your case is that the JS snippets target the same class (but with a different post URLs), so this is not going to work. And with require_tree the order of the files might not be what you want.
A new 3.1 app generates files for views (I think), but the expectation is that they will target unique attributes (from a site perspective) in the markup, because all the files get included in the application.js
To get around the problem of JS clashes. I would suggest that you refactor the JS snippet so that it is more generic. You could use a data-post-url attribute on the sortable object:
<ul class="sortable_drag_n_drop" data-post-url="/candidate_opportunities/sort">
and then collect the url in your JS.
Not only is that DRYer, but you have less overall JS and can fully use the pipeline as intended.
I'm frustrated on Rails asset pipeline. Maybe not the whole asset pipeline thing but the way Rails organize javascript is really not logical.
As of now, Rails has a separate javascript file per controller. Which is somewhat good in terms of logical organization. But then the asset pipeline compresses all those files into one big js file. So basically your js files are well organized but then they are loaded all at once, resulting to variable clashes, function clashes, other code clashes and other unexpected behavior. Because what we really want as developers is a simple way to execute or load a page specific javascript.
One famous approach is to just include a specific javascript file per page. Yes, that will work, but we are not using the performance boost given by the asset pipeline if we are requesting different javascript files per page.
My solution is to create an javascript object that holds all the page-specific functions then fetch and execute them once the matching controller-action pair is executed by Rails. Something like this:
PageJs = {};
PageJs["users/new"] = function(){
alert("I will be called when users/new action is executed");
};
Basically, that's the core idea. Well I've implemented that idea and created a gem for it. Checkout Paloma, and see how you can logically organize your js files and execute page-specific javascript without complex setups.
Here's an example on how Paloma is used:
Javascript file:
Paloma.callbacks['users/new'] = function(params){
// This will only run after executing users/new action
alert('Hello New Sexy User');
};
Rails controller:
def UsersController < ApplicationController
def new
#user = User.new
# No special function to call,
# the javascript callback will be executed automatically
end
end
That's just a quick example, but it has lot more to offer, and I'm sure it can solve your problem. Easily.
Thank You!
Related
I have a very strange requirement that I need to bundle everything together in one HTML page with my Durandal Single Page application. I can make this away with my dependencies as I am defining them with a name:
define("models.mapper", [], function() {
});
However, it seems like it will not be possible to bundle durandal stuff as it defines modules without names:
define(['require', 'jquery'], function(require, $) {
// ....
}
This is fine when you want to make it work with path references but it seems like this will make it hard to inline this into HTML. Any ideas or suggestions on this?
Require.JS requires you to have only one anonymous define per file so that it can use the file path+name relative to the base path to give it a name. If you would like to have the durandal source inline on your page as well then you'll need to update their define lines to give them the appropriate names (i.e. define('durnadal/system', ......).
An easier approach may be to just build your source code in the structure of a normal durandal project and then use the RequireJS optimizer (http://requirejs.org/docs/optimization.html) to build them into a single JS file - if you configure this correctly without minification then you can just paste the file contents into a script tag on your page and it'll still be legible!
If you really wanted to you could then just continue developing in the single HTML file however you really should look at automating all of this into a grunt workflow and it shouldn't be too hard and you'll have much easier to manage code. Note that you may even be able to use the durandal grunt task to do this, but I'm not sure what options it allows you to provide but you can definitely use the requirejs grunt task and build it into your workflow without minification. With some templating task you could then inject that output into your final HTML page.
I'm trying to update the javascript on a large scale, php/codeigniter based website. It has many, many php partials for code being loaded onto certain pages, but only one footer, header. Most of the partials have inline script tags in them for loading javascript. This needs to be fixed and because the site is very modular with components being used multiple times across pages, require.js seems like a pretty good solution.
So, it's instantiate javascript, we typically do this.
<script type="javascript" src="../js/scriptname.js">
<script type="javascript">
DP.scriptname.init(parameters)
</script>
I'd like to get away from this and just have a have a single entry point for the js using require.
My question is this: what's the best way to instantiate javascript for certain pages using require? Do I need I need to continue including my scripts in the partial and then do something like writing a require module for that specific page and then wrap it all in my data-main script like this? We're planning on using Backbone and Marionette as well, but I won't be able to use the Backbone router to do anything like setting hash URLs. Should I use the URLs to instantiate my require modules perhaps?
Ok, hope someone can help. My experience has typically been in building single page websites. This is different. Thanks
Cam
Well, if I understand your question correctly, you can use Require JS in such way.
First of all, write a config in which you can describe mapping between module names and concrete files. For example:
requirejs.config({
baseUrl: 'js/modules' // by default load any module using this path
});
After that you should refactor your existing module and adjust it to the AMD format (see http://requirejs.org/docs/whyamd.html)
As a result of this step you will have something like this (let's say in file 'js/modules/scriptname.js'):
// module has the same name as the file, which contains it - "scriptname"
define(function () {
// define the module value by returning a value
return function () {};
});
And as a final step you can refactor your inline script and use this module in such way:
<script type="javascript">
// when we place name of the module as a first argument of the "define"
// function, Require JS find file that contains it and load it asynchronously
define(["scriptname"], function (scriptname) {
// "scriptname" now contains value that we recieve from the module definition
scriptname.init(parameters);
});
</script>
Hope this helps.
Note. My solution based on this section of official Require JS documentation: http://requirejs.org/docs/api.html#jsfiles
This question seems to come up a lot, so I'll point you to some resources that may help:
How does RequireJS work with multiple pages and partial views? - https://stackoverflow.com/a/10816983/617615
Modular HTML components with RequireJS - http://simonsmith.io/modular-html-components-with-requirejs/
Example RequireJS-based project that has multiple pages that share a common set of modules - https://github.com/requirejs/example-multipage
I tried unsuccessfully to add a google map(externally loaded script) to a meteor app, and I noticed there were two kinds of problems:
If I do the simple thing and add the main API script to my <head></head>, then it gets rendered last.
When this happens, I am obliged to insert any scripts that depend on the API again in my template's <head> - after the main API script. (otherwise scripts complain they don't see the API blabla..)
Then the time for the actually function call comes - and now putting it inside <head> after the rest won't work. You need to use Template.MyTemplate.rendered.
Basically my question is:
What's the cleanest way to handle these kinds of things?
Is there some other variable/method I can use to make sure my Google main API file is called very first in my HTML?
I just released a package on atmosphere (https://atmosphere.meteor.com) that might help a bit. It's called session-extras, and it defines a couple functions that I've used to help with integrating external scripts. Code here: https://github.com/belisarius222/meteor-session-extras
The basic idea is to load a script asynchronously, and then in the callback when the script has finished loading, set a Session variable. I use the functions in the session-extras package to try to make this process a bit smoother. I have a few functions that have 3 or 4 different dependencies (scripts and subscriptions), so it was starting to get hairy...
I suppose I should add that you can then conditionally render templates based on whether all the dependencies are there. So if you have a facebook button, for example, with helpers that check the Session variables, you can give it a "disabled" css class and show "loading facebook..." until all the necessary scripts have loaded.
edit 03/14/2013
There is also an entirely different approach that is applicable in many cases: create your own package. This is currently possible with Meteorite (instructions), and the functionality should soon be available in Meteor itself. Some examples of this approach are:
jquery-rate-it: https://github.com/dandv/meteor-jquery-rateit
meteor-mixpanel: https://github.com/belisarius222/meteor-mixpanel
If you put a js file in a package, it loads before your app code, which is often a good way to include libraries. Another advantage of making a package is that packages can declare dependencies on each other, so if the script in question is, for example, a jQuery plugin, you can specify in the package's package.js file that the package depends on jQuery, and that will ensure the correct load order.
Sometimes it gets a little more interesting (in the Chinese curse sense), since many external services, including mixpanel and filepicker.io, have a 2-part loading process: 1) a JS snippet to be included at the end of the body, and 2) a bigger script loaded from a CDN asynchronously by that snippet. The js snippet generally (but not always!) makes some methods available for use before the bigger script loads, so that you can call its functions without having to set up more logic to determine its load status. Mixpanel does that, although it's important to remember that some of the JS snippets from external services expect you to set the API key at the end of the snippet, guaranteed to be before the bigger script loads; in some cases if the script loads before the API key is set, the library won't function correctly. See the meteor-mixpanel package for an example of an attempt at a workaround.
It's possible to simply download the bigger js file yourself from the CDN and stick it in your application; however, there are good reasons not to do this:
1) the hosted code might change, and unless you check it religiously, your code could get out of date and start to use an old version of the API
2) these libraries have usually been optimized to load the snippet quickly in a way that doesn't increase your page load time dramatically. If you include the bigger JS file in your application, then your server has to serve it, not a CDN, and it will serve it on initial page load.
It sounds like you're loading your Javascript files by linking it with HTML in your template. There's a more Meteor way of doing this:
From the Meteor Docs:
Meteor gathers all JavaScript files in your tree with the exception of
the server and public subdirectories for the client. It minifies this
bundle and serves it to each new client. You're free to use a single
JavaScript file for your entire application, or create a nested tree
of separate files, or anything in between.
So with that in mind, rather than link the gmaps.js into head, just download the un-minified version of gmaps and drop it in you application's tree.
Also from the Meteor Docs:
It is best to write your application in such a way that it is
insensitive to the order in which files are loaded, for example by
using Meteor.startup, or by moving load order sensitive code into
Smart Packages, which can explicitly control both the load order of
their contents and their load order with respect to other packages.
However sometimes load order dependencies in your application are
unavoidable. The JavaScript and CSS files in an application are loaded
according to these rules:
Files in the lib directory at the root of your application are loaded
first.
[emphasis added]
And if the sequence is still an issue, drop the js file into client/lib and it will load before all the Javascript you've written.
I've used meteor-external-file-loader and a bit of asynchronous looping to load some scripts, which will load javascript (or stylesheets) in the order you specify.
Make sure to have meteorite and add the package above >> mrt add external-file-loader
Here's the function I wrote to make use of this package:
var loadFiles = function(files, callback) {
if (!callback) callback = function() {};
(function step(files, timeout, callback) {
if (!files.length) return callback();
var loader;
var file = files.shift();
var extension = file.split(".").pop();
if (extension === "js")
loader = Meteor.Loader.loadJs(file, function() {}, timeout);
else if (extension === "css") {
Meteor.Loader.loadCss(file);
loader = $.Deferred().resolve();
}
else {
return step(files, timeout, callback);
}
loader.done(function() {
console.log("Loaded: " + file);
step(files, timeout, callback);
}).fail(function() {
console.error("Failed to load: " + file);
step(files, timeout, callback);
});
})(files, 5000, callback);
}
Then to use this, add to one of your created methods for a template like so:
Template.yourpage.created = function() {
var files = [
"//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js",
"javascripts/bootstrap.min.js"
];
loadFiles(files, function() {
console.log("Scripts loaded!");
});
}
Quick edit: Found it's just a good idea to place the functionality from the .created method in the /lib folder in Meteor.startup();
Meteor.startup(function() {
if (Meteor.isClient) {
// Load files here.
}
});
Caveat: Lots of javascript files = really long load time.... Not sure how that will be affected by the normal meteor javascript files, and the load order there. I would just make sure that there are no conflicts until users take action on the website (or that if there is, they are loaded first).
I'm starting in javascript development, and did a simple project with node.js as a rest API and a client using backbone, everything look perfectly till I want to get my templates out of my js.
I found different approaches, some of them with some time (like one year old) but I can't understand which one could be better:
A .js file with a var with the html code
pros -> easy to load, easy to pass to underscore to compile it.
cons -> scape every single line.
app.templates.view = " \
<h3>something code</h3> \
";
load template:
template: _.template(app.templates.view)
External template in Underscore
Use require.js to load with text plug-in.
pros -> load different templates as needed.
cons -> I don't like the approach to put everything inside a "loader" function...
define(["TemplateEngine", "text!templates/template.html"], function(...
RequireJS: Loading modules including templates and CSS
A function that loads the templates with an AJAX petition.
pros -> You can load the template that you need and adds local storage posibilities.
cons -> Seems that I have to merge all my templates into one file for production environments.
function() {
var templateLoader = {... $.get calls ...}
Best way to asynchronously load underscore templates
And a Jquery plug-in for template loading that I really liked but it seems that it didn't go to release?
http://api.jquery.com/jQuery.template/
It seems that require is the best approach, but maybe I'm missing something, I do wan't to make things as clean as possible since I'm in the learning/having fun phase :D
Any good article or github project with a good structure or any light on this will be appreciated.
Thanks.
Excuse any major spelling mistake, not an English speaker :)
--EDIT--
found some interesting videos to understand how to start and wrap things with require.js
http://www.youtube.com/watch?v=VGlDR1QiV3A
http://www.youtube.com/watch?v=M-wjQjsryMY
I would recommend using require.js with text plugin. Mixing html templates as strings in javascript variable is bad idea, as well as using something like <script type="text/template"></script>.
Here is one very good series on backbone.js which covers template loading and project build as well: http://dailyjs.com/2012/11/29/backbone-tutorial-1/. Github project is also provided there.
Require is a good option from the ones you listed.
Is there a reason you haven't considered simply:
Storing templates in the pages that use them as <script type='text/template'> nodes?
Storing templates as text (non-JS) files and loading them via XHR on pages that use them?
I'm using underscore template engine for an backbone application. As of now I have over 15 templates in the <head>. Its getting hard to maintain. So far, most of the solutions I seen to manage templates ended up needing them to be js files. That's also a headache, I prefer them to be html files for editing purposes.
I took a look at requirejs and not sure if I need that since it kinda revolves around a more modular approach that I can't say I'm using at the moment (although I will soon).
What will be the best way to manage templates and load/cache them as needed?
Personally we needed a robust solution at my company, so we went with:
Require.js - for module loading
Handlebars - for more powerful templating than Underscore can offer
HBS - an excellent require plug-in from Alex Sexton that handles bringing compiled templates in via Require
With this setup I can keep all of my templates in their own file, and then to use them I have files like this:
define(['template!path/to/someTemplate'], function(someTemplate) {
var MyNewView = BaseView.extend({template: someTemplate});
$('body').append(new MyNewView().render().el);
}
(and as you might guess we have a base Backbone view called BaseView which uses the view's template property to render the view).
Now, all that being said, if you don't need such a robust setup then Require may not be for you. In that case I would do the following:
Put all of your templates in to one or more HTML files; wrap them in script tags, like so:
<script id="dummyTemplate" type='text/template'>
<span>I'm a template!</span>
</script>
Write some code on your server-side to include those HTML files in the main HTML file you send to the client
Write a function which takes a template ID, gets the text of that element, compiles it in to a template, and returns that template (maybe cache the compiled templates if you want ... of course, with Underscore templates I don't think you even need compiling, so you can skip all that).
Use your function to access your templates: $("#something").html(templateFunc('dummyTemplate').template())
This will allow you to store your templates in html files (for syntax coloring), but still access them conveniently in JS. You can also divide your templates between as many files as you want, as long as you can write include logic to bring them in.
If you do opt for Require though, definitely check out the HBS plugin. And if you haven't looked at Handlebars templates yet, you might want to; they're far more powerful than Underscore ones (but like any good templating system, don't allow for too much logic).
Not sure what you mean by it being unmaintainable. Is it just a long list?
You don't need to keep your templates in the head. They can be at the bottom of your body as well. They just need to be defined before you try to use them.
One thing you might look into, depending on the server technology you are using would be to separate your templates into a different HTML file and include it at runtime.