Node.js with Express: Importing client-side javascript using script tags in Jade views?

51,355

Solution 1

I've done the same using the solution from this thread:

http://groups.google.com/group/express-js/browse_thread/thread/8c2006dc7bab37b1/f9a273c836e0a2ac

You can declare a "scripts" variable into view options:

app.js:

app.set('view options', { locals: { scripts: ['jquery.js'] } });  // You can declare the scripts that you will need to render in EVERY page

Than you can have an helper that renders the script tags into the head of the layout

renderScriptTags() Helper code:

app.helpers({ renderScriptTags: function(scripts) {
  return scripts.map(function(script) {
    return '<script src="scripts/' + script + '"></script>';
  }).join('\n ');

Into the layout template in the head section you will have:

- renderScriptTags(scripts)

Now, to add a script on the head tag, you'll just need to push the script into the "scripts" variable on your jade content template (body template):

- scripts.push('myscript.js'); 

In this way the page will render jquery.js and myscript.js into the head of the page

UPDATE

It seems that newest express version handles the locals in a different way, to make this work properly, you can do this (I'm not sure it is the optimal solution though, I'd need to dig this a little)

You can use the renderScriptTags() helper of the previous method in your layout template like before.

But don't set the scripts variables into locals, instead create a dynamic helper that will make a scripts variable available in our templates:

app.dynamicHelpers({
  scripts: function(req, res){
    return ['jquery.js']; //this will be available in all views
  }
});

And then, to add a specific script, from your body template (exactly as before):

- scripts.push('myscript.js'); 

Now, for this specific view, you should have jquery.js and myscript.js rendered properly

Solution 2

You can have them on the layout and specify which libraries to load on the "controllers".

// layout.jade
!!!
html

    head
        title= title || 'Title not set.'
        -each script in scripts 
          script(type='text/javascript', src= script)
    body
        #header
            h1 Header.

        #content!= body //- this renders the body of an individual view

        #footer
            p Footer.

And your "controller":

// app.js
app.get('/', function (req, res) {
  res.render({
    scripts: ['jquery.min.js', '/nowjs/now.js']
  }
}

Solution 3

It's possible to do it The Right Way (tm) in the latest Jade (0.28.1) by keeping it all inside templates/views, without hacking page contents (script links) elsewhere:

  • Declare the head as a named block in your template:
doctype 5
html
  head
    // named block allows us to append custom head entries in each page
    block head
        title= title
        link( rel='stylesheet', href='/css/style.css' )
        script( type="text/javascript", src="/js/some-default-script.js" )
  body
    block content
  • Append your page-specific head elements (including script tags) in your views:
extends layout

// here we reference the template head and append to it
block append head
    meta( name="something", content="blah" )
    link( href="/css/another.css", rel="stylesheet", type="text/css" )
    style
        div.foo {
            position: absolute;
        }
    script( src="/js/page-specific-script.js" )

block content
    #page-contents-follows

Solution 4

I assume the problem (from briefly reading this) is that you are not "flushing" the array, setting its .length to 0 to remove old values, so each request could just be pushing more and more strings

Solution 5

Here is an alternate way to do it ( using ShadowCloud's answer ). By generalizing a bit, you can specify both local and remote scripts, and then defer them until after page load:

app.js:

app.dynamicHelpers({
    scripts: function() {
        //scripts to load on every page
        return ['js/jquery.min.js','js/jquery-ui.min.js','js/all.js'];
    }
});

then, you can add local or remote scripts at any point inside a view

//- local script
- scripts.push('js/myPage.js');
//- remote script ( note: this is a schemeless url. You can use http(s)? ones too )
- scripts.push('//platform.twitter.com/widgets.js')

layout.jade: ( I put it at the end of body to load the visible stuff first, but it can go anywhere really )

//- Bring the scripts into a client-side array,
//-    and attach them to the DOM one by one on page load
script
    var page_scripts = !{'["' + scripts.join('","') + '"]'};
    function loadJS() {
        for(var i in page_scripts) {
            var e = document.createElement("script");
            e.src = page_scripts[i];
            document.body.appendChild(e);
        }
    }
    // Check for browser support of event handling capability
    if (window.addEventListener)
        window.addEventListener("load", loadJS, false);
    else if (window.attachEvent)
        window.attachEvent("onload", loadJS);
    else window.onload = loadJS;
Share:
51,355
Sukhpreet Singh Alang
Author by

Sukhpreet Singh Alang

Updated on April 05, 2020

Comments

  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang about 4 years

    I've got a node.js express server running with the Jade template engine.

    I've got a layout jade file which imports the body of individual views like so:

    !!!
    html
    
        head
            title= title || 'Title not set.'
    
        body
            #header
                h1 Header.
    
            #content!= body //- this renders the body of an individual view
    
            #footer
                p Footer.
    

    For example, the following index page:

    p Welcome to the front page.
    
    p This page serves as a now.js test.
    

    This works fine. However, I now want to include two client-side javascript libraries specifically for this index page (and thus not very every page, which is why I cannot put it in the head of layout).

    This works:

    //- import jquery
    script(type='text/javascript', src='./jquery-1.5.2.min.js');
    
    //- import now.js (hosts itself)
    script(type='text/javascript', src='/nowjs/now.js')
    
    //- import the chat client
    script(type='text/javascript', src='./indexChatClient.js')
    
    p Welcome to the front page.
    
    p This page serves as a now.js test.
    

    However, this loads the scripts to the body of the complete page, which is not valid HTML, right?

    As far as I know, the scripts should be loaded into the head if I want to do it properly, but the head section is handled by the layout file.

    So, how would I properly include these client-side javascript libraries specifically for a certain view/page?

  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang about 13 years
    You are simply adding these to a list of general scripts which are then added to the head in the general layout file. However, I need to set specific scripts for specific views.
  • BFil
    BFil about 13 years
    You can set the view-specific script into the view by pushing it into the scripts variable, so that it will be rendered in the layout head, isn't this exactly what you're trying to accomplish?
  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang about 13 years
    @ShadowCloud, I'm not sure, where do you push to the scripts var? Doesn't that cause the script to be loaded in other views as well as the scripts var seems to be declared in the main scope?
  • BFil
    BFil about 13 years
    @Tom: I noticed that the locals are handled in a different way in the newer versions of express, i provided you a modified solutions in my updated answer (maybe there are better solutions, but it should work)
  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang about 13 years
    @ShadowCloud where do you call script.push? In a specific view?
  • BFil
    BFil about 13 years
    @Tom: yes, in your content templates
  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang about 13 years
    @ShadowCloud, does this not change the scripts array, thus also including this script in the upcoming views?
  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang about 13 years
    I guess this is better, as scripts should not be set within the view but in the controller.
  • BFil
    BFil about 13 years
    @Tom: nope, the scripts variable is modified in that request only (that is what local was for). the dynamicHelper attaches the scripts variable to each incoming request
  • masylum
    masylum about 13 years
    I prefer having them on the controllers so you can reuse that code easily
  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang about 13 years
    how do you set a script that is included everywhere without explicitly setting it in an individual render/view?
  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang about 13 years
    @ShadowCloud, scripts is a function, not an array so you cannot use push etc. This does not work.
  • Sukhpreet Singh Alang
    Sukhpreet Singh Alang about 13 years
    but I should not need to do that right? I thought the locals were set each time a view is rendered? Also, is this way of adding scripts not very inefficient, since every request basically has the same scripts thus it should be cached as fixed html rather than iterating over the array each time?
  • Evan
    Evan almost 13 years
    @ShadowCloud I'm trying to use your renderScriptsTags funciton in my layout. I call it in the head section by referencing '- renderScriptTags(scripts)'. I've checked in the debugger and your function correctly generates the script tags but they are not being placed into the html generated by jade. Has something changed in how helpers and jade work since you answered this question? Thanks!
  • BFil
    BFil almost 13 years
    I'm not sure, it doesn't seem it changed to me, though you can check the change logs yourself: github.com/visionmedia/express/blob/master/History.md and github.com/visionmedia/jade/blob/master/History.md
  • Tim Banks
    Tim Banks about 12 years
    Does the - renderScriptTags(scripts) call need to be != renderScriptTags(scripts)? I couldn't get the script tags to be outputted when only using the "-".
  • hillmark
    hillmark over 11 years
    To Make a script appear everywhere in Express 3.0 without explicitly setting it in an individual view you can use app.locals, i.e: app.locals.globalScripts = ['/lib/jquery-min.js', '/lib/bootstrap.min.js']; and in the view: -each script in globalScripts script(type='text/javascript', src= script) as per masylum's answer above.
  • CocoHot
    CocoHot over 9 years
    But doesn't this still load all the scripts in all views? I thought the OP wants to load only on specific view?