Tutorials

Flexible JavaScript and CSS File Inclusion for ExpressionEngine with embeds and PHP

January 13, 2009
by Ryan Masuga

How to use an embed and a little PHP to create an easy, flexible way to include global scripts or stylesheets, and also allow for adding links to extra files on a per-template basis.

EE Script Embeds

One of the powerful things about ExpressionEngine (and something that can be very frustrating for people new to the CMS) is that there is very frequently more than one way to do just about anything. This allows for very creative problem solving when you get familiar with the system, but until then often results in questions like “How do I do X?”, or, “What’s the best way to approach Z?”.

This article isn’t going to solve any of the major problems you may have with process or “best practices”, but it will show you one method of using an embed and a little PHP to create a flexible way to include global scripts or stylesheets, and also allow for adding links to extra files on a per-template basis. To demonstrate, I’ll use the template code currently used on devot:ee.

A Few Notes on Setup

I always keep my CSS and JS files outside of ExpressionEngine. This is a personal preference. The arguments go back and forth in the EE forums over whether of not to put CSS and JS files in EE or in external directories, but in the end, it really doesn’t matter. Go with what works for you. I like editing CSS files in CSS>dit that actually have a “.css” file extension, and I’m not gaining anything significant other than a clean root directory if I keep my CSS and JS inside of EE, so for the purposes of this article, we’ll assume I’m referencing JavaScript files that reside in a /js directory, and CSS files that live in /css (both at the root of your site).

I generally start by making a “common” template group which will hold templates that get included globally, such as header, footer, etc. Two of these templates are always “_scripts” and “_styles”. (I prefer to use the underscore to designate hidden files in EE, meaning files that are not accessible directly from a URL, but that’s just a personal preference, and not the focus of this article)

The _scripts and _styles templates

The first step is to create the templates that we want to include, and to enable PHP processing on them. There is absolutely nothing wrong with enabling PHP on EE templates. PHP processing is off by default as a security measure.

  1. Create your templates in the control panel (Templates → New Template)
  2. Set PHP parsing to “output” on both templates. (Select your template group, and then click “Preferences”. You’ll see Template Preferences fro your template group). Set Allow PHP? to “Yes”, and leave PHP Parsing Stage set to “Output”.

The _styles Template

Following is the content of the styles template:

<link rel="stylesheet" href="{site_url}css/default.css" type="text/css" />

{if "{embed:add}" != ""}
<?php $splitcontents = explode('|', '{embed:add}');
foreach($splitcontents as $file) {
 echo '<link rel="stylesheet" href="{site_url}css/'.$file.'.css" type="text/css" />'."\n";
} ?>
{/if}

You can see that I am including a single CSS file globally at this point (default.css), and that the rest will be up to our embed variable and some PHP.

What the rest of the template is doing is asking if there is anything in embed:add. If we have something, take whatever it is, explode on the ‘pipe’ character, and output a link to a stylesheet for everything we find. In this way you could add any number of extra stylesheets to a page by separating them with the ‘pipe’ character.

The _scripts Template

The scripts template is very similar to the _styles template:

<script src="/mint/?js" type="text/javascript">
<script type="text/javascript" src="{site_url}js/jquery-1.2.6.min.js">

{if "{embed:add}" != ""}
<?php $splitcontents = explode('|', '{embed:add}');
foreach($splitcontents as $file) {
 echo '<script type="text/javascript" src="{site_url}js/'.$file.'.js">'."\n";
} ?>
{/if}

Here you can see that the Mint and jQuery javascript files are global and will be included sitewide. Everything else will be up to our embed variable and some PHP—and this works exactly the same way here as it does on the _styles template. For each pipe-delimited item, the script will output a link to the necessary javascript file.

The Embeds

At the top of all the templates through out the site including the article template (the one you’re reading now) I have the following two embeds:

{embed="common/_styles"}
{embed="common/_scripts"}

Simple enough. I embed the global CSS and JS. I always leave the closing “head” tag exposed for maximum flexibility—in case I need to add anything at all to the head of a particular template.

Pass The Embed Variables

{embed="common/_styles"}
{embed="common/_scripts" add="syntax"}

