Element.implement({
    getValues: function() {
        var formData = $H();

        // Get all input elements in the form
        this.getElements('input, select, textarea', true).each(function(el){
            // Ignore input elements that: do not have a name, are disabled, have the types submit, reset or file
            if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file')
                return;

            // Work out the value based on the type of input it is
            var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
                // Select elements should return the selected option
                return opt.value;
            }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;

            // Convert the value into an array if it isn't already and store the values
            $splat(value).each(function(val){
              if (typeof val != 'undefined') {
                if (formData[el.name]) {
                  formData[el.name] = $splat(formData[el.name]);
                  formData[el.name].push(val);
                } else
                  formData[el.name] = val;
              }
            });
        });

        return formData;
    },

    getJSONValues: function(escape) {
        // Similar to getValues but removes PHP specific array identifiers []
        // so that it can be encoded into JSON objects
        var formData = $H();
        escape = (escape == true);

        // Get all input elements in the form
        this.getElements('input, select, textarea', true).each(function(el){
            // Ignore input elements that: do not have a name, are disabled, have the types submit, reset or file
            if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file')
                return;

            // Work out the value based on the type of input it is
            var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
                // Select elements should return the selected option
                return opt.value;
            }) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;

            // Convert the value into an array if it isn't already and store the values
            var iteration = 0;

            $splat(value).each(function(val){
                if (typeof val != 'undefined') {
                  if (escape) {
                    val = (encodeURIComponent) ? encodeURIComponent(val) : escape(val);
                    val = val.replace(/%22/g, '\"');
                  }

                  // If this is an array, treat it as such
                  if (el.name.match(/\[\]/g) == '[]') {
                      var name = el.name.replace(/\[\]$/,'');
                      formData[name] = $splat(formData[name]);
                      formData[name].push(val);
                  } else
                      formData[el.name] = val;
                }
            });
        });

        return formData;
    },

    setValues: function(data) {
        // Get elements with the class input
        $$('.input').each(function(el){
            // Ignore input elements that: do not have a name, are disabled, have the types submit, reset or file
            if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file' || el.type == 'hidden')
                return;

            if (el.type == 'text' || el.type == 'textarea') {
                el.set('value', data[el.id]);
            } else if (el.type == 'select-one') {
                for (var i=0; i<el.length; i++) {
                    if (el[i].value == data[el.id])
                        el.selectedIndex = i;
                }
            } else if (el.type == 'checkbox') {
                el.checked = (data[el.id]) ? true : false;
            }
        });
    },

    /*  Property: isVisible
        Returns a boolean; true = visible, false = not visible.

        Example:
        >$(id).isVisible()
        > > true | false    */
    isVisible: function() {
        return this.getStyle('display') != 'none';
    },

    /*  Property: hide
        Hides an element (display = none)

        Example:
        > $(id).hide()
        */
    hide: function() {
        this.originalDisplay = this.getStyle('display');
        this.setStyle('display','none');
        return this;
    },

    /*  Property: show
        Shows an element (display = what it was previously or else display = block)

        Example:
        >$(id).show() */
    show: function(display) {
        this.originalDisplay = (this.originalDisplay=="none")?'block':this.originalDisplay;
        this.setStyle('display',(display || this.originalDisplay || 'block'));
        return this;
    },

     /*  Property: toggle
     *  Toggles the display of the element
     *
     *  Example:
     *  >$(id).toggle()
     */
    toggle: function(display) {
        if (this.isVisible())
            this.hide();
        else
            this.show(display);
    }

});

Hash.implement({
  toQueryString: function(base){
    var queryString = [];
    Hash.each(this, function(value, key){
      if (base) key = base;
      var result;
      switch ($type(value)){
        case 'object': result = Hash.toQueryString(value, key); break;
        case 'array':
          var qs = {};
          value.each(function(val, i){
            qs[i] = val;
          });
          result = Hash.toQueryString(qs, key);
        break;
        default: result = key + '=' + encodeURIComponent(value);
      }
      if (value != undefined) queryString.push(result);
    });

    return queryString.join('&');
  }
});

//
// Modal Backdrop class
//
var ModalBackdrop = new Class({
    options: {
      onComplete: Class.empty,
      onResize: Class.empty
    },

    initialize: function(options) {
        this.setOptions(options);

        // Add backdrop element
        if (!$('modalBackdrop')) {
            new Element('div', {'id': 'modalBackdrop',
                                'styles': { 'height': window.getHeight()+'px',
                                            'opacity' : 0,
                                            'display': 'none' }}).injectInside(document.body);
        }

        // Display effects
        this.fx = {
            overlayShow: $('modalBackdrop').get('tween', { property: 'opacity', duration: 'short', onComplete: this.onShowComplete.bind(this)}),
            overlayHide: $('modalBackdrop').get('tween', { property: 'opacity', duration: 'short', onComplete: this.onHideComplete.bind(this)})
        };

        // Attach the onResize event
        window.addEvent('resize', this.onWindowResize.bind(this));
    },

    show: function() {
        // Set its display style to be visible
        $('modalBackdrop').style.display = '';

        if (window.ie6)
            this.hideSelects();

        // Resize the backdrop
        this.onWindowResize();
        this.fx.overlayShow.start(0, 0.75);
    },

    hide: function() {
        this.fx.overlayHide.start(0.75, 0);
    },

    onShowComplete: function() {
        this.options.onComplete();
    },

    onHideComplete: function() {
        $('modalBackdrop').style.display = 'none';

        if (window.ie6)
            this.showSelects();

        this.options.onComplete();
    },

    onWindowResize: function() {
        // Set the backdrop height to the page height first, while we then
        // calculate how high the page is. This prevents the backdrop height
        // from being the defining factor in page height on pages with a
        // short amount of content...
        $('modalBackdrop').style.height = window.getHeight() + 'px';

        // Set the height of the backdrop as appropriate
        var backdropHeight = 0;
        if (window.getScrollHeight() > window.getHeight()) {
            // The viewport is larger than the page, so use the viewport to
            // determine the size of the dialog background
            backdropHeight = window.getScrollHeight();
        } else {
            // The page exceeds the visible area of the page, so make the modal
            // backdrop span through to the end of the page
            backdropHeight = window.getHeight();
        }
        $('modalBackdrop').style.height = backdropHeight + 'px';


        // Call the hook function for children using ModalBackdrop
        this.options.onResize();

        return true;
    },

    hideSelects: function() {
        var selects = $$('select');
        for (var i = 0; i < selects.length; i++) {
            var thisSelect = selects[i];
            if (thisSelect.clientWidth == 0 || thisSelect.clientHeight == 0 ||
                (thisSelect.nextSibling != null && thisSelect.nextSibling.className == 'selectReplacement')) {
                continue;
            }

            var newSpan = document.createElement('span');

            newSpan.style.height = (thisSelect.clientHeight - 5) + 'px';
            newSpan.style.width = (thisSelect.clientWidth - 6) + 'px';
            newSpan.className = 'selectReplacement';

            try {
                newSpan.innerHTML = thisSelect.options[thisSelect.selectedIndex].innerHTML;
            } catch (e) { }


            thisSelect.cachedDisplay = thisSelect.style.display;
            thisSelect.style.display = 'none';
            thisSelect.parentNode.insertBefore(newSpan, thisSelect.nextSibling);

            thisSelect = null;
        }

        selects = null;
    },

    showSelects: function() {
        var selects = $$('select');
        for (var i = 0; i < selects.length; i++) {
            var thisSelect = selects[i];
            if (thisSelect.clientWidth == 0 || thisSelect.clientHeight == 0 ||
                thisSelect.nextSibling == null || thisSelect.nextSibling.className != 'selectReplacement') {
                continue;
            }
            thisSelect.parentNode.removeChild(thisSelect.nextSibling);
            thisSelect.style.display = thisSelect.cachedDisplay;

            thisSelect = null;
        }

        selects = null;
    }

});
ModalBackdrop.implement(new Options);


