How can I include javascript assets selectively in SailsJS? - javascript

In a Sails.js application, how can I include javascript assets selectively?
For instance, if I have an admin page and admin.js lives inside the assets/js directory. How do I keep the admin.js from loading on the public index page?
I'm aware that I could move the js out to the public directory, and include the script in my admin view's template. But I'm still unable to include it after the assets.js() call inserts it's javascript. I need it to be inserted after the sails.io.js script is loaded.
Is there any way to selectively load scripts and still have access to the sails.io.js which is automatically included with the assets.js() function call? Is there a better paradigm for this kind of situation?
EDIT:
Since the release of SailsJS 0.9 and the restructuring of the asset management system, this question doesn't really apply anymore.

Sailsjs uses asset-rack to serve /assets. With the default layout page, sailsjs serves pages that look like (dummy2.js is included with an explicit < script >):
<html>
<head>
...
<script type="text/javascript" src="/assets/mixins/sails.io-d338eee765373b5d77fdd78f29d47900.js"></script>
<script type="text/javascript" src="/assets/js/dummy0-1cdb8d87a92a2d5a02b910c6227b3ca4.js"></script>
<script type="text/javascript" src="/assets/js/dummy1-8c1b254452f6908d682b9b149eb55d7e.js"></script>
</head>
<body>
...
<script src="/public/dummy2.js"></script>
...
</body>
</html>
So sailsjs does not concatenate files (at least not in development mode). sails.io (socket-io) is always included before /assets/js in layout, and before < script > on the page.
It looks like your admin.js is expecting a condition which sails.io has not yet set, perhaps its negotiating a transport with the server? Try waiting for the condition to be set.

In a Sails.js application, how can I include javascript assets selectively?
I selectively load js assets using a wrapper around assets.js(). This snippet uses Jade's "-" and "!{...}". EJS would instead use "<%...%>" and "<%=...%>"
<!-- JavaScript and stylesheets from your assets folder are included here -->
!{assets.css()}
-function selectAssets (assetsjs, files, ifInclude) {
- return assetsjs.split('\n').reduce(function (prev, curr, i) {
- var frag = curr.match(/src="\/assets\/js\/\w{1,}\-/);
- if(frag) {
- var file = frag[0].substring(16, frag[0].length - 1);
- if ((files.indexOf(file) === -1) == ifInclude) { return prev; }
- }
- return prev + curr + '\n';
- }, '');
-}
//!{assets.js()} this line is replaced by:
!{selectAssets(assets.js(), ['dummy1', 'dummy5', 'dummy6'], false)}
The above would not include /assets/js/dummy1.js, dummy5, dummy6 with the layout. If you wanted to include dummy1 and dummy5 on a particular page, you would place
!{selectAssets(assets.js(), ['dummy1', 'dummy5'], true)}
on that page.
Note: The code assumes file name don't contain "-". Its straighforward to generalize for css and templates. sails-io would be a special case for mixins.

I know this is a old question but since people are still looking for this.
Sails has a folder called tasks in the root after you create a new project.
The file you are looking for is
pipeline.js
That file holds a variable called jsFilesToInject
// Client-side javascript files to inject in order
// (uses Grunt-style wildcard/glob/splat expressions)
var jsFilesToInject = [
// Load sails.io before everything else
// 'js/dependencies/sails.io.js',
'js/sails.io.js'
// Dependencies like jQuery, or Angular are brought in here
'js/dependencies/**/*.js',
// All of the rest of your client-side js files
// will be injected here in no particular order.
'js/**/*.js'
];
Just put your script that you want loaded before sails.io.js.
This is relevant for sails 0.11.x
another way that is valid is to create a views/partials folder
and create a new file like mapScripts.ejs
drop the script tags there and in your view use
<% include ../partials/mapScripts %>

Yes, you put a condition in template. :)
or add another "block" for your js.
extends ../layout
block body
<h1>hello</h1>
block jsscripts
<scripts> ..... </script>

To answer part of your question about where included files are located.
In 0.10 version order of the files is set in file tasks/values/injectedFiles.js as I recall in previous versions it was determined in Gruntfile.js itself.

You can add reference of your custom file in tasks/pipeline.js
var jsFilesToInject = [
'js/dependencies/sails.io.js',
'js/dependencies/**/*.js',
'js/**/*.js',
'js/yourcustomFile.js'
];

Related

In Meteor JS, how to control Javascript load order in relation to DOM load order? For animations

