// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
if(typeof Effect == 'undefined')
  throw("controls.js requires including script.aculo.us' effects.js library");

var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
  baseInitialize: function(element, update, options) {
    this.element     = $(element);
    this.update      = $(update);
    this.hasFocus    = false;
    this.changed     = false;
    this.active      = false;
    this.index       = 0;
    this.entryCount  = 0;

    if(this.setOptions)
      this.setOptions(options);
    else
      this.options = options || {};

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow ||
      function(element, update){
        if(!update.style.position || update.style.position=='absolute') {
          update.style.position = 'absolute';
          Position.clone(element, update, {
            setHeight: false,
            offsetTop: element.offsetHeight
          });
        }
        Effect.Appear(update,{duration:0.15});
      };
    this.options.onHide = this.options.onHide ||
      function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if(typeof(this.options.tokens) == 'string')
      this.options.tokens = new Array(this.options.tokens);

    this.observer = null;

    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);

    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
  },

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix &&
      (Prototype.Browser.IE) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update,
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },

  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },

  hide: function() {
    this.stopIndicator();
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    if(this.iefix) Element.hide(this.iefix);
  },

  startIndicator: function() {
    if(this.options.indicator) Element.show(this.options.indicator);
  },

  stopIndicator: function() {
    if(this.options.indicator) Element.hide(this.options.indicator);
  },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         if(Prototype.Browser.WebKit) Event.stop(event);
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         if(Prototype.Browser.WebKit) Event.stop(event);
         return;
      }
     else
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
      this.observer =
        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  },

  activate: function() {
    this.changed = false;
    this.hasFocus = true;
    this.getUpdatedChoices();
  },

  onHover: function(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex)
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },

  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },

  onBlur: function(event) {
    // needed to make click events working
    setTimeout(this.hide.bind(this), 250);
    this.hasFocus = false;
    this.active = false;
  },

  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ?
          Element.addClassName(this.getEntry(i),"selected") :
          Element.removeClassName(this.getEntry(i),"selected");

      if(this.hasFocus) {
        this.show();
        this.active = true;
      }
    } else {
      this.active = false;
      this.hide();
    }
  },

  markPrevious: function() {
    if(this.index > 0) this.index--
      else this.index = this.entryCount-1;
    this.getEntry(this.index).scrollIntoView(true);
  },

  markNext: function() {
    if(this.index < this.entryCount-1) this.index++
      else this.index = 0;
    this.getEntry(this.index).scrollIntoView(false);
  },

  getEntry: function(index) {
    return this.update.firstChild.childNodes[index];
  },

  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },

  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');

    var lastTokenPos = this.findLastToken();
    if (lastTokenPos != -1) {
      var newValue = this.element.value.substr(0, lastTokenPos + 1);
      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
      if (whitespace)
        newValue += whitespace[0];
      this.element.value = newValue + value;
    } else {
      this.element.value = value;
    }
    this.element.focus();

    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
  },

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
      this.update.innerHTML = choices;
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.down());

      if(this.update.firstChild && this.update.down().childNodes) {
        this.entryCount =
          this.update.down().childNodes.length;
        for (var i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } else {
        this.entryCount = 0;
      }

      this.stopIndicator();
      this.index = 0;

      if(this.entryCount==1 && this.options.autoSelect) {
        this.selectEntry();
        this.hide();
      } else {
        this.render();
      }
    }
  },

  addObservers: function(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;
    if(this.getToken().length>=this.options.minChars) {
      this.startIndicator();
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
  },

  getToken: function() {
    var tokenPos = this.findLastToken();
    if (tokenPos != -1)
      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
    else
      var ret = this.element.value;

    return /\n/.test(ret) ? '' : ret;
  },

  findLastToken: function() {
    var lastTokenPos = -1;

    for (var i=0; i<this.options.tokens.length; i++) {
      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
      if (thisTokenPos > lastTokenPos)
        lastTokenPos = thisTokenPos;
    }
    return lastTokenPos;
  }
}

Ajax.Autocompleter = Class.create();
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    entry = encodeURIComponent(this.options.paramName) + '=' +
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams)
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }

});

// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
//                    text only at the beginning of strings in the
//                    autocomplete array. Defaults to true, which will
//                    match text at the beginning of any *word* in the
//                    strings in the autocomplete array. If you want to
//                    search anywhere in the string, additionally set
//                    the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
//                   a partial match (unlike minChars, which defines
//                   how many characters are required to do any match
//                   at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
//                 Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.