//
// Modal Dialog class
//
var ModalDialog2 = new Class({
    options: {
        heading: null,
        action: null,
        url: null,
        width: 40,
        height: 60,
        onLoaded: Class.empty,
        onPageReload: Class.empty,
        reloadOnSave: true,
        closeMode: 'cancel',
        cancelButton: 'Cancel',
        cancelEnabled: true,
        visible: false,
        saveButton: 'Save',
        saveCallback: null,
        saveEnabled: true,
        deleteButton: false,
        modalForm: true,
        saveCheck: function() { return true; }
    },

    initialize: function(options) {
        this.setOptions(options);
        this.initialized = false;
        this.returnHeaders = new Array();

        // Set the action url
        if (this.options.action == null)
          this.options.action = this.currentUrl();

        window.addEvent('domready', this.domInitialize.bind(this));
    },

    domInitialize: function() {
        if (!$('modalDialog2')) {
            // Create the dialog HTML elements
            new Element('div', {'id': 'modalDialog2', 'styles': { 'visibility': 'hidden' }}).injectInside(document.body);
            new Element('div', {'id': 'modalDialog2Container'}).injectInside('modalDialog2');
            new Element('div', {'id': 'modalDialog2Banner'}).injectInside('modalDialog2Container');
            new Element('div', {'id': 'modalDialog2Heading'}).injectInside('modalDialog2Banner');
            if (this.options.modalForm) {
                new Element('form', {'id': 'modalDialog2Form', 'method': 'post', 'action': this.options.action }).injectInside('modalDialog2Container');
                new Element('div', {'id': 'modalDialog2Content'}).injectInside('modalDialog2Form');
                new Element('div', {'id': 'modalDialog2Footer'}).injectInside('modalDialog2Form');
            } else {
                new Element('div', {'id': 'modalDialog2Content'}).injectInside('modalDialog2Container');
                new Element('div', {'id': 'modalDialog2Footer'}).injectInside('modalDialog2Container');
            }
            new Element('input', {'type': 'button', 'class': 'modalDialog2' + this.options.saveButton, 'id': 'modalDialogSave'}).injectInside('modalDialog2Footer');
            new Element('input', {'type': 'button', 'class': 'modalDialog2Delete', 'id': 'modalDialogDelete'}).injectInside('modalDialog2Footer');
            new Element('input', {'type': 'button', 'class': 'modalDialog2' + this.options.cancelButton, 'id': 'modalDialogCancel'}).injectInside('modalDialog2Footer');
        }


        // Per-dialog initialisation
        var nextEffect = this.nextEffect.bind(this);
        var windowResize = this.onWindowResize.bind(this);
        this.backdrop = new ModalBackdrop({ onComplete: nextEffect, onResize: windowResize });

        // Delete button visibility
        if (this.options.deleteButton)
            $('modalDialogDelete').show();
        else
            $('modalDialogDelete').hide();

        // Keyboard handler
        this.eventKeyDown = this.keyboardListener.bindWithEvent(this);

        // IE6 onScroll handler
        if (window.ie6)
            window.addEvent('scroll', this.onScrollIE6.bind(this));

        // Initialised
        this.initialized = true;
    },

    keyboardListener: function(event) {
        switch (event.key) {
            case 'esc':
                if (this.options.cancelButton)
                  this.cancel();
                break;
        }
    },

    add: function(args) {
        args = args || {};
        this.show($merge({'prefix': 'add'}, args));
    },

    edit: function(args) {
        args = args || {};
        this.show($merge({'prefix': 'edit'}, args));
    },

    copy: function(args) {
        args = args || {};
        this.show($merge({'prefix': 'copy'}, args));
    },

    show: function(args) {
        // Trap attempts to show the dialog before it is initialised
        if (!this.initialized)
            return;

        // Initialise the dialog state to clean up from previous displays
        this.step = 1;
        $('modalDialog2').style.width = '';
        $('modalDialog2').style.height = '';
        $('modalDialog2').style.visibility = 'hidden';
        $('modalDialog2').style.display = '';

        // Set the classnames for the dialog buttons
        $('modalDialogSave').className = 'modalDialog2' + this.options.saveButton;
        $('modalDialogCancel').className = 'modalDialog2' + this.options.cancelButton;

        // Set the display of the delete button
        if (this.options.deleteButton)
          $('modalDialogDelete').style.display = '';
        else if ($('modalDialogDelete'))
          $('modalDialogDelete').style.display = 'none';

        if (this.options.heading) {
            var prefix = $defined(args) && $defined(args.prefix) ? args.prefix : '';
            if (prefix == 'add')
                var prefix_display = 'Add ';
            else if (prefix == 'edit')
                var prefix_display = 'Edit ';
            else if (prefix == 'copy')
                var prefix_display = 'Copy ';
            else
                var prefix_display = '';

            var heading = $defined(args) && $defined(args.heading) ? args.heading : prefix_display + this.options.heading;
            $('modalDialog2Heading').set('text', heading);
        } else
            $('modalDialog2Heading').empty();

        // Initiate the AJAX request for the dialog content. We do this as early
        // as possible so that we maximise how long we have for the content to
        // arrive from the network.
        $('modalDialog2Content').set('html', '');

        // Leave 'prefix' argument in array so the dialog
        // knows whether it's an edit or add dialog
        //args = args ? $H(args).erase('prefix') : null;
        this.ajax = new Request.HTML({
            url: this.options.url,
            method: 'get',
            update: 'modalDialog2Content',
            headers: { 'X-SENTRAL-Relative-Root': rel_root },
            data: args,
            evalScripts: true
          }).send().chain(function() {
              if ($('modalDialog2').style.visibility == '') {
                // The dialog has already loaded so focus on the top input
                try {
                    $('modalDialog2').getElement('input').focus();
                } catch(e) { }
              }
            }
          );


        // Calculate the pixel dimensions of the dialog frame. Do this by
        // setting the size of the dialog frame (in em's), then querying
        // the browser to see what values it calculated. These pixel units
        // are then used to centre the dialog frame in the browser window.
        $('modalDialog2Content').style.height = this.options.height + 'em';
        $('modalDialog2Content').style.width = this.options.width + 'em';

        // For IE6, we need to set the width of the outer explicitly or else it
        // will expand to the full width of the browser... :-(
        if (window.ie6)
            $('modalDialog2').style.width = $('modalDialog2Content').getSize().x + 'px';

        // For IE6 and 7, form elements can be taller than other browsers, so multiply the size of the
        // content
        if (window.ie6 || window.ie7)
          $('modalDialog2Content').style.height = $('modalDialog2Content').getSize().y * 1.025;

        var frameWidth = $('modalDialog2').offsetWidth;
        var frameHeight = $('modalDialog2').offsetHeight;

        // Set position of dialog frame
        $('modalDialog2').style.top = Math.max((window.getHeight() - frameHeight) / 2, 0) + 'px';
        $('modalDialog2').style.left = Math.max((window.getWidth() - frameWidth) / 2, 0) + 'px';

        // Attach the save/cancel button handlers
        $('modalDialogSave').addEvent('click', this.save.bind(this));
        if ($('modalDialogDelete'))
          $('modalDialogDelete').addEvent('click', this.deleteSave.bind(this));
        $('modalDialogCancel').addEvent('click', this.cancel.bind(this));
        document.addEvent('keydown', this.eventKeyDown);

        // Set the initial OK/Save button state
        $('modalDialogSave').disabled = !this.options.saveEnabled;
        $('modalDialogCancel').disabled = !this.options.cancelEnabled;

        // If save button is set to false, hide it
        if (!this.options.saveButton)
          $('modalDialogSave').style.display = 'none';
        if (!this.options.cancelButton)
          $('modalDialogCancel').style.display = 'none';

        // Display the modal backdrop
        this.backdrop.show();
    },

    // Event handlers
    nextEffect: function() {
        switch (this.step++) {
        /*
         * Opening animations
         */
            case 1:
               // Display the dialog in its initial collapsed state
                $('modalDialog2').style.visibility = '';

                // Fix firefox wierdness with overflow affecting hidden elements
                $('modalDialog2Content').style.overflow = 'auto';

                // No-op
                this.options.visible = true;

                // Focus the first input element within the dialog contents
                try {
                    $('modalDialog2').getElement('input').focus();
                } catch(e) { }

                // In IE6, make sure we are in the right position
                if (window.ie6)
                    this.onWindowResize();

                break;

        /*
         * Closing animations
         */
            case 2:
                // Fix firefox wierdness with overflow affecting hidden elements
                this.options.visible = false;
                $('modalDialog2Content').style.overflow = 'hidden';

                $('modalDialog2').style.display = 'none';
                $('modalDialog2Content').style.display = '';
                $('modalDialogSave').disabled = false;
                $('modalDialogCancel').disabled = false;
                if ($('modalDialog2Saving'))
                    $('modalDialog2Saving').remove()
                if (this.closeMode == 'save') {
                    if ($type(this.options.saveComplete) == 'function') {
                        // User-defined callback function
                        this.options.saveComplete($('modalDialog2Form').getValues());
                        return;
                    } else if (this.options.reloadOnSave) {
                        // Dialog closed via Save button: reload the page
                        this.reloadPage();
                        return;
                    }
                }

                // Dialog closed via Cancel button: fade-out backdrop
                // and return user to the current page without reload
                this.backdrop.hide();
                break;
        }
    },

    onScrollIE6: function() {
        // In IE6, we can't use position: fixed, so we use JavaScript to
        // reposition the dialog to the appropriate coordinates... :-(
        this.onWindowResize();
    },

    onWindowResize: function() {
        if (this.options.visible) {
            // Determine new horizontal and vertical location of dialog
            var dialogLeft = (window.getWidth() - $('modalDialog2').offsetWidth) / 2;
            var dialogTop = (window.getHeight() - $('modalDialog2').offsetHeight) / 2;
            dialogLeft = Math.max(dialogLeft, 0);
            dialogTop = Math.max(dialogTop, 0);

            // IE6 doesn't support position: fixed, so we need to allow
            // for the scroll height in calculating the new top position
            if (window.ie6)
                dialogTop += window.getScrollTop();

            // Move the dialog
            $('modalDialog2').style.left = dialogLeft + 'px';
            $('modalDialog2').style.top = dialogTop + 'px';
        }
    },

    close: function() {
        // Remove save/cancel button handlers
        $('modalDialogSave').removeEvents('click');
        $('modalDialogCancel').removeEvents('click');
        if ($('modalDialogDelete'))
          $('modalDialogDelete').removeEvents('click');
        document.removeEvent('keydown', this.eventKeyDown);

        // Close any open calendar dialogs
        // XXX this possibly does not belong here
        try {
            deInitCalendar(false);
        } catch (e) { }


        // Begin the close animations
        this.nextEffect();

    },

    cancel: function() {
        // Close the dialog
        this.closeMode = 'cancel';
        this.close();
    },

    deleteSave: function() {
        if (confirm('Are you sure you wish to remove this entry?')) {
            new Element('input', {'type': 'hidden', 'name': 'modalDelete', 'id': 'modalDeleteInput', 'value': 'delete'}).injectInside('modalDialog2Form');
            this.save();
        }
    },

    save: function() {
        // If a saveCheck function has been defined and it returns false, then exit
        if (!this.options.saveCheck())
          return false;

        $('modalDialog2Content').style.display = 'none';
        $('modalDialogSave').disabled = true;
        $('modalDialogCancel').disabled = true;

        // Create and display the saving indicator

        new Element('div', {
            'id': 'modalDialog2Saving',
            'styles': {
              'width':  $('modalDialog2Content').style.width,
              'height': $('modalDialog2Content').style.height,
              'text-align': 'center' }}).injectBefore('modalDialog2Content');
        if (this.options.saveButton == 'Ok' || this.options.saveButton == 'Print') {
            $('modalDialog2Saving').appendText("Please wait...");
        } else {
            $('modalDialog2Saving').appendText("Saving...");
        }

        // Handle save action
        if ($type(this.options.saveCallback) == 'function') {
            if (this.options.saveCallback($('modalDialog2Form').getValues())) {
                this.saveComplete();
            } else {
                this.saveFailed();
            }
        } else {
            // Submit the form data via an AJAX POST request
            this._ajax = new Ajax($('modalDialog2Form').getProperty('action'),
                                  { method: 'post',
                                    data: $('modalDialog2Form').toQueryString(),
                                    onComplete: this.saveComplete.bind(this),
                                    onFailure:  this.saveFailed.bind(this) });

            this._ajax.request();
        }

    },

    saveComplete: function(responseText) {
        if (typeof(this._ajax) != 'undefined')
          this.populateReturnHeaders(this._ajax.getAllHeaders());
        if ($type(this.options.saveCallback) == 'function') {
            // Save callback completed
            this.closeMode = 'save';
            this.close.delay(500, this);
        } else {
            // AJAX callback
            if (responseText == 'OK') {
                $('modalDialog2Saving').set('text', 'Save successful');
                this.closeMode = 'save';
                this.close.delay(500, this);
            } else
                this.saveFailed(responseText);
        }
    },
    saveFailed: function(responseText) {
        var errorHtml;
        responseText = $defined(responseText) ? responseText : 'FAIL';
        if (responseText == 'FAIL') {
            // Show a generic error message
            errorHtml = "An error was encountered trying to save your changes.<br /><br />" +
                        "Please ensure you have completed all required fields and<br />" +
                        "double-check all of your inputs are valid, then try again.<br /><br />" +
                        "If this problem persists, please contact GP Technology Solutions.";
        } else {
            // Display the user defined error
            errorHtml = 'An error was encountered while trying to save your changes.<br /><br />' + responseText;
        }

        // Set the error text
        $('modalDialog2Saving').setHTML(
          errorHtml + "<br><br>" +
          "<a href=\"javascript:$('modalDialog2Saving').remove(); return false\" " +
             "onClick=\"$('modalDialog2Content').style.display = ''; " +
                       "$('modalDialogSave').disabled = false; " +
                       "$('modalDialogCancel').disabled = false; " +
                       "$('modalDialog2Saving').remove(); " +
                       "return false\">Click Here to Go Back</a>");

    },

    reloadPage: function() {
        // Reload the page by setting the URL, to avoid warnings about
        // resubmiting any page POST data. We remove the anchor as otherwise
        // the browser tries to navigate within the current page and no
        // reload of the page ever happens!
        var url = window.location.href.split('#')[0];

        // Call any user-specified reload handler, which gives consumers
        // the opportunity to modify the URL that will be loaded
        var new_url = this.options.onPageReload(url);
        url = new_url ? new_url : url;

        // Reload the specified URL
        window.location.href = url;
    },

    currentUrl: function() {
        return window.location.href.split('#')[0];
    },

    populateReturnHeaders: function(returnHeaders) {
      // If the there are headers to return, populate a hash array with they key/data pairings
      if (returnHeaders != null) {
        pairings = returnHeaders.split("\n");
        for (i = 0; i < pairings.length; i++) {
          // Foreach pairing, split it into a key and a value
          keyvalue = pairings[i].split(":", 2);
          this.returnHeaders[keyvalue[0]] = keyvalue[1];
        }
      }
    }
});
ModalDialog2.implement(new Options);



