content: /**
   * 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)
          );
      }
  }
  