Package org.olat.core.gui.control

Source Code of org.olat.core.gui.control.JSAndCSSAdderImpl

/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS,
* <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/

package org.olat.core.gui.control;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.ComponentRenderer;
import org.olat.core.gui.components.delegating.DelegatingComponent;
import org.olat.core.gui.control.winmgr.Command;
import org.olat.core.gui.control.winmgr.CommandFactory;
import org.olat.core.gui.control.winmgr.WindowBackOfficeImpl;
import org.olat.core.gui.render.RenderResult;
import org.olat.core.gui.render.Renderer;
import org.olat.core.gui.render.RenderingState;
import org.olat.core.gui.render.StringOutput;
import org.olat.core.gui.render.URLBuilder;
import org.olat.core.gui.translator.Translator;
import org.olat.core.logging.AssertException;
import org.olat.core.util.StringHelper;

/**
* Description:<br>
* responsible for rendering the &lt;link rel.. and &lt;script src=... tags in
* the html header.<br>
* we do not need remove methods, since in ajax-mode, any change will lead to a
* page reload.
* <P>
* Initial Date:  04.05.2006 <br>
*
* @author Felix Jost
*/
public class JSAndCSSAdderImpl extends JSAndCSSAdder implements ComponentRenderer {

  private DelegatingComponent dc;
 
  private HashMap<String, String> keyToPath = new HashMap<String, String>(10); // keys: key of a class e.g. 'org.olat.mypackage'; values: global mappath e.g. /m/10/

  private List<String> curCssList = new ArrayList<String>();
  private List<String> prevCssList = new ArrayList<String>();
  private Set<String> curCssForceSet = new HashSet<String>(3);
  private Set<String> prevCssForceSet = new HashSet<String>(3);

  private Set<String> allCssKeepSet = new HashSet<String>(16);
  private Set<String> allJsKeepSet = new HashSet<String>(16);
 
  private List<String> curJsList = new ArrayList<String>();
  private List<String> prevJsList = new ArrayList<String>();

 
  private Set<String> cssToAdd;  // the css to add for the next round
  private Set<String> cssToRemove;  // the css to remove for the next round
  private List<String> jsToAdd; // the js to add for the next round
 
  private List<String> cssToRender; // the css's to render
  private List<String> jsToRender; // the js's to render
 
  // FIXME:fj: make the rawset deprecated; all raw includes can be replaced by a css or js include; the js calls can be moved to the velocity files.
  // for QTIEditormaincontroller / Displaycontroller -> Autocomplete files which need are dynamic files to be included ->
  // simplest sol would be: get the content of the file (in utf-8) and put it into <script> tags of the appropriate velocitycontainer.
  private Set<String> curRawSet = new HashSet<String>(2);
  private Set<String> oldRawSet = new HashSet<String>(2);
 
  private static final int MINIMAL_REFRESHINTERVAL = 1000;//in [ms]
  private int refreshInterval = -1;
  private final WindowBackOfficeImpl wboImpl;

  private Map<String, Class> jsPathToBaseClass = new HashMap<String, Class>();
  private Map<String,String> jsPathToJsFileName = new HashMap<String, String>();
  private Map<String,String> jsPathToEvalBeforeAJAXAddJsCode = new HashMap<String, String>();
  private Map<String,String> jsPathToEvalFileEncoding = new HashMap<String, String>();
 
  private static final String ENCODING_DEFAULT = "utf-8";

  private Map<String, String> cssPathToId = new HashMap<String, String>();
  private Map<String, Integer> cssPathToIndex = new HashMap<String, Integer>();
  private final Comparator<String> cssIndexComparator = new Comparator<String>(){
    public int compare(String css1, String css2) {
      int index1 = cssPathToIndex.get(css1);
      int index2 = cssPathToIndex.get(css2);
      return (index1 - index2);
    }
  };
  private int cssCounter = 0;

  private boolean requiresFullPageRefresh = false;
 