var OverlayFix = new Class({
    initialize: function(el) {
        this.elementId = el;
        if (window.ie){
            $(this.elementId).addEvent('trash', this.destroy.bind(this));
            this.fix = new Element('iframe', {
                properties: {
                    frameborder: '0',
                    scrolling: 'no',
                    src: 'javascript:false;'
                },
                styles: {
                    position: 'absolute',
                    border: 'none',
                    display: 'none',
                    filter: 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'
                }
            }).injectAfter($(this.elementId));
        }
    },

    show: function() {
        if (this.fix) this.fix.setStyles($extend(
            $(this.elementId).getCoordinates(), {
                display: '',
                zIndex: ($(this.elementId).getStyle('zIndex') || 1) - 1
            }));
        return this;
    },

    hide: function() {
        if (this.fix) this.fix.setStyle('display', 'none');
        return this;
    },

    destroy: function() {
        this.fix.remove();
    }

});


//
// Sentral classes
//
var Sentral = new Class({
    Implements: [Options]
});

Sentral.Tips = new Class({
    Extends: Sentral,

    options: {
        classname: 'tips'
    },

    initialize: function(options) {
        this.setOptions(options);
        window.addEvent('domready', this.build.bind(this));
    },
    prepare: function() {
        if (this.options.classname) {
            $$('.' + this.options.classname).each(function(element, index) {
                if (element.get('title')) {
                    var content = element.get('title').split('::');
                    element.store('tip:title', content[0]);
                    element.store('tip:text', content[1]);
                }
            });
        }
    },
    build: function() {
        // Prepare the title & text
        this.prepare();

        // build tips
        var tipz = new Tips('.' + this.options.classname, {
            className: 'tooltip'
        });

    	tipz.addEvents({
    		'show': function(tip) {
    			tip.fade('in');
    		},
    		'hide': function(tip) {
    			tip.fade('out');
    		}
    	});

    }
});