I've got a template that I downloaded:
http://halibegic.com/projects/merlin/
I want to use it in Meteor and I'm having major issues with
<script src="assets/js/script.js"></script>
at the bottom on line 444 not loading in the right order. When the page loads, none of the 4 functions specified in this js file work.
initNavbar();
initPortfolio();
initAnimations();
initTwitterFeed();
I have all the css, fonts, images, and js files in my public folder and they are all correctly referenced in the HTML. They are not in the lib directory which loads before everything else.
I think it's because the script is somehow loading before the DOM is loaded, so it has no DOM to apply things to.
Things I've tried:
When I change the name of script.js to main.js and change line 444 to <script src="assets/js/main.js"></script> the animations still don't work.
When I add this into the script file it still doesn't load correctly:
$(document).ready(function () {
initNavbar();
initPortfolio();
initAnimations();
initTwitterFeed();
});
I can do
Template.layout.rendered/created = function () { add in all the function code and call them here }
but this seems like an uncredibly, INCREDIBLY messy and inefficient way to do this. I need to specify the load order of individual files, not code. I have around five .js files in this template and I don't want to have to cut out their code and paste it all into one Template.layout.rendered/created function.
All you need to do is to load the javascript after the template is rendered.
Template.yourLayout.created = function() {
$('head').append('<script type="text/javascript" src="assets/js/script.js">');
}
If you have scripts loaded in $(window).load() or $(document).ready(), remember to get that out as well. You could run them in the promise of $getScript as well. This is your case:
$.getScript('assets/js/script.js', function() {
initNavbar();
initPortfolio();
initAnimations();
initTwitterFeed();
$(".loader .fading-line").fadeOut();
$(".loader").fadeOut("slow");
});
None of the above answers worked for me, so I kept hacking away until finally the following worked:
Template.layout.rendered = function() {
// hack: these third party header animation scripts must be inserted at the bottom of body
$('body').append('<script type="text/javascript" src="assets/js/classie-main.js">');
$('body').append('<script type="text/javascript" src="assets/js/cbpAnimatedHeader.js">');
};
I put this in my layout.js file.
Thanks to imslavko for the answer!
http://docs.meteor.com/#meteor_startup
On a server, the function will run as soon as the server process is finished starting. On a client, the function will run as soon as the DOM is ready.
So I put this into my client/views/application/layout.js. It uses the jQuery $.getScript, so you have to make sure that jQuery is loaded before you try this:
Meteor.startup( function () {
$.getScript("assets/js/jquery.min.js");
$.getScript("assets/js/bootstrap.min.js");
$.getScript("assets/js/isotope.pkgd.min.js");
$.getScript("assets/js/imagesloaded.min.js");
$.getScript("assets/js/jquery.scrollTo.min.js");
$.getScript("assets/js/jquery.nav.min.js");
$.getScript("assets/js/jquery.appear.min.js");
$.getScript("assets/js/twitterFetcher.min.js");
$.getScript("assets/js/script.js");
});
So all of these files will now load AFTER the DOM loads, and therefore animations work.
It's clear from here: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 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.
When not using special filenames and directories:
Files in subdirectories are loaded before files in parent directories, so that files in the deepest subdirectory are loaded first, and files in the root directory are loaded last. - Within a directory, files are loaded in alphabetical order by filename.
Below is a complete list of special file and directory names that control file load order:
lib
After sorting as described above, all files under directories named lib are moved before everything else, preserving their order.
main.*
All files that match main.* are moved after everything else, preserving their order.
I've used this pattern
Template.layout.created = function() {
var jsLibs = [
'vendors/Flot/jquery.flot.js',
'vendors/Flot/jquery.flot.pie.js',
'vendors/Flot/jquery.flot.pie.js',
'vendors/Flot/jquery.flot.pie.js',
'vendors/Flot/jquery.flot.pie.js',
'vendors/Flot/jquery.flot.pie.js',
'vendors/Flot/jquery.flot.pie.js',
'vendors/Flot/jquery.flot.pie.js',
'vendors/Flot/jquery.flot.time.js',
'vendors/Flot/jquery.flot.stack.js',
'vendors/Flot/jquery.flot.resize.js',
'js/flot/jquery.flot.orderBars.js',
'js/flot/curvedLines.js',
'js/flot/date.js',
'js/flot/jquery.flot.spline.js',
'js/flot/curvedLines.js',
'vendors/bootstrap/dist/js/bootstrap.min.js',
'vendors/bootstrap/dist/js/bootstrap.min.js',
'vendors/fastclick/lib/fastclick.js',
'vendors/nprogress/nprogress.js',
'vendors/Chart.js/dist/Chart.min.js',
'vendors/bernii/gauge.js/dist/gauge.min.js',
'vendors/bootstrap-progressbar/bootstrap-progressbar.min.js',
'vendors/iCheck/icheck.min.js',
'vendors/skycons/skycons.js',
'js/maps/jquery-jvectormap-2.0.3.min.js',
'js/moment/moment.min.js',
'js/datepicker/daterangepicker.js',
'build/js/custom.min.js',
'js/maps/jquery-jvectormap-world-mill-en.js',
'js/maps/jquery-jvectormap-us-aea-en.js',
'js/maps/gdp-data.js'
];
jsLibs.forEach((lib) => {
$('head').append(`<script type="text/javascript" src="${lib}">`);
});
}
Then move all the $(document).ready() calls into Template.layout.onRendered and the click events into Template.layout.events({

From Play! to Scalatra, templating headaches, directory structures

I'm trying to convert a Play! 2.0 application into a Scalatra application. I've had some success, but there are 3 issues remaining, 1 of which has its own ticket.
1) I understand that src/main/webapp/WEB-INF/views and src/main/webapp/WEB-INF/layouts contain the layouts in a standard directory structure. I was not able to get this to work in a different heirarchy, though, e.g. if I have 2 servlets and wanted different views for them:
- WEB-INF
- servlet1
- views
- layouts
- servlet2
- views
- layouts
In the example I provided, I can't adequately reference anything not directly under WEB-INF/views or WEB-INF/layouts. Presumably, it's because I'm not declaring something in web.xml correctly?
// works
get("/") {
contentType = "text/html"
templateEngine.layout("/WEB-INF/views/app.jade")
}
// no worky
get("/") {
contentType = "text/html"
templateEngine.layout("/WEB-INF/servlet1/views/app.jade") // where servlet1/layouts/default.jade exists
}
2) What's up with templating? For this conversion to work, I need to be able to use Underscore templates in conjunction with whatever is available to me from Scalatra (Jade, Mustache, etc). I chose Jade because all the default examples use it.
I'm really running into two sub-issues here.
1) I can't seem to use Underscore templates with Jade, even though I have included the javascripts in this ticket. Maybe this works, maybe it doesn't. This is probably because....
2) My include statements look like tags instead of actually including the partial, so it's really hard to test the first sub-issue.
<!-- / Nav -->
<include>nav</include>
<!-- / Action1 -->
<include>action1</include>
<include>action2</include>
<!-- / Wireframe -->
<div id="default-region">
<script id="template-layout" type="text/template">
<div id="region-nav"></div>
<div id="region-content"></div>
</script>
</div>
<!-- / CSS and JavaScripts relative to Backbone app -->
<include>assets</include>
What the equivalent app.jade file looks like:
// Nav
include nav
// Action1
include action1
include action2
// Wireframe
div#default-region
script#template-layout(type="text/template")
div#region-nav
div#region-content
// CSS and JavaScripts relative to Backbone app
include assets
Any help would be appreciated!
You can structure your views differently or you have to provide a different filesystem configuration for your views. You can configure that by overriding getRenderContext.
You don't need to call templateEngine.layout, but instead use our helper methods.
For jade with a layout that becomes jade("my_view", "layout" -> "/WEB-INF/layouts/authenticated.jade")
As for including those tags, etc. perhaps you mean this?