Autocompleter.Local = Class.create();
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
  initialize: function(element, update, array, options) {
    this.baseInitialize(element, update, options);
    this.options.array = array;
  },

  getUpdatedChoices: function() {
    this.updateChoices(this.options.selector(this));
  },

  setOptions: function(options) {
    this.options = Object.extend({
      choices: 10,
      partialSearch: true,
      partialChars: 2,
      ignoreCase: true,
      fullSearch: false,
      selector: function(instance) {
        var ret       = []; // Beginning matches
        var partial   = []; // Inside matches
        var entry     = instance.getToken();
        var count     = 0;

        for (var i = 0; i < instance.options.array.length &&
          ret.length < instance.options.choices ; i++) {

          var elem = instance.options.array[i];
          var foundPos = instance.options.ignoreCase ?
            elem.toLowerCase().indexOf(entry.toLowerCase()) :
            elem.indexOf(entry);

          while (foundPos != -1) {
            if (foundPos == 0 && elem.length != entry.length) {
              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
                elem.substr(entry.length) + "</li>");
              break;
            } else if (entry.length >= instance.options.partialChars &&
              instance.options.partialSearch && foundPos != -1) {
              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
                  foundPos + entry.length) + "</li>");
                break;
              }
            }

            foundPos = instance.options.ignoreCase ?
              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
              elem.indexOf(entry, foundPos + 1);

          }
        }
        if (partial.length)
          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
        return "<ul>" + ret.join('') + "</ul>";
      }
    }, options || {});
  }
});

// AJAX in-place editor
//
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor

// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
  setTimeout(function() {
    Field.activate(field);
  }, 1);
}

