// PersistHistory.js
// Javascript Behaviour for the PersistHistory Control
// Copyright (c) by Matthias Hertel, http://www.mathertel.de
// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
// -----
// To enable the PersistHistory just include this file in the page or use the
// PersistHistory control.
// -----
// 05/12/2006 21:25:24 created by Matthias Hertel
// 28.05.2006 declaration of stateList and _propList via attributes enabled.
// 18.06.2006 removed an IE memory leak by freeing the iframe reference correctly.
// 12.08.2006 "bad request" on ASP.NET Development Server removed.
// 16.09.2006 context on event-methods is now set to the bound object.
// 18.11.2007 using the OpenAjax hub instead of PageProperties
// 18.12.2007 Simplifications and documentation.
// 19.01.2008 implementing the initstate method to support initial values in a solid way.

jcl.PersistHistoryBehavior = {
/// <summary>Implementation of a JavaScript Behavior that captures the specified events of the eventnamespace
/// and builds a url-hash value for recording changes in the browser history.
/// The url can be used to bookmark the current state of the page and new events will be published when loading
/// the page again.</summary>

// --- attributes
eventnamespace: "jcl", /// <summary>Namespace of the events published by contained elements without a full qualified namespace.</summary>
eventnames: null, /// <summary>All event names that that correspond to a part of the url-hash.</summary>
stateeventnames: null, /// <summary>All page properties that will trigger a new history entry.</summary>

_timer: null, /// <summary>Reference to the timer used for the delayed url changes.</summary>
_newState: false, /// <summary>Flag that indicates a necessary new history entry.</summary>

_hiddenFrame: null, /// <summary>Reference to the &lt;iframe&gt; element that is used with the IE to record a history entry.</summary>
_saveHash: "", /// <summary>The last value of the hash part of the url.</summary>

_propList: {}, /// <summary>The current values from the subscribed events.</summary>
_dirty: {}, /// <summary>An object with flags for the changed values from the subscribed events.</summary>

init: function () {
  /// <summary>Initializing the control.</summary>
  var n;
  if (this.eventnames) {
    if (typeof(this.eventnames) == "string")
      this.eventnames = this.eventnames.split(';');

    for (n = 0; n < this.eventnames.length; n++) {
      if (this._propList[this.eventnames[n]] == null)
        this._propList[this.eventnames[n]] = "";
    } // for

    OpenAjax.hub.subscribe(this.eventnamespace + ".*", this._handleEvent, this);
  } // if

  if (this.stateeventnames && (typeof(this.stateeventnames) == "string")) {
    this.stateeventnames = this.stateeventnames.split(';');
    for (n = 0; n < this.stateeventnames.length; n++) {
      this.stateeventnames[this.stateeventnames[n]] = true;
    } // for
  } // if

  this._hiddenFrame = document.getElementById("PersistHistoryFrame");
}, // init


initstate: function () {
  /// <summary>After loading the page the event values that are persisted to cookies
  /// will be published.</summary>
  // raise all persisted values
  this._saveHash = "###";
  this.check();
}, // initstate


term: function() {
  /// <summary>Remove all links to html objects.</summary>

  this._hiddenFrame = null;
}, // term


setDefaultValue: function(eventName, eventData) {
  var n = eventName.lastIndexOf('.');
  if (eventName.substr(0, n) == this.eventnamespace) {
    eventName = eventName.substr(n+1);
    this._propList[eventName] = eventData;
    this._dirty[eventName] = true;
  }
}, // setDefaultValue


// ----- Methods -----

_handleEvent: function(eventName, eventData) {
  /// <summary>persist value of the event into the url.</summary>
  eventName = eventName.substr(this.eventnamespace.length+1);
  if ((this._propList[eventName] != null) && (this._propList[eventName] != eventData)) {
    if (this.stateeventnames[eventName])
      this._newState = true;

    if (this._timer != null)
      window.clearTimeout(this._timer);

    this._propList[eventName] = eventData;
    this._dirty[eventName] = true;

    this.nextHash = this.getNewHash();

    if (this._newState) {
      this._timer = window.setTimeout(this.setState.bind(this), 400);
    } else {
      this._timer = window.setTimeout(this.setUrl.bind(this), 400);
    } // if
  } // if
}, // GetValue


setUrl: function() {
  /// <summary>just change the current url, but do not record it in the history.</summary>
  this._timer = null;
  var newHash = this.getNewHash();

  if (newHash != this._saveHash) {
    this._saveHash = newHash;
    // window.location.replace() does this job in IE and Mozilla/FireFox.
    window.location.replace(newHash);
  } // if

  if (this._hiddenFrame != null) {
    var d = this._hiddenFrame.contentDocument || this._hiddenFrame.contentWindow.document;
    // save the current state
    d.getElementsByName("h")[0].value=newHash;
  }

  this._timer = window.setTimeout(this.check.bind(this), 400);
  this._newState = false;
}, // setUrl


setState: function() {
  /// <summary>change the current url, and record it in the history.</summary>
  this._timer = null;
  var newHash = this.getNewHash();

  if (this._hiddenFrame != null) {
    var d = this._hiddenFrame.contentDocument || this._hiddenFrame.contentWindow.document;
    // save the current state
    d.getElementsByName("h")[0].value=window.location.hash;
  }

  // window.location.hash = ... does this job ...
  window.location.hash = newHash;
  if (newHash != this._saveHash) {
    // ... workaround for IE: save the current hash
    if (jcl.isIE) {
      window.setTimeout(this.pushHistory.bind(this), 100);
      window.location.hash = newHash;
    }

    this._saveHash = newHash;
  } // if

  this._timer = window.setTimeout(this.check.bind(this), 400);
  this._newState=false;
}, // setState


getNewHash: function () {
  var newHash = "#";
  for (p in this._propList) {
    if (newHash.length > 1)
      newHash += "&";
    newHash += p + "=" + this._encodeParam(String(this._propList[p]));
  }
  return(newHash);
},


pushHistory: function () {
  if (this._hiddenFrame != null) {
    var d = this._hiddenFrame.contentDocument || this._hiddenFrame.contentWindow.document;
    this._hiddenFrame.src = jcl.GetControlsPath() + "jclHelper.htm?" + (new Date()).valueOf();
  } // if
},


check: function() {
  /// <summary>Detect any changes of the current url.</summary>
  if (window.location.hash == this._saveHash) {
    this._timer = window.setTimeout(this.check.bind(this), 400);

  } else {
    if (this._hiddenFrame != null) {
      var d = this._hiddenFrame.contentDocument || this._hiddenFrame.contentWindow.document;
      // save the current state
      if ((d.getElementsByName("h").length == 1) && (d.getElementsByName("h")[0].value != window.location.hash)) {
        // this was a hyperlink's action -> record last state
        window.setTimeout(this.pushHistory.bind(this), 100);
        this._timer = window.setTimeout(this.check.bind(this), 400);
        return;
      }
    } // if

    var h = window.location.hash;
    this._saveHash = h;
    if (h.length > 1) {
      var hashMap = h.substr(1).split('&');
      for (n = 0; n < hashMap.length; n++) {
        p = hashMap[n].split('=');
        if (this._propList[p[0]] != unescape(p[1])) {
          this._propList[p[0]] = unescape(p[1]);
          this._dirty[p[0]] = true;
        } // if
      } // for
    } // if

    // broadcast the change notification of a property
      for (var eventName in this._dirty) {
        if (this._dirty[eventName])
          OpenAjax.hub.publish(this.eventnamespace + "." + eventName, this._propList[eventName]);
        this._dirty[eventName] = false;
      }

    this._timer = window.setTimeout(this.setState.bind(this), 400);
  } // if
}, // check


_encodeParam: function(txt) {
  txt = txt.replace(/%/g, "%25").replace(/#/g, "%23").replace(/&/g, "%26");
  txt = txt.replace(/\+/g, "%2B").replace(/\//g, "%2F").replace(/\?/g, "%3F");
  txt = txt.replace(/\=/g, "%3D").replace(/ /g, "+");
  return(txt);
} // _encodeParam

} // jcl.PersistHistoryBehavior

if (jcl.isIE) {
  document.write("<iframe id='PersistHistoryFrame' src='" + jcl.GetControlsPath() + "jclHelper.htm' style='display:none'></iframe>");
} // if