"Append JavaScript at the end of my html file" - Component for Tapestry 5.1

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

"Append JavaScript at the end of my html file" - Component for Tapestry 5.1

janp
This post describes a service, that enables Tapestry to append JavaScript files and script parts at the end of a html file. Script files and scripts can be "dropped into the service" from any page class or component and will be rendered in that order (FIFO) at the end of the page.

Upgrade Tapestry 5.0.x to Tapestry 5.1. loads included JavaScript files in the HTML head. Reasons are discussed in the Upgrade Notes: http://tapestry.apache.org/tapestry5.1/upgrade.html
project-wide release notes. They told me that in fix for bug TAP5-544:
Firstly, JavaScript libraries would have to move (back) to the head, permanently, no configuration [...].
Maybe I did not understand the real concern, but: Why isn't it up to the developer to decide where to place script files?
I decided to write a component that appends my script files at the end of my html files. The most important reason for this descicion is, that--following Yahoo's performance guidelines--I'd like to first load my HTML DOM before loading the script files.

Write a Service to Collect Files to Append
We need an Interface to be able to use the perthread scope (more about scoypes: "a per-thread instance is created on demand, behind a shared proxy").
The Interface just consists of 4 methods. 2 to add Script files and Scripts to the list and 2 to get the lists when they should be rendered:
public interface IAppendedScriptsService {

    /**
     * Add a script file to the list of script files to append.
     *
     * @param scriptFile
     */
    public void addScriptFile(String scriptFile);

    /**
     * Add a script to the list of scripts to append.
     *
     * @param script
     */
    public void addScript(String script);

    /**
     * Returns a list with script files to append. Needs to be a list to preserve order of script files.
     *
     * @return list with script files to append.
     */
    public List getScriptFiles();

    /**
     * Returns a list with scripts to append. Needs to be a list to preserve order of scripts.
     *
     * @return list with scripts to append.
     */
    public List getScripts();
}
The implementation of the Interface could looks like the following block. Don't forget to set the scope to perthread.
@Scope(value = ScopeConstants.PERTHREAD)
public class AppendedScriptsService implements IAppendedScriptsService {

    private final List scriptFiles = new ArrayList();
    private final List scripts = new ArrayList();

    @Override
    public void addScriptFile(String scriptFile) {
        this.scriptFiles.add(scriptFile);
    }

    @Override
    public void addScript(String script) {
        this.scripts.add(script);
    }

    @Override
    public List getScriptFiles() {
        return this.scriptFiles;
    }

    @Override
    public List getScripts() {
        return this.scripts;
    }

}
Build the Component
The Component was easy to write. It consists of just one method to render the scripts:
    // Inject the service we just wrote.
    @Inject
    private IAppendedScriptsService appendedScriptsService;

    // This method is called when component should be rendered. (Markup writer infos here)
    void beginRender(MarkupWriter markupWriter) {

        // Output script files.
        if (!this.appendedScriptsService.getScriptFiles().isEmpty()) {
            for (final String file : this.appendedScriptsService.getScriptFiles()) {
                // There might be another (tapestry conform way) to create the source link. My way is
                String fileUrl = "MyAssetsServerURL".resolveRelative(file);
                markupWriter.element("script", "type", "text/javascript", "src", fileUrl);
                markupWriter.end();
            }
        }

        // Output script snippets.
        if (!this.appendedScriptsService.getScripts().isEmpty()) {
            for (final String script : this.appendedScriptsService.getScripts()) {
                markupWriter.element("script", "type", "text/javascript");
                markupWriter.getElement().text(script);
                markupWriter.end();
            }
        }

    }
That's all to implement. Just one last step to make this run:

Activate Service in AppModule
To activate your Service, following method must be inserted into AppModule.java (or just insert the binder-line if there is already such a method):
public static void bind(ServiceBinder binder) {
  binder.bind(IAppendedScriptsService.class, AppendedScriptsService.class);
}
User Component
To use this Component, you just have to add this into a class of a page or component:

@Inject
private IAppendedScriptsService appendedScriptsService;

/**
* Append JavaScripts
**/
void setupRender() {
    this.appendedScriptsService.addScriptFile("UrlToScript");
    appendedScriptsService.addScript("var x = 'cool';");
}
The order of scripts and script files is preserved.

Do you have any comments? Please, let's discuss this.



Reply | Threaded
Open this post in threaded view
|

Re: "Append JavaScript at the end of my html file" - Component for Tapestry 5.1

Howard Lewis Ship
The correct approach is to modify the DocumentLinker service (which is
internal) to place the <script> tags where you want.

On Thu, Oct 21, 2010 at 1:52 AM, janp <[hidden email]> wrote:

>
> This post describes a service, that enables Tapestry to append JavaScript
> files and script parts at the end of a html file. Script files and scripts
> can be "dropped into the service" from any page class or component and will
> be rendered in that order (FIFO) at the end of the page.
>
> Upgrade Tapestry 5.0.x to Tapestry 5.1. loads included JavaScript files in
> the HTML head. Reasons are discussed in the Upgrade Notes:
> http://tapestry.apache.org/tapestry5.1/upgrade.html
> project-wide release notes. They told me that in fix for bug TAP5-544:
> Firstly, JavaScript libraries would have to move (back) to the head,
> permanently, no configuration [...].
> Maybe I did not understand the real concern, but: Why isn't it up to the
> developer to decide where to place script files?
> I decided to write a component that appends my script files at the end of my
> html files. The most important reason for this descicion is, that--following
> Yahoo's performance guidelines--I'd like to first load my HTML DOM before
> loading the script files.
>
> Write a Service to Collect Files to Append
> We need an Interface to be able to use the perthread scope (more about
> scoypes: "a per-thread instance is created on demand, behind a shared
> proxy").
> The Interface just consists of 4 methods. 2 to add Script files and Scripts
> to the list and 2 to get the lists when they should be rendered:
> public interface IAppendedScriptsService {
>
>    /**
>     * Add a script file to the list of script files to append.
>     *
>     * @param scriptFile
>     */
>    public void addScriptFile(String scriptFile);
>
>    /**
>     * Add a script to the list of scripts to append.
>     *
>     * @param script
>     */
>    public void addScript(String script);
>
>    /**
>     * Returns a list with script files to append. Needs to be a list to
> preserve order of script files.
>     *
>     * @return list with script files to append.
>     */
>    public List getScriptFiles();
>
>    /**
>     * Returns a list with scripts to append. Needs to be a list to preserve
> order of scripts.
>     *
>     * @return list with scripts to append.
>     */
>    public List getScripts();
> }
> The implementation of the Interface could looks like the following block.
> Don't forget to set the scope to perthread.
> @Scope(value = ScopeConstants.PERTHREAD)
> public class AppendedScriptsService implements IAppendedScriptsService {
>
>    private final List scriptFiles = new ArrayList();
>    private final List scripts = new ArrayList();
>
>    @Override
>    public void addScriptFile(String scriptFile) {
>        this.scriptFiles.add(scriptFile);
>    }
>
>    @Override
>    public void addScript(String script) {
>        this.scripts.add(script);
>    }
>
>    @Override
>    public List getScriptFiles() {
>        return this.scriptFiles;
>    }
>
>    @Override
>    public List getScripts() {
>        return this.scripts;
>    }
>
> }
> Build the Component
> The Component was easy to write. It consists of just one method to render
> the scripts:
>    // Inject the service we just wrote.
>    @Inject
>    private IAppendedScriptsService appendedScriptsService;
>
>    // This method is called when component should be rendered. (Markup
> writer infos here)
>    void beginRender(MarkupWriter markupWriter) {
>
>        // Output script files.
>        if (!this.appendedScriptsService.getScriptFiles().isEmpty()) {
>            for (final String file :
> this.appendedScriptsService.getScriptFiles()) {
>                // There might be another (tapestry conform way) to create
> the source link. My way is
>                String fileUrl = "MyAssetsServerURL".resolveRelative(file);
>                markupWriter.element("script", "type", "text/javascript",
> "src", fileUrl);
>                markupWriter.end();
>            }
>        }
>
>        // Output script snippets.
>        if (!this.appendedScriptsService.getScripts().isEmpty()) {
>            for (final String script :
> this.appendedScriptsService.getScripts()) {
>                markupWriter.element("script", "type", "text/javascript");
>                markupWriter.getElement().text(script);
>                markupWriter.end();
>            }
>        }
>
>    }
> That's all to implement. Just one last step to make this run:
>
> Activate Service in AppModule
> To activate your Service, following method must be inserted into
> AppModule.java (or just insert the binder-line if there is already such a
> method):
> public static void bind(ServiceBinder binder) {
>  binder.bind(IAppendedScriptsService.class, AppendedScriptsService.class);
> }
> User Component
> To use this Component, you just have to add this into a class of a page or
> component:
>
> @Inject
> private IAppendedScriptsService appendedScriptsService;
>
> /**
> * Append JavaScripts
> **/
> void setupRender() {
>    this.appendedScriptsService.addScriptFile("UrlToScript");
>    appendedScriptsService.addScript("var x = 'cool';");
> }
> The order of scripts and script files is preserved.
>
> Do you have any comments? Please, let's discuss this.
>
>
>
>
> --
> View this message in context: http://tapestry.1045711.n5.nabble.com/Append-JavaScript-at-the-end-of-my-html-file-Component-for-Tapestry-5-1-tp3230082p3230082.html
> Sent from the Tapestry - Dev mailing list archive at Nabble.com.
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>



--
Howard M. Lewis Ship

Creator of Apache Tapestry

The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!

(971) 678-5210
http://howardlewisship.com

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: "Append JavaScript at the end of my html file" - Component for Tapestry 5.1

janp
Thank you for that hint. I started to investigate how to change it in the DocumentLinker Service.

If I understand the JavaDoc right, it is still in the head tag in the current 5.2, but in the SNAPSHOT, it is configurable. So it will be configurable again in the final 5.2 release?