Ajax.InPlaceEditor = Class.create();
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
Ajax.InPlaceEditor.prototype = {
  initialize: function(element, url, options) {
    this.url = url;
    this.element = $(element);

    this.options = Object.extend({
      paramName: "value",
      okButton: true,
      okLink: false,
      okText: "ok",
      cancelButton: false,
      cancelLink: true,
      cancelText: "cancel",
      textBeforeControls: '',
      textBetweenControls: '',
      textAfterControls: '',
      savingText: "Saving...",
      clickToEditText: "Click to edit",
      okText: "ok",
      rows: 1,
      onComplete: function(transport, element) {
        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
      },
      onFailure: function(transport) {
        alert("Error communicating with the server: " + transport.responseText.stripTags());
      },
      callback: function(form) {
        return Form.serialize(form);
      },
      handleLineBreaks: true,
      loadingText: 'Loading...',
      savingClassName: 'inplaceeditor-saving',
      loadingClassName: 'inplaceeditor-loading',
      formClassName: 'inplaceeditor-form',
      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
      highlightendcolor: "#FFFFFF",
      externalControl: null,
      submitOnBlur: false,
      ajaxOptions: {},
      evalScripts: false
    }, options || {});

    if(!this.options.formId && this.element.id) {
      this.options.formId = this.element.id + "-inplaceeditor";
      if ($(this.options.formId)) {
        // there's already a form with that name, don't specify an id
        this.options.formId = null;
      }
    }

    if (this.options.externalControl) {
      this.options.externalControl = $(this.options.externalControl);
    }

    this.originalBackground = Element.getStyle(this.element, 'background-color');
    if (!this.originalBackground) {
      this.originalBackground = "transparent";
    }

    this.element.title = this.options.clickToEditText;

    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
    Event.observe(this.element, 'click', this.onclickListener);
    Event.observe(this.element, 'mouseover', this.mouseoverListener);
    Event.observe(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.observe(this.options.externalControl, 'click', this.onclickListener);
      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  },
  enterEditMode: function(evt) {
    if (this.saving) return;
    if (this.editing) return;
    this.editing = true;
    this.onEnterEditMode();
    if (this.options.externalControl) {
      Element.hide(this.options.externalControl);
    }
    Element.hide(this.element);
    this.createForm();
    this.element.parentNode.insertBefore(this.form, this.element);
    if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
    // stop the event to avoid a page refresh in Safari
    if (evt) {
      Event.stop(evt);
    }
    return false;
  },
  createForm: function() {
    this.form = document.createElement("form");
    this.form.id = this.options.formId;
    Element.addClassName(this.form, this.options.formClassName)
    this.form.onsubmit = this.onSubmit.bind(this);

    this.createEditField();

    if (this.options.textarea) {
      var br = document.createElement("br");
      this.form.appendChild(br);
    }

    if (this.options.textBeforeControls)
      this.form.appendChild(document.createTextNode(this.options.textBeforeControls));

    if (this.options.okButton) {
      var okButton = document.createElement("input");
      okButton.type = "submit";
      okButton.value = this.options.okText;
      okButton.className = 'editor_ok_button';
      this.form.appendChild(okButton);
    }

    if (this.options.okLink) {
      var okLink = document.createElement("a");
      okLink.href = "#";
      okLink.appendChild(document.createTextNode(this.options.okText));
      okLink.onclick = this.onSubmit.bind(this);
      okLink.className = 'editor_ok_link';
      this.form.appendChild(okLink);
    }

    if (this.options.textBetweenControls &&
      (this.options.okLink || this.options.okButton) &&
      (this.options.cancelLink || this.options.cancelButton))
      this.form.appendChild(document.createTextNode(this.options.textBetweenControls));

    if (this.options.cancelButton) {
      var cancelButton = document.createElement("input");
      cancelButton.type = "submit";
      cancelButton.value = this.options.cancelText;
      cancelButton.onclick = this.onclickCancel.bind(this);
      cancelButton.className = 'editor_cancel_button';
      this.form.appendChild(cancelButton);
    }

    if (this.options.cancelLink) {
      var cancelLink = document.createElement("a");
      cancelLink.href = "#";
      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
      cancelLink.onclick = this.onclickCancel.bind(this);
      cancelLink.className = 'editor_cancel editor_cancel_link';
      this.form.appendChild(cancelLink);
    }

    if (this.options.textAfterControls)
      this.form.appendChild(document.createTextNode(this.options.textAfterControls));
  },
  hasHTMLLineBreaks: function(string) {
    if (!this.options.handleLineBreaks) return false;
    return string.match(/<br/i) || string.match(/<p>/i);
  },
  convertHTMLLineBreaks: function(string) {
    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
  },
  createEditField: function() {
    var text;
    if(this.options.loadTextURL) {
      text = this.options.loadingText;
    } else {
      text = this.getText();
    }

    var obj = this;

    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
      this.options.textarea = false;
      var textField = document.createElement("input");
      textField.obj = this;
      textField.type = "text";
      textField.name = this.options.paramName;
      textField.value = text;
      textField.style.backgroundColor = this.options.highlightcolor;
      textField.className = 'editor_field';
      var size = this.options.size || this.options.cols || 0;
      if (size != 0) textField.size = size;
      if (this.options.submitOnBlur)
        textField.onblur = this.onSubmit.bind(this);
      this.editField = textField;
    } else {
      this.options.textarea = true;
      var textArea = document.createElement("textarea");
      textArea.obj = this;
      textArea.name = this.options.paramName;
      textArea.value = this.convertHTMLLineBreaks(text);
      textArea.rows = this.options.rows;
      textArea.cols = this.options.cols || 40;
      textArea.className = 'editor_field';
      if (this.options.submitOnBlur)
        textArea.onblur = this.onSubmit.bind(this);
      this.editField = textArea;
    }

    if(this.options.loadTextURL) {
      this.loadExternalText();
    }
    this.form.appendChild(this.editField);
  },
  getText: function() {
    return this.element.innerHTML;
  },
  loadExternalText: function() {
    Element.addClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = true;
    new Ajax.Request(
      this.options.loadTextURL,
      Object.extend({
        asynchronous: true,
        onComplete: this.onLoadedExternalText.bind(this)
      }, this.options.ajaxOptions)
    );
  },
  onLoadedExternalText: function(transport) {
    Element.removeClassName(this.form, this.options.loadingClassName);
    this.editField.disabled = false;
    this.editField.value = transport.responseText.stripTags();
    Field.scrollFreeActivate(this.editField);
  },
  onclickCancel: function() {
    this.onComplete();
    this.leaveEditMode();
    return false;
  },
  onFailure: function(transport) {
    this.options.onFailure(transport);
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
      this.oldInnerHTML = null;
    }
    return false;
  },
  onSubmit: function() {
    // onLoading resets these so we need to save them away for the Ajax call
    var form = this.form;
    var value = this.editField.value;

    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
    // to be displayed indefinitely
    this.onLoading();

    if (this.options.evalScripts) {
      new Ajax.Request(
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this),
          asynchronous:true,
          evalScripts:true
        }, this.options.ajaxOptions));
    } else  {
      new Ajax.Updater(
        { success: this.element,
          // don't update on failure (this could be an option)
          failure: null },
        this.url, Object.extend({
          parameters: this.options.callback(form, value),
          onComplete: this.onComplete.bind(this),
          onFailure: this.onFailure.bind(this)
        }, this.options.ajaxOptions));
    }
    // stop the event to avoid a page refresh in Safari
    if (arguments.length > 1) {
      Event.stop(arguments[0]);
    }
    return false;
  },
  onLoading: function() {
    this.saving = true;
    this.removeForm();
    this.leaveHover();
    this.showSaving();
  },
  showSaving: function() {
    this.oldInnerHTML = this.element.innerHTML;
    this.element.innerHTML = this.options.savingText;
    Element.addClassName(this.element, this.options.savingClassName);
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
  },
  removeForm: function() {
    if(this.form) {
      if (this.form.parentNode) Element.remove(this.form);
      this.form = null;
    }
  },
  enterHover: function() {
    if (this.saving) return;
    this.element.style.backgroundColor = this.options.highlightcolor;
    if (this.effect) {
      this.effect.cancel();
    }
    Element.addClassName(this.element, this.options.hoverClassName)
  },
  leaveHover: function() {
    if (this.options.backgroundColor) {
      this.element.style.backgroundColor = this.oldBackground;
    }
    Element.removeClassName(this.element, this.options.hoverClassName)
    if (this.saving) return;
    this.effect = new Effect.Highlight(this.element, {
      startcolor: this.options.highlightcolor,
      endcolor: this.options.highlightendcolor,
      restorecolor: this.originalBackground
    });
  },
  leaveEditMode: function() {
    Element.removeClassName(this.element, this.options.savingClassName);
    this.removeForm();
    this.leaveHover();
    this.element.style.backgroundColor = this.originalBackground;
    Element.show(this.element);
    if (this.options.externalControl) {
      Element.show(this.options.externalControl);
    }
    this.editing = false;
    this.saving = false;
    this.oldInnerHTML = null;
    this.onLeaveEditMode();
  },
  onComplete: function(transport) {
    this.leaveEditMode();
    this.options.onComplete.bind(this)(transport, this.element);
  },
  onEnterEditMode: function() {},
  onLeaveEditMode: function() {},
  dispose: function() {
    if (this.oldInnerHTML) {
      this.element.innerHTML = this.oldInnerHTML;
    }
    this.leaveEditMode();
    Event.stopObserving(this.element, 'click', this.onclickListener);
    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
    if (this.options.externalControl) {
      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
    }
  }
};