How to include Javascript objects in a Jade template before Jade compilation

I am working with a website built with Jade/Express for a few weeks now. I recently organized the image folder for the website so all the images were disbursed between several folders to make it easier to use and sort through.
To make it easier to make changes to the hierarchy of images (and other such files) I wrote a script that contains some globals for file paths. Now I have been trying to get the script to run so that I can call functions inside the jade template to automatically use these globals.
For example. Images are now sorted into several folders:
File Hierarchy
img/
glyphs/
interactivity/
buttons/
...
In my path manager script, I created several functions, including the following:
In: path-manager.js
images_root_path = "/img/";
glyph_path = images_root_path + "glyphs/";
function getGlyph(color, name) {
return glyph_path + color + "/" + name;
}
I tried several methods to get the script to execute before the template. Here is one of the attempts:
In page.jade
include ../../../public/js/lib/path-manager.js
=NamespacePathManager();
The above is, in theory, supposed to include the js and then I execute the namespace below to make the functions available, but that isn't working.
This is a portion of the Jade template that I want to use the function in:
In page.jade after the script include
span.challenge-time
img(src=getGlyph("black","stopwatch.png"), style="margin-right:5px;")
The above example should return: "/img/glyphs/black/stopwatch.png"
The problem is, I believe, that the scripts I am trying to make available server-side to the jade template are not being executed before the jade template is rendered. Everything I have tried doing to get this to work always results in an error saying that the server doesn't recognize the function getGlyph or when I started using the namespace function, NamespacePathManager
Summary: I want a javascript file to execute before a jade template is rendered into a webpage so that I can call functions and variables from that javascript on the server to use while rendering the jade template. My problem is that all the methods I have tried are unable to execute the javascript before the Jade is rendered.
Update
One work around I found was to put the javascript into unbuffered code directly on the page including a jade. This isn't quite the elegant solution I was looking for, but it works for now
- some code
- more code
This code is executed inline. The downside is that I have to include it on every page manually - instead of just including it once and having the functions available everywhere.
You can register helper methods in Express that will then be accessible in the views.
So in your case, the path-manager.js can be the helper file that you register, and contains:
var images_root_path = "/img/";
var glyph_path = images_root_path + "glyphs/";
exports.helpers = {
getGlyph: function (color, name) {
return glyph_path + color + "/" + name;
}
// Other helper methods here..
};
Then when setting up the express server, you register the helper
var app = express.createServer();
// app.configure here...
// app.use ...
app.helpers(require('./path-manager.js').helpers);
// Routes set up here ..
Finally, you can call the helper method from Jade view like this:
span.challenge-time
img(src='#{getGlyph("black","stopwatch.png")}', style='margin-right:5px;')
There's a good write up on this topic at DailyJS http://dailyjs.com/2011/01/03/node-tutorial-8/