  public JSAndCSSAdderImpl(WindowBackOfficeImpl wboImpl) {
    this.wboImpl = wboImpl;
    dc = new DelegatingComponent("jsAndCssAdderDeleComp", this);
    dc.setDomReplaceable(false);
    cssToRender = curCssList;
    jsToRender = curJsList;
  }
 
  /**
   * @see org.olat.core.gui.control.JSAndCSSAdder#addRequiredJsFile(java.lang.Object,
   *      java.lang.String)
   */
  public void addRequiredJsFile(Class baseClass, String jsFileName) {
    addRequiredJsFile(baseClass, jsFileName, ENCODING_DEFAULT, null);
  }

  /**
   * @see org.olat.core.gui.control.JSAndCSSAdder#addRequiredJsFile(java.lang.Object,
   *      java.lang.String, java.lang.String)
   */
  public void addRequiredJsFile(Class baseClass, String jsFileName, String fileEncoding) {
    addRequiredJsFile(baseClass, jsFileName, fileEncoding, null);
  }

  /**
   * @see org.olat.core.gui.control.JSAndCSSAdder#addRequiredJsFile(java.lang.Class,
   *      java.lang.String, java.lang.String, java.lang.String)
   */
  public void addRequiredJsFile(Class baseClass, String jsFileName, String fileEncoding,
      String AJAXAddJsCode) {
    String jsPath = getMappedPathFor(baseClass, jsFileName);
    if (!curJsList.contains(jsPath)) {
      //System.out.println("reqJs:"+jsPath);
      curJsList.add(jsPath);
      jsPathToBaseClass.put(jsPath, baseClass);
      jsPathToJsFileName.put(jsPath, jsFileName);
      if (StringHelper.containsNonWhitespace(AJAXAddJsCode)) {
        jsPathToEvalBeforeAJAXAddJsCode.put(jsPath, AJAXAddJsCode);
      }
      if (fileEncoding != null) {
        jsPathToEvalFileEncoding.put(jsPath, fileEncoding);
      } else {
        jsPathToEvalFileEncoding.put(jsPath, ENCODING_DEFAULT);
      }
    }   
  }

  /**
   * @see org.olat.core.gui.control.JSAndCSSAdder#addRequiredCSSFile(java.lang.Class, java.lang.String, boolean)
   */
  public void addRequiredCSSFile(Class baseClass, String cssFileName, boolean forceRemove) {
    addRequiredCSSFile(baseClass, cssFileName, forceRemove, JSAndCSSAdder.CSS_INDEX_BEFORE_THEME);
  }

  /**
   * @see org.olat.core.gui.control.JSAndCSSAdder#addRequiredCSSFile(java.lang.Class, java.lang.String, boolean, int)
   */
  public void addRequiredCSSFile(Class baseClass, String cssFileName, boolean forceRemove, int cssLoadIndex) {
    String cssPath = getMappedPathFor(baseClass, cssFileName);
    addRequiredCSSPath(cssPath, forceRemove, cssLoadIndex);
  }
 
  /**
   * @see org.olat.core.gui.control.JSAndCSSAdder#addRequiredCSSPath(java.lang.String, boolean, int)
   */
  public void addRequiredCSSPath(String cssPath, boolean forceRemove, int cssLoadIndex) {
    if (!curCssList.contains(cssPath)) {
      //System.out.println("reqCss:"+cssPath+" force "+forceRemove);
      String id = cssPathToId.get(cssPath);
      if (id == null) { // no html id for this stylesheet import yet -> create one
        cssPathToId.put(cssPath, "o_css"+(++cssCounter));
      }
      curCssList.add(cssPath);
      if (forceRemove) {
        curCssForceSet.add(cssPath);
      }
      cssPathToIndex.put(cssPath, cssLoadIndex);
      // sort css after index
      Collections.sort(curCssList, cssIndexComparator);
    }
  }
 