Ajax.InPlaceCollectionEditor = Class.create();
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
  createEditField: function() {
    if (!this.cached_selectTag) {
      var selectTag = document.createElement("select");
      var collection = this.options.collection || [];
      var optionTag;
      collection.each(function(e,i) {
        optionTag = document.createElement("option");
        optionTag.value = (e instanceof Array) ? e[0] : e;
        if((typeof this.options.value == 'undefined') &&
          ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
        if(this.options.value==optionTag.value) optionTag.selected = true;
        optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
        selectTag.appendChild(optionTag);
      }.bind(this));
      this.cached_selectTag = selectTag;
    }

    this.editField = this.cached_selectTag;
    if(this.options.loadTextURL) this.loadExternalText();
    this.form.appendChild(this.editField);
    this.options.callback = function(form, value) {
      return "value=" + encodeURIComponent(value);
    }
  }
});

// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields

Form.Element.DelayedObserver = Class.create();
Form.Element.DelayedObserver.prototype = {
  initialize: function(element, delay, callback) {
    this.delay     = delay || 0.5;
    this.element   = $(element);
    this.callback  = callback;
    this.timer     = null;
    this.lastValue = $F(this.element);
    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  },
  delayedListener: function(event) {
    if(this.lastValue == $F(this.element)) return;
    if(this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
    this.lastValue = $F(this.element);
  },
  onTimerEvent: function() {
    this.timer = null;
    this.callback(this.element, $F(this.element));
  }
};

var CustomDraggable = Class.create();
CustomDraggable.removeOnDrag = false;
CustomDraggable.removeOnDrop = true;

CustomDraggable.prototype = (new Rico.Draggable()).extend( {
   initialize: function( name, htmlElement ) {
      this.name          = name;
      this.orgStyle      = null;
      this.htmlElement   = $(htmlElement);
      this.selected      = false;
      this.removeOnDrop  = true;
      this.removeOnDrag  = false;
      dndMgr.whileDragging = false;
   },

   startDrag: function() {
      dndMgr.whileDragging = true;
      var el = this.htmlElement;
      el.style.zIndex += 5000;
      if(navigator.userAgent.toLowerCase().indexOf("msie") >= 0){
        el.style.filter = 'Alpha(opacity=50)';
      }else{
        el.style.opacity = '0.5';
      }
    },

   cancelDrag: function() {
      dndMgr.whileDragging = false;
      var el = this.htmlElement;
      el.style.zIndex -= 5000;
      if(navigator.userAgent.toLowerCase().indexOf("msie") >= 0){
        el.style.filter = 'Alpha(opacity=100)';
      }else{
        el.style.opacity = '1.0';
      }
  },

   endDrag: function() {
      dndMgr.whileDragging = false;
      var el = this.htmlElement;
      el.style.zIndex -= 5000;
      if(navigator.userAgent.toLowerCase().indexOf("msie") >= 0){
        el.style.filter = 'Alpha(opacity=100)';
      }else{
        el.style.opacity = '1.0';
      }
   },

   getSingleObjectDragGUI: function() {
       if(CustomDraggable.removeOnDrag)
         var el = this.htmlElement;
       else
         var el = this.htmlElement.cloneNode(true);
       return el;
   }
} );

var CustomDropzone = Class.create();

CustomDropzone.prototype = (new Rico.Dropzone()).extend( {
   initialize: function( htmlElement ) {
      this.htmlElement  = $(htmlElement);
      this.absoluteRect = null;
      this.acceptName = null;
      this.extend(arguments[1]);
   },

   canAccept: function(draggableObjects) {
        if(this.acceptName == draggableObjects[0].name){
            return true;
        }
        return false;
   },

   accept: function(draggableObjects) {

      var htmlElement = this.getHTMLElement();
      if ( htmlElement == null )
         return;

      n = draggableObjects.length;
      var theGUI = draggableObjects[0].getDroppedGUI();

      if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" ){
         theGUI.style.position = "static";
         theGUI.style.top = "";
         theGUI.style.top = "";
      }
       this.completion(theGUI);
   },

   completion: function(theGUI) {},

   /**
    * Zeigt die Acceptance einer Dropzone an (z.B. andere BGColor)
    */
   activate: function() {
      var htmlElement = this.getHTMLElement();
      if (htmlElement == null  || this.showingActive)
         return;

      this.showingActive = false;
   },

   /**
    * Deaktiviert die Acceptance-Anzeige einer Dropzone an (z.B. andere BGColor)
    */
   deactivate: function() {
      var htmlElement = this.getHTMLElement();
      if (htmlElement == null || !this.showingActive)
         return;

      this.showingActive = false;
   },

   /**
    * Zeigt den Hover-Status einer Dropzone an (z.B. farbiger Rahmen)
    */
   showHover: function() {
      var htmlElement = this.getHTMLElement();
      if ( htmlElement == null || this.showingHover )
         return;

      this.showingHover = true;

      if (Element.hasClassName(htmlElement, 'ContentBox')) {

        this.saveBorderWidth = htmlElement.style.borderWidth;
        this.saveBorderStyle = htmlElement.style.borderStyle;
        this.saveBorderColor = htmlElement.style.borderColor;

        var BoxClone = htmlElement;

        htmlElement.style.borderWidth = "1px";
        htmlElement.style.borderStyle = "solid";
        htmlElement.style.borderColor = "#ff0000";

        htmlElement.parentNode.replaceChild(BoxClone, htmlElement);

      }
      else {
        this.saveBackgroundColor = htmlElement.style.backgroundColor;
        var currentColor = Rico.Color.createColorFromBackground(htmlElement);
        currentColor.isBright() ? currentColor.darken(0.05) : currentColor.brighten(0.05);
        htmlElement.style.backgroundColor = currentColor.asHex();
      }
   },

   hideHover: function() {
      var htmlElement = this.getHTMLElement();
      if ( htmlElement == null || !this.showingHover )
         return;

      this.showingHover = false;

      var BoxClone = htmlElement;

      if (Element.hasClassName(htmlElement, 'ContentBox') && this.saveBorderWidth) {
        htmlElement.style.borderWidth = this.saveBorderWidth;
        htmlElement.style.borderStyle = this.saveBorderStyle;
        htmlElement.style.borderColor = this.saveBorderColor;
      }
      else {
        htmlElement.style.backgroundColor = this.saveBackgroundColor;
        htmlElement.style.borderStyle = "";
        htmlElement.style.borderColor = "";
        htmlElement.style.borderWidth = "";
      }

      htmlElement.parentNode.replaceChild(BoxClone, htmlElement);

   }

} );
/*
 ModalBox - The pop-up window thingie with AJAX, based on prototype and script.aculo.us.

 Copyright Andrew Okonetchnikov (andrej.okonetschnikow@gmail.com), 2006
 All rights reserved.

 VERSION 1.4
 Last Modified: 20/06/2006

 Changelog:

 var 1.4: (06/20/2006)
 Added:   Core definitions rewriten. Modalbox can now be accessed thorugh Modalbox object with public methods show and hide
 Added:   License added
 Changed:  kbdHandler method is now public, so it can be stopped from other functions
 Fixed:   Stopping of event observing in hide method
 Fixed:   Hiding selects for IE issue (was applied on element ID)
 Removed:  Redundant 'globalMB' global variable removed
 Removed:  Scroll window events observerving
 Removed:  Redundant effect ScalyTo
 Issue:   IE display bug then hidding scrollbars. Document body should have zero margins

 var 1.3: (06/18/2006)
 Added:   ModalBox will now get focus after opening
 Added:   Keystrokes handler added (Tab key is looped on ModalBox and closing ModalBox by pressing Esc)
 Added:   Window scrolling disabled (known issue: content jupms on top then opening ModalBox)
 Fixed:   All dependent event handlers now unloads then closing ModalBox
 Fixed:   SELECT element hiding function executes now only in MSIE
 Fixed:   'Close' button has now href attribute to receive focus
 Fixed:   Click on 'Close' button doesn't adds an href value to URL string

 ver 1.2:
 Added: Global variable 'globalMB' added to the file. Use this variable to acces one instance of ModalBox and call methods on it

 ver 1.1:
 Added: Added SELECT elements hiding for IE (should be rewriten later)

 ver 1.0:
 Added: Core class description


Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * Neither the name of the David Spurr nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

* http://www.opensource.org/licenses/bsd-license.php

See scriptaculous.js for full scriptaculous licence

*/
if (!window.Modalbox) {
  var Modalbox = new Object();
}