MVC4 Beta Minification and Bundling: Ordering files and debugging in browser

I've started using bundling and minification included with the MVC4 Beta. I'm running into a few issues with it:
For one thing, if I use the classic <script src="Folder/js" type="text/javascript"/> bundling, it seems like I have to rename my files to make sure they're bundled in the proper order.
Let's say I have three javascript files: "ants.js", "bugs.js", "insects.js"
ants.js depends on bugs.js
bugs.js depends on insects.js
Default bundling seems to bundle them in alphabetical order.
To get them to bundle properly, I have to rename them to: "0.insects.js", "1.bugs.js", "2.ants.js"
That's really hackish and there has to be a cleaner way.
The next problem I'm having is debugging. I like to step through the javascript in my testing browsers, is there a way to turn off just the minification while in DEBUG mode?
EDIT: To be clear, I know I can create bundles and register them from C#, it just seems really ugly to have to do it that way.
To temporarily get non-minified output use this
public class NonMinifyingJavascript : IBundleTransform
{
public void Process(BundleContext context, BundleResponse bundle)
{
if(bundle == null)
{
throw new ArgumentNullException("bundle");
}
context.HttpContext.Response.Cache.SetLastModifiedFromFileDependencies();
foreach(FileInfo file in bundle.Files)
{
HttpContext.Current.Response.AddFileDependency(file.FullName);
}
bundle.ContentType = "text/javascript";
//base.Process(context, bundle);
}
}
If you wanted it based totally on a config setting, I imagine you could create an IBundle transform that delegates to this one or JsMinify depending on your config setting
In order to control the ordering of the javascript files you need to use the BundleFileSetOrdering
var javascriptBundle = new Bundle("~/site/js", new NonMinifyingJavascript());
//controls ordering for javascript files, otherwise they are processed in order of AddFile calls
var bootstrapOrdering = new BundleFileSetOrdering("bootstrap");
//The popover plugin requires the tooltip plugin
bootstrapOrdering.Files.Add("bootstrap-tooltip.js");
bootstrapOrdering.Files.Add("bootstrap-popover.js");
BundleTable.Bundles.FileSetOrderList.Add(bootstrapOrdering);
javascriptBundle.AddDirectory("~/Scripts", "bootstrap-*.js");
I use the MVC default NoTransform instead of the NonMinifyingJavascript proposed by chrisortman. As far as I know it does the same.
But still not optimal. Ideally I want a script tag for each idividual script file when I want to debug. This makes debugging a lot easier with VS11, which I like to use (one debugger so I can debug js and c# in one debug session).
So I created this little helper:
#helper RenderScriptTags(string virtualPath)
{
if (Minify /* some appsetting */)
{
<script src="#System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl(virtualPath)"></script>
}
else
{
foreach (var file in System.Web.Optimization.BundleResolver.Current.GetBundleContents(virtualPath))
{
<script src="#Url.Content(file)"></script>
}
}
}
#RenderScriptTags("~/libraries")
I have a single page app, so I have this in my main cshtml file, but it can easily be generalized by moving this to an htmlhelper extension method.
Works nice!
This code takes also into account the BundleFileSetOrdering if you have set one!
Might also take a look at RequestReduce. It bundles your scripts and CSS without any coding or configuration by looking at how they are laid out on your page and bundling according to that. It also allows you to turn off bundling and minification via web.config or for individual requests via a querystring param: RRFilter=disabled.
I ran into this same problem yesterday and couldn't find a good solution with the new System.Web.Optimization namespace. There were some broken MSDN links, so the fact that everything is in beta means it may change, but I digress...
You could always load the scripts differently during development than in production. Easy to do with an AppSetting:
#if (System.Configuration.
ConfigurationManager.AppSettings["BundleResources"] != null)
{
#* load the css & js using bundles *#
}
else
{
#* load the css & js files individually*#
}
You can then enable / disable the optimization stuff by commenting out an appsetting in web.config:
<appSettings>
...
<!--<add key="BundleResources" value="uhuh" />-->
...
</appSettings>