//
// Sentral.Utils.CheckboxToggle
//   Manages mass select/unselect/toggle functionality for groups of checkboxes
//
Sentral.Utils = {};
Sentral.Utils.CheckboxToggle = new Class({
    Implements: [Options],

    options: {
        toggle: null
    },

    // The list of elements to toggle on/off
    elements: null,

    initialize: function(elements, options) {
        this.setOptions(options);
        this.elements = elements;

        if ($defined(this.options.toggle)) {
            window.addEvent('domready', function() {
                $(this.options.toggle).addEvent('click', function() {
                    this.select($(this.options.toggle).get('checked'));
                }.bind(this));
            }.bind(this));
        };
    },

    select: function(state) {
        $$(this.elements).each(function(el) {
            if (el != $(this.options.toggle)) {
                el.set('checked', state);
                el.fireEvent('click', el);
            }
        }.bind(this));
    },

    selectAll: function() {
        this.set(true);
    },

    deselectAll: function() {
        this.select(false);
    }
});

Sentral.Utils.TableHighlight = new Class({
    Implements: [Options],

    options: {
        toggles: null
    },

    table: null,

    // The table element to apply to
    initialize: function(table, options) {
        this.setOptions(options);
        this.table = table;

        // Attach to the row select checkboxes when the DOM is ready
        if ($defined(this.options.toggles)) {
            window.addEvent('domready', function() { this.attach(this.options.toggles) }.bind(this));
        }
    },

    toggleHighlight: function(el) {
        if (el.get('checked'))
            el.getParent('tr').addClass('highlight');
        else
            el.getParent('tr').removeClass('highlight');
    },

    attach: function(elements) {
        $(this.table).getElements(elements).each(function(el) {
            el.addEvent('click', this.toggleHighlight.bind(this, el));
        }.bind(this));
    }
});

Sentral.DropDown = new Class({
    Implements: [Options],

    options: {
      element: null,
      trigger: null
    },

    initialize: function(options) {
        this.setOptions(options);

        // Attach to the trigger
        if (this.options.trigger) {
            window.addEvent('domready', function() {
                $(this.options.trigger).addEvent('click', this.showHandler.bind(this));
            }.bind(this));
        }

        this.boundHide = this.hide.bind(this);
    },

    showHandler: function(e) {
        this.show();
        e.stop();
    },

    show: function() {
        // Hide the menu when clicking outside the menu
        $(document.body).addEvent('click', this.boundHide);

        // Display the drop down
        $(this.options.element).show();
    },

    hide: function() {
        // Hide the element
        $(this.options.element).hide();

        // Remove the document body onclick event
        $(document.body).removeEvent('click', this.boundHide);
    }
});

/* AJAX student search */
Sentral.QuickSearch = new Class({
    Implements: [Options],
    options: {
        title           : null,
        search_value    : 'Search...',
        width           : '200',
        url             : '/ajax/searchStudent',
        auto_select     : false,
        delay           : 500,
        onSelectResult  : function(id, data) { },
        onSearchSuccess : function() { },
        onClose         : function() { }
    },

    // Construct takes an element and the Class options
    initialize: function(element, options) {
        this.setOptions(options);

        // Create the search elements
        this.elements = new Array;

        this.elements.container = new Element('div', {
                'class'     : 'sentral_quicksearch',
                'styles'    : {
                    'width' : this.options.width + 'px'
                },
                'events'    : {
                    'mousemove' : this.resetNavigation.bind(this)
                }
            }).inject(element);

        if (this.options.title !== null) {
            // Insert a title if needed
            var title = new Element('h1').inject(this.elements.container);
            title.appendText(this.options.title)
        }

        // Create the input element
        this.elements.input = new Element('input', {
                'type'          : 'text',
                'class'         : 'input',
                'autocomplete'  : 'off',
                'name'          : this.options.field_name,
                'alt'           : this.options.search_value,
                'value'         : this.options.search_value,
                'events'        : {
                    'focus'     : this.focusSearch.bind(this),
                    'click'     : function(e) { e.stopPropagation(); },
                    'keydown'   : this.navigateResults.bind(this),
                    'keyup'     : this.startSearch.bind(this)
                }
            }).inject(this.elements.container);

        // Create a container for the search results
        this.elements.results_container = new Element('div', {
                'class' : 'results_container',
                'styles'        : {
                    'width' : this.options.width + 'px' }
            }).inject(this.elements.container);

        this.elements.results_container.hide();

        // Create a cancel button
        this.elements.cancel = new Element('img', {
                'class'     : 'cancel',
                'height'    : 16,
                'width'     : 16,
                'src'       : '/_common/images/icons/symbols/delete16.png',
                'events'    : {
                    'click' : this.stopSearch.bind(this)
                }
            }).inject(this.elements.results_container);

        // Create a search results container
        this.elements.results = new Element('div', {
                'class' : 'results'
            }).inject(this.elements.results_container);

    },

    focus:  function() {
        // Focus on the search box
        this.elements.input.focus();
    },

    focusSearch: function() {
        // What happens when the search box is focused
        this.elements.input.select();
    },

    navigateResults: function(e) {
        // Handles the keyboard nagivation between results
        switch((window.event) ? window.event.keyCode : e.event.keyCode) {
            case 16: case 17: case 18: case 19: case 37: case 39:
                //Shift, Ctrl, Alt, Pause, Arrow Left, Arrow Right

                // Do nothing
                break;

            case 38:
                //Arrow Up

                // Select the previous element on the list
                var previous_element = this.elements.results_container.getElements('tr.result').getLast();
                var selected_element = this.elements.results_container.getElement('tr.result.selected');

                if (selected_element != null) {
                    // We already have a selected element
                    selected_element.removeClass('selected');

                    if (selected_element.getPrevious('tr.result') != null)
                        previous_element = selected_element.getPrevious('tr.result');
                };

                if (previous_element != null)
                    previous_element.addClass('selected');

                break;

            case 40:
                // Arrow Down

                // Select the next element on the list
                var next_element = this.elements.results_container.getElement('tr.result');
                var selected_element = this.elements.results_container.getElement('tr.result.selected');

                if (selected_element != null) {
                    // We already have a selected element
                    selected_element.removeClass('selected');

                    if (selected_element.getNext('tr.result') != null)
                        next_element = selected_element.getNext('tr.result');
                };

                if (next_element != null)
                    next_element.addClass('selected');

                break;

            case 13:
                // Enter

                // Simulate a mouse click on the selected element
                var selected_element = this.elements.results_container.getElement('tr.result.selected');

                if (selected_element != null)
                    selected_element.fireEvent('click');

                return false;
                break;
        }

    },

    resetNavigation: function(e) {
        // If the mouse has moved, remove any selected element
        var selected_element = this.elements.results_container.getElement('tr.result.selected');

        if (selected_element != null)
            selected_element.removeClass('selected');
    },

    startSearch: function(e) {
        // Add an event to the text input to control the key strokes
        switch((window.event) ? window.event.keyCode : e.event.keyCode) {
            case 13:
                // If auto select on enter is enabled, do a search with auto selection on
                if (this.options.auto_select == true && this.elements.results_container.getElement('tr.result.selected') == null) {
                    this.auto_select = true;
                    this.elements.input.select();
                    $clear(this.searchTimer);
                    this.searchTimer = this.performSearch.delay(500, this);
                }
                break;

            case 16: case 17: case 18: case 19: case 37: case 38: case 39: case 40:
                // Don't search on these keys
                break;

            default:
                // Otherwise engage the search timer
                this.auto_select = false;
                $clear(this.searchTimer);
                this.searchTimer = this.performSearch.delay(500, this);
            }
    },

    performSearch: function() {
        if (this.elements.input.value) {

            if (this.request)
                this.request.cancel();

            this.elements.results.empty();
            this.elements.results_container.show();

            this.request = new Request.JSON({
                url             : this.options.url,
                data            : 'query=' + this.elements.input.value,
                useSpinner      : true,
                spinnerTarget   : this.elements.results,
                onSuccess       : this.displayResults.bind(this)
            }).get();
        }
    },

    stopSearch: function() {
        if (this.request)
            this.request.cancel();

        // Run a custom close function
        this.options.onClose();

        this.elements.results_container.hide();
        this.elements.input.focus();
    },

    resetSearch: function() {
        // Stops the search and sets the input back to its default
        this.stopSearch();
        this.elements.input.value = this.options.search_value;
    },

    displayResults: function(response) {
        // Run a custom search success function
        this.options.onSearchSuccess();

        this.elements.results.empty();

        // Build the results display
        var table = new Element('table').inject(this.elements.results);
        var table_body = new Element('tbody').inject(table);

        response.results.each(function(result) {
            // Display each result
            var table_row = new Element('tr', {
                    'class'     : 'result',
                    'events'    : {
                        'click' : this.options.onSelectResult.pass([result.id, result])
                    }
                }).inject(table_body);

            if (result.icon) {
                // If the results include an icon, place it here
                var table_data = new Element('td', {
                        'class' : 'icon'
                    }).inject(table_row)

                new Element('img', {
                        'src'       : result.icon,
                        'height'    : response.icon_size,
                        'width'     : response.icon_size
                    }).inject(table_data)
            }

            // Title data
            var table_data = new Element('td', {
                    'class'     : 'title',
                    'colspan'   : (result.icon ? '1' : '2')
                }).inject(table_row);
            table_data.appendText(result.title);

            // Description data
            var table_data = new Element('td', {
                    'class' : 'description'
                }).inject(table_row);
            table_data.appendText(result.description);
        }.bind(this));

        if (response.more_results) {
            // This search returns more rows
            var table_row = new Element('tr', {
                    'class'     : 'more_results'
                }).inject(table_body);

            var table_data = new Element('td', {
                    'colspan'   : 3
                }).inject(table_row);
            table_data.appendText('More results found, please refine the search');
        }

        if (response.results.length == 0) {
            // No results found
            var table_row = new Element('tr', {
                    'class' : 'no_result'
                }).inject(table_body);

            var table_data = new Element('td').injectInside(table_row);
            table_data.appendText('No results found.');
        }

        if (this.auto_select && response.results.length == 1) {
            // Auto select the only result
            this.elements.results_container.hide();
            this.options.select_result.attempt(response.results[0].id);
        }
    }

});


/*
 *
 * XXX BEGIN Legacy Quick Search
 * Required by Welfare that has a particularly complex implementation
 * of Quick Search, which needs to be rolled out ASAP
 *
 */

var QuickSearchId = 1;
var QuickSearchElementId = {};
Sentral.LegacyQuickSearch = new Class({
    Implements: [Options],
    options: {
        field_name: 'search',
        search_value: 'Search...',
        width: '200',
        url: '/ajax/searchStudent',
        fn: 'selectStudent',
        result_class: 'floatSearch',
        callback: '',
        onfocus: '',
        onsearch: '',
        oncancel: '',
        data: $H()
    },

    // Construct takes an element and the Class options
    initialize: function(element, options) {
        this.setOptions(options);
        this.container = element;
        this.id = QuickSearchId++;

        // Link id to container
        QuickSearchElementId[this.container] = this.id;

        // Construct the DOM inside the container
        window.addEvent('domready', function() {
            // Create an input text
            var input_div = new Element('div').inject(this.container);
            var input = new Element('input', {'type' : 'text',
                                              'id' : this.options.field_name + this.id,
                                              'styles' : {'width' : this.options.width + 'px'},
                                              'autocomplete' : 'off',
                                              'name' : this.options.field_name,
                                              'alt': this.options.search_value,
                                              'value': this.options.search_value}).inject(input_div);

            // Add the onfocus events
            input.addEvent('focus', function() { input.select(); }.pass(input.event));
            if ($type(this.options.onfocus) == 'function')
                input.addEvent('focus', function() { return this.options.onfocus(); }.bind(this));

            // Stop any click events to the input from bubbling up
            input.addEvent('click', function(ev) { ev.stopPropagation(); }.pass(input.event));

            // Create a search result & cancel button container
            this.search_result_container = new Element('div', {
                'id': 'search_result_container' + this.id,
                'class': this.options.result_class,
                'styles': { 'position': 'absolute', 'display': 'none'}
            }).inject(input_div, 'after');
            this.search_result_container.hide();

            // Create an element for cancelling the search
            this.search_cancel = new Element('div', {'class' : 'floatSearchCancel'}).inject(this.search_result_container);
            new Element('img', {'height' : 16,
                                'width' : 16,
                                'src' : '/_common/images/icons/symbols/delete16.png'}).inject(this.search_cancel);

            this.search_cancel.addEvent('click', this.stopSearch.bind(this));
            this.search_cancel.addEvent('click', function(ev) { ev.stopPropagation(); }.pass(this.search_cancel.event));

            // Create a search result container
            this.search_result = new Element('div', {'id' : 'search_result' + this.id}).inject(this.search_result_container);

            // add an event to the text input
            input.addEvent('keydown', function(e) {
                switch((window.event) ? window.event.keyCode : e.event.keyCode){
                case 16: case 17: case 18: case 19: case 37: case 39: break;//shift, ctrl, alt, pause, arrow left, arrow right
                case 38://arrow up
                    var elms = this.search_result_container.getElementsByTagName("*");
                    var found=-1, first=null, last=null;
                    for(var i = elms.length-1; 0 <= i; --i) {
                       if(elms[i].className == 'floatSearchRow'){
                            if(last == null) last = elms[i];
                            first=elms[i];
                            if(found == 1){ elms[i].className = 'floatSearchRow floatSearchSelected'; found = 0;}
                        }
                        else if(elms[i].className == 'floatSearchRow floatSearchSelected'){
                            if(last == null) last = elms[i];
                            first=elms[i];
                            elms[i].className = 'floatSearchRow'; found = 1;
                        }
                    }
                    if(found==1 && first!=null) first.className = 'floatSearchRow floatSearchSelected';
                    if(found==-1 && last!=null) last.className = 'floatSearchRow floatSearchSelected';
                    break;
                case 40://arrow down
                    var elms = this.search_result_container.getElementsByTagName("*");
                    var found=-1, first=null, last=null;
                    for(var i = 0; i < elms.length; ++i) {
                       if(elms[i].className == 'floatSearchRow'){
                            if(first == null) first = elms[i];
                            last=elms[i];
                            if(found == 1){ elms[i].className = 'floatSearchRow floatSearchSelected'; found = 0;}
                        }
                        else if(elms[i].className == 'floatSearchRow floatSearchSelected'){
                            if(first == null) first = elms[i];
                            last=elms[i];
                            elms[i].className = 'floatSearchRow'; found = 1;
                        }
                    }
                    if(found==1 && last!=null) last.className = 'floatSearchRow floatSearchSelected';
                    if(found==-1 && first!=null) first.className = 'floatSearchRow floatSearchSelected';
                    break;
                case 13://enter
                    var elms = this.search_result_container.getElementsByTagName("*");
                    for(var i = 0; i < elms.length; ++i) {
                       if(elms[i].className == 'floatSearchRow floatSearchSelected'){
                           if(elms[i].onclick != null) elms[i].onclick.apply(this,new Array(e));
                           elms[i].className = 'floatSearchRow';
                           return false;
                        }
                    }
                    return false;
                    break;
                }
            }.bind(this));

            input.addEvent('keyup', function(e) {
                switch((window.event) ? window.event.keyCode : e.event.keyCode){
                case 13: case 16: case 17: case 18: case 19: case 37: case 38: case 39: case 40: break;
                default: this.searchTimeout();
                }
            }.bind(this));

            input.addEvent('mousemove', function(e) {
                var elms = this.search_result_container.getElementsByTagName("*");
                for(var i = 0; i < elms.length; ++i) {
                    if(elms[i].className == 'floatSearchRow floatSearchSelected')
                        elms[i].className = 'floatSearchRow';
                }
            }.bind(this));
        }.bind(this));

    },

    searchTimeout: function() {
        // If the search timer exists, clear it and then set it again
        try { clearTimeout(this.quick_search_timer); } catch (err) { };
            this.quick_search_timer = setTimeout(function() { this.search(); }.bind(this), 500);
    },

    search: function() {
        var value = $(this.options.field_name + this.id).value;
        if (value != '') {
            new Request.HTML({
              url : this.options.url,
              data: this.options.data.toQueryString() + '&query=' + $(this.options.field_name + this.id).value + '&fn=' + this.options.fn + '&callback=' + this.options.callback,
              update: this.search_result,
              onComplete: function() {
                if ($type(this.options.onsearch) == 'function')
                        this.options.onsearch();
                this.search_result_container.show();
              }.bind(this)
            }).get();
        }
    },

    stopSearch: function() {
          if ($type(this.options.onsearch) == 'function')
                  this.options.oncancel();
      this.search_result_container.hide();
    }
});

/*
 * END Legacy Quick Search
 */


Sentral.AutoGrow = new Class({
  Implements: [Options],

  options: {
    min_height: 15,
    max_height: 300,//-1 for no maximum hight
    extra_padding: 15,
    timer_timeout: 10
  },

  textarea: null,
  text_size: null,
  timer: null,

  initialize: function(textarea, options) {
    this.setOptions(options);
    this.textarea = textarea;

    // Attach the AutoGrower to the elements onkeyup event
    window.addEvent('domready', function() {
        $(this.textarea).addEvent('keydown', this.pollTimer.bind(this));
        this.growElement(this);
    }.bind(this));

    // Attach this to the window onresize event
    // IF statement prevents IE6 from carking it
    if (!Browser.Engine.trident || Browser.Engine.version > 4)
        window.addEvent('resize', this.growElement.bind(this));
  },

  pollTimer: function() {
    // Starts the timer to grow the element, this allows for a lot of typing without constantly growing
    // the textarea
    try {
      clearTimeout(this.timer);
    } catch(e) {

    }

    this.timer = setTimeout(this.growElement.bind(this), this.options.timer_timeout);
  },

  growElement: function() {
    // Grows the element to the size of the text inside it

    // Create a div with the text in it if one is not already present
    if (!this.text_size) {
      this.text_size = new Element('div', {
          'text'    : $(this.textarea).value + ' ',
          'styles'  : {
              'position'    : 'absolute',
              'background-color' : 'white',
              'width'   : $(this.textarea).getScrollSize().x + 'px',
              'visibility'  : 'hidden',
              'padding' : '0 3px',
              'font-size' : $(this.textarea).style.fontSize,
              'font-weight' : $(this.textarea).style.fontWeight,
              'font-family' : $(this.textarea).style.fontFamily
            }
        }).inject($(this.textarea), 'after');
      if (Browser.Engine.trident && Browser.Engine.version <= 5)
          this.text_size.setStyle('word-wrap', 'break-word');
      else
          this.text_size.setStyle('white-space', 'pre-wrap');
    } else {
      var textwidth = parseInt($(this.textarea).getScrollSize().x)-6;
      if (textwidth < 0)
          textwidth = 0;
      this.text_size.setStyle('width', textwidth + 'px');
      this.text_size.set('text', $(this.textarea).value + ' ');
    }

    // Get the height of this div and make sure it is within the minimum and maximum dimensions
    var text_height = this.text_size.getDimensions().height + this.options.extra_padding + 10;
    if(this.options.max_height != -1) text_height = text_height.limit(this.options.min_height, this.options.max_height);
    else if(text_height < this.options.min_height) text_height = this.options.min_height;

    // Set the size of the textarea to be the text height
    if (text_height != $(this.textarea).getScrollSize().y) {
      $(this.textarea).setStyle('height', text_height + 'px');
    }
  }

});

/* Sentral Options */
Sentral.Options = new Class();

Sentral.Options.add = function(element, quantity) {
  // Adds an element to the selected options if it is not already added
  var element = $(element);

  // The first option is never added
  if (element.selectedIndex == 0)
    return false;

  var container_element = element.getParent();

  var already_selected = false;

  // Find any currently selected items and if we already have this as a selected element then we don't
  // need to continue
  container_element.getElements('span.sentral-options-selected').each(function(selected_element) {
    if (selected_element.getElement('input').value == element.value)
      already_selected = true;
    });

  if (!already_selected) {
    // This element is not in the list of selected elements so add it
    var new_selection = new Element('span', {
      'class' : 'sentral-options-selected'
    }).inject(container_element, 'bottom');

    container_element.appendText(' ');

    var new_selection_input = new Element('input', {
      'type'  : 'hidden',
      'value' : element.value,
      'name'  : element.options[0].value + '[]'
    }).inject(new_selection);

    // Show quantity input if more than 1
    if (quantity > 1) {
      new_selection.set('style', 'padding: 5px; line-height: 26px;');

      var quantity_select = new Element('select', { 'name': element.options[0].value + '_quantity[]' }).inject(new_selection, 'bottom');
      for (x = 1; x <= quantity; x++)
        new Element('option', { 'value': x }).inject(quantity_select, 'bottom').set('text', x);
    }

    new_selection.appendText(' ' + element.options[element.selectedIndex].label);

    var new_selection_remove = new Element('a', {
      'events'  : {
        'click' : function() { Sentral.Options.remove(this); return false; }
      },
      'href'    : '#'
    }).inject(new_selection, 'bottom');

    new_selection_remove.set('text', 'x');
  }

  // Set the selection box back to the beggining
  element.selectedIndex = 0;

}

Sentral.Options.remove = function(element) {
  // Removes an element for the selected options
  var element = $(element);

  var container_element = element.getParent();

  container_element.dispose();
}

/* Sentral MultiSelect */
Sentral.MultiSelect = new Class();

Sentral.MultiSelect.click = function(element) {
  // A Multiselect Button was clicked
  var element = $(element);

  if (!element.hasClass('selected')) {
    // This element is not currently selected
    $(element).getElement('input').checked = true;
    $(element).getParent().getElements('label').each(function(item) {item.removeClass('selected');});
    $(element).addClass('selected');
  }

}


//
// Sentral Calendar Element
//

var CalendarId = $H();

Sentral.Calendar = new Class({
    Implements: [Options],

    options: {
      containerId: null,
      style: null,
      date: new Date().getTime()
    },

    initialize: function(name, parent, options) {
      this.name = name;
      this.parent = parent;
      this.setOptions(options);

      // If the name passed is an array, e.g. "var[]", set an incremental
      // integer for the id to keep it unique
      if (this.name.match(/\[\]/g) == '[]') {
        // Set an index for this name if it doesn't exist
        if (CalendarId[this.name] === undefined)
            CalendarId[this.name] = 1;

        this.id = this.name.replace(/\[\]$/, CalendarId[this.name]);

        // Increment the id integer for the next calendar element
        CalendarId[this.name]++;
      } else
          this.id = this.name;

      this.add();
    },

    add: function() {
      var date = Date.parse(this.options.date);

      var containerDiv = new Element('div', { 'class': 'date_div',
                                     'id': this.options.containerId,
                                     'style': this.options.style
      }).inject(this.parent);

      var date_span = new Element('span', { 'id': this.id }).inject(containerDiv);
      date_span.setText(date.getDate() + '/' + (date.getMonth()+1) + '/' + date.getFullYear() + ' ');

      var img = new Element('img', { 'src': '/_common/images/icons/symbols/calendar16.png',
                                     'style': 'width: 16px; height: 16px;',
                                     'alt': 'Choose Date'
      }).inject(containerDiv);
      img.addEvent('click', function(el, id) { showCalendar(el, id); }.pass([img, this.id]));

      new Element('input', { 'type': 'hidden',
                             'id': this.id + '_input',
                             'name': this.name,
                             'value': date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate()
      }).inject(this.parent);
    }
});

var EditRows = $H();
Sentral.EditRow = new Class({
    Implements: [Options],

    options: {
        deleteButton: false,
        reloadOnSave: false,
        reloadOnDelete: false,
        removeCallback: false,
        url: document.location.href
    },

    initialize: function(name, url, options) {
        if (EditRows.getLength() == 0)
            this.firstRow = true;

        EditRows[name] = this;

        this.setOptions(options);
        this.items = [];

        this.request = new Request({
            'url': this.options.url,
            'method': 'post',
            onRequest: function() { this.saving = true; }.bind(this),
            onSuccess: function(response) {
                this.saving = false;

                if (response == 'OK') {
                    if ((this.options.reloadOnSave && !this.deleting) || (this.options.reloadOnDelete && this.deleting))
                        window.location.reload();

                    this.activeItem.saveOk();
                    this.activeItem.doActions();
                    this.activeItem = this.queuedItem;
                    this.queuedItem = null;
                } else {
                    this.activeItem.saveFail();
                    this.activeItem.doActions();
                }
            }.bind(this)
        });

        window.addEvent('domready', function(name) { this.domInitialize(name); }.bind(this).pass(name));
    },

    domInitialize: function(name) {
        var items = $$(name);

        // Create new object of EditRowItem and add it to the array
        var id = 1;
        items.each(function(el) {
            var item = new Sentral.EditRowItem(id, el, this, { 'deleteButton': this.options.deleteButton });
            this.items[id] = item;
            id++;
        }, this);

        // Create instance of Sentral Tips
        new Sentral.Tips();

        // Save active rows when user clicks away from element
        if (this.firstRow) {
            // Cycle through each row object and call the save method
            // to ensure that clicking away saves all edited rows
            document.addEvent('click', function() {
                EditRows.each(function(row) {
                    row.doActions();
                });
            });
        }
    },

    doActions: function() {
        if (this.activeItem)
            this.activeItem.doActions();
    },

    // this.queuedItem is used when AJAX is being fired.
    // Otherwise there is a race condition as to who finishes
    // first. If setActiveItem finishes first then the AJAX
    // will retoggle the new element, not the old one
    setActiveItem: function(item) {
        if (item.isActive())
            this.activeItem = null;
        else if (this.saving)
            this.queuedItem = item;
        else
            this.activeItem = item;
    },

    remove: function() {
        delete this.items[this.activeItem.id];
    }
});

Sentral.EditRowItem = new Class({
	Implements: [Options],

    initialize: function(id, el, parent, options) {
        this.id = id;
        this.container = el;
        this.parent = parent;
        this.status = 'ok';
		this.setOptions(options);

        var view = el.getChildren('.view');
        this.view = view[0];

        var edit = el.getChildren('.edit');
        this.edit = edit[0];

        // Correct the size of any text input
        this.edit.getElements('input[type=text]').each(function(input) {
            input.setStyle('width', '80%');
        });

        if (this.options.deleteButton)
            this.deletebtn = new Element('img', {
                'src': '/_common/images/icons/symbols/delete12.png',
                'title': 'Delete',
                'style': 'width: 12px; height: 12px; float: right; padding: 3px;'
            }).inject(this.edit, 'top');

        // Setup tooltip
        this.container.addClass('tips');
        this.container.setProperty('title', 'Edit Row::Double click this item to edit');

        // Set indicator symbols on end of container
        this.okIcon = new Element('span', { 'style': 'float: right; opacity: 0; filter:alpha(opacity=0); display: none; padding: 3px;' }).inject(this.container, 'top');
        new Element('img', { 'src': '/_common/images/icons/symbols/tick12.png' }).inject(this.okIcon);
        this.okIcon.set('tween', { property: 'opacity', duration: 600 });

        this.failIcon = new Element('span', { 'style': 'float: right; opacity: 0; filter:alpha(opacity=0); display: none; padding: 3px;' }).inject(this.container, 'top');
        new Element('img', { 'src': '/_common/images/icons/symbols/warning12.png' }).inject(this.failIcon);
        this.failIcon.set('tween', { property: 'opacity', duration: 600 });

        this.editIcon = new Element('span', { 'style': 'float: right; opacity: 0.5; filter:alpha(opacity=50); padding: 3px;' }).inject(this.container, 'top');
        new Element('img', { 'src': '/_common/images/icons/symbols/edit12.png' }).inject(this.editIcon);
        this.editIcon.set('tween', { property: 'opacity', duration: 400 });

        // Set effects for container
        this.container.addEvent('mouseover', function() { this.get('tween').start(0.5, 1); }.bind(this.editIcon));
        this.container.addEvent('mouseout', function() { this.get('tween').start(0.5); }.bind(this.editIcon));
        this.container.setStyle('cursor', 'pointer');

        // Hide edit container
        this.edit.setStyle('display', 'none');

        // Toggle View / Edit
        this.container.addEvent('dblclick', function() { this.doActions(); return false; }.bind(this));

        // Capture single click to stop unnecessary save() calls
        this.container.addEvent('click', function() { return false; });

        // Delete item
        if (this.options.deleteButton)
            this.deletebtn.addEvent('click', function() { this.remove(); }.bind(this));
    },

    doActions: function() {
        if (!this.isActive() && this.parent.activeItem)
            this.parent.activeItem.doActions();

        if (this.isActive() && this.getValue() != this.cacheValue)
            this.save();
        else
            this.toggle();
    },

    toggle: function() {
        this.view.toggle();
        this.edit.toggle();
        this.editIcon.toggle();

        // Toggle status icons
        if (this.status == 'fail') {
            this.failIcon.style.display = this.editIcon.style.display;
            this.okIcon.hide();
        } else {
            this.okIcon.style.display = this.editIcon.style.display;
            this.failIcon.hide();
        }

        // Save current values as query string for comparison on save()
        // This allows us to cater for any number of fields in the container
        this.cacheValue = this.getValue();

        // Toggle active item that is set in parent
        this.setActive();
    },

    save: function() {
        // Save edit string via AJAX
        this.parent.request.send(this.getValue());

        // Update cache value
        this.cacheValue = this.getValue();
    },

    saveOk: function() {
        this.status = 'ok';

        this.okIcon.show();
        this.okIcon.get('tween').start(0, 1).chain(
            function() { setTimeout(function() { this.start(1, 0); }.bind(this), 3000); },
            function() { this.set('display', 'none'); }
        );

        // Update View string
        var display = this.edit.getChildren('.display');
        if (display[0].get('tag') == 'select')
            var value = display[0].options[display[0].selectedIndex].get('text');
        else
            var value = display[0].get('value');
        this.view.set('text', value);

        // Run Callback
        if (this.saveCallback)
            eval('this.' + this.saveCallback + '()');
    },

    saveFail: function() {
        this.status = 'fail';

        this.failIcon.show();
        this.failIcon.get('tween').start(0, 1);
    },

    remove: function() {
        if (confirm('Are you sure you want to delete this item?')) {
            new Element('input', { 'type': 'hidden', 'name': 'deleteRow', 'value': 'true' }).inject(this.edit);
            this.saveCallback = 'removeCallback';
            this.parent.deleting = true;
            this.save();
        }
    },

    removeCallback: function() {
        this.container.remove();
        this.parent.remove();
    },

    isActive: function()    { return (this.parent.activeItem && this.parent.activeItem.container == this.container); },
    setActive: function()   { this.parent.setActiveItem(this); },
    getValue: function()    { return this.edit.getValues().toQueryString(); }
});