  /**
   *
   * requires that a full page reload takes places. 
   * sometimes eval'ing a more complex js lib (such as tiny mce) directly into global context does not work (timing issues?)
   * this should be used only rarely when complex js is executed and has errors in it,
   * since a full page refresh is slower than a ajax call.
   * <br>
   * when a component is validated (last cycle before rendering), and a full page refresh is required, then a full page request command is
   * sent via JSON to the browser which then executes it using document.location.replace(...). Since this step involves two calls (JSON+reload),
   * this is slower than a normal full page click (aka known as non-ajax mode).
   *
   */
  public void requireFullPageRefresh() {
    requiresFullPageRefresh = true;
  }
 
  public boolean finishAndCheckChange() {
    // ----- find out whether there are any freshly added or removed css classes. -----
    // create new sets since we need to keep the original list untouched
    // (e.g. needed for rendering when doing a browser full page reload, or when in non-ajax-mode)
    Set<String> curCss = new HashSet<String>(curCssList);
    Set<String> prevCss = new HashSet<String>(prevCssList);
    curCss.removeAll(prevCssList); // the current minus the previous ones = the new ones to be added
    curCss.removeAll(allCssKeepSet); // but take those away which were used earlier and didn't need to be removed
    prevCss.removeAll(curCssList); // the previous minus the current ones = the ones not needed anymore = to be deleted
    prevCss.retainAll(prevCssForceSet); // only keep those css in the remove collection which really need to be removed (flagged as forceremove)
    cssToAdd = curCss;
    cssToRemove = prevCss;
    // ----- find out whether there are new js libs to be added. -----
    // it doesn't make sense to require a removal of js libs (js libs should never interfere which each other by design) -
    // therefore we only have to take care about new ones to be added.
    List<String> curJs = new ArrayList<String>(curJsList);
    curJs.removeAll(allJsKeepSet); // the current minus the previously added ones = the new ones to be added
    jsToAdd = curJs;

    //System.out.println("---- css-add:\n"+cssToAdd);
    //System.out.println("---- css-remove:\n"+cssToRemove);
    //System.out.println("---- js-add:\n"+jsToAdd);
   
    // raw set -> deprecated, see comments at variable declaration
    boolean wasRawChanged = false;
    if (curRawSet.size() != oldRawSet.size()) {
      wasRawChanged = true;
    } else {
      // same size, but could still be different:
      wasRawChanged = !curRawSet.containsAll(oldRawSet);
    }

   
    // ----- end-of-calculations: make the cur to the prev for the next add-round -----
    // css
    List<String> tmpCss = prevCssList;
    prevCssList = curCssList;
    cssToRender = curCssList;
    tmpCss.clear();
    curCssList = tmpCss;
   
    // remember which non-remove-force css entries have once already been added
    allCssKeepSet.addAll(cssToAdd);
    allCssKeepSet.removeAll(curCssForceSet);
   
    // change current cssFrceSet and clear it for the next validate process
    Set forceTmp = prevCssForceSet;
    prevCssForceSet = curCssForceSet;
    curCssForceSet = forceTmp;
    curCssForceSet.clear();
   
    // js
    allJsKeepSet.addAll(jsToAdd);
   
    List<String> jsTmp = prevJsList;
    jsTmp.clear();
    prevJsList = curJsList;
    curJsList = jsTmp;
    jsToRender = prevJsList;
    // raw set -> deprecated, see comments at variable declaration
    Set<String> tmp = oldRawSet;
    oldRawSet = curRawSet;
    curRawSet = tmp;
    curRawSet.clear();   
   
    // set and reset update/refresh intervall for ajax polling
    wboImpl.setRequiredRefreshInterval(refreshInterval);
    refreshInterval = -1;

    boolean fullPageRefresh = requiresFullPageRefresh;
    requiresFullPageRefresh = false;
   
    return wasRawChanged || fullPageRefresh;
  }

