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: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to