Here’s where the magic happens. If we are on a template where we need to add any extra scripts or CSS files (or both) we just insert the necessary “add” embed variable and feed it the name of the file(s) without the file extension (which we’re handling automatically in the PHP loops). The two embeds here will output the following:

<link rel="stylesheet" href="http://devot-ee.com/css/default.css" type="text/css" />

<script src="/mint/?js" type="text/javascript">
<script type="text/javascript" src="http://devot-ee.com/js/jquery-1.2.6.min.js">

<script type="text/javascript" src="http://devot-ee.com/js/syntax.js">

Note the extra “syntax.js” file that was added to the head, path and all. Again, you can add multiple filenames, separated by pipes. If you have a page that has a special form on it that needs all sorts of extra scripts, your embeds might look like this:

{embed="common/_styles" add="validation|contact-form"}
{embed="common/_scripts" add="syntax|jquery.validate.min|jquery.numeric|misc-functions"}

Those embeds would output these lines in the head of your template:

<link rel="stylesheet" href="http://devot-ee.com/css/default.css" type="text/css" />

<link rel="stylesheet" href="http://devot-ee.com/css/validation.css" type="text/css" />
<link rel="stylesheet" href="http://devot-ee.com/css/contact-form.css" type="text/css" />

<script src="/mint/?js" type="text/javascript">
<script type="text/javascript" src="http://devot-ee.com/js/jquery-1.2.6.min.js">

<script type="text/javascript" src="http://devot-ee.com/js/syntax.js">
<script type="text/javascript" src="http://devot-ee.com/js/jquery.validate.min.js">
<script type="text/javascript" src="http://devot-ee.com/js/jquery.numeric.js">
<script type="text/javascript" src="http://devot-ee.com/js/misc-functions.js">

Taking It Just One Step Further

I like to try and push things a little bit when possible – to make things as flexible as I can. So what about being able to easily include JavaScript or CSS files that you keep within EE as well? Just because I’m not storing any JS or CSS in EE now doesn’t mean I won’t in the future. To do this we just need to modify the PHP in our embedded templates.

Here’s a _scripts template further modified to output two different types of path:

<script type="text/javascript" src="{site_url}js/jquery-1.2.6.min.js">

{if "{embed:add}" != ""}
<?php
$splitcontents = explode('|', '{embed:add}');
foreach($splitcontents as $file) {
 if (substr_count($file, '/') < 1) {
  echo '<script type="text/javascript" src="{site_url}js/'.$file.'.js"></script>'."\n";
  } else {
  echo '<script type="text/javascript" src="{path='.$file.'}"></script>'."\n";
 }
}
?>
{/if}

We’re still exploding the contents of the “add” embed variable on the pipe character, only this time we’re looking for the forward slash (”/”) which we’ll use to tell the script that we’re looking at a template group/template combo as opposed to a file that we know resides in our root /js directory. If the script finds a forward slash, it will output a different line.

As an example, let’s assume we have one global JS file (jQuery), one JS file in EE that we need to include, and one JS file from our external /js directory. The embed would look something like this:

{embed="common/_scripts" add="eescripts/functions|jquery.numeric"}

The output would look like this:

<script type="text/javascript" src="http://devot-ee.com/js/jquery-1.2.6.min.js">

<script type="text/javascript" src="http://devot-ee.com/eescripts/functions/">
<script type="text/javascript" src="http://devot-ee.com/js/jquery.numeric.js">

This is by no means a be-all end-all way to do things, but it has worked for me as an easy way to include extra scripts and stylesheets on a per-template basis.

Have a better or easier method? Have questions? Let us know in the comments.

15 Comments:

Jesse 01.13.09

Hey Ryan,

Thanks for the quick pointer.  I like being able to have both a global included stylesheet as well as being able to easily add sheets to specific pages. 

Very helpful!

Chad Crowell 01.13.09

Nice insight Ryan- I may have to give this a try.  I also keep CSS and JS files outside of EE, but I don’t quite get this flexible with my embeds.  Toward the bottom of my article here, my methods are discussed.  A perfect example of your note above that there are so many ways to accomplish something.

Matt 01.13.09

Great tip Ryan, thanks!

Sean 01.13.09

Interesting. I do it a little different. I have a template called meta which has everything I need in there and then place the embed tag between the head tags.

On other pages if I need to add something then I just add it after the embed but before the closing tag. Probably not as elegent or as flexible as this way, but it works for me.

