Jump to content

How to insert scripts and stylesheets from your module?


hdesigns
 Share

Recommended Posts

I open this topic because in another one (http://processwire.com/talk/topic/1732-shop-for-processwire-apeisa/) there was a discussion going on that -in my point of view- went off-topic.

Here's what it's all about:

Say you want to create a module that needs some additional scripts/stylesheets what is the best way to accomplish that?

Right now I only know three ways (which all have their weaknesses):

First: Just edit the head.inc and include those scripts in the head-tag.

OK, this is pretty easy but it has two drawbacks:

- The scripts are now on every page (in the front-end)

- You have to edit the head.inc*

Second: Include

<?php foreach($config->scripts->unique() as $file) echo "\n\t<script type='text/javascript' src='$file'></script>"; ?>

in your head.inc and add the scripts from your module with

$this->config->scripts->add($this->config->urls->PaymentExample . "script.js");

This seems pretty neat because the scripts will only be included where they are needed, but:

- It is not very flexible. What about in-site scripts?

- You have to edit the head.inc*

Third: Use the hooking- mechanism of PW by adding

$this->addHookAfter('Page::render', $this, 'addScripts');

to the init- function of your module.

This is the addScripts function:

public function addScripts($event) {
		$page = $event->object; 

		// don't add this to the admin pages
		if($page->template == 'admin') return;

               //other mechanisms to ensure the script only loads when this module was called in the front-end
      
      $additionalHeadScripts = '[myScripts...]';

		$event->return = str_replace("</head>", $additionalHeadScripts.'</head>', $event->return); 
	}

Of course this approach also has its drawbacks:

- It is a bit difficult

- It probably makes your PW installation run a bit slower because the addScripts- Methods gets called on every page request.

*Why is it a bad thing to edit the head.inc to provide scripts/stylesheets for your module?

I personally think if you want to distribute a module the admin that wants to integrate this module should just hit the install button and do some configurations and that's it. This is called usability and I believe in it.

In my opinion it is not user/admin friendly if you have to pack a readme file to the module that says "but first you have to alter your
template file to make that module work"...

That said, if

<?php foreach($config->scripts->unique() as $file) echo "\n\t<script type='text/javascript' src='$file'></script>"; ?>

was already in the default template head.inc file I would not hesitate one second to use the second approach for my module. Because if an admin creates his/her own template and it is not based on the default template it's his/her fault...

So, what do you think? Which is the best approach for you and why?

Or do you have another approach?

Maybe it would also be a good thing if there was something in the API that we could use for this need. A more standardized approach?

  • Like 3
Link to comment
Share on other sites

If the module needs JS/CSS in the frontend (e.g. in templates), I don't see any solution other than let the dev add those scripts himself or using the Page::render hook, assuming that the module is autoload.

Why?

Because the module creator can't know your template structure. There are lots of different approaches, using a head.inc file is just one of them.

Adding the scripts/styles to the config arrays will only work if those arrays are used to output the scripts in the templates.

This is because Pw does not force you how to generate the markup, it's pure freedom :)

  • Like 3
Link to comment
Share on other sites

I'm totally confused by this discussion. What do modules have to do with head.inc?

Edit:

OK, I have read Wanze's post and it is beginning to make sense. This is not an everyday case for modules but a situation where interaction with template files is required (right?)...I just needed to clarify lest new comers to modules got confused :D

Edited by kongondo
Link to comment
Share on other sites

  • 2 weeks later...

If I understand this correctly, I think I would choose the third method. This should be in the head section of your html, then then the template structure doesn't matter. I would imagine you can save the css in the module folder and pass the full path/to/module/css/style.css to $config->scripts->unique. 

Using this method does raise some questions, I'm pretty new to PW and don't know how it works yet. 

How are dependancies handled?

How are duplicate files handled? 

Link to comment
Share on other sites

  • 1 year later...

I came across this thread while working on my first PW module that needs to load quite a lot of scripts. So I am going to revive this thread because the OP makes an important point here: how to best find the balance between easy module integration and freedom for PW users when it comes to template structure . 

In my opinion, best practices for script inclusion should also be considered here. So while giving users the most possible freedom in how to structure their templates, they should at the same time be made aware of ways how to best implement script loading. Not only with PW, but in general.

I for myself am still learning a lot about web development best practices while developing sites with PW. There is a lot of useful information to be found here in the forums which is absolutely great.

One thing that most of web devs seem to agree upon is that loading scripts at the end of your template just before </body> increases site loading speed and thus user experience. Besides that it also makes sure that the DOM is already available to those scripts.

And here lies the culprit of approach 3 proposed by the OP: all additional scripts get loaded in the <head> which slows down page loading time.

Thus I would implement the addScripts method like this

public function addScripts($event) {
		$page = $event->object; 

		// don't add this to the admin pages
		if($page->template == 'admin') return;

               //other mechanisms to ensure the script only loads when this module was called in the front-end
      
      $additionalScripts = '[myScripts...]';

		$event->return = str_replace("</body>", $additionalHeadScripts.'</body>', $event->return); 
	}

Only drawback  I can see: following this approach, the module developer has to make sure that all inline scripts wait for the scripts they depend upon before they are executed (eg with $(window).load or $(document).ready)

Link to comment
Share on other sites

One thing that most of web devs seem to agree upon is that loading scripts at the end of your template just before </body> increases site loading speed and thus user experience.

There could be multiple valid reasons to use 'inline' javascripts. Going for an assumption based approach (as 'automaticly' placing before </body>) will steal some developers freedom.

In my opinion:  When developers want to use your module it's not bad they are following your instructions to make it work.

Link to comment
Share on other sites

When developers want to use your module it's not bad they are following your instructions to make it work.

That's true.

But if your instruction go something like: "You need to include these 4 scripts in your head tag to make this module work and you need to download them from these 4 different places first", it may drive people off.

Imagine a developer who cares about page loading optimization and he cannot do it right because of your module. Or he has to go and alter the module code so it fits his needs.

Wouldn't it be better if the module followed commonly accepted best practices and people who use the module don't need to care about things like manual script inclusion?

I personally don't feel really comfortable with the proposed approach 3, too. The $this->config->scripts->add() method seems to be the preferable way to go.

But then you need to instruct the users of your module to include the necessary php for rendering the scripts tags before </body>. Which I consider to be a PW best practice anyway when it comes to conditional loading of scripts.

The only point that I really wanted to make is that we should follow best practices and include our scripts at the end of the page and not in the head. If this limits developers freedom, it at least forces something on them that is generally a good thing.

Link to comment
Share on other sites

I personally don't feel really comfortable with the proposed approach 3, too. The $this->config->scripts->add() method seems to be the preferable way to go.

But then you need to instruct the users of your module to include the necessary php for rendering the scripts tags before </body>. Which I consider to be a PW best practice anyway when it comes to conditional loading of scripts.

This method has a major drawback, as you might load unnecessary scripts from the backend (other modules running in the backend will use this array also for storing scripts/styles). A better apporach IMO would be if the module uses its own array to ensure that only the needed files are loaded.

  • Like 1
Link to comment
Share on other sites

I use AIOM+ and then regular php to manage the array before the AIOM+ is called..

im usually setting the scripts array in the init, like

$minifyCSS = true;
$minifyJS = true;

$stylesheets = array(
    'assets/bootstrap/css/bootstrap.min.css',
    'assets/css/font-awesome.min.css',
    'assets/css/style.css',
    'assets/css/custom.css',
);

$jsfiles = array(
    'assets/js/jquery-2.1.3.min.js',
    'assets/js/imagesloaded.pkgd.js',
    'assets/js/custom.js',
);

in templates i sometimes do this (if styles/scripts need to be before last one, e.g. if last style is custom/override)

$jsOffset = -1;
$cssOffset = -1;

//1.) Event Calendar
array_splice($jsfiles, $jsOffset, 0, 'plugins/eventCalendar/js/jquery.event.calendar.js');
array_splice($jsfiles, $jsOffset, 0, 'plugins/eventCalendar/js/languages/jquery.event.calendar.en.js');
array_splice($stylesheets, $cssOffset, 0, 'plugins/eventCalendar/css/jquery.event.calendar.css');

or simple:

$jsfiles[]	= 'plugins/soundManager/js/soundmanager2-nodebug-jsmin.js';
$jsfiles[] 	= 'plugins/soundManager/page-player/script/page-player.js';
$stylesheets[] 	= 'plugins/soundManager/page-player/css/page-player.css';

then outputting:

if($minifyCSS == true) {
    echo "\t" . '<link rel="stylesheet" type="text/css" href="' . AllInOneMinify::CSS($stylesheets) . '" />' . "\n";    
    
} else {
    foreach($stylesheets as $stylesheet) {
        echo "\t<link href='{$templates}{$stylesheet}' rel='stylesheet'>\n";
    }
} // END IF MINIFY

this lets you toggle AIOM and good for development and troubleshooting stuff..

i should also clarify that for brevity, i setup a lot of aliases in the init, like:

$templates = $config->urls->templates;
  • Like 4
Link to comment
Share on other sites

  • 2 months later...

Hmm.

Wordpress uses a script/styles wp_enqueue_scripts hook and a wp_enqueue_script/style function that is quite intelligent. It allows you to enqueue a script and any dependencies. WP then makes sure the dependency is loaded before the script that depends on it. Even if the dependency is not itself queued to be loaded on that page, the dependency argument will force it to be loaded.

https://codex.wordpress.org/Function_Reference/wp_enqueue_script

For instance, if your script depended on jquery but jquery was not loaded by default (or you wanted to ensure it was), doing this...

function my_scripts_loader(){
  wp_enqueue_script('my_script_handle', 'my_script_url', array( 'jquery' ));
}

add_action( 'wp_enqueue_scripts', 'my_scripts_loader' );

...would load the script previously enqueued with the handle jquery.

And then if your script was itself needed for another dependent script, you could:

function next_script_loader(){
  wp_enqueue_script('next_script_handle', 'next_script_url', array( 'my_script_handle' ));
}

add_action( 'wp_enqueue_scripts', 'next_script_loader' );

So the script load order would be:

jquery

my_script_handle

next_script_handle

Scripts/styles enqueued this way are spat out in the wp_head or wp_footer template function. There's an in_footer argument for putting a script in the footer.

Probably wouldn't be hard to use that system in PW core, or roll your own. It's all about queueing the enqueued scripts and loading any given dependencies in the right order.

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...