MVC and javascript new approach; Q: May I have problems in future with this approach?

I will explain my idea behind this:
I use python for google app engine + js + css
the main project will be stored under the src folder like this:
\src
\app <--- here goes all the python app for gae
\javascript <--- my non-packed javascript files
\static_files <--- static files for gae
now the javascript dir looks like this
\javascript
\frameworks <--- maybe jQuery && jQueryUI
\models <--- js files
\controllers <--- js files
\views <--- HTML files!
app.js <--- the main app for js
compile.py <--- this is the file I will talk more
About compile.py:
This file will have 2 methods one for the min and other for the development javascript file;
When is run will do:
Join all the files with "js" extension;
The app.js contains a variable named "views" and is an object, like a hash; Then the compiler copy the contents of each file with "html" extension located in the "/javascript/views/" dir using this rule;
example: if we have a view like this "/views/login.html" then the "views" js var will have a property named "login"; views['login'] = '...content...';
example2: "/views/admin/sexyBitcy.html" then view['admin.sexyBitcy'] = '...content...' or whatever exists in that html file..;
Then this big file will be saved into the "/src/static_files/core.js"; if is minified will be saved as "/src/static_files/core.min.js";
The javascript will use dependenccy injection, or sort of it. (:
I will explain how it will work then:
the index.html that is loaded when you come into the site loads the core.js and the jquery.js;
the core.js will create the layout of the page, as SEO is not important for the most of the pages;
the core.js uses the controllers-models-views to create the layout of course; the html for the layout is inside the var "views"; will be a heavy variable of course!
Some code:
mvcInjector = new MVCInjector;
mvcInjector.mapView(views['login'], 'login', LoginController);
parent = $('#jscontent');
jquery
view = mvcInjector.instanceView('login', parent); // <--- this will create the contents of the views['login'] in the parent node "parent = $('#jscontent');" then will instance the LoginController that will map the "SkinParts" (like in FLEX if you know); what does it mean map the "SkinParts"? - when the user will click on a button an handler for that action is defined in the controller; ex.:
// LoginController
this.init = function(){
// map skin parts
this.mapSkinPart('email', 'input[name]="email"');
this.mapSkinPart('submit', 'input[name]="submit"');
// link skin parts to handlers
this.getSkinPart('submit').click = this.login;
}
// handlers
this.login = function(event){
// connect to the db
// some problems here the get the value as the "this" keyword references to the this of the controller class, I will work it around soon
alert('open window button1' + this.getSkinPart('email').value());
}
If something is not clear just say something, I will be happy to explain;
So the question remains: is this scalable, manageable and fast enough for a big RIA application build with javascript+jquery and maybe with jqueryUI?
Thanks ;)
I like your idea quit a bit.
I would think about loading html pages by ajax, if they are big and there are many of them...
Have a look on angular project, I hope, it could help you a lot. It's a kind of JS framework, designed to work together with jQuery. Well suitable for test driven development.
It uses html as templates, you can simply create your own controllers, use dependency injector, etc... Feel free to ask any question on mailing list.
Then, I must recommend JsTestDriver - really cool test runner for JS (so you can easily run unit tests in many browsers, during development - let's say after save...)

Categories