Package fiftyfive.wicket.js

Source Code of fiftyfive.wicket.js.DomReadyTemplate

/**
* Copyright 2014 55 Minutes (http://www.55minutes.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fiftyfive.wicket.js;

import java.util.HashMap;
import java.util.Map;

import fiftyfive.wicket.js.locator.DependencyCollection;
import fiftyfive.wicket.js.locator.JavaScriptDependencyLocator;

import org.apache.wicket.Component;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.javascript.DefaultJavaScriptCompressor;
import org.apache.wicket.javascript.IJavaScriptCompressor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.string.interpolator.PropertyVariableInterpolator;
import org.apache.wicket.util.template.PackageTextTemplate;
import org.apache.wicket.util.template.TextTemplate;


/**
* Injects DOM-ready Javascript into the {@code <head>} using the
* interpolated contents of a separate JavaScript template.
* The code in the associated JavaScript file will run when the page loads,
* and also every time the component to which it is attached is repainted via
* Wicket ajax.
* <p>
* <b>This behavior will cause jQuery to be added to the &lt;head&gt; if it
* is not there already.</b> Your script can rely on the {@code jQuery}
* object being available.
* <p>
* Internally Wicket's {@link PropertyVariableInterpolator} is used to
* perform substitutions in the template using the
* <code>${propertyExpression}</code> syntax. Two keys are available for you
* to use in your templates:
* <ul>
* <li><b>{@code component}</b> is the component to which the
*     {@code DomReadyTemplate} is bound. The most common use is to obtain
*     the markup ID of the component like this:
*     <code>${component.markupId}</code>.</li>
* <li><b>{@code behavior}</b> is the {@code DomReadyTemplate} object itself.
*     If you extend {@code DomReadyTemplate} to create a custom JavaScript
*     behavior, you can declare properties on your subclass that can then be
*     used in the JavaScript template like this:
*     <code>${behavior.propertyName}</code>.</li>
* </ul>
* <p>
* {@code DomReadyTemplate} is ideal for rich Wicket components that are
* tightly integrated with associated JavaScript or have complex JavaScript
* initialization code.
* <p>
* Other benefits:
* <ul>
* <li>Dependency resolution is performed on the associated JavaScript file.
*     Your associated JavaScript file can declare the
*     libraries and files it needs to function, and these dependencies will be
*     included in the {@code <head>} automatically.
*     Refer to the <a href="package-summary.html#dependency-resolution">dependency
*     resolution guide</a> for more information.</li>
* <li>Just like a subclass of a panel can override the HTML of its parent,
*     a subclass of a panel using {@code DomReadyTemplate} can override the
*     JavaScript. For example, if {@code MyPanel} is subclassed as
*     {@code MyExtendedPanel}, the associated JavaScript will be loaded
*     from {@code MyExtendedPanel.js}, and only if that doesn't exist will
*     the original {@code MyPanel.js} be used. This allows you to subclass
*     an existing component, completely customize the JavaScript of that
*     component, but leave the Java code (and HTML) untouched.</li>
* </li>
* <p>
* Here is a trival example:
* <pre class="example">
* <b>// HelloPanel.java</b>
* // Panel that replaces its contents with "Hello" on DOM-ready
* public class HelloPanel extends DomReadyTemplatePanel
* {
*     public HelloPanel(String id)
*     {
*         super(id);
*         add(new DomReadyTemplate(getClass()));
*     }
* }</pre>
* <pre class="example">
* <b>// HelloPanel.js</b>
* // Depends on jQuery
* //= require jquery
* jQuery("#${component.markupId}").text("Hello");</pre>
* <p>
* When this panel is used on a page, the {@code <head>} will automatically
* gain code like this:
* <pre class="example">
* &lt;script type="text/javascript"&gt;
* jQuery(function(){
*     jQuery("#panel1").text("Hello");
* });
* &lt;/script&gt;</pre>
*
* @since 2.0
*/
public class DomReadyTemplate extends AbstractJavaScriptContribution
{
    private static final DefaultJavaScriptCompressor DEFAULT_COMPRESSOR =
        new DefaultJavaScriptCompressor();
       
    private Class<?> templateLocation;
    private transient DependencyCollection dependencies;
    private transient ResourceReference template;
    private transient String readyScript;
   
    /**
     * Constructs a {@code DomReadyTemplate} to be used with a panel.
     * Like this:
     * <pre class="example">
     * public class MyPanel extends Panel
     * {
     *     public MyPanel(String id)
     *     {
     *         super(id);
     *         add(new DomReadyTemplate(getClass()));
     *     }
     * }</pre>
     *
     * @param templateLocation The class that will be used to resolve the
     *                         location of the JavaScript template. For
     *                         example, if the class is {@code MyClass.class},
     *                         the template will be loaded from
     *                         {@code MyClass.js} in the same classpath
     *                         location. In most cases you will pass the result
     *                         of {@code getClass()} so it is possible for
     *                         subclasses to contribute their own template.
     */
    public DomReadyTemplate(Class<?> templateLocation)
    {
        internalInit(templateLocation);
    }
   
    /**
     * Constructor for subclasses that wish to define a custom template-based
     * behavior. In this case the template will be loaded from a JavaScript
     * file that is the same name as the {@code DomReadyTemplate} subclass.
     * <p>
     * In other words:
     * <pre class="example">
     * // JavaScript will be loaded from MyCustomBehavior.js
     * public class MyCustomBehavior extends DomReadyTemplate
     * {
     *     public MyCustomBehavior()
     *     {
     *         super();
     *     }
     * }</pre>
     */
    protected DomReadyTemplate()
    {
        internalInit(getClass());
    }
   
    private void internalInit(Class<?> templateLocation)
    {
        this.templateLocation = templateLocation;
    }
   
    /**
     * Returns the {@link IJavaScriptCompressor} that will be used to
     * compress the JavaScript before it is added to the {@code <head>}.
     * By default this returns an instance of
     * {@code DefaultJavaScriptCompressor}.
     */
    protected IJavaScriptCompressor getCompressor()
    {
        return DEFAULT_COMPRESSOR;
    }
   
    /**
     * Stores a reference to the component so that it can be used to
     * substitute <code>${component}</code> expressions in the JavaScript
     * template.
     * <p>
     * Also calls {@code setOutputMarkupId(true)} on the component. We do
     * this because the most common technique for integrating JavaScript
     * handlers with a component is by using its markup ID.
     */
    @Override
    public void bind(Component component)
    {
        super.bind(component);
        component.setOutputMarkupId(true);
    }
   
    /**
     * Releases internal caches.
     */
    @Override
    public void detach(Component component)
    {
        super.detach(component);
        this.dependencies = null;
        this.template = null;
        this.readyScript = null;
    }
   
    /**
     * Renders script src tags for all the dependencies that were discovered
     * via <a href="http://getsprockets.org">Sprockets</a> syntax in the
     * JavaScript template, plus a DOM-ready section containing the contents
     * of the template after variable substitutions have been performed.
     */
    @Override
    public void renderHead(Component comp, IHeaderResponse response)
    {
        if(null == this.dependencies) load(comp);
       
        renderDependencies(response, this.dependencies, this.template);
        renderDomReady(response, this.readyScript);
    }
   
    /**
     * Loads the JavaScript template, determines its dependencies, and then
     * uses PropertyVariableInterpolator to perform variable substitutions.
     * The results are cached in member variables that will be cleared when
     * detach() is called.
     */
    private void load(Component comp)
    {
        JavaScriptDependencyLocator locator = settings().getLocator();
        this.dependencies = new DependencyCollection();
        locator.findAssociatedScripts(this.templateLocation, this.dependencies);
       
        this.template = this.dependencies.getRootReference();
        if(null == this.template)
        {
            throw new WicketRuntimeException(String.format(
                "Failed to locate JavaScript template for %s. The JavaScript file must be " +
                "the same name as the class but with a '.js' extension and be present in " +
                "the same classpath location.",
                this.templateLocation));
        }
       
        TextTemplate tt = new PackageTextTemplate(
            this.template.getScope(),
            this.template.getName(),
            "application/javascript",
            settings().getEncoding()
        );
       
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("component", comp);
        map.put("behavior", this);
       
        this.readyScript = getCompressor().compress(
            PropertyVariableInterpolator.interpolate(tt.getString(), map)
        );
    }
}
TOP

Related Classes of fiftyfive.wicket.js.DomReadyTemplate

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.