  /**
   * @see org.olat.core.gui.control.JSAndCSSAdder#getMappedPathFor(java.lang.Class, java.lang.String)
   */
  public String getMappedPathFor(Class baseClass, String fileName) {
    String packkey = getKey(baseClass);
    String mappath = keyToPath.get(packkey);
    if (mappath == null) {
      synchronized (keyToPath) {
        mappath = keyToPath.get(packkey);
        if (mappath == null) {
          // never used before, get a path from the global mapper provider
          mappath = wboImpl.getWinmgrImpl().getMapPathFor(baseClass);
          keyToPath.put(packkey, mappath);
        }       
      }
    }
    if (fileName == null) {
      return mappath;
    } else {
      return mappath + "/" + fileName;
    }
  }

  /**
   * @param baseClass
   * @return
   */
  private String getKey(Class baseClass) {
    String cla = baseClass.getName();
    int ls = cla.lastIndexOf('.');
    // post: ls != -1, since we don't use the java default package
    String pkg = cla.substring(0, ls);
    // using baseClass.getPackage() would add unneeded inefficient and synchronized code
    return pkg;
  }
 

  /**
   * @return
   */
  public Component getJsCssRawHtmlHeader() {
    return dc;
  }

  /**
   *
   * @see org.olat.core.gui.components.ComponentRenderer#render(org.olat.core.gui.render.Renderer, org.olat.core.gui.render.StringOutput, org.olat.core.gui.components.Component, org.olat.core.gui.render.URLBuilder, org.olat.core.gui.translator.Translator, org.olat.core.gui.render.RenderResult, java.lang.String[])
   */
  @SuppressWarnings("unused")
  public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator,
      RenderResult renderResult, String[] args) {
    // The render argument is used to indicate rendering before and after themes loading
    if (args == null || args.length != 1) {
      throw new AssertException("Programming error: can't render JSAndCSSAdder without 'pre-theme' or 'post-thee' render argument");
    }
    boolean postThemeRendering = args[0].equals("post-theme") ? true : false;
   
    // clear the added-since-last-fullpagerefresh, since we are doing a full page refresh here (only then is the <head> part here rerendered.)
    // this aims to minimize the number of js and css "imports" in the html head when using the non-ajax-mode (only those imports really needed are listed)
    allCssKeepSet.clear();
    allJsKeepSet.clear();
   
    //sb.append("<!-- css and js include test \n");
    //sb.append("js-files:\n");
    // JS scripts are rendered when in pre-theme rendering phase
    if (!postThemeRendering) {
      for (Iterator<String> it_js = jsToRender.iterator(); it_js.hasNext();) {
        String jsExpr = it_js.next();
        sb.append("<script type=\"text/javascript\" src=\"").append(jsExpr).append("\"></script>\n");
      }
    }
   
    // sort css files
     
    //sb.append("css-files:\n");
    for (Iterator<String> it_css = cssToRender.iterator(); it_css.hasNext();) {
      String cssExpr = it_css.next();
      // render post-theme css when in post-theme rendering phase and pre-theme
      // css when in pre-them rendering phase. List is sorted after index
      int cssIndex = cssPathToIndex.get(cssExpr);
      if ((postThemeRendering && cssIndex > JSAndCSSAdder.CSS_INDEX_THEME)
          || (!postThemeRendering && cssIndex < JSAndCSSAdder.CSS_INDEX_THEME)) {
        String acssId = cssPathToId.get(cssExpr);
        // use media=all to load always and use @media screen/print within the stylesheet
        sb.append("<link id=\"").append(acssId).append("\" rel=\"StyleSheet\" href=\"").append(cssExpr).append("\" type=\"text/css\" media=\"all\" />\n");
      }
    }
   
    if (postThemeRendering) {
      // Render raw header after theme. See also OLAT-4262
      for (Iterator<String> it_raw = oldRawSet.iterator(); it_raw.hasNext();) {
        String rawE = it_raw.next();
        sb.append("\n").append(rawE);
      }     
    }
    //sb.append("\n-->\n");
  }

  /*
   * (non-Javadoc)
   *
   * @see org.olat.core.gui.components.ComponentRenderer#renderHeaderIncludes(org.olat.core.gui.render.Renderer,
   *      org.olat.core.gui.render.StringOutput,
   *      org.olat.core.gui.components.Component,
   *      org.olat.core.gui.render.URLBuilder,
   *      org.olat.core.gui.translator.Translator,
   *      org.olat.core.gui.render.RenderingState)
   */
  public void renderHeaderIncludes(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator,
      RenderingState rstate) {
    //
  }

  public void renderBodyOnLoadJSFunctionCall(Renderer renderer, StringOutput sb, Component source, RenderingState rstate) {
    //
  }

  /**
   * @see org.olat.core.gui.control.JSAndCSSAdder#addRequiredRawHeader(java.lang.Class)
   */
  public void addRequiredRawHeader(Class baseClass, String rawHeader) {
    curRawSet.add(rawHeader);
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.JSAndCSSAdder#setRequiredRefreshInterval(java.lang.Class, int)
   */
  public void setRequiredRefreshInterval(Class baseClass, int refreshIntervall) {
    if(refreshIntervall < MINIMAL_REFRESHINTERVAL){
      throw new AssertException("Poll refresh intervall is smaller then defined MINIMAL value " + MINIMAL_REFRESHINTERVAL);
    }
    // idea: baseClass for later de-prioritising by configuration
    if (this.refreshInterval == -1 || refreshIntervall < this.refreshInterval) {
      this.refreshInterval = refreshIntervall;
      //System.out.println("setting new refresh intervall: "+this.refreshInterval);
    } // else we already have a request that requires a higher frequency of updates, we will take that one
  }
 
 
  public Command extractJSCSSCommand() {
    try {     
      JSONObject root = new JSONObject();
     
      //css to add
      JSONArray cssAdd = new JSONArray();
      root.put("cssadd", cssAdd);
      for (String addCss : cssToAdd) {
        // the id and the whole relative css path, e.g. /g/4/my.css
        JSONObject styleinfo = new JSONObject();
        String cssId = cssPathToId.get(addCss);
        styleinfo.put("id", cssId);
        styleinfo.put("url", addCss);
        // on js level only pre and post theme rendering supported
        styleinfo.put("pt", cssPathToIndex.get(addCss) > JSAndCSSAdder.CSS_INDEX_THEME ? true : false);
        cssAdd.put(styleinfo);
      }
     
      //css to remove
      JSONArray cssRemove = new JSONArray();
      root.put("cssrm", cssRemove);
      for (String removeCss : cssToRemove) {
        // the id and the whole relative css path, e.g. /g/4/my.css
        JSONObject styleinfo = new JSONObject();
        String cssId = cssPathToId.get(removeCss);
        styleinfo.put("id", cssId);
        styleinfo.put("url", removeCss);
        cssRemove.put(styleinfo);
      }
     
      //jsToAdd
      JSONArray jsAdd = new JSONArray();
      root.put("jsadd", jsAdd);
      for (String addJs : jsToAdd) {
        // load file with correct encoding. OLAT files are all UTF-8, but some
        // libraries like TinyMCE are ISO-88591. The window.execScript() in IE
        // can fail when the string has the wrong encoding (IE error 8002010)
        String fileEncoding = jsPathToEvalFileEncoding.get(addJs);
        JSONObject fileInfo = new JSONObject();
        fileInfo.put("url", addJs);
        fileInfo.put("enc", fileEncoding);
        // add code to be executed before the js code is inserted
        if (jsPathToEvalBeforeAJAXAddJsCode.containsKey(addJs)) {
          fileInfo.put("before", jsPathToEvalBeforeAJAXAddJsCode.get(addJs));         
        }
        jsAdd.put(fileInfo);
      }
      Command com = CommandFactory.createJSCSSCommand();
      com.setSubJSON(root);
      return com;
    } catch (JSONException e) {
      throw new AssertException("wrong data put into json object", e);
    }
  }

}
TOP

Related Classes of org.olat.core.gui.control.JSAndCSSAdderImpl

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.