Modalbox.Methods = {

  iframeOnload: function() { },

  _toggledSelects : Array(),

  setOptions: function(options) {
    var bodyDimensions = Element.getDimensions(document.getElementsByTagName('body')[0])

    this.options = {
      overlayClose: true, // Close modal box by clicking on overlay
      maxWidth: bodyDimensions.width * 0.8,
      maxHeight: bodyDimensions.height * 0.8,
      onComplete: function() {},
      onHide: true,
      width: 600,
      height: 400
    };
    Object.extend(this.options, options || {});
  },

  _init: function() {
    // Define there page content starts (first element after body)
    this.pageContent = document.body.childNodes[0];

    //Create the overlay
    this.MBoverlay = document.createElement("div");
    this.MBoverlay.id = "MB_overlay";

    this.hide = this.hide.bindAsEventListener(this);
    this.onClose = this.options.onComplete;

    if(this.options.overlayClose)
      Event.observe(this.MBoverlay, "click", this.hide );

     document.body.insertBefore(this.MBoverlay, document.body.firstChild);

    //Create the window
    this.MBwrapper = document.createElement("div");
    this.MBwrapper.id = "MB_wrapper";

    this.MBwindow = document.createElement("div");
    this.MBwindow.id = "MB_window";
    this.MBwindow.style.display = "none";

    this.MBcontent = document.createElement("div");
    this.MBcontent.id = "MB_content";

    if(this.overlayEl) {
      this.MBiframe = document.createElement("div");
    } else {
      this.MBiframe = document.createElement("iframe");
    }

    this.MBiframe.id = "MB_iframe";
    this.MBiframe.setAttribute('frameborder', 0, false);
    Element.setStyle(this.MBiframe, {
      border: 'none',
      width: '100%'
    });

    this.MBcontent.appendChild(this.MBiframe);
    this.MBwindow.appendChild(this.MBcontent);
    this.MBwrapper.appendChild(this.MBwindow);

    this._insertAfter(this.MBoverlay, this.MBwrapper, this.pageContent);

    this.isInitialized = true;
  },

  show: function(title, url, options, El) {

    this.title = title;
    this.url = url;

    // changed 28.02
    if(El) {
      this.overlayEl = El;
    } else {
      this.overlayEl = false;
    }

    this.setOptions(options);
    if(!this.isInitialized)
      this._init();

    // Initial scrolling position of the window. To be used for remove scrolling effect during ModalBox appearing
    this.initScrollX = window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft;
    this.initScrollY = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;

    if (navigator.appVersion.match(/\bMSIE\b/))
    {
      document.body.style.position = "relative";
      document.body.style.height = document.documentElement.clientHeight + "px";
      document.body.style.width = document.documentElement.clientWidth + "px";
    }
    document.body.style.overflow = 'hidden';
    /*
    if(!this.MBcaption.childNodes)
      this.MBcaption.appendChild(document.createTextNode(title));
    else
      this.MBcaption.innerHTML = title;
    */

    if(this.MBwindow.style.display == "none") {  // First modal box appearing

      //this._toggleSelects(); Führt zu Fehlern im IE, aiskommentiert von Nikolaj

      this._setOverlay();
      this._setWidth();
      this._setPosition();
      Effect.Appear(this.MBwindow, {duration: 0.3, afterFinish: this.loadContent.bind(this) } );
    } else {
      this.currentDims = [this.MBwindow.offsetWidth, this.MBwindow.offsetHeight];
      new Effect.ScaleBy(this.MBwindow,
                 (this.options.width - this.currentDims[0]), //New width calculation
                 (this.options.height - this.currentDims[1]), //New height calculation
                {afterFinish: this._loadAfterResize.bind(this),
                beforeStart: function(effect) {
                  effect.element.firstChild.childNodes[1].innerHTML = "Loading...";
                  effect.element.firstChild.childNodes[1].style.height = "auto";
                }
      });
    }

    this._setWidthAndPosition = this._setWidthAndPosition.bindAsEventListener(this);
    this.kbdHandler = this.kbdHandler.bindAsEventListener(this);

    Event.observe(window, "resize", this._setWidthAndPosition );
    Event.observe(document, "keypress", this.kbdHandler );
  },

  hide: function(argument) {
    if(argument) Event.stop(argument); // If an event given as an argument, stop this event
    if(this.options.onHide) {
      //
      Effect.Fade(this.MBwindow, {duration:0.35, afterFinish: this._deinit.bind(this) } );
    }
    //this._showSelects();
  },

  loadContent: function ()
  {
    var url = this.url;
    var MBcontent = this.MBcontent;
    var self = this;

    if(this.overlayEl) {
      var overlayClone = this.overlayEl.cloneNode(true);
      this.MBiframe.appendChild(overlayClone);
      Element.setStyle(this.MBiframe.firstChild, { display:'block'});
    } else {
      var iFrameElement = this.MBiframe;
      iFrameElement.src = url;
    }

    //var iFrameElement = this.MBiframe;
    /*
    Event.observe(iFrameElement, 'load', function() {
      Element.show(iFrameElement);

      alert(iFrameElement);
      alert(iFrameElement.document);
      return;

      if (window.innerHeight) {
        this.overlayHeight = window.innerHeight;
      }
      else {
        this.overlayHeight = document.documentElement.clientHeight;
      }
      $("page").style.height = this.overlayHeight + 'px';

    });
    */
    //iFrameElement.src = url;
  },

  moveFocus: function() {
    // Move focus on content area
    this.MBcontent.focus();

    // If the ModalBox frame containes form elements or links, first of them will bi focused after loading content
    // Trying to find focusable element in loaded content
    this.focusEl = $$("#MB_content input", "#MB_content textarea", "#MB_content select", "#MB_content button", "#MB_content a", "#MB_close").first();
    // Set focus on the first element
    this.focusEl.focus();
  },

  _loadAfterResize: function() {
    this._setWidth();
    this._setPosition();
    this.loadContent();
  },

  kbdHandler: function(e) {
    switch(e.keyCode)
    {
      case Event.KEY_TAB:
        // Find last 'focusable' element in ModalBox content to catch event on it. If no elements found, uses close ModalBox button
        this.lastFocusEl = $$("#MB_close", "#MB_content input", "#MB_content textarea", "#MB_content select", "#MB_content button", "#MB_content a").last();
        if(Event.element(e) == this.lastFocusEl)
        {
          Event.stop(e);
          this.moveFocus();
        }
      break;

      case Event.KEY_ESC:
        this.hide(e);
      break;
    }
  },

  resize: function (width, height) {

    if (!this.options) {
      return false;
    }
    this.options.width = width;
    this.options.height = height;

    this._setWidth();
    this._setWidthAndPosition();
  },

  _deinit: function()
  {

    //Event.stopObserving(this.MBclose, "click", this.hide );
    if(this.options.overlayClose)
    Event.stopObserving(this.MBoverlay, "click", this.hide );
    Event.stopObserving(window, "resize", this._setWidthAndPosition );
    Event.stopObserving(document, "keypress", this.kbdHandler );

    Effect.toggle(this.MBoverlay, 'appear', {duration: 0.35, afterFinish: this._removeElements.bind(this) });
    this.onClose();

    //this._toggleSelects(); Auskommentiert von Nikolaj wegen Probleme im IE 7
  },

  _removeElements: function () {
    if (navigator.appVersion.match(/\bMSIE\b/))
    {
      document.body.style.position = "";
      document.body.style.height = "";
      document.body.style.width = "";
    }
    else {
      document.body.style.overflow = "auto";
    }

    if($(this.MBoverlay.id)) {
      Element.remove(this.MBoverlay);
    }

    if($(this.MBwrapper.id)) {
      Element.remove(this.MBwrapper);
    }
    this.isInitialized = false;
  },

  _setOverlay: function () {
    var array_page_size = this._getWindowSize();
    if((navigator.userAgent.toLowerCase().indexOf("firefox") != -1)) {
      this.MBoverlay.style.width = "100%";
    }
    else {
      this.MBoverlay.style.width = array_page_size[0] + "px";
    }

    var max_height = Math.max(this._getScrollTop() + array_page_size[1], this._getScrollTop() + this.options.height + 30);
    this.MBoverlay.style.height = max_height + "px";
//    alert("bg: " + Element.getStyle(this.MBoverlay, 'backgroundColor'));
  },

  _setWidth: function () {

    //Set size
    this.MBwrapper.style.width = this.options.width + 10 +"px";
    this.MBwindow.style.width = this.options.width + "px";

    this.MBwrapper.style.height = this.options.height + "px";
    this.MBwindow.style.height = this.options.height + "px";
    this.MBcontent.style.height = this.options.height + "px";
    this.MBiframe.style.height = this.options.height + "px";
    this.MBiframe.style.width = this.options.width + "px";
  },

  _setPosition: function () {
    var array_page_size = this._getWindowSize();
    this.MBwrapper.style.left = ((array_page_size[0] - this.MBwrapper.offsetWidth) / 2 ) + "px";
    this.MBwindow.style.left = "0px";
    this.MBwrapper.style.top = this._getScrollTop() + "px";

    // Einblend-Position bestimmen
    if (window.innerHeight) {
      this.overlayHeight = window.innerHeight;
    }
    else {
      this.overlayHeight = document.documentElement.clientHeight;
    }
    this.MBwindow.style.top = Math.max(0, ((this.overlayHeight - this.options.height) / 2)) + 'px';
  },

  _setWidthAndPosition: function () {
    this._setOverlay();
    this._setPosition();
  },

  _getWindowSize: function (){
    var window_width, window_height;
    if (self.innerHeight) {  // all except Explorer
      window_width = self.innerWidth;
      window_height = self.innerHeight;
    } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
      window_width = document.documentElement.clientWidth;
      window_height = document.documentElement.clientHeight;
    } else if (document.body) { // other Explorers
      window_width = document.body.clientWidth;
      window_height = document.body.clientHeight;
    }
    return [window_width, window_height];
  },

  _insertAfter: function(parentNodeLink, insertion, insertionNodeReference) {
    return parentNodeLink.parentNode.insertBefore(insertion, insertionNodeReference);
  },

  _getScrollTop: function () {
    //From: http://www.quirksmode.org/js/doctypes.html
    var theTop;
    if (document.documentElement && document.documentElement.scrollTop)
      theTop = document.documentElement.scrollTop;
    else if (document.body)
      theTop = document.body.scrollTop;
    return theTop;
  },

  // For IE browsers -- hiding all SELECT elements
  _toggleSelects: function() {
    if (navigator.appVersion.match(/\bMSIE\b/))
    {
      var selectsList = this.pageContent.getElementsByTagName("select");
      var selects = $A(selectsList);
      selects.each( function(select) {
        select.style.visibility = (select.style.visibility == "hidden") ? "" : "hidden";
      });
    }
  },

  /**
   * For IE browsers -- show all SELECT elements
   *
   * @author Nikolaj
   */
  _showSelects : function() {
   if (navigator.appVersion.match(/\bMSIE\b/))
    {
      var selectsList = this.pageContent.getElementsByTagName("select");
      var selects = $A(selectsList);
      selects.each( function(select) {
        select.style.visibility = "visible";
      });
    }
  }
}

Object.extend(Modalbox, Modalbox.Methods);

Effect.ScaleBy = Class.create();
Object.extend(Object.extend(Effect.ScaleBy.prototype, Effect.Base.prototype), {
  initialize: function(element, byWidth, byHeight) {
    this.element = $(element)
    var options = Object.extend({
    scaleFromTop: true,
      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
      scaleByWidth: byWidth,
    scaleByHeight: byHeight
    }, arguments[3] || {});
    this.start(options);
  },
  setup: function() {
    this.elementPositioning = this.element.getStyle('position');

    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;

    this.dims = null;
    if(this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
   if(/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if(!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];

  this.deltaY = this.options.scaleByHeight;
  this.deltaX = this.options.scaleByWidth;
  },
  update: function(position) {
    var currentHeight = this.dims[0] + (this.deltaY * position);
  var currentWidth = this.dims[1] + (this.deltaX * position);

    this.setDimensions(currentHeight, currentWidth);
  },

  setDimensions: function(height, width) {
    var d = {};
    d.width = width + 'px';
    d.height = height + 'px';

  var topd  = (height - this.dims[0])/2;
  var leftd = (width  - this.dims[1])/2;
  if(this.elementPositioning == 'absolute') {
    if(!this.options.scaleFromTop) d.top = this.originalTop-topd + 'px';
    d.left = this.originalLeft-leftd + 'px';
  } else {
    if(!this.options.scaleFromTop) d.top = -topd + 'px';
    d.left = -leftd + 'px';
  }
    this.element.setStyle(d);
  }
});

// hook to window-object-attribute
window.modal = Modalbox;