// script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008

// 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/

// AJAX in-place editor and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).

// Wollyhood Custom rewrite (Add support for controls location, tinyMCE, ...)

//if(typeof Effect == 'undefined')
//  throw("controls.js requires including script.aculo.us' effects.js library");

// 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.WhInPlaceEditor = Class.create({
    initialize: function(element, url, options) {
        this.url = url;
        this.element = element = $(element);
        this.prepareOptions();
        this._controls = {};
        arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
        Object.extend(this.options, options || {});
        if (!this.options.formId && this.element.id) {
            this.options.formId = this.element.id + '-inplaceeditor';
            if ($(this.options.formId))
                this.options.formId = '';
        }
        if (this.options.externalControl)
            this.options.externalControl = $(this.options.externalControl);
        if (!this.options.externalControl)
            this.options.externalControlOnly = false;
        this._originalBackground = this.element.getStyle('background-color') || 'transparent';
        //this.element.title = this.options.clickToEditText;
        this._boundCancelHandler = this.handleFormCancellation.bind(this);
        this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
        this._boundFailureHandler = this.handleAJAXFailure.bind(this);
        this._boundSubmitHandler = this.handleFormSubmission.bind(this);
        this._boundWrapperHandler = this.wrapUp.bind(this);

        if (this.options.secondCancelControl) {
            this.options.secondCancelControl = $(this.options.secondCancelControl)
            var ipe = this;
            var oldclick = this.options.secondCancelControl.onclick;
            this.options.secondCancelControl.onclick = function() { ipe.leaveEditMode(); oldclick(); };
        }

        this.registerListeners();
    },
    checkForEscapeOrReturn: function(e) {
        if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
        if (Event.KEY_ESC == e.keyCode)
            this.handleFormCancellation(e);
        else if (Event.KEY_RETURN == e.keyCode)
            this.handleFormSubmission(e);
    },
    createControl: function(mode, handler, extraClasses) {
        var control = this.options[mode + 'Control'];
        var text = this.options[mode + 'Text'];
        if ('button' == control) {
            var btn = document.createElement('input');
            btn.type = 'submit';
            btn.value = text;
            btn.className = 'editor_' + mode + '_button';
            if ('cancel' == mode)
                btn.onclick = this._boundCancelHandler;

            if (this.options.controlsLocation)
                if (this.options.controlsLocationBefore)
                this.options.controlsLocation.insertBefore(btn, this.options.controlsLocation.firstChild);
            else
                $(this.options.controlsLocation).appendChild(btn);
            else
                this._form.appendChild(btn);

            this._controls[mode] = btn;
        } else if ('link' == control) {
            var link = document.createElement('a');
            link.href = '#';
            link.appendChild(document.createTextNode(text));
            link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
            link.className = 'editor_' + mode + '_link';
            if (extraClasses)
                link.className += ' ' + extraClasses;

            //alert(this.options.controlsLocation);

            if (this.options.controlsLocation)
                if (this.options.controlsLocationBefore) {
                this.options.controlsLocation.insertBefore(link, this.options.controlsLocation.firstChild);
            }
            else
                $(this.options.controlsLocation).appendChild(link);
            else
                this._form.appendChild(link);

            this._controls[mode] = link;

        }
    },
    createEditField: function() {
        var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());

        var fld;
        if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
            fld = document.createElement('input');
            fld.type = 'text';
            var size = this.options.size || this.options.cols || 0;
            if (0 < size) fld.size = size;
        } else {
            fld = document.createElement('textarea');
            fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
            fld.cols = this.options.cols || 40;
        }
        fld.id = this.options.formId + '-field';
        fld.name = this.options.paramName;
        fld.value = text; // No HTML breaks conversion anymore
        fld.className = 'editor_field';
        if (this.options.submitOnBlur)
            fld.onblur = this._boundSubmitHandler;
        this._controls.editor = fld;
        if (this.options.loadTextURL)
            this.loadExternalText();
        this._form.appendChild(this._controls.editor);
    },
    createForm: function() {
        var ipe = this;
        function addText(mode, condition) {
            var text = ipe.options['text' + mode + 'Controls'];
            if (!text || condition === false) return;

            var textSpan = document.createElement('span');
            textSpan.appendChild(document.createTextNode(text));

            if (ipe.options.controlsLocation) {
                if (ipe.options.controlsLocationBefore)
                    ipe.options.controlsLocation.insertBefore(textSpan, ipe.options.controlsLocation.firstChild);
                else
                    $(ipe.options.controlsLocation).appendChild(textSpan);
            }
            else
                ipe._form.appendChild(textSpan);

            ipe._controls['span' + mode] = textSpan;
        };
        this._form = $(document.createElement('form'));
        this._form.id = this.options.formId;
        this._form.addClassName(this.options.formClassName);
        this._form.onsubmit = this._boundSubmitHandler;
        this.createEditField();
        //if ('textarea' == this._controls.editor.tagName.toLowerCase())
        //  this._form.appendChild(document.createElement('br'));
        if (this.options.onFormCustomization)
            this.options.onFormCustomization(this, this._form);

        if (this.options.controlsLocationBefore) {
            addText('After', this.options.okControl || this.options.cancelControl);
            this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
            addText('Between', this.options.okControl && this.options.cancelControl);
            this.createControl('ok', this._boundSubmitHandler);
            addText('Before', this.options.okControl || this.options.cancelControl);
        }
        else {
            addText('Before', this.options.okControl || this.options.cancelControl);
            this.createControl('ok', this._boundSubmitHandler);
            addText('Between', this.options.okControl && this.options.cancelControl);
            this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
            addText('After', this.options.okControl || this.options.cancelControl);
        }
    },
    destroy: function() {
        if (this._oldInnerHTML)
            this.element.innerHTML = this._oldInnerHTML;
        this.leaveEditMode();
        this.unregisterListeners();
    },
    enterEditMode: function(e) {
        if (this._saving || this._editing) return;
        this._editing = true;
        this.triggerCallback('onEnterEditMode');
        if (this.options.externalControl)
            this.options.externalControl.hide();
        this.element.hide();
        this.createForm();
        this.element.parentNode.insertBefore(this._form, this.element);
        if (!this.options.loadTextURL)
            this.postProcessEditField();
        if (e) Event.stop(e);
        this.options.mceCall(this._form.id + '-field');
        //createCommentEditor(this._form.id + '-field');
    },
    enterHover: function(e) {
        if (this.options.hoverClassName)
            this.element.addClassName(this.options.hoverClassName);
        if (this._saving) return;
        this.triggerCallback('onEnterHover');
    },
    getText: function() {
        return this.element.innerHTML;
    },
    handleAJAXFailure: function(transport) {
        this.triggerCallback('onFailure', transport);
        if (this._oldInnerHTML) {
            this.element.innerHTML = this._oldInnerHTML;
            this._oldInnerHTML = null;
        }
    },
    handleFormCancellation: function(e) {
        this.wrapUp();
        if (e) Event.stop(e);
    },
    handleFormSubmission: function(e) {
        var form = this._form;

        $(form.id + '-field').value = tinyMCE.get(form.id + '-field').getContent();

        var value = $F(this._controls.editor);

        this.prepareSubmission();
        var params = this.options.callback(form, value) || '';
        if (Object.isString(params))
            params = params.toQueryParams();
        params.editorId = this.element.id;
        if (this.options.htmlResponse) {
            var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
            Object.extend(options, {
                parameters: params,
                onComplete: this._boundWrapperHandler,
                onFailure: this._boundFailureHandler
            });
            new Ajax.Updater({ success: this.element }, this.url, options);
        } else {
            var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
            Object.extend(options, {
                parameters: params,
                onComplete: this._boundWrapperHandler,
                onFailure: this._boundFailureHandler
            });
            new Ajax.Request(this.url, options);
        }
        if (e) Event.stop(e);
    },
    leaveEditMode: function() {
        this.element.removeClassName(this.options.savingClassName);
        this.removeForm();
        this.leaveHover();
        this.element.style.backgroundColor = this._originalBackground;
        this.element.show();
        if (this.options.externalControl)
            this.options.externalControl.show();
        this._saving = false;
        this._editing = false;
        this._oldInnerHTML = null;
        this.triggerCallback('onLeaveEditMode');
    },
    leaveHover: function(e) {
        if (this.options.hoverClassName)
            this.element.removeClassName(this.options.hoverClassName);
        if (this._saving) return;
        this.triggerCallback('onLeaveHover');
    },
    loadExternalText: function() {
        this._form.addClassName(this.options.loadingClassName);
        this._controls.editor.disabled = true;
        var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
        Object.extend(options, {
            parameters: 'editorId=' + encodeURIComponent(this.element.id),
            onComplete: Prototype.emptyFunction,
            onSuccess: function(transport) {
                this._form.removeClassName(this.options.loadingClassName);
                var text = transport.responseText;
                if (this.options.stripLoadedTextTags)
                    text = text.stripTags();
                this._controls.editor.value = text;
                this._controls.editor.disabled = false;
                this.postProcessEditField();
                tinyMCE.get(this._form.id + '-field').setContent(text);                
            } .bind(this),
            onFailure: this._boundFailureHandler
        });
        new Ajax.Request(this.options.loadTextURL, options);
    },
    postProcessEditField: function() {
        var fpc = this.options.fieldPostCreation;
        if (fpc)
            $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
    },
    prepareOptions: function() {
        this.options = Object.clone(Ajax.WhInPlaceEditor.DefaultOptions);
        Object.extend(this.options, Ajax.WhInPlaceEditor.DefaultCallbacks);
        [this._extraDefaultOptions].flatten().compact().each(function(defs) {
            Object.extend(this.options, defs);
        } .bind(this));
    },
    prepareSubmission: function() {
        this._saving = true;
        this.removeForm();
        this.leaveHover();
        this.showSaving();
    },
    registerListeners: function() {
        this._listeners = {};
        var listener;
        $H(Ajax.WhInPlaceEditor.Listeners).each(function(pair) {
            listener = this[pair.value].bind(this);
            this._listeners[pair.key] = listener;
            if (!this.options.externalControlOnly)
                this.element.observe(pair.key, listener);
            if (this.options.externalControl)
                this.options.externalControl.observe(pair.key, listener);
        } .bind(this));
    },
    removeForm: function() {
        if (!this._form) return;
        this._form.remove();
        this._form = null;

        if (this._controls['ok']) $(this._controls['ok']).remove();
        if (this._controls['cancel']) $(this._controls['cancel']).remove();
        if (this._controls['spanBefore']) $(this._controls['spanBefore']).remove();
        if (this._controls['spanBetween']) $(this._controls['spanBetween']).remove();
        if (this._controls['spanAfter']) $(this._controls['spanAfter']).remove();

        this._controls = {};
    },
    showSaving: function() {
        this._oldInnerHTML = this.element.innerHTML;
        this.element.innerHTML = this.options.savingText;
        this.element.addClassName(this.options.savingClassName);
        this.element.style.backgroundColor = this._originalBackground;
        this.element.show();
    },
    triggerCallback: function(cbName, arg) {
        if ('function' == typeof this.options[cbName]) {
            this.options[cbName](this, arg);
        }
    },
    unregisterListeners: function() {
        $H(this._listeners).each(function(pair) {
            if (!this.options.externalControlOnly)
                this.element.stopObserving(pair.key, pair.value);
            if (this.options.externalControl)
                this.options.externalControl.stopObserving(pair.key, pair.value);
        } .bind(this));
    },
    wrapUp: function(transport) {
        this.leaveEditMode();
        // Can't use triggerCallback due to backward compatibility: requires
        // binding + direct element
        this._boundComplete(transport, this.element);
    }
});

Object.extend(Ajax.WhInPlaceEditor.prototype, {
  dispose: Ajax.WhInPlaceEditor.prototype.destroy
});


//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only  exists for a while,  in order to  let ****
//**** users adapt to  the new API.  Read up on the new ****
//**** API and convert your code to it ASAP!            ****

Ajax.WhInPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
  if (!options) return;
  function fallback(name, expr) {
    if (name in options || expr === undefined) return;
    options[name] = expr;
  };
  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
    options.cancelLink == options.cancelButton == false ? false : undefined)));
  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
    options.okLink == options.okButton == false ? false : undefined)));
  fallback('highlightColor', options.highlightcolor);
  fallback('highlightEndColor', options.highlightendcolor);
};

Object.extend(Ajax.WhInPlaceEditor, {
  DefaultOptions: {
    ajaxOptions: { },
    autoRows: 3,                                // Use when multi-line w/ rows == 1
    cancelControl: 'link',                      // 'link'|'button'|false
    cancelText: 'cancel',
    clickToEditText: 'Click to edit',
    externalControl: null,                      // id|elt
    externalControlOnly: false,
    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
    formClassName: 'inplaceeditor-form',
    formId: null,                               // id|elt
    highlightColor: '#ffff99',
    highlightEndColor: '#ffffff',
    hoverClassName: '',
    htmlResponse: true,
    loadingClassName: 'inplaceeditor-loading',
    loadingText: 'Loading...',
    okControl: 'button',                        // 'link'|'button'|false
    okText: 'ok',
    paramName: 'value',
    rows: 1,                                    // If 1 and multi-line, uses autoRows
    savingClassName: 'inplaceeditor-saving',
    savingText: 'Saving...',
    size: 0,
    stripLoadedTextTags: false,
    submitOnBlur: false,
    textAfterControls: '',
    textBeforeControls: '',
    textBetweenControls: ' - ',
    mceCall: createCommentEditor
  },
  DefaultCallbacks: {
    callback: function(form) {
      return Form.serialize(form);
    },
    onComplete: function(transport, element) {
      // For backward compatibility, this one is bound to the IPE, and passes
      // the element directly.  It was too often customized, so we don't break it.
      // new Effect.Highlight(element, {
      //  startcolor: this.options.highlightColor, keepBackgroundImage: true });
    },
    onEnterEditMode: null,
    onEnterHover: function(ipe) {
      //ipe.element.style.backgroundColor = ipe.options.highlightColor;
      //if (ipe._effect)
      //  ipe._effect.cancel();
    },
    onFailure: function(transport, ipe) {
      alert('Error communication with the server: ' + transport.responseText.stripTags());
    },
    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
    onLeaveEditMode: null,
    onLeaveHover: function(ipe) {
      // ipe._effect = new Effect.Highlight(ipe.element, {
      //  startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
      //  restorecolor: ipe._originalBackground, keepBackgroundImage: true
      // });
    }
  },
  Listeners: {
    click: 'enterEditMode',
    keydown: 'checkForEscapeOrReturn',
    mouseover: 'enterHover',
    mouseout: 'leaveHover'
  }
});

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

Form.Element.DelayedObserver = Class.create({
  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));
  }
});