OrganizedFellow 01.13.09

Simply. Amazing.

The ability to tweak a simple EE embed, to THIS?

I’ve been at it all night re-writing some of my code to use this new method.

Thanks for a simple alternative!

OrganizedFellow 01.13.09

How would you further extend the code to output a different media?

all vs screen vs print?

Ryan Masuga 01.13.09

Ryan Masuga

OrganizedFellow: As I mentioned, this is only one (simple!) way to do this sort of thing. You may be able to extend this method by making two or more parameters. Perhaps the CSS embed is a little different:

{embed=“common/_styles” print=“print” screen=“comments|other”}

Then the PHP could be changed to look for “embed:print” and “embed:screen” to output the proper type of line for each.

John Faulds 01.13.09

I haven’t worked on too many sites that require the inclusion of different stylesheets or scripts on a per-page basis (most are applied globally), but when I do want to apply them to a certain page or template group, I use {if segment_x == “xxxxx”}include stuff here{/if} instead.

I also move all my javascript to the end of the page (in my footer embed) wherever possible to improve page loading.

E-man 01.14.09

Pretty sweet and thorough this, will definitely try this out.

Paul Frost 01.15.09

My option doesn’t need an extra embedded template call or any PHP and allows calls to files on another site, like Google.

I am open to suggestions as to why it may be better to use your option.

I have a global/_header template that has {embed:scripts} in it along with all the other HTML head stuff.

Then in the template holding the content I call the _header with the following code:

{embed=“global/_header”

scripts=’[removed][removed]’

}

This is using the option to add variables to the embed call.

I also add other variables to the above like:

page_title=

meta_description=

navigation_location=

I suppose you could add additional CSS call here too.

Ryan Masuga 01.15.09

Ryan Masuga

Paul: I definitely have other embeds in my document in addition to the two I present here. I have a “_pagetop”, a “_header”, and sometimes, depending on the build, a “_meta”. Each of those has different embed variables.

This article is not really about the best way to do CSS and JS – it’s really about using some PHP to loop over a pipe-delimited list pulled from an embed variable – which can be handy in many different situations.

Paul Frost 01.15.09

I suppose what I don’t get is what the advantage is of your proposal, as it adds an additional call to the server (which must increase the workload). When you could just add the script links as the embedded variable rather than a word variable that needs another template to convert it into the script link.

Can you elaborate on how it could be “handier” to use a PHP loop?

How would you link to files on other servers, like the Google hosted jQuery or Googlemaps?

Sorry if I am being dense, I just want to understand any options that may improve my sites.

Ryan Masuga 01.15.09

Ryan Masuga

You could certainly just include the script calls in a regular fashion – no problem there. I personally like my templates very minimal and clean, so a single line that can call 5-6 javascript files, for me, is great. For me, it is cleaner and handier to type add=“jquery.numeric|validation” to the embed that is already on the template than to have the two entire extra script lines there.

Does this work for other servers? No, not as outlined above. You can just write in the whole line for any files that reside on other servers, or you can get creative and do something I’ve never thought of (and please share—I’d love to hear it). Odds are if you’re calling Google’s jQuery you’re doing that globally anyway, and would just have that call in your scripts embed. You can even forget about calling CSS or JS on a per-template basis and just load it all every time, bypassing this whole issue!

Depending on the situation, an additional call to the server may be a big deal – or it may not. Depends on the server, the traffic, and numerous other factors.

Your suggestion for just adding the script links in the variable is certainly a good one though (sorry the <script> code got stripped) so thanks for sharing!

coti 04.11.09

what if we want to read the code from a file and output on the html page..

some javascripts must be executed as they are.. not via inclusion..

i have try this way..

{if “{embed:add}” != “”}<?php $splitcontents = explode(’|’, ‘{embed:add}’);

foreach($splitcontents as $jscode){
      $content = file_get_contents(’{site_url}js/’.$jscode.’.js’);

      echo ‘[removed]’.$content.’[removed]’.”\n”;
  }
?>
{/if}

but no luck any ideeas? thank you

Jeff 05.01.09

Jeff

This is great! Thanks Ryan

You must be registered member to comment. If you're already a member, log in now, and if not go register (it's free and easy!).