Including Page Specific Assets - javascript

Using the Grails asset-pipeline plugin, I'm trying to figure out the best way to include assets (javascript) on a page versus having them compiled into the application.js file. Currently, this is what I'm doing...
At the bottom of my layout file:
<g:set var="workflow" value="${pageProperty(name: 'meta.workflow')}"/>
<asset:javascript src="application.js"/>
<g:if test="${workflow == 'storeAdmin'}">
<asset:javascript src="store/script.js"/>
</g:if>
In views/store/index.gsp header:
<meta name="workflow" content="storeAdmin" />
While this works, it feels like a hack. The reason I have to do it like this is because if I simply try and include the asset in the view itself, instead of the layout, it always gets rendered before the application.js, which means anything in that script that depends on global code will fail; code that requires jQuery for example.

I typically use a convention for naming page specific javascript assets that reflects the name of the controller and action. Using these and the <asset:assetPathExists> tag I can just add the following into my layout:
<asset:assetPathExists src="${params?.controller ?: 'home'}_${params?.action ?: 'index'}.js">
<asset:javascript src="${params?.controller ?: 'home'}_${params?.action ?: 'index'}.js" />
</asset:assetPathExists>

Related

Proper way to move multiple .js includes into one include?

I have 2 Razor .cshtml files that have a similar structure like the following:
<script type="text/javascript" src='#Url.Content("~/Scripts/jqwidgets/jqxcore.js")'></script>
<script type="text/javascript" src='#Url.Content("~/Scripts/jqwidgets/jqxdata.js")'></script>
<script type="text/javascript" src='#Url.Content("~/Scripts/jqwidgets/jqxbuttons.js")'></script>
...
<More header stuff (different in both files)>
<script language="javascript" type="text/javascript">
function commonFunctionForJqWidgets() {
...
callSomethingFromJqWidgetsLibrary();
...
}
</script>
I am writing common code for these 2 files and would like to put the common code in another .js (or .cshtml) file instead of duplicating it in both places. However, the common code requires including some of the jqwidgets includes since it calls library functions for it.
What would be the proper way to handle this? Should I simply add a new .cshtml file, move all of the includes in there, and then define my common functions in there as well?
Usually when you have common UI code, you may put that inside a partial view which can be included in other views as needed. But in your case, It is more like a bunch of javascript files you want in multiple files. So you may create a script bundle which will have all those scripts and use that in your pages as needed.
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
// Your existing bundles here
bundles.Add(new ScriptBundle("~/bundles/jQxWidgets")
.Include(
"~/Scripts/jqwidgets/jqxcore.js",
"~/Scripts/jqwidgets/jqxdata.js",
"~/Scripts/jqwidgets/jqxbuttons.js",
"~/Scripts/SomeOtherCustomJsfileRelatedtojQxWidgets.js"));
}
}
And in your specific views, you can include this inside the Scripts section.
#section Scripts
{
#Scripts.Render("~/bundles/jQxWidgets")
}
Or if you have more than just the scripts as your common stuff, create a partial view and include your common stuff there and include this partial view in your other views as needed.
Create a partial view called CommonGrid.cshtml inside your ~/Views/Shared folder
<h2>Common header for both the pages </h2>
#section Scripts
{
#Scripts.Render("~/bundles/jQxWidgets")
}
And in your other views
UserList.cshtml
<h1>Users</h1>
#Html.Partial("CommonGrid")
ProductList.cshtml
<h1>Products</h1>
#Html.Partial("CommonGrid")
You can also make your CommonGrid strongly typed to a view model and create a property in your page specific view model of this type(of CommonGrid's viewmodel) and pass that in the Html.Partial method.

Defer loading and parsing of PrimeFaces JavaScript files

While analyzing the performance of a JSF 2.1 + PrimeFaces 4.0 webapp with Google PageSpeed, it recommends among others to defer parsing of JavaScript files. On a test page with a <p:layout> and a form with <p:watermark> and <p:fileUpload> which looks like follows ...
<p:layout>
<p:layoutUnit position="west" size="100">Test</p:layoutUnit>
<p:layoutUnit position="center">
<h:form enctype="multipart/form-data">
<p:inputText id="input" />
<p:watermark for="input" value="watermark" />
<p:focus for="input" />
<p:fileUpload/>
<p:commandButton value="submit" />
</h:form>
</p:layoutUnit>
</p:layout>
... it lists the following JavaScript files which could be deferred:
primefaces.js (219.5KiB)
jquery-plugins.js (191.8KiB)
jquery.js (95.3KiB)
layout.js (76.4KiB)
fileupload.js (23.8KiB)
watermark.js (4.7KiB)
It links to this Google Developers article wherein deferred loading is explained as well as how to achieve it. You basically need to dynamically create the desired <script> during the onload event of the window. At its simplest form whereby old and buggy browsers are completely ignored, it looks like this:
<script>
window.addEventListener("load", function() {
var script = document.createElement("script");
script.src = "filename.js";
document.head.appendChild(script);
}, false);
</script>
Okay, this is doable if you have control over those scripts, but the listed scripts are all forcibly auto-included by JSF. Also, PrimeFaces renders a bunch of inline scripts to HTML output which are directly calling $(xxx) from jquery.js and PrimeFaces.xxx() from primefaces.js. This would mean that it would not easily be possible to really defer them to onload event as you would only end up with errors like $ is undefined and PrimeFaces is undefined.
But, it should be technically possible. Given that only jQuery doesn't need to be deferred as many of the site's custom scripts also rely on it, how could I block JSF from forcibly auto-including the PrimeFaces scripts so that I can defer them, and how could I deal with those inline PrimeFaces.xxx() calls?
Use <o:deferredScript>
Yes, it is possible with the <o:deferredScript> component which is new since OmniFaces 1.8.1. For the technically interested, here's the involved source code:
The UI component: DeferredScript
The HTML renderer: DeferredScriptRenderer
The JS helper: deferred.unminified.js
Basically, the component will during the postAddToView event (thus, during the view build time) via UIViewRoot#addComponentResource() add itself as a new script resource in end of <body> and via Hacks#setScriptResourceRendered() notify JSF that the script resource is already rendered (using Hacks class as there's no standard JSF API approach for that (yet?)), so that JSF won't forcibly auto-include/render the script resource anymore. In case of Mojarra and PrimeFaces, a context attribute with key of name+library and a value of true has to be set in order to disable auto-inclusion of the resource.
The renderer will write a <script> element with OmniFaces.DeferredScript.add() whereby the JSF-generated resource URL is passed. This JS helper will in turn collect the resource URLs and dynamically create new <script> elements for each of them during the onload event.
The usage is fairly simple, just use <o:deferredScript> the same way as <h:outputScript>, with a library and name. It doesn't matter where you place the component, but most self-documenting would be in the end of the <h:head> like this:
<h:head>
...
<o:deferredScript library="libraryname" name="resourcename.js" />
</h:head>
You can have multiple of them and they will ultimately be loaded in the same order as they're declared.
How to use <o:deferredScript> with PrimeFaces?
This is a little tricky, indeed because of all those inline scripts generated by PrimeFaces, but still doable with a helper script and accepting that jquery.js won't be deferred (it can however be served via a CDN, see later). In order to cover those inline PrimeFaces.xxx() calls to primefaces.js file which is almost 220KiB large, a helper script needs to be created which is less than 0.5KiB minified:
DeferredPrimeFaces = function() {
var deferredPrimeFaces = {};
var calls = [];
var settings = {};
var primeFacesLoaded = !!window.PrimeFaces;
function defer(name, args) {
calls.push({ name: name, args: args });
}
deferredPrimeFaces.begin = function() {
if (!primeFacesLoaded) {
settings = window.PrimeFaces.settings;
delete window.PrimeFaces;
}
};
deferredPrimeFaces.apply = function() {
if (window.PrimeFaces) {
for (var i = 0; i < calls.length; i++) {
window.PrimeFaces[calls[i].name].apply(window.PrimeFaces, calls[i].args);
}
window.PrimeFaces.settings = settings;
}
delete window.DeferredPrimeFaces;
};
if (!primeFacesLoaded) {
window.PrimeFaces = {
ab: function() { defer("ab", arguments); },
cw: function() { defer("cw", arguments); },
focus: function() { defer("focus", arguments); },
settings: {}
};
}
return deferredPrimeFaces;
}();
Save it as /resources/yourapp/scripts/primefaces.deferred.js. Basically, all what it does is capturing the PrimeFaces.ab(), cw() and focus() calls (as you can find in the bottom of the script) and deferring them to the DeferredPrimeFaces.apply() call (as you can find halfway the script). Note that there are possibly more PrimeFaces.xxx() functions which need to be deferred, if that is the case in your app, then you can add them yourself inside window.PrimeFaces = {} (no, it's in JavaScript not possible to have a "catch-all" method to cover the undetermined functions).
Before using this script and <o:deferredScript>, we first need to determine the auto-included scripts in the generated HTML output. For the test page as shown in the question, the following scripts are auto-included in generated HTML <head> (you can find this by rightclicking the page in webbrowser and choosing View Source):
<script type="text/javascript" src="/playground/javax.faces.resource/jquery/jquery.js.xhtml?ln=primefaces&v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/jquery/jquery-plugins.js.xhtml?ln=primefaces&v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/primefaces.js.xhtml?ln=primefaces&v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/layout/layout.js.xhtml?ln=primefaces&v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/watermark/watermark.js.xhtml?ln=primefaces&v=4.0"></script>
<script type="text/javascript" src="/playground/javax.faces.resource/fileupload/fileupload.js.xhtml?ln=primefaces&v=4.0"></script>
You need to skip the jquery.js file and create <o:deferredScripts> in exactly the same order for the remaining scripts. The resource name is the part after /javax.faces.resource/ excluding the JSF mapping (.xhtml in my case). The library name is represented by ln request parameter.
Thus, this should do:
<h:head>
...
<h:outputScript library="yourapp" name="scripts/primefaces.deferred.js" target="head" />
<o:deferredScript library="primefaces" name="jquery/jquery-plugins.js" />
<o:deferredScript library="primefaces" name="primefaces.js" onbegin="DeferredPrimeFaces.begin()" />
<o:deferredScript library="primefaces" name="layout/layout.js" />
<o:deferredScript library="primefaces" name="watermark/watermark.js" />
<o:deferredScript library="primefaces" name="fileupload/fileupload.js" onsuccess="DeferredPrimeFaces.apply()" />
</h:head>
Now all those scripts with a total size of about 516KiB are deferred to onload event. Note that DeferredPrimeFaces.begin() must be called in onbegin of <o:deferredScript name="primefaces.js"> and that DeferredPrimeFaces.apply() must be called in onsuccess of the last <o:deferredScript library="primefaces">.
In case you're using PrimeFaces 6.0 or newer, where the primefaces.js has been replaced by core.js and components.js, use the below instead:
<h:head>
...
<h:outputScript library="yourapp" name="scripts/primefaces.deferred.js" target="head" />
<o:deferredScript library="primefaces" name="jquery/jquery-plugins.js" />
<o:deferredScript library="primefaces" name="core.js" onbegin="DeferredPrimeFaces.begin()" />
<o:deferredScript library="primefaces" name="components.js" />
<o:deferredScript library="primefaces" name="layout/layout.js" />
<o:deferredScript library="primefaces" name="watermark/watermark.js" />
<o:deferredScript library="primefaces" name="fileupload/fileupload.js" onsuccess="DeferredPrimeFaces.apply()" />
</h:head>
As to performance improvement, important measuring point is the DOMContentLoaded time as you can find in bottom of Network tab of Chrome's developer tools. With the test page as shown in the question served by Tomcat on a 3 year old laptop, it decreased from ~500ms to ~270ms. This is relatively huge (almost the half!) and makes the most difference on mobiles/tablets as they render HTML relatively slow and touch events are fully blocked until the DOM content is loaded.
Noted should be that you're in case of (custom) component libraries dependent on whether they obey the JSF resource management rules/guidelines or not. RichFaces for example didn't and homebrewed another custom layer over it, making it impossible to use <o:deferredScript> on it. See also what is the resource library and how should it be used?
Warning: if you're adding new PrimeFaces components on the same view afterwards and are facing JavaScript undefined errors, then the chance is big that the new component also comes with its own JS file which should also be deferred, because it's depending on primefaces.js. A quick way to figure the right script would be to check the generated HTML <head> for the new script and then add another <o:deferredScript> for it based on the above instructions.
Bonus: CombinedResourceHandler recognizes <o:deferredScript>
If you happen to use OmniFaces CombinedResourceHandler, then it's good to know that it transparently recognizes <o:deferredScript> and combines all deferred scripts with the same group attribute into a single deferred resource. E.g. this ...
<o:deferredScript group="essential" ... />
<o:deferredScript group="essential" ... />
<o:deferredScript group="essential" ... />
...
<o:deferredScript group="non-essential" ... />
<o:deferredScript group="non-essential" ... />
... will end up in two combined deferred scripts which are loaded synchronously after each other. Note: the group attribute is optional. If you don't have any, then they will just all be combined into a single deferred resource.
As a live example, check the bottom of <body> of the ZEEF site. All essential PrimeFaces-related scripts and some site-specific scripts are combined in the first deferred script and all non-essential social media related scripts are combined in the second deferred script. As to performance improvement of ZEEF, on a test JBoss EAP server on modern hardware, the time to DOMContentLoaded went from ~3s to ~1s.
Bonus #2: delegate PrimeFaces jQuery to CDN
In any case, if you're already using OmniFaces, then you can always use CDNResourceHandler to delegate the PrimeFaces jQuery resource to a true CDN by the following context param in web.xml:
<context-param>
<param-name>org.omnifaces.CDN_RESOURCE_HANDLER_URLS</param-name>
<param-value>primefaces:jquery/jquery.js=http://code.jquery.com/jquery-1.11.0.min.js</param-value>
</context-param>
Note that jQuery 1.11 has some major performance improvements over 1.10 as internally used by PrimeFaces 4.0 and that it's fully backwards compatible. It saved a couple of hundred milliseconds when initializing drag'n'drop on ZEEF.
Initially posted as an answer to Defer primefaces.js loading
Adding another solution to this question for anyone else that encounters the same.
You will need to customize the primefaces HeadRenderer to achieve the ordering pagespeed recommends. While this is something that could have been implemented by PrimeFaces, I do not see it in v5.2.RC2. These are the lines in encodeBegin that need change:
96 //Registered Resources
97 UIViewRoot viewRoot = context.getViewRoot();
98 for (UIComponent resource : viewRoot.getComponentResources(context, "head")) {
99 resource.encodeAll(context);
100 }
Simply write a custom component for the head tag, then bind it to a renderer that overrides above behavior.
Now you wouldn't want to duplicate the entire method just for this change, it may be cleaner to add a facet called "last" and move script resources to its beginning in your renderer as new deferredScript components. Let me know if there's interest, and I'll create a fork to demonstrate how.
This approach is "future proof" in the sense that it doesn't break as new resource dependencies are added to components, or as new components are added to the view.

I want to include everything from my main page except one js file ..

I have a use case in which i want to include my main.gsp file in my page list.gsp , that is easily achieved by doing
<meta name="layout" content="main"/>
But the problem is that it has jQuery 1.6 version but i want an alternative to that a file called as jquery.js and i when i am including it as well in addition to the main layout its conflicting and creating a problem ..
like
<script type="text/javascript" src="${resource(dir: 'js/jquery', file: 'jquery.js')}"></script>
The above is not working , so what i have thought is either manually differentiate the files to be included on my list.gsp page or is there a way to include everything that is on main.gsp page except this jQuery1.6.js file ??
I am open to any other suggestions..
Thanks in advance
A dirty method could be to put the script import in a if block that checks for a page property, and in your list.gsp set that property.
In list.gsp:
<body fooProperty="1">
And in main.gsp:
<g:if test="${pageProperty(name:'body.fooProperty) ?: false}">
<%--includes you want for list.gsp here--%>
</g:if>
<g:else>
<%--normal script link -%>
</g:else>
Forces a if/else on every single page load you got, but there isnt many other ways to do it. Might be to set a hidden page property and write a loader that overrides the 1.6 import in javascript itself.
Put in your main.gsp
${if(!params.jqueryVersion) params.jqueryVersion=''}
<script type="text/javascript" src="${resource(dir: 'js/jquery', file: 'jquery${params.jqueryVersion)}.js')}"></script>
and in your list controller return
[myInstancesList:list, jqueryVersion:'-1.4.2']

Referencing javascript from trac wiki

I have a problem running javascripts from trac.
I know there are security issues around this, but my trac installation is only used as an intranet.
I have got the following code to work (requires setting rendering_unsafe_content = true under [wiki] in trac.ini):
{{{
#!html
<script type="text/javascript" >
document.write("This is a test")
</script>
}}}
However, replacing this with the javascript in a seperate file will fail:
{{{
#!html
<script type="text/javascript" src="/tracproject/htdocs/test.js" >
</script>
}}}
where tracproject is the root folder of trac and test.js contains document.write("This is a test").
Any clues?
Have you tried the 'Add Headers Plugin' (http://trac-hacks.org/wiki/AddHeadersPlugin) ? It looks like it allows you to do include custom javascript like you want but in a more straightforward way than having to modify templates directly.
The option is [wiki] render_unsafe_content (see documentation). You can reference the file in your site htdocs directory on the path /tracproject/chrome/site/test.js. I tried your example just now and it work correctly once the src path is changed.
See the TracInterfaceCustomization page for more details.

Relative Paths in Javascript in an external file

So I'm running this javascript, and everything works fine, except the paths to the background image. It works on my local ASP.NET Dev environment, but it does NOT work when deployed to a server in a virtual directory.
This is in an external .js file, folder structure is
Site/Content/style.css
Site/Scripts/myjsfile.js
Site/Images/filters_expand.jpg
Site/Images/filters_colapse.jpg
then this is where the js file is included from
Site/Views/ProductList/Index.aspx
$("#toggle").click(function() {
if (left.width() > 0) {
AnimateNav(left, right, 0);
$(this).css("background", "url('../Images/filters_expand.jpg')");
}
else {
AnimateNav(left, right, 170);
$(this).css("background", "url('../Images/filters_collapse.jpg')");
}
});
I've tried using '/Images/filters_collapse.jpg' and that doesn't work either; however, it seems to work on the server if I use '../../Images/filters_collapse.jpg'.
Basically, I want have the same functionallity as the ASP.NET tilda -- ~.
update
Are paths in external .js files relative to the Page they are included in, or the actual location of the .js file?
JavaScript file paths
When in script, paths are relative to displayed page
to make things easier you can print out a simple js declaration like this and using this variable all across your scripts:
Solution, which was employed on StackOverflow around Feb 2010:
<script type="text/javascript">
var imagePath = 'http://sstatic.net/so/img/';
</script>
If you were visiting this page around 2010 you could just have a look at StackOverflow's html source, you could find this badass one-liner [formatted to 3 lines :) ] in the <head /> section
get the location of your javascript file during run time using jQuery by parsing the DOM for the 'src' attribute that referred it:
var jsFileLocation = $('script[src*=example]').attr('src'); // the js file path
jsFileLocation = jsFileLocation.replace('example.js', ''); // the js folder path
(assuming your javascript file is named 'example.js')
A proper solution is using a css class instead of writing src in js file.
For example instead of using:
$(this).css("background", "url('../Images/filters_collapse.jpg')");
use:
$(this).addClass("xxx");
and in a css file that is loaded in the page write:
.xxx {
background-image:url('../Images/filters_collapse.jpg');
}
Good question.
When in a CSS file, URLs will be relative to the CSS file.
When writing properties using JavaScript, URLs should always be relative to the page (the main resource requested).
There is no tilde functionality built-in in JS that I know of. The usual way would be to define a JavaScript variable specifying the base path:
<script type="text/javascript">
directory_root = "http://www.example.com/resources";
</script>
and to reference that root whenever you assign URLs dynamically.
For the MVC4 app I am working on, I put a script element in _Layout.cshtml and created a global variable for the path required, like so:
<body>
<script>
var templatesPath = "#Url.Content("~/Templates/")";
</script>
<div class="page">
<div id="header">
<span id="title">
</span>
</div>
<div id="main">
#RenderBody()
</div>
<div id="footer">
<span></span>
</div>
</div>
I used pekka's pattern.
I think yet another pattern.
<script src="<% = Url.Content("~/Site/Scripts/myjsfile.js") %>?root=<% = Page.ResolveUrl("~/Site/images") %>">
and parsed querystring in myjsfile.js.
Plugins | jQuery Plugins
Please use the following syntax to enjoy the luxury of asp.net tilda ("~") in javascript
<script src=<%=Page.ResolveUrl("~/MasterPages/assets/js/jquery.js")%>></script>
I found this to work for me.
<script> document.write(unescape('%3Cscript src="' + window.location.protocol + "//" +
window.location.host + "/" + 'js/general.js?ver=2"%3E%3C/script%3E'))</script>
between script tags of course... (I'm not sure why the script tags didn't show up in this post)...
You need to add runat="server" and and to assign an ID for it, then specify the absolute path like this:
<script type="text/javascript" runat="server" id="myID" src="~/js/jquery.jqGrid.js"></script>]
From the codebehind, you can change the src programatically using the ID.
This works well in ASP.NET webforms.
Change the script to
<img src="' + imagePath + 'chevron-large-right-grey.gif" alt="'.....
I have a master page for each directory level and this is in the Page_Init event
Dim vPath As String = ResolveUrl("~/Images/")
Dim SB As New StringBuilder
SB.Append("var imagePath = '" & vPath & "'; ")
ScriptManager.RegisterClientScriptBlock(Me, Me.GetType(), "LoadImagePath", SB.ToString, True)
Now regardless of whether the application is run locally or deployed you get the correct full path
http://localhost:57387/Images/chevron-large-left-blue.png

Categories