/**
 * Author: Herve Menager
 * Organization:'Biological Software and Databases' Group, Institut Pasteur, Paris.
 * Distributed under GPLv2 Licence. Please refer to the COPYING.LIB document.
 */

Array.prototype.isArray = true;
// this piece of code lets you iterate synchronously/asynchronously with a function
// over a list
// stolen and extended from "http://blog.jcoglan.com/2010/08/30/the-potentially-asynchronous-loop/"
Array.prototype.asyncEach = function(iterator, callback) {
  var list = this,
      n = list.length,
      i = -1,
      calls = 0,
      looping = false;

  var iterate = function() {
    calls -= 1;
    i += 1;
    if (i === n) {
      if (callback) callback();
      return;
    }
    iterator(list[i], resume);
  };

  var loop = function() {
    if (looping) return;
    looping = true;
    while (calls > 0) iterate();
    looping = false;
  };

  var resume = function() {
    calls += 1;
    if (typeof setTimeout === 'undefined') loop();
    else setTimeout(iterate, 1);
  };
  resume();
};

if (!('console' in window)) {
    window.console = {};
}
var names = ['log', 'debug', 'info', 'warn', 'error', 'assert', 'dir', 'dirxml', 'group', 'groupEnd', 'time', 'timeEnd', 'count', 'trace', 'profile', 'profileEnd'];
for (var i = 0; i < names.length; ++i) {
    if (!(names[i] in console))
      window.console[names[i]] = function() {
    };
}

Effect.DefaultOptions.duration = 0.2;

Form.encodingMethods = {
  // these methods are a workaround to the limitations of IE6 (at least) which does not understand the enctype property

  setMultipartEncoding: function(form, options) {
    if (Prototype.Browser.IE) {
      form.encoding = 'multipart/form-data';
    }else {
      form.enctype = 'multipart/form-data';
    }
  },

  setUrlEncoding: function(form, options) {
    if (Prototype.Browser.IE) {
      form.encoding = 'application/x-www-form-urlencoded';
    }else {
      form.enctype = 'application/x-www-form-urlencoded';
    }
  }

};
Object.extend(Form, Form.encodingMethods);

/**
 * @class Feed is a class that downloads/updates JSON Data from a given URL
 *        and stores it.
 */
var Feed = Class.create({

    initialize: function(url) {
        this.url = url;
        this.data = new Object();
        this._datastr = '';
        this.getEnabled = true;
        // kept for comparison sake
        document.observe('item:remove',
        function(e) {
            if (e.memo.feed == this) {
                this.get();
            }
        }
        .bind(this));
        this.callbacks = new Array();
    },

    get: function(callback) {
      if (callback) {this.callbacks.push(callback)}
      if (this.getEnabled) {
        this.getEnabled = false;
        updateProgressReports();
        $$('.refresh_link').invoke('addClassName', 'disabled');
        new Ajax.Request(this.url, {
            method: 'get',
            onSuccess: function(transport) {
                this._process(transport);
                while (this.callbacks.length > 0) {
                	var cb = this.callbacks.shift();
                	cb();
				}
                $$('.refresh_link').invoke('removeClassName', 'disabled');
            }.bind(this),
            onComplete: function() {
              this.getEnabled = true;
            }.bind(this)
        });
      }
    },

    _process: function(transport) {
        if (this._datastr != transport.responseText) {
            this._datastr = transport.responseText;
            var response = this._datastr.evalJSON();
            this.previousData = this.data;
            this.data = response;
            document.fire('feed:update/' + this.url, {});
        }
    }
    
});

var updateProgressReports = function() {
  $$('.progressReport').each(
    function(el) {
      new Ajax.Request(el.readAttribute('data-url'),
        {
           method: 'get',
           onSuccess: function(transport) {
             $(el).value = transport.responseText;
           },
           onComplete: function(transport) {
             this.scrollTop = 999999;
           }.bind(el)
        }
      );
    }
  );
};

/**
 * @class FeedView is a class that provides a view to a Feed object which is actually a list
 */
var FeedView = Class.create({

    data: null,
    property: null,
    feed: null,

    initialize: function(el) {
        this.el = $(el);
        this.onUpdate();
        document.observe('feed:update/' + this.feed.url,
        function(e) {
            this.onUpdate();
        }.bind(this));
    },

    onUpdate: function() {
        //this.el.setLoading();
        this.data = this.extract(this.feed.data);
        this.build();
        //this.el.unsetLoading();
    },

    check: function() {

        },

    build: function() {
        },

    extract: function(data) {
        if (this.path) {
            this.path.split('.').each(function(property) {
                data = data[property];
            });
        }
        return data;
    }
});

/**
 * @class ListView
 */
var ListView = Class.create(FeedView, {

    data: $H(),
    filterFn: null,
    sortFn: null,
    htmlProperties: $A([]),

    extract: function($super, data) {
        var data = $H($super(data));
        this.htmlProperties.each(function(prop) {
            data.each(function(item) {
                if (item.value[prop]) {
                    item.value[prop] = item.value[prop].escapeHTML();
                }
            });
        });
        if (this.filterFn && data) {
            data.each(function(pair) {
                if (!this.filterFn(pair)) {
                    data.unset(pair.key);
                }
            }
            .bind(this));
        }
        if (this.sortFn) {
            data = data.sortBy(this.sortFn);
        }
        if (!data) {
            return $H();
        }
        else {
            return data;
        }
    },

    check: function() {
        }

});


// user-editable part, wraps and enhances scriptaculous' InPlaceEditor
// el: element that contains the editable text
// url: edition server callback
// title: title attribute that should override scriptaculous "click to edit"
// parameters: parameter string that finishes with "[name of the parameter for new value]="
var UserEditable = function(el, url, title, parameters) {
    var editButton = Builder.node('button', {className: 'user-editable'},'edit').hide();
    el.insert({after: editButton});
    var to;
    el.observe('mouseover', function(e) {
        if (to) window.clearTimeout(to);
        editButton.show();
        });
    el.observe('mouseout', function(e) {
        to = setTimeout(function() {editButton.hide()},1000);
        });
    new Ajax.InPlaceEditor(el, url, {
      callback: function(form, value) { return parameters + encodeURIComponent(value)},
      clickToEditText: title,
      externalControl: editButton,
      externalControlOnly: editButton,
      rows: 1,
      onComplete: function() {editButton.hide();}
    });
};

var LinksList = Class.create(ListView, {

	editUrl: null,

    build: function() {
        // remove previous options
        this.el.immediateDescendants().invoke('remove');
        // add html list items
        if (this.data) {
      	  this.el.show();
          this.data.each(function(item) {
            var a = new Element('a', {
              'href': this.liHrefT.evaluate(item).gsub('/', '--'),
              'title': this.liTitleT.evaluate(item),
              'class': this.liClassT.evaluate(item)
              }).update(this.liTextT.evaluate(item));
            if (this.liDataPrefixT) {a.setAttribute('data-prefix', this.liDataPrefixT.evaluate(item))}
            var li = new Element('li').update(a);
            this.el.appendChild(li);
            if (this.editUrl) {
              UserEditable(a, this.editUrl, this.liTitleT.evaluate(item), 'id=' + item.key + '&userName=');
            }
            }.bind(this));
        }
        var containerAndTitle = $A([this.el.up('dd'),this.el.up('dd').previous('dt')]);
        if(this.data.size()==0){
          containerAndTitle.invoke('hide');
        }else{
          containerAndTitle.invoke('show');
        }
    }

});

/**
 * @class BookmarkLinksList is a View of the list of files It displays the list of
 *        files in an HTML unordered list
 *
 * @extends LinksList
 */
var BookmarkLinksList = Class.create(LinksList, {

    liHrefT: new Template('#data::#{key}'),
    // to add support for bookmark tabs, add "::#{key}"
    liTitleT: new Template('#{value.title}'),
    liDataPrefixT: new Template('#{value.bioTypes} #{value.dataType}'),
    liClassT: new Template('link'),
    liTextT: new Template('#{value.userName}'),
    path: 'data',
	editUrl: 'data_rename.py',

    sortFn: function(data) {
        return data.value.dataType + data.value.userName;
    }

});

/**
 * @class JobLinksList is a View of the list of jobs It displays the list of
 *        jobs in an HTML unordered list
 *
 * @extends LinksList
 */
var JobLinksList = Class.create(LinksList, {
    filterFn: function(pair) {
      return !pair.value.owner;
    },
    liHrefT: new Template('#jobs::#{key}'),
    liTitleT: new Template('status: #{value.status}'),
    liClassT: new Template('link jobstatus #{value.status}'),
    liTextT: new Template('#{value.userName}'),
    path: 'jobs',
	editUrl: 'session_job_rename.py',

    build: function($super) {
      // add the remote server name to the displayed job name if not local
      if (this.data) {
        this.data.each(function(item) {
          if (item.value.server != 'local') {
            item.value.programName += '@' + item.value.server;
          }
         });
      }
      // then proceed...
      $super();
    },

    sortFn: function(job) {
        return job.value.jobSortDate;
    }

});

/**
 * @class WorkflowsLinksList is a View of the list of 
 *        user workflows. It displays the list of files
 *        in an HTML unordered list
 *
 * @extends LinksList
 */
var WorkflowsLinksList = Class.create(LinksList, {

    liHrefT: new Template('#forms::#{key}'),
    liTitleT: new Template('#{value.id}'),
    liClassT: new Template('link'),
    liTextT: new Template('#{value.id}'),
    path: 'workflows',

    sortFn: function(data) {
        return data.id;
    }

});

/**
 * @class DataTable is a View of the Data class It displays
 *        filtered elements of a list as an HTML table It is the equivalent to a
 *        View in a classical MVC
 *
 */
var DataTable = Class.create(ListView, {

	editColumns: $H({}),

    build: function() {
        // remove previous options
        this.el.childElements().invoke('remove');
        // table headers
        var tr = document.createElement('tr');
        this.columns.each(function(column) {
            var th = document.createElement('th');
            th.innerHTML = column.key;
            tr.appendChild(th);
        }
        .bind(this));
        this.el.appendChild(tr);
        // table body (data)
        if (this.data) {
            this.data.each(function(item) {
                var tr = document.createElement('tr');
                this.el.appendChild(tr);
                this.columns.each(function(column) {
                    var td = document.createElement('td');
                    td.innerHTML = column.value.evaluate(item);
                    var valueEl = td.down();
                    tr.appendChild(td);
                    var editUrl = this.editColumns.get(column.key);
                    if (editUrl) {
                        UserEditable(valueEl, editUrl, '', 'id=' + item.key + '&userName=');
                    }
                }
                .bind(this));
            }
            .bind(this));
        }
    }

});

var checkAllCheckBoxes = function(selector,value) {
  $$(selector).each(function(el) {el.checked = value;});
};

/**
 * @class BookmarksTable is a view of the list of files stored in a
 *        MobyleSession. Additionally, it provides control (removal, renaming)
 *        over these data
 * @extends DataTable
 */
var BookmarksTable = Class.create(DataTable, {

    filter: null,

	editColumns: $H({
        'Name': 'data_rename.py'
	}),

    columns: $H({
        'Name': new Template('<a class="link" href="#data::#{key}">#{value.userName}</a>'),
        'Type': new Template('#{value.bioTypes} #{value.dataType}'),
        'Format': new Template('#{value.format}'),
        'Size': new Template('#{value.size} KiB'),
        'Text': new Template('#{value.title}'),
        '<label><input name="remove_all" type="checkbox" onclick="checkAllCheckBoxes(\'.data_item\',this.checked);" />select all/none</label>': new Template('<input type="checkbox" class="data_item" name="id" value="#{key}" />')
    }),
    path: 'data',
    htmlProperties: $A(['title']),

    initialize: function($super, el) {
        $super(el);
        this.el.observe('submit',
        function(e) {
            e.stop();
            new Ajax.Request(e.element().action, {
                method: 'post',
                postBody: e.element().serialize(),
                onCreate: e.element().disable.bind(e.element()),
                onComplete: function() {
                    document.fire('item:remove', {
                        feed: user.workspace
                    });
                }
            });
        });
    },

    sortFn: function(data) {
        return data.value.dataType + data.value.userName;
    }

});

/**
 * @class JobsTable is a view of the list of jobs stored in a
 *        MobyleSession. Additionally, it provides control (removal)
 *        over these jobs
 * @extends DataTable
 */
var JobsTable = Class.create(DataTable, {

	editColumns: $H({
        'Name': 'session_job_rename.py'
	}),

    filterFn: function(pair) {
      return !pair.value.owner;
    },
    columns: $H({
        'Name': new Template('<a class="link" href="#jobs::#{key}">#{value.userName}</a>'),
        'Identifier': new Template('<a class="link" href="#jobs::#{key}">#{value.simple_id}</a>'),
        'Date': new Template('<a class="link" href="#jobs::#{key}">#{value.jobDate}</a>'),
        'Status': new Template('<span class="jobstatus #{value.status}">#{value.status}</span>'),
        'Message': new Template('#{value.status_message}'),
        '<label><input name="remove_all" type="checkbox" onclick="checkAllCheckBoxes(\'.job_item\',this.checked);" />select all/none</label>': new Template('<input type="checkbox" class="job_item" name="id" value="#{key}" />')
    }),
    path: 'jobs',
    htmlProperties: $A(['title']),

    initialize: function($super, el) {
        $super(el);
        this.el.observe('submit',
        function(e) {
            e.stop();
            new Ajax.Request(e.element().action, {
                method: 'post',
                postBody: e.element().serialize(),
                onCreate: e.element().disable.bind(e.element()),
                onComplete: function() {
                    document.fire('item:remove', {
                        feed: user.workspace
                    });
                }
            });
        });
        this.build = this.build.wrap(function(callOriginal) {
                        if (this.data) {
                          this.data.each(function(item) {
                            item.value.simple_id = item.value.jobID.split('.').last();
                            item.value.name_and_id = item.value.userName + item.value.simple_id;
                          });
                        }
                        callOriginal();
                     }.bind(this));
        $('sortJobsBy').observe('change', function() {
          this.onUpdate();
        }.bind(this));
    },

    sortFn: function(job) {
        return job.value[$('sortJobsBy').value];
    }

});


Data = {};

Data.View = Class.create({

    view_id: 0,

    initialize: function(source, url, extract, path) {
        this.view_id = Data.View.view_id++;
        this.source = source;
        this.url = url;
        this.extract = extract;
        this.path = path;
        this.build();
        if (this.url) {
            document.observe('feed:update/' + this.url, this.build.bindAsEventListener(this));
        }
    },

    build: function(e) {
        this.data = this.source;
        if (this.path) {
            this.path.split('.').each(function(property) {
                this.data = this.data[property];
            }.bind(this));
        }
        this.data = this.extract(this.data);
        if (this.url) {
            document.fire('view:build/' + this.url + '/' + this.view_id);
        }
    }
});

Data.extract = function(property, filter, sort) {
    return function(data) {
        if (property) {
            property.split('.').each(function(pitem) {
                data = data[pitem];
            }.bind(this));
        }
        data = $H(data);
        if (filter) {
            data.each(function(pair) {
                if (!filter(pair)) {
                    data.unset(pair.key);
                }
            });
        }
        data = sort ? data.sortBy(sort) : data;
        return data ? data : $H();
    }
};

Builder.dataSelect = function(view, tpls, attrs) {
    var sel = Builder.node('select', attrs);
    sel.selectedIndex = -1;
    var build = function() {
        // backing up current selected entry before rebuilding it
        var selEntryBackup = sel.selectedIndex && sel.selectedIndex != -1 ? sel.options[sel.selectedIndex] : null;
        // disable and hide control if no data is to be shown
        sel.style.visibility = view.data.size() == 0 ? 'hidden' : 'visible';
        // remove previous options
        sel.immediateDescendants().invoke('remove');
        // default blank value
        sel.insert(Builder.node('option', {'selected': 'selected'}));
        // other options
        view.data.each(function(item) {
            sel.insert({
                'bottom': Builder.node('option', {
                    'value': tpls['value'].evaluate(item),
                    'title': tpls['title'].evaluate(item)
                },
                tpls['text'].evaluate(item))
            });
        });
        if (selEntryBackup) {
            $A(sel.options).each(function(opt) {
                if (opt.value == selEntryBackup.value) {
                    opt.selected = true;
                }
            }.bind(this));
        }else {
            sel.selectedIndex = -1;
        }
        sel.fire('component:build');
    }
    build();
    if (view.url) {
        document.observe('view:build/' + view.url + '/' + view.view_id,
        function(e) {
            build();
        }.bind(this));
    }
    return sel;
};

Builder.list = function(view, root, buildItemFn) {
    var build = function() {
        root.immediateDescendants().invoke('remove');
        view.data.each(function(item) {
            root.insert(buildItemFn(item));
        });
        root.fire('component:build');
    }
    build();
    if (view.url) {
        document.observe('view:build/' + view.url,
        function(e) {
            build();
        }.bind(this));
    }
    return root;
};

Builder.historySelect = function(pdef, userMode, control) {
    var getFilter = function() {
        return function(bookmark) {
            var bookmark = bookmark.value;
            if (filterPipe(bookmark.dataType, bookmark.format, bookmark.bioTypes, pdef.datatype, pdef.formats, pdef.biotype, true)) {
                return bookmark.userModes.include(userMode);
            }
            return false;
        }
    }
    var sb = Builder.node('input', {
        type: 'button',
        value: 'select'
    });
    var view = new Data.View(user, user.workspace.url, Data.extract('workspace.data.data', getFilter()));
    var ds = Builder.dataSelect(view, {
        'value': new Template('#{key}'),
        'title': new Template('#{value.userName} first line: #{value.title}'),
        'text': new Template('#{value.userName}')
    },
    {
        id: pdef.program + '::' + pdef.name + '::' + userMode + '::history'
    });
    var el = Builder.node('span', [ds, sb]);
    var build = function() {
        sb.style.visibility = ds.style.visibility;
    };
    build();
    ds.observe('component:build', build);
    var load = function() {
        if (ds.value != '') {
            // call to history
            new Ajax.Request('data_get.py', {
                method: 'get',
                parameters: {
                    'id': ds.value
                },
                onSuccess: function(r) {
                    pdef.json_load(control, r.responseText);
                },
                onComplete: function() {
                    ds.selectedIndex = 0;
                    ds.enable();
                }
            });
        }
    }
    sb.observe('click', load);
    sb.observe('databox:history_load', load);
    return el;
};

var filterPipe = function(sourceDatatype, sourceFormat, sourceBiotypes, destDatatype, destFormats, destBiotypes, allowConversions) {
    var sourceDatatypeHierarchy = portal.properties.datatypes[sourceDatatype];
    if (sourceDatatypeHierarchy && sourceDatatypeHierarchy.ancestorTypes.include(destDatatype)) {
        if (sourceBiotypes == null || destBiotypes == null ||
        sourceBiotypes.size() == 0 || destBiotypes.size() == 0 ||
        sourceBiotypes.any(function(sourceBiotype) {
            return destBiotypes.include(sourceBiotype);
        })
        ) {
            if (destFormats.include(sourceFormat) || destFormats.size() == 0 || sourceFormat == null) {
                // formats identical
                return true;
            } else if (allowConversions && portal.properties.conversions.any(function(conversion) {
                return sourceDatatypeHierarchy.ancestorTypes.include(conversion.dataType) &&
                conversion.fromFormat == sourceFormat && destFormats.include(conversion.toFormat);
            })) {
                // formats compatible using converters
                return true;
            }
        }
    }
    // any incompatibility case
    return false;
};

Builder.pipesSelect = function(datatype, biotypes, format, insertElement) {
    var getFilter = function() {
        return function(input) {
            var input = input.value;
            return filterPipe(datatype, format, biotypes, input.dataTypeClass, input.dataTypeFormats, input.bioTypes, true);
        }
    }
    var sb = Builder.node('input', {
        type: 'submit',
        value: 'further analysis'
    });
    sb.observe('click', function(ev) {
    	var form = ev.element().up('form');
    	form.callType.value = 'further';
    }.bindAsEventListener(this));
    var sort = function(item) {
        return item.value.programName.toLowerCase() + item.value.name.toLowerCase();
    }
    var view = new Data.View(portal.properties, null, Data.extract(null, getFilter(), sort), 'program_workflow_inputs');
    var ds = Builder.dataSelect(view, {
        'value': new Template('#{value.programPID}|#{value.name}'),
        'title': new Template('#{value.programTitle} - #{value.prompt}'),
        'text': new Template('#{value.programName} (#{value.name})')
    },
    {
        name: 'next',
        style: 'width: 15em;'
    });
    var el = Builder.node('span', [insertElement, ds, sb]);
    var build = function() {
        el.style.visibility = ds.style.visibility;
    };
    build();
    ds.observe('component:build', build);
    return el;
};

Builder.uploadClient = function(pdef, control) {
    var file = Builder.node('input', {
        type: 'file',
        name: 'data_input_upload'
    });
    var datatype = Builder.node('input', {
        type: 'hidden',
        name: 'datatype_class',
        value: pdef.datatype,
        className: 'protected'
    });
    var datatype_superclass = Builder.node('input', {
        type: 'hidden',
        name: 'datatype_superclass',
        value: pdef.datatypeSuperClass,
        className: 'protected'
    });
    var target = $('data_upload_target');
    var el = Builder.node('span', {
        className: 'upload'
    },
    [file, datatype, datatype_superclass]);
    var onLoaded = function() {
        file.value = '';
        var iframeDoc;
        if (window.frames && window.frames.data_upload_target &&
        (iframeDoc = window.frames.data_upload_target.document)) {
            var iframeBody = iframeDoc.body;
            var txt = iframeBody.innerText ? iframeBody.innerText : iframeBody.textContent;
        }
        pdef.json_load(control, txt);
        target.stopObserving('load');
    }
    var upload = function() {
        var form = el.up('form');
        disList = form.select('select', 'input', 'textarea').filter(function(el) {
            return ! el.disabled;
        });
        disList.invoke('disable');
        file.enable();
        datatype.enable();
        datatype_superclass.enable();
        target.observe('load', onLoaded);
        var actionBck = form.getAttribute('action');
        var targetBck = form.getAttribute('target');
        var methodBck = form.getAttribute('method');
        form.setAttribute('action', 'data_upload.py');
        form.setAttribute('target', target.identify());
        form.setAttribute('method', 'POST');
        Form.setMultipartEncoding(form);
        form.submit();
        form.setAttribute('action', actionBck);
        disList.invoke('enable');
        delete form['target'];
        form.setAttribute('method', methodBck);
        Form.setUrlEncoding(form);
    }
    file.observe('change', upload);
    return el;
};

Builder.dbSelect = function(pdef, control) {
    var getFilter = function() {
        return function(bank) {
            bank = bank.value;
            if (bank.dataType == pdef.datatype) {
                // datatype filter
                if (pdef.biotype == null ||
                bank.bioTypes == null ||
                // biotype(s) filter
                pdef.biotype.blank &&
                pdef.biotype.blank() ||
                bank.bioTypes.blank &&
                bank.bioTypes.blank() ||
                pdef.biotype.size &&
                pdef.biotype.size() == 0 ||
                bank.bioTypes.size &&
                bank.bioTypes.size() == 0 ||
                bank.bioTypes.include(pdef.biotype) ||
                pdef.biotype.findAll &&
                pdef.biotype.findAll(function(bt) {
                    if (bank.bioTypes.include(bt))
                    return bt;
                }).size() >
                0) {
                    return true;
                }
            }
            return false;
        }
    }
    var id = Builder.node('input', {
        type: 'text'
    });
    var sb = Builder.node('input', {
        type: 'button',
        value: 'select'
    });
    var view = new Data.View(portal.properties, null, Data.extract(null, getFilter()), 'banks');
    var ds = Builder.dataSelect(view, {
        'value': new Template('#{key}'),
        'title': new Template('#{value.label}'),
        'text': new Template('#{key}')
    },
    {});
    var el = Builder.node('span', [ds, id, sb]);
    var build = function() {
        id.style.visibility = ds.style.visibility;
        sb.style.visibility = ds.style.visibility;
    };
    build();
    ds.observe('component:build', build);
    sb.observe('click',
    function(e) {
        if (ds.value != '' && id.value != '') {
            portal.startWaiting();
            // call to history
            new Ajax.Request('bank_get.py', {
                method: 'get',
                parameters: {
                    'db': ds.value,
                    'id': id.value
                },
                onSuccess: function(r) {
                    pdef.json_load(control, r.responseText);
                },
                onComplete: function() {
                    portal.stopWaiting();
                    ds.selectedIndex = -1;
                    id.value = '';
                    ds.enable();
                }
            });
        }
    });
    return el;
};

/**
 * @class DataSelect is a View of the Data class It displays
 *        filtered elements of a list as an HTML select input It is the
 *        equivalent to a View in a classical MVC
 *
 */
var DataSelect = Class.create(ListView, {

    cpEls: [],

    build: function() {
        // backing up current selected entry before rebuilding it
        if (this.el.selectedIndex && this.el.selectedIndex != -1) {
            var selEntryBackup = this.el.options[this.el.selectedIndex];
        }
        // set visibility for select, label and button
        if (this.data.size() == 0) {
            this.el.disable();
            this.cpEls.each(function(el) {
                el = $(el);
                el.hide();
                Form.Element.disable(el);
            });
        }
        else {
            this.el.enable();
            this.cpEls.each(function(el) {
                el = $(el);
                el.show();
                Form.Element.enable(el);
            });
        }
        // remove previous options
        this.el.immediateDescendants().invoke('remove');
        // default (blank) option
        this.el.insert({
            'bottom': Builder.node('option', {
                'value': ''
            },
            '< >')
        });
        // add html options
        this.data.each(function(item) {
            this.el.insert({
                'bottom': Builder.node('option', {
                    'value': this.optionValue.evaluate(item),
                    'title': this.optionTitle.evaluate(item)
                },
                this.optionText.evaluate(item))
            });
        }
        .bind(this));
        if (selEntryBackup && !selEntryBackup.value.blank()) {
            // only restore an entry if one had been selected...
            this.selectEntry(selEntryBackup.value, true);
        }
    },

    selectEntry: function(value, silentChange) {
        $A(this.el.options).each(function(option) {
            if (option.value == value) {
                option.selected = true;
                if (!silentChange) {
                    if (this.el && this.el.onchange) {
                        this.el.onchange();
                    }
                    if (this.submitEl && this.submitEl.onclick) {
                        this.submitEl.onclick();
                    }
                }
            }
        }
        .bind(this));
    }

});

var FilesGauge = Class.create(ListView, {

    path: 'data',

    initialize: function($super, el) {
        this.gaugeSize = parseFloat($('sessionLimit').value);
        $super(el);
    },

    build: function() {
        // loading the data from the model
        this.level = 0.0;
        if (this.data) {
            this.data.each(function(item) {
                this.level = this.level + parseFloat(item.value.size);
            }
            .bind(this));
        }
        this.usage = this.level / this.gaugeSize * 100;
        this.el.style.width = this.usage + '%';
    }

});

var User = Class.create({

    initialize: function() {
        this.workspace = new Feed('session_workspace.py');
        this.updateFromCookie();
    },

    setEmail: function(email) {
        editCookieValue('email', email);
        this.updateFromCookie();
    },

    updateFromCookie: function() {
        if (document.cookie.toString() == '') {
            $('nocookie').show();
        }
        else {
            $('nocookie').hide();
        }
        var sessionKey = readCookieValue('sessionKey');
        if (sessionKey != this.sessionKey) {
            this.sessionKey = sessionKey;
            document.fire('user:sessionKey');
            this.workspace.get();
            if (typeof _gaq != 'undefined') { // GA set session key
              _gaq.push(['_setCustomVar',
                    1,
                    'session_key',
                    this.sessionKey,
                    3
                 ]);
            }
        }
        var email = readCookieValue('email');
        if (email != this.email) {
            this.email = email;
            document.fire('user:email');
            $('userEmail').innerHTML = this.email;
            if (typeof _gaq != 'undefined') { // GA set e-mail
              _gaq.push(['_setCustomVar',
                    2,
                    'e-mail',
                    this.email,
                    3
                 ]);
            }
        }
        var activated = (readCookieValue('activated') == 'True');
        if (activated != this.activated) {
            this.activated = activated;
            document.fire('user:activated');
            if (!this.activated) {
                $$("[href='#user::activate']").each(function(aEl) {aEl.up().show();});
                $('userEmail').addClassName('inactive');
                $('userStatus').addClassName('inactive');
            }
            else {
                $$("[href='#user::activate']").each(function(aEl) {aEl.up().hide();});
                $('userEmail').removeClassName('inactive');
                $('userStatus').removeClassName('inactive');
            }
            if (typeof _gaq != 'undefined') { // GA set session activation state
              _gaq.push(['_setCustomVar',
                    3,
                    'session_activation_state',
                    this.activated,
                    3
                 ]);
            }
        }
        var authenticated = (readCookieValue('authenticated') == 'True');
        if (authenticated != this.authenticated) {
            this.authenticated = authenticated;
            document.fire('user:authenticated');
            if (this.authenticated) {
                $$("[href='#user::signin']").each(function(aEl) {aEl.up().hide();});
                $('userStatus').innerHTML = '(registered)';
                if ($('userOpenIdsignInOpen')) {
                    $('userOpenIdsignInOpen').up().hide();
                }
            }
            else {
                $$("[href='#user::signin']").each(function(aEl) {aEl.up().show();});
                $('userStatus').innerHTML = options['anonymousSession'] ? '(guest)' : '(register to submit)';
                if ($('userOpenIdsignInOpen')) {
                    $('userOpenIdsignInOpen').up().show();
                }
            }
            if (typeof _gaq != 'undefined') { // GA set session type
              _gaq.push(['_setCustomVar',
                    4,
                    'session_type',
                    this.authenticated ? 'registered' : 'guest',
                    3
                 ]);
            }
        }
    }

});

var methods = $H({
    'forms': new Template('form.py?id=#{id}'),
    'jobs': new Template('job_view.py?id=#{id}'),
    'data': new Template('data_view.py?id=#{id}'),
    'tutorials': new Template('#{id}'),
    'viewer': new Template('viewer_view.py?#{parameters}')
});

// list of non-databox types
var simpleDatatypes = ['Choice', 'MultipleChoice', 'Boolean', 'Integer', 'Float', 'String', 'Filename', 'MultipleText'];

var Databox = Class.create({

    initialize: function(paramEl) {
        this.paramEl = paramEl;
        this.form = paramEl.up('form');
        this.name = paramEl.readAttribute('data-parametername');
        this.input = paramEl.select('[name="' + paramEl.readAttribute('data-parametername') + '"]')[0];
        this.idx = 0;
        this.datatype = paramEl.readAttribute('data-datatype');
        this.datatypeSuperClass = paramEl.readAttribute('data-datatype-superclass');
        if (paramEl.readAttribute('data-ismultiple') == 'true') {
          var existing_databoxes = this.existingDataboxes();
          if (existing_databoxes.size() != 0) {
            prvIdx = parseInt(existing_databoxes.collect(function(databoxId) {return databoxId.split('[')[1].split(']')[0];}).max());
            this.idx = prvIdx + 1;
            this.input = paramEl.select('[name="' + this.name + '[' + prvIdx.toPaddedString(4) + ']"]')[0];
          }
          this.name += '[' + this.idx.toPaddedString(4) + ']';
          this.datatype = this.datatype.replace('Multiple', '');
          this.datatypeSuperClass = this.datatypeSuperClass.replace('Multiple', '');
        }else {
        }
        this.program = this.form.readAttribute('id');
        this.biotype = paramEl.readAttribute('data-biotype').strip().split(' ').without('');
        this.formats = paramEl.readAttribute('data-formats').strip().split(' ').without('');
        this.control = this.form[this.name];
        this.databoxType = this.input.type && this.input.type == 'file' ? 'binary' : 'text';
        if (this.databoxType == 'binary') {
            this.input.type = 'text';
        }
        var ip = this.input.clone();
        ip.value = this.input.value;
        ip.setAttribute('class', 'databox_paste');
        ip.setAttribute('name', this.name);
        ip.observe('change', onFormParameterModification);
        var id = this.input.clone();
        //id.disable();
        id.setAttribute('readonly', 'readonly');
        id.setAttribute('class', 'databox_db');
        id.setAttribute('name', this.name);
        var iu = this.input.clone();
        iu.disable();
        //iu.setAttribute('readonly','readonly');
        iu.setAttribute('class', 'databox_upload');
        iu.setAttribute('name', this.name);
        var ir = this.input.clone();
        ir.disable();
        //ir.setAttribute('readonly','readonly');
        ir.setAttribute('class', 'databox_result');
        ir.setAttribute('name', this.name);
        var irp = Builder.node('input', {
            type: 'hidden',
            className: 'bookmark_ref databox_paste',
            name: this.name + '.ref'
        });
        irp.store('databox', this);
        var ird = irp.clone();
        ird.setAttribute('class', 'bookmark_ref databox_db');
        ird.store('databox', this);
        var iru = irp.clone();
        iru.setAttribute('class', 'bookmark_ref databox_upload');
        iru.store('databox', this);
        var irr = irp.clone();
        irr.setAttribute('class', 'bookmark_ref databox_result');
        irr.store('databox', this);
        var inp = Builder.node('input', {
            type: 'hidden',
            name: this.name + '.name'
        });
        var ind = inp.clone();
        var inu = inp.clone();
        var inr = inp.clone();
        var hsp = Builder.historySelect(this, 'paste', {
            'ref': irp,
            'value': ip,
            'name': inp
        });
        var hsd = Builder.historySelect(this, 'db', {
            'ref': ird,
            'value': id,
            'name': ind
        });
        var hsu = Builder.historySelect(this, 'upload', {
            'ref': iru,
            'value': iu,
            'name': inu
        });
        var hsr = Builder.historySelect(this, 'result', {
            'ref': irr,
            'value': ir,
            'name': inr
        });
        var dbs = Builder.dbSelect(this, {
            'ref': ird,
            'value': id,
            'name': ind
        });
        var upu = Builder.uploadClient(this, {
            'ref': iru,
            'value': iu,
            'name': inu
        });
        var pastePanel = Builder.node('div', [Builder.node('table', {
            style: 'width: 100%;'
        },
        Builder.node('tbody', [Builder.node('tr', [Builder.node('td', ['Enter your data below:']), Builder.node('td', {
            },
        hsp)])])), ip, irp, inp]);
        var dbPanel = Builder.node('div', [Builder.node('table', {
            style: 'width: 100%;'
        },
        Builder.node('tbody', [Builder.node('tr', [Builder.node('td', dbs), Builder.node('td', {
            },
        hsd)])])), id, ird, ind]);
        var uploadPanel = Builder.node('div', [Builder.node('table', {
            style: 'width: 100%;'
        },
        Builder.node('tbody', [Builder.node('tr', [Builder.node('td', upu), Builder.node('td', {
            },
        hsu)])])), iu, iru, inu]);
        var resultPanel = Builder.node('div', [Builder.node('table', {
            style: 'width: 100%;'
        },
        Builder.node('tbody', [Builder.node('tr', [Builder.node('td', {
            },
        hsr)])])), ir, irr, inr]);
        var choices = $H({
            'paste': pastePanel,
            'db': dbPanel,
            'upload': uploadPanel,
            'result': resultPanel
        });
        var def = 'paste';
        if (this.databoxType == 'binary') {
            def = 'upload';
            choices.unset('paste');
        }
        var pairs = $H(choices).collect(function(choice) {
            var inputModeAttrs = {
                    type: 'radio',
                    name: this.name + '.mode',
                    value: choice.key
              };
            if (choice.key == def) {
              inputModeAttrs.checked = true;
            }
            var pair = {
                'choice': Builder.node('li',
                            {className: 'menu ' + (choice.key == def ? 'selected' : '')},
                            Builder.node('label', [choice.key, Builder.node('input', inputModeAttrs)])),
                'content': Builder.node('div', {
                             style: choice.key == def ? 'display:visible' : 'display:none'
                             },
                           choice.value)
            };
            pair.choice.writeAttribute('data-choicefor', pair.content.identify());
            var build = function() {
                // "build" disables a choice if it contains only invisible or disabled inputs
                if (pair.content.select('input', 'select', 'textarea').detect(function(el) {
                    return ((el.style.visibility == 'visible' || !el.style.visibility) && !el.disabled && !el.readOnly && !(el.type == 'hidden'));
                })) {
                    pair.choice.show();
                }
                else {
                    pair.choice.hide();
                }
            };
            build();
            pair.content.observe('component:build', build);
            return pair;
        }.bind(this));
        if (this.databoxType == 'binary') {
            this.ebtn = '';
        } else {
            this.ebtn = Builder.node('button', 'EDIT');
            // edit button
            this.ebtn.observe('click',
            function(e) {
                var value = this.el.select('[name="' + this.name + '"]').pluck('value').join('');
                pairs[0].choice.fire('databox:switch');
                ip.value = value;
            }.bind(this));
        }
        this.cbtn = Builder.node('button', 'CLEAR');
        // clear button
        this.cbtn.observe('click',
        function(e) {
            e.stop();
            this.clear();
        }.bind(this));
        this.rbtn = '';
        if (paramEl.readAttribute('data-ismultiple') == 'true') {
          this.rbtn = Builder.node('button', {className: 'databox_remove_button'},'REMOVE');
          this.rbtn.observe('click', function(e) {
            e.stop();
            this.remove();
          }.bind(this));

          this.paramEl.observe('multiple_parameter:add_remove', function(e) {
            this.manageRbtn();
          }.bind(this));
        }
        this.paramEl.observe('form:reset', function(e) {
          if (this.existingDataboxes().size() > 1) {
            this.remove();
          }else {
            this.clear();
          }
        }.bind(this));
        var handles = Builder.node('ul', {
            className: 'handlesList'
        },
        [pairs.pluck('choice'), Builder.node('li', {
            className: 'ctrl menu'
        },
        [this.ebtn, this.cbtn, this.rbtn])]);
        handles.observe('click', this.choose.bind(this));
        handles.observe('databox:switch', this.choose.bind(this));
        var panels = Builder.node('div', {
            className: 'panelsList'
        },
        pairs.pluck('content'));
        databoxContents = [handles, panels];
        this.el = Builder.node('div', {'data-databoxname': this.name, 'data-databoxforparametername': this.paramEl.readAttribute('data-parametername'), 'data-databoxindex': this.idx}, databoxContents);
        this.el.identify();
    },

    existingDataboxes: function() {
      // get the list of databox elements corresponding to the same parameter
      return this.paramEl.select('[data-databoxname]').invoke('readAttribute', 'data-databoxname');
    },

    manageRbtn: function() {
      if (this.existingDataboxes().size() == 1) {
        this.rbtn.disable();
      }else {
        this.rbtn.enable();
      }
    },

    remove: function() {
      // remove the current databox (unless it's the last one for this parameter)
      if (this.existingDataboxes().size() > 1) {
        this.el.remove();
      }
      this.paramEl.fire('multiple_parameter:add_remove');
    },

    choose: function(e) {
        var h = e.findElement().hasAttribute('data-choicefor') ? e.findElement() : e.findElement().up('li[data-choicefor]');
        if (h) {
            var p = $(h.readAttribute('data-choicefor'));
            // "choose" shows a choice and hides and resets values of all the others
            h.down('input').checked = 'true';
            h.addClassName('selected');
            h.siblings().invoke('removeClassName', 'selected');
            p.show();
            p.siblings().invoke('hide');
            this.clear();
        }
        e.stop();
    },

    clear: function() {
        var els = this.el.select('li[data-choicefor]');
        var targets = els.collect(function(el) {return $(el.readAttribute('data-choicefor'));});
        targets.invoke('select', 'select', 'input', 'textarea').flatten().each(function(input) {
            if (!input.hasClassName('protected')) {
                input.value = input.defaultValue;
                onFormParameterModification(input);
            }
        });
        targets.invoke('select', 'input[type="hidden"]').flatten().each(function(input) {
            if (!input.hasClassName('protected')) {
                input.value = '';
                //hidden inputs do not have a significant default value, we assume it is blank...
            }
        });
        if ($(this.ebtn)) {
          this.ebtn.disabled = false;
        }
        //this.paramEl.removeClassName('modified');
    },

    json_load: function(target, responseText) {
        target.value.up('div').removeAllMessages();
        var txt = responseText.strip();
        if (txt != '') {
            if (txt.isJSON()) {
                user.workspace.get();
                response = txt.evalJSON();
                if (response.content != null) {
                    this.clear();
                    if ($(this.ebtn) && response.headFlag == 'HEAD') {
                      this.ebtn.disabled = true;
                    }else {
                      this.ebtn.disabled = false;
                    }
                    if (this.databoxType == 'binary') {
                      target.value.value = response.userName;
                    } else {
                      target.value.value = response.content;
                    }
                    if(target && response.safeFileName) {target.ref.value = response.safeFileName;}
                    if(target && response.userName) { target.name.value = response.userName;}
                    this.paramEl.addClassName('modified');
                    // fire event that tells "simple form display" may have to adapt
                    this.paramEl.fire('form:parameter_load');
                }
                if (response.errormsg) {
                    //target.ref.retrieve('databox').el.setMessage(response.errormsg);
                    target.value.up('div').setMessage(response.errormsg);
                }
            }
            else {
                target.value.up('div').setMessage('request failed, please check your data');
            }
        }
    }

});

var Filebox = Class.create({

    initialize: function(dataEl, options) {
        this.options = options ? options : {
                'position': 'after',
                'components': ['FullScreen']
            };
        this.dataEl = dataEl;
        this.metaEl = dataEl.up('[data-datatype]') || this.dataEl;
        this.name = this.metaEl.readAttribute('data-parametername');
        this.datatype = this.metaEl.readAttribute('data-datatype');
        this.datatypeSuperclass = this.metaEl.readAttribute('data-datatype-superclass');
        this.biotypes = this.metaEl.readAttribute('data-biotype').split(' ').without('');
        this.format = this.metaEl.readAttribute('data-format').split(' ');
        this.inputModes = this.metaEl.readAttribute('data-inputmodes').split(' ');
        this.src = this.dataEl.readAttribute('data-src');
        this.fileName = this.dataEl.readAttribute('data-filename');
        this.components = $A();
        // components requested in options
        this.options.components.each(
          function(componentName) {
            this.components.push(this['build' + componentName]());
          }.bind(this));
        var iD = $H();
        iD.set(this.options.position, Builder.node('div', {
            className: 'controlbox'
        },
        [this.components]));
        this.dataEl.insert(iD.toObject());
    },

    buildFullScreen: function() {
      // return full screen button
      return Builder.node('button', {onclick: 'window.open("' + this.src + '","_blank")'}, ['full screen']);
    },

    buildWidgets: function() {
      // return the list of suitable widgets (viewers)
        var dataSrc = this.src;
        var view = new Data.View(portal.properties, null, Data.extract('viewer_inputs', this.pipeFilter.bind(this)));
        return Builder.list(view, Builder.node('span'),
          function(item) {
              var vHref = '#viewer::program=' + item.value.programPID + '&parameter=' + item.value.name + '|' + dataSrc;
              return Builder.node('a', {
                  href: vHref,
                  className: 'modalLink',
                  'data-viewername': item.value.programPID
              },
              [Builder.node('button', [item.value.programName])]);
          });
    },

    buildBookmarkPipeCommon: function() {
      // return bookmark and pipe common elements button
      var inputModeInputs = this.inputModes.collect(
    	function(im) {
        	return Builder.node('input', {type: 'hidden', name: 'inputModes', value: im});
    	});
      var r = [Builder.node('input', {type: 'hidden', name: 'safeFileName', value: this.fileName
        }), inputModeInputs, Builder.node('input', {type: 'hidden', name: 'parameter', value: this.name
        }), Builder.node('input', {type: 'hidden', name: 'callType', value: 'bookmark'
        }), Builder.node('input', {type: 'hidden', name: 'datatype_class', value: this.datatype
        }), Builder.node('input', {type: 'hidden', name: 'datatype_superclass', value: this.datatypeSuperclass
        }),
        this.biotypes.collect(function(str) {
          return Builder.node('input', {
              type: 'hidden',
              name: 'biotype',
              value: str
          });
        }),
        this.format.collect(function(str) {
          return Builder.node('input', {
              type: 'hidden',
              name: 'format',
              value: str
          });
        })];
      var jobEl = this.dataEl.up('.job');
      if (jobEl) {
          var jobid = jobEl.readAttribute('data-jobid');
        r.push(Builder.node('input', {type: 'hidden', name: 'job', value: jobid}));
      }
      return r;
    },

    buildBookmark: function() {
      // return the bookmark form
        this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
        return Builder.node('form', {
                action: 'data_bookmark.py',
                className: 'bookmark'
            },
            [this.buildBookmarkPipeCommon(),
            Builder.node('input', {
                type: 'submit',
                value: 'bookmark'
            }), ' as ', Builder.node('input', {
                type: 'text',
                name: 'userName',
                size: '8',
                value: this.fileName
            })]);
    },

    buildPipes: function() {
      // return the pipes selection form
        this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
        return Builder.node('form', {
                className: 'bookmark further'
            },
            [this.buildBookmarkPipeCommon(),
             this.pipesSelect]);
    },

    buildBookmarkPlusPipes: function() {
      // return the bookmark and/or pipes selection controls in a single form
        this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
        return Builder.node('form', {
                action: 'data_bookmark.py',
                className: 'bookmark further'
            },
            [this.buildBookmarkPipeCommon(),
            Builder.node('input', {
                type: 'submit',
                value: 'bookmark'
            }), ' as ', Builder.node('input', {
                type: 'text',
                name: 'userName',
                size: '8',
                value: this.fileName
            }), ' or ',
            this.pipesSelect]);
    },

    buildWidgetBookmarkPlusPipes: function() {
      var userFileName = this.fileName;
      if (this.metaEl.readAttribute('data-extension')) {
        userFileName += '.' + this.metaEl.readAttribute('data-extension');
      }
      // return the bookmark and/or pipes selection controls in a single form
        this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
        var bn = Builder.node('form', {
                className: 'bookmark further',
                action: 'data_upload.py'
            },
            [
            Builder.node('em', [this.metaEl.readAttribute('data-prompt')]),
            Builder.node('textarea', {'style': 'display: none;', 'name': 'data_input_upload'}),
            this.buildBookmarkPipeCommon(),
            Builder.node('input', {
                type: 'submit',
                value: 'bookmark'
            }), Builder.node('input', {
                type: 'hidden',
                name: 'base64encoded',
                value: this.metaEl.readAttribute('data-base64encoded')
            }), ' as ', Builder.node('input', {
                type: 'text',
                name: 'data_input_upload.name',
                size: '8',
                value: userFileName
            }), ' or ',
            this.pipesSelect]);
        bn.onOk = function(data) {
          portal.stopWaiting();
          var msgText = 'Your data ('+ data.userName + ") is now accessible <a href='#data::" + data.safeFileName + "'><strong>here</strong></a>";
          bn.insert({'before': msgText});
        }
        bn.observe('submit',
          function() {
            bn.data_input_upload.value = eval(this.metaEl.readAttribute('data-portcode'));
          }.bindAsEventListener(this));
        return bn;
    },

    buildRemoveBookmark: function() {
      // return the remove bookmark button
      return Builder.node('a', {href: '#user::bookmarkremove::' + this.fileName, className: 'modalLink'},
                Builder.node('button', {type: 'button'}, ['remove bookmark'])
              );
    },

    buildRenameBookmark: function() {
      // return the rename bookmark ??
    },

    build: function() {
        //this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
    },

    pipeFilter: function(input) {
        var input = input.value;
        return filterPipe(this.datatype, this.format, this.biotypes, input.dataTypeClass, input.dataTypeFormats, input.bioTypes, false);
    }

});

var JobStatus = Class.create({

    initialize: function(initMessage) {
      this.lastMessage = initMessage;
      this.pid = initMessage.pid;
      this.id = initMessage.id;
      this.delay = 2.5;
      this.times = 0;
      this.maxTimes = 4;
      portal.startWaiting();
      portal.hideAllModal();
      dhtmlHistory.add('jobs::' + this.pid);
      // setting the dhtmlHistory here is hacky, but it allows to directly display
      // the job status upon refresh.
      if (this.running()) {
        portal.startWaiting();
        this.trigger();
      }else {
        this.show();
      }
    },

    running: function() {
      return ['submitted', 'pending', 'running'].include(this.lastMessage.status);
    },

    trigger: function() {
      if (this.times == this.maxTimes) {
        this.show();
      }else {
        setTimeout(this.check.bind(this), this.delay * 1000);
        this.times += 1;
      }
    },

    show: function() {
      user.workspace.get();
      portal.go('jobs::' + this.pid);
    },

    check: function() {
      new Ajax.Request(
        'job_status.py', {
          method: 'GET',
          parameters: $H({'jobId': this.id}),
          onSuccess: function(resp) {
            this.lastMessage = resp.responseText.evalJSON();
            if (this.running()) {
              this.trigger();
            }else {
              this.show();
            }
          }.bind(this)}
      );
    }

});

/**
 * tabMethods is the set of methods that manage tabbed panels manipulation
 */
var tabMethods = {

    /**
   *
   * @param {Element}
   *            element container.
   * @param {Data}
   * insert a tabs container inside any given container
   *            ajaxData [optional] the list to which this tabsContainer registers as a view.
   */
    insertTabsContainer: function(element) {
        if (!$(element).down('.handlesList')) {
            $(element).insert({
                'bottom': "<ul class='handlesList'></ul><div class='panelsList'></ul>"
            });
        }
    },

    /**
   * insert a tab in a given tabs container.
   *
   * @param {Element}
   *            element the element containing the tabs (handles+panels).
   * @param {String}
   *            id the id of the tab to be created.
   * @param {String}
   *            label the label of the tab to be created (displayed in the
   *            handle).
   * @param {boolean}
   *            close indicates wether or not a close control should be
   *            created.
   */
    insertTab: function(element, id, label, close, handleClassName) {
        if (! ($(element).down('ul.handlesList') &&
        $(element).down('div.panelsList'))) {
            $(element).insertTabsContainer();
        }
        // create tab html if it does not exist
        if (!$(id)) {
            // insert handle
            $(element).down('ul.handlesList').__insertHandle(id, label, close, handleClassName);
            // insert panel
            return $(element).down('div.panelsList').__insertPanel(id);
        }else {
          return $(id);
        }
        //$('ms-' + id).showTab();
    },

    /**
   * insert the html for a tabbed panel
   *
   * @param {Element}
   *            element the tab panels container.
   * @param {String}
   *            id the id of the created tab.
   */
    __insertPanel: function(element, id) {
        return $(element).insert(Builder.node('div', {
            className: 'tabPanel',
            id: 'ms-' + id
        }));
    },

    /**
   * insert the html for a tab handle
   *
   * @param {Element}
   *            element the tab handles container.
   * @param {String}
   *            id the id of the tab to be created.
   * @param {String}
   *            label the label of the tab to be created (displayed in the
   *            handle).
   * @param {boolean}
   *            close indicates wether or not a close control should be
   *            created.
   * @param {className}
   *            className is the value of the class HTML attribute for the handle.
   */
    __insertHandle: function(element, id, label, close, className) {
        var closeLink = close ? Builder.node('a', {
            href: '#' + id,
            className: 'closeTab',
            title: 'close this tab'
        },
        'x') : '';
        var handle = Builder.node('li', {className: 'menu ' + className},
        [Builder.node('a', {
            href: '#' + id,
            className: 'link'
        },
        label), closeLink]);
        return $(element).insert(handle);
    },

    updateHandleLabel: function(element, id, label) {
        $$('li a.link[href="#' + id + '"]').invoke('update', label);
    },

    /**
   * display a tab
   *
   * @param {Element}
   *            element the panel of the element that should be displayed.
   */
    showTab: function(element) {
        if ($(element).hasClassName('tabPanel')) {
            var handleId = $$(
            '.handlesList .link[href="#' + element.id.substr(3) + '"]',
            '.handlesList .link[href="' + location.href.split('#').first() + '#' + element.id.substr(3) + '"]'
            //this line is for ie7 compatibility
            )[0];
            var element = $(handleId);
        }
        else {
            throw Error('please ask for a tab (' + element.id + ')');
        }
        //element.style.backgroundImage="url(/portal/css/../images/add_stat.gif)";
        //if (element.next()) {element.next().style.backgroundImage="url(/portal/css/../images/add_stat.gif)";}
        var liEl = $(element.up('li'));
        liEl.siblings().each(function(el) {
            if (el.down('a.link')) {
                //el.down('a').style.backgroundImage="";
                //if (el.down('a').next()) {el.down('a').next().style.backgroundImage="";}
                el.removeClassName('selected');
                var panelId = el.down('a').readAttribute('href').split('#').last();
                $('ms-' + panelId).hide();
            }
        });
        liEl.addClassName('selected');
        var panel = $('ms-' + element.readAttribute('href').split('#').last());
        panel.show();
        panel.setStyle({borderTopColor: element.up('li').getStyle('backgroundColor')});
        // recursively show the parent tabs of the shown tab
        if ($(element).up('.tabPanel') && !panel.up('.tabPanel').visible()) {
            $(element).up('.tabPanel').showTab();
        }
        if (panel.down('ul.handlesList') && panel.down('ul.handlesList').down('li')) {
            if (!panel.down('ul.handlesList').down('li.selected')) {
                panel.down('.tabPanel').showTab();
            }
        }
    },

    /**
   * hides an element this method overrides prototypejs', because display:none
   * only sometimes is not always sufficient to hide a panel in IE
   *
   * @param {Element}
   *            element the element to hide.
   */
    hide: function(element) {
        $(element).style.display = 'none';
        $(element).style.visibility = 'hidden';
        return element;
    },

    /**
   * show an element this method overrides prototypejs', because display:none only
   * sometimes is not always sufficient to hide a panel in IE
   *
   * @param {Element}
   *            element the element to show.
   */
    show: function(element) {
        $(element).style.display = '';
        $(element).style.visibility = '';
        return element;
    },

    setLoading: function(element) {
        $(element).addClassName('loading');
    },

    unsetLoading: function(element) {
        $(element).removeClassName('loading');
    },

    /**
   * remove a tab
   *
   * @param {String}
   *            id the id of the element that should be removed.
   */
    removeTab: function(element) {
        var id = $(element).id;
        var tab = $$(
        '.handlesList a[href="#' + id.substr(3) + '"]',
        '.handlesList a[href="' + location.href.split('#').first() + '#' + id.substr(3) + '"]'
        ).first().up('li');
        if (tab.match('.selected')) {
            var nextTab = tab.next() ? tab.next() : tab.previous();
            if (nextTab) {
                var nextId = nextTab.down('a.link').readAttribute('href').substr(1);
                portal.go(nextId);
            }
        }
        element.remove();
        tab.remove();
    }

};

/**
 * formMethods is the set of methods that manage form-related operations
 */
var formMethods = {
    /**
   * sets an error message corresponding to the element
   *
   * @param {element}
   *            element the DOM element.
   * @param {message}
   *            message the message to display.
   */
    setMessage: function(element, message, formSummary) {
        var id = element.identify();
        var m = Builder.node('div', {
            className: 'errormsg',
            'data-messagefor': id
        },
        message);
        if ((element.tagName == 'FORM')) {
          element.insert({'top': m});
        }else {
          element.insert({'before': m});
        }
        if (formSummary) {
            var s = Builder.node('div', {
                className: 'errormsg'
            },
            [element.readAttribute('data-parametername'),
            ':',
            message.clone(true)]);
            element.up('form').insert({
                'top': s
            });
        }
    },

    /**
   * removes every error message corresponding to the control
   *
   * @param {element}
   *            element the DOM element.
   */
    removeMessage: function(element) {
        if (element.id) {
            $$('[data-messagefor="#' + element.id + '"]').invoke('remove');
        }
    },

    /**
     * removes every error message contained in the element's descendants
     *
     * @param {element}
     *            element the DOM element.
     */
    removeAllMessages: function(element) {
        element.select('.errormsg').invoke('remove');
    },

    /**
     * submits the form using the AJAX wrapper
     *
     * @param {form}
     *            element the DOM form element.
     */
    _submit: function(form) {
        var action = form.readAttribute('action');
        if (form.hasClassName('program')) {
            form.onOk = function(data) {
                if (data.emailNeeded) {
                    document.observe('user:email',
                    function(e) {
                        document.stopObserving('user:email');
                        this._submit();
                    }.bind(form));
                    document.observe('user:email:cancel',
                    function(e) {
                        document.stopObserving('user:email');
                    }.bind(form));
                    portal.go('user::email');
                    return;
                }
                if (data.activationNeeded) {
                    document.observe('user:activated',
                    function(e) {
                        document.stopObserving('user:activated');
                        this._submit();
                    }.bind(form));
                    portal.go('user::activate');
                    return;
                }
                new JobStatus(data);
            }
        }
        if (form.callType && form.callType.value == 'further') {
            if (form.next && !form.next.value.blank()) {
                form.onOk = function(response) {
                	form.callType.value = 'bookmark';
                    var program = form.next.value.split('|')[0];
                    var param = form.next.value.split('|')[1];
                    var callback = function() {
                        var getFormAndLoadHistory = function() {
                            try {
                                var paramEl = $(program).select('[data-parametername="' + param + '"]')[0];
                                var isMultiple = paramEl.readAttribute('data-ismultiple') == 'true';
                                paramEl.fire('parameter:pipe', response);
                            } catch (e) {
                                logError(e);
                            }
                        }
                        user.workspace.get(getFormAndLoadHistory);
                    }
                    portal.go('forms::' + program, callback);
                }
            }
        }
        if (form.hasClassName('help')) {
           form.onOk = function(response) {
             //portal.go('welcome');
             var response = response;
             var display_sent_message = function() {
               $('help_request_confirmation_message').innerHTML = response.msg;
             }
             portal.go('user::helpsent', display_sent_message);
           }
        }
        if (action != null) {
            new Ajax.Request(action, {
                method: form.getAMethod(),
                parameters: form.serialize({hash: true}),
                onCreate: function(resp) {
                    this.removeAllMessages();
                }.bind(form),
                onFailure: function(resp) {
                    portal.stopWaiting();
                  logError(resp);
                }.bind(form),
                onSuccess: function(resp) {
                  var okCallbackEnroute = false;
                    if (resp.responseText && resp.responseText.isJSON()) {
                        data = resp.responseText.evalJSON();
                        if (data.errormsg) {
                            if (data.errorparam && this.down('[data-parametername="' + data.errorparam + '"]')) {
                                var error_msg = Builder.node('span', [data.errormsg,
                                    Builder.node('a', {className: 'modalLink', href: '#user::help::' + data.id + '::' + data.errormsg + '::' + data.errorparam}, [
                                        Builder.node('button', 'get help')])]);
                                this.down('[data-parametername="' + data.errorparam + '"]').setMessage(error_msg, true);
                            } else {
                                this.setMessage(data.errormsg);
                            }
                        }
                        else {
                            if (form.hasClassName('modal')) {
                              portal.hideAllModal();
                            }
                            if (this.onOk) {
                                this.onOk(data);
                                okCallbackEnroute = true;
                            }
                            this.onOk = null;
                        }
                    }
                  if (!okCallbackEnroute) portal.stopWaiting();
                    user.updateFromCookie();
                    // GA event tracking
                    try {
                      var gCategory = ''; gAction = ''; gLabel = '';
                      switch (action) {
                        case 'data_upload.py':
                          gCategory = 'bookmark';
                          gAction = 'upload';
                          break;
                        case 'session_job_submit.py':
                          gCategory = 'service';
                          gAction = 'run';
                          gLabel = form.readAttribute('id');
                          break;
                        case 'programs_list.py':
                          gCategory = 'services_list';
                          gAction = 'display';
                          gLabel = form.serialize();
                          break;
                        case 'data_remove.py':
                          gCategory = 'bookmark';
                          gAction = 'remove';
                          break;
                        case 'session_job_remove.py':
                          gCategory = 'job';
                          gAction = 'remove';
                          break;
                        case 'data_bookmark.py':
                          gCategory = 'bookmark';
                          gAction = 'create_from_result';
                          break;
                        case 'session_setemail.py':
                          gCategory = 'session';
                          gAction = 'set_email';
                          gLabel = user.email;
                          break;
                        case 'session_signin.py':
                          gCategory = 'session';
                          gAction = 'signin';
                          gLabel = user.sessionKey;
                          break;
                        case 'session_signout.py':
                          gCategory = 'session';
                          gAction = 'signout';
                          gLabel = user.sessionKey;
                          break;
                        case 'session_register.py':
                          gCategory = 'session';
                          gAction = 'register';
                          gLabel = user.sessionKey;
                          break;
                        case 'session_activate.py':
                          gCategory = 'session';
                          gAction = 'activate';
                          gLabel = user.sessionKey;
                          break;
                        case 'help_request.py':
                          gCategory = 'session';
                          gAction = 'ask_help';
                          gLabel = form.serialize();
                          break;
                      }
                      gTrack(gCategory, gAction, gLabel);
                    }catch (e) {
                      console.log('error while tracking form submission');
                    }
                }.bind(form)
            });
        }
        else {
            if (form.onOk) {
              form.onOk(form.serialize({
                  hash: true
              }));
              form.onOk = null;
            }
        }
    },

    /**
     * get the real HTTP method which will be used for AJAX calls
     *
     * @param {form}
     *            element the DOM form element.
     */
    'getAMethod': function(form) {
      //  in Mobyle,
      // where POST is the default
      var man = form.getAttributeNode('method');
      return (man && man.specified && man.value != '') ? man.value : 'POST';
    }

};

Element.addMethods(tabMethods);
Element.addMethods(formMethods);

var Portal = Class.create({

    initialize: function(el) {
        this.el = $(el);
        this.properties = portalProperties;
        this.filesList = new(Class.create(BookmarkLinksList, {
            feed: user.workspace
        }))('filesListUl');
        this.userWorkflowsList = new(Class.create(WorkflowsLinksList, {
            feed: user.workspace
        }))('userWorkflowsListUl');
        this.forms = $H();
        // setting up the data management page
        this.dataManagement = new(Class.create(BookmarksTable, {
            feed: user.workspace
        }))('dataManagement');
        this.dataGauge = new(Class.create(FilesGauge, {
            feed: user.workspace
        }))('sessionUsage');
        this.jobsList = new(Class.create(JobLinksList, {
            feed: user.workspace
        }))('jobsListUl');
        this.jobsManagement = new(Class.create(JobsTable, {
            feed: user.workspace
        }))('jobsManagement');
        new ToolTipManager();
        if ($('autoRefreshFreq').value > 0) {
            new PeriodicalExecuter(function() {
                user.workspace.get();
            },
            $('autoRefreshFreq').value);
        }
        // in the case of jobs, set up a workspace listener which will update or remove the job tab
        // if its status has changed or if it has been removed
        document.observe('feed:update/' + user.workspace.url,
        function() {
            $$('.job').each(function(el) {
                var jobPid = el.readAttribute('data-jobpid');
                var jobStatus = el.readAttribute('data-jobstatus');
                var jobMessage = el.readAttribute('data-jobmessage');
                var id = 'jobs::' + jobPid;
                if (!user.workspace.data.jobs[jobPid]) {
                    // remove if job no longer in the workspace
                    this.closeTab(id);
                } else {
                    if (user.workspace.previousData &&
                    user.workspace.previousData.jobs &&
                    user.workspace.previousData.jobs[jobPid] &&
                    (user.workspace.data.jobs[jobPid].status != user.workspace.previousData.jobs[jobPid].status ||
                    user.workspace.data.jobs[jobPid].status != jobStatus ||
                    user.workspace.data.jobs[jobPid].status_message != user.workspace.previousData.jobs[jobPid].status_message ||
                    user.workspace.data.jobs[jobPid].status_message != jobMessage)) {
                      var id = 'jobs::' + jobPid;
                      // update job if its status has changed
                      this._get(id, null, true);
                    }
                }
            }.bind(this));
        }.bind(this));
        // in the case of data, set up a workspace listener which will remove the data tab if it has been removed
        document.observe('feed:update/' + user.workspace.url,
        function() {
            $$('.bookmark').each(function(el) {
                var dataPid = el.readAttribute('data-bookmarkpid');
                var id = 'data::' + dataPid;
                if (!user.workspace.data.data[dataPid]) {
                    // remove if data no longer in the workspace
                    this.closeTab(id);
                } else {
                    var format = el.readAttribute('data-format');
                    var datatype = el.readAttribute('data-datatype');
                    var biotype = el.readAttribute('data-biotype');
                    var inputmodes = el.readAttribute('data-inputmodes');
                    var username = el.readAttribute('data-username');
                    if (user.workspace.data.data[dataPid].format != format ||
                    user.workspace.data.data[dataPid].dataType != datatype ||
                    user.workspace.data.data[dataPid].bioTypes != biotype ||
                    user.workspace.data.data[dataPid].userModes != inputmodes ||
                    user.workspace.data.data[dataPid].userName != username
                    ) {
                        // update bookmark if one of its properties has changed
                        this._get(id, null, true);
                    }
                }
            }.bind(this));
            $$('.bookmark_ref[value!=""]').each(function(el) {
                var dataPid = el.readAttribute('value');
                if (!user.workspace.data.data[dataPid]) {
                    // clear loaded databox value if it is no longer in the workspace
                    el.retrieve('databox').clear();
                }
            });
        }.bind(this));
        // here we process preloaded form values to load them into the requested forms
        var preload_forms = $('mobyle_load').select('input').invoke('readAttribute', 'data-servicename').uniq();
        preload_forms.each(function(service_name) {
          this.go('forms::' + service_name, function() {
            var parameter_names = $('mobyle_load').select('input[data-servicename="' + service_name + '"]').invoke('readAttribute', 'data-parametername').uniq();
            parameter_names.each(function(parameter_name) {
              var parameter_values = $('mobyle_load').select('input[data-servicename="' + service_name + '"][data-parametername="' + parameter_name + '"]').invoke('readAttribute', 'value');
              var formParam = $(service_name)[parameter_name];
              if (formParam && formParam.value != null) {
                //non-databox parameter
                formParam.value = parameter_values;
              }else {
                //databox parameter
                // switch the databox to "paste" mode
                $(service_name).select('[name="' + parameter_name + '.mode"][value="paste"]')[0].fire('databox:switch');
                // load data
                $(service_name).select('[name="' + parameter_name + '"][class="databox_paste"]')[0].value = parameter_values;
              }
            });
          });
        }.bind(this));
    },

    closeTab: function(id) {
        if ($('ms-' + id)) {
            $('ms-' + id).removeTab();
            dhtmlHistory.add(where());
        }
    },

    _get: function(id, callback, update) {
        var path = id.strip().split('::');
        var request = {
            'method': 'get',
            parameters: {},
            update: update,
            id: id
        };
        switch (path[0]) {
        case 'user':
            request.insert = function(response, request) {
                $('userPopupOverlay').insert(Builder.node('span', {
                    id: 'modalId'
                }));
                $('modalId').insert(response.responseText);
                if (Prototype.Browser['IE'] == true) {
                  $('modalId').down('.modal').insert(Builder.node('iframe', {}));
                }
            }
            switch (path[1]) {
            case 'signin':
                request.url = 'session_signin_form.py';
                break;
            case 'signout':
                request.url = 'session_signout_form.py';
                break;
            case 'register':
                request.url = 'session_register_form.py';
                break;
            case 'activate':
                request.url = 'session_activate_form.py';
                break;
            case 'email':
                request.url = 'session_email_form.py';
                break;
            case 'help':
                request.url = 'session_job_help_form.py';
                request.parameters['id'] = path[2];
                request.parameters['message'] = path[3];
                request.parameters['param'] = path[4];
                break;
            case 'helpsent':
                request.url = htDir + 'session_job_help_confirm.html';
                request.parameters['message'] = path[2];
                break;
            case 'jobremove':
                request.url = 'session_job_remove_form.py';
                request.parameters['id'] = path[2];
                break;
            case 'bookmarkremove':
                request.url = 'data_remove_form.py';
                request.parameters['id'] = path[2];
                break;
            }
            break;
        case 'welcome':
        case 'forms':
        case 'data':
        case 'jobs':
        case 'tutorials':
            request.parent = $('ms-' + path.slice(0, -1).join('::'));
            request.parameters['id'] = path.slice(1).join('::');
            request.insert = function(response, request) {
                // if an error is sent back display it as a modal window ...
                if (response.headerJSON && response.headerJSON.error) {
                    portal.stopWaiting();
                  $('userPopupOverlay').show();
                    $('userPopupOverlay').insert(Builder.node('span', {
                      id: 'modalId'
                    }));
                    $('modalId').insert(response.responseText);
                    return;
                }
                // do not insert new HTML if another XMLHTTPRequest has
                // already inserted the code (sometimes race conditions
                // are created by Firefox/RSH event handling)
                if($('ms-' + id)!=null && !update){
                    return;
                }
                // ... otherwise go on
                if (response.headerJSON && response.headerJSON.title) {
                    var title = response.headerJSON.title;
                }
                else if ($$('*[href=#"' + id + '"]')) {
                    var title = $$('*[href="#' + id + '"]')[0].title;
                }
                else {
                    var title = id;
                }
                var contents = null;
                if (! request.update) {
                  contentsId = 'ms-' + id + '-contents';
                  request.parent.insertTab(id, title, true, path[0]);
                  $('ms-' + id).insert({top: Builder.node('div', {id: contentsId})});
                  contents = $(contentsId);
                }else {
                  request.parent.updateHandleLabel(id, title);
                  contents = $$('[data-pid="' + request.parameters['id'] + '"]').first();
                }
                contents.update(response.responseText);
                contents.select('textarea').each(
                // empty textareas which contain only a whitespace (XSL limitation)
                function(el) {
                    if(el.value == ' ') {el.value = '';}
                }
                );
                // workflow graph events handling
                contents.select('.workflow_graph').each(function(objectEl) {
                    objectEl.onload = function(e) {
                      var svgdoc = e.element().contentDocument;
                      svgdoc.onclick = function(e) {
                        e = Event.extend(e);
                        var el = Event.element(e);
                        var ancs = Element.ancestors(el);
                        var el = ancs.find(function(el) {
                          if (el.tagName == 'svg:a') {
                            return el;
                          }
                        });
                        if (el) {
                          var destination = el.getAttributeNS('http://www.w3.org/1999/xlink', 'href').substr(1);
                          var jobPid = destination.split('::').last();
                          portal.go(id + '::' + jobPid);
                        }
                        Event.stop(e);
                      };
                    }.bind(this);
                }.bind(this));
            }
            switch (path[0]) {
            case 'forms':
                request.url = 'form.py';
                request.callback = function(id) {
                    if (!$('ms-' + id)) return;
                    // when a form is loaded, we create the databoxes dynamically
                    new FormParametersManager($('ms-' + id + '-contents').down('form'));
                    if (portal.properties.simple_forms_active) {
                      new FormSimpleParametersToggleManager($('ms-' + id + '-contents').down('form'));
                    }
                }
                break;
            case 'jobs':
                request.url = 'job_view.py';
                request.parameters['pid'] = path.slice(1).join('::');
                request.callback = function(id) {
                    if (!$('ms-' + id + '-contents')) return;
                    // when a job is loaded, we create the resultboxes dynamically
                    $('ms-' + id + '-contents').select('.job_results [data-filename]').each(function(el) {
                        new Filebox(el, {
                            'position': 'after',
                            'components': ['FullScreen', 'Widgets', 'BookmarkPlusPipes']
                        });
                    });
                    updateProgressReports();
                    // if the job is "imported" by opening a portal link, refresh workspace after
                    // job view added the job to it.
                    if (!user.workspace.data.jobs || !user.workspace.data.jobs[request.parameters['pid']]) {
                      user.workspace.get();
                    }
                }
                break;
            case 'data':
                request.url = 'data_view.py';
                request.callback = function(id) {
                    // when a data bookmark is loaded, we create the bookmarkboxes dynamically
                    $('ms-' + id + '-contents').select('[data-filename]').each(function(el) {
                        new Filebox(el, {
                            'position': 'before',
                            'components': ['FullScreen', 'RemoveBookmark', 'Widgets', 'Pipes']
                        });
                    });
                }
                break;
            case 'tutorials':
                request.url = 'tutorial.py';
                break;
            }
            break;
        case 'viewer':
            request.url = 'viewer_view.py';
            request.callback = function(id) {
                // when a data bookmark is loaded, we create the bookmarkboxes dynamically
                $('modalId').select('[data-viewerport]').each(function(el) {
                    new Filebox(el, {
                        'position': 'after',
                        'components': ['WidgetBookmarkPlusPipes']
                    });
                });
            }
            request.parameters = path[1].toQueryParams();
            request.insert = function(response, request) {
                var box = Builder.node('div', {
                    className: 'modal viewer'
                });
                var vHref = request.url + '?' + $H(request.parameters).toQueryString();
                box.insert(Builder.node('div', {
                    className: 'controls',
                    style: 'text-align: right; padding-top: 2px; z-index:100001;'
                },
                [
                Builder.node('a', {
                    href: vHref,
                    title: 'open in a new tab',
                    target: '_blank',
                    className: 'detachModal'
                },
                ['>']), Builder.node('a', {
                    className: 'closeModal',
                    title: 'close this view',
                    href: '#'
                },
                ['X'])]));
                box.insert(response.responseText);
                $('userPopupOverlay').insert(Builder.node('span', {
                    id: 'modalId'
                }));
                $('modalId').insert(box);
            }
            break;
        }
        if (callback) {
            if (request.callback) {
                request.callback = request.callback.wrap(function(callOriginal, id) {
                    callOriginal(id);
                    callback(id);
                });
            } else {
                request.callback = callback;
            }
        }
        if (request.url == null) {
          gTrack('error', 'report', request);
          portal.go('welcome');
          return;
        }
        new Ajax.Request(request.url, {
            method: request.method,
            parameters: request.parameters,
            onSuccess: function(response) {
                request.insert(response, request);
                if (request.callback) {
                    request.callback(request.id);
                }
            },
            onFailure: function(response) {
              portal.stopWaiting();
              callback = dhtmlHistory.add(where());
            },
            onException: function(response) {
              portal.stopWaiting();
              callback = dhtmlHistory.add(where());
            }
        });
    },

    go: function(id,callback, update) {
        var info = id.split('::');
        gTrack(info[0], 'display', info[1]);
        this.hideAllModal();
        var path = id.strip().split('::');
        var idlist = path.inject($A(), function(list, value, index) {
          if (list.length == 0) {
            list[list.length] = value;
          }else {
            list[list.length] = list[list.length - 1] + '::' + value;
          }
          return list;
        });
        idlist = idlist.without('user', 'viewer');
        idlist.asyncEach(function(id, resume) {this._go(id, resume);}.bind(this), callback);
    },

    _go: function(id, callback, update) {
          if (update || !this._exists(id)) {
              portal.startWaiting();
              if (callback) {
                  callback = callback.wrap(function(callOriginal, id) {
                      callOriginal(id);
                      portal._show(id);
                  });
              } else {
                  callback = portal._show.bind(this, id);
              }
              this._get(id, callback, update);
          }else {
              this._show(id);
              if (callback) {
                callback(id);
              }
          }
    },

    _exists: function(id, callback) {
        switch (id) {
        case 'user':
        case 'viewer':
            return false;
        default:
            return $('ms-' + id);
        }
    },

    _show: function(id, callback) {
        var path = id.split('::').without('');
        portal.stopWaiting();
        switch (path[0]) {
        case 'user':
        case 'viewer':
            $('userPopupOverlay').show();
            process_autofocus();
            return;
        default:
            if ($('ms-' + id)) {
              $('ms-' + id).showTab();
            }
        }
        dhtmlHistory.add(where());
    },

    hideAllModal: function(element) {
        if($('modalId')) {$('modalId').remove();}
        $('userPopupOverlay').hide();
    },

    startWaiting: function() {
      $('waitOverlay').show();
    },

    stopWaiting: function() {
      $('waitOverlay').hide();
    }

});

var where = function() {
    if ($('userPopupOverlay').visible()) {
        var el = $('userPopupOverlay').select('modal').detect(function(el) {
            return el.visible();
        });
        return el && el.id ? el.id.substr(3) : null;
    }
    else {
        var stateIds = $A(['user', 'welcome', 'forms', 'data', 'jobs', 'tutorials']);
        var els = $('portalMain').select('.tabPanel').findAll(function(el) {
            var parent = el.id.substr(3).split('::').first();
            return (((el.offsetHeight + el.offsetWidth) > 0) && stateIds.include(parent));
        });
        var id = els.pluck('id').sortBy(function(s) {
            return s.length;
        }).last();
        return id.substr(3);
    }
};

var ResizeHandle = Class.create({

    initialize: function(container, handle, type) {
        this.container = $(container);
        var handle = $(handle);
        /* Add property to container to store position variables */
        this.container.moveposition = {
            x: 0,
            y: 0
        };
        this.moveListener = function(event) {
            /* Calculate how far the mouse moved */
            var moved = {
                x: (event.pointerX() - this.container.moveposition.x),
                y: (event.pointerY() - this.container.moveposition.y)
            };
            /* Reset container's x/y utility property */
            this.container.moveposition = {
                x: event.pointerX(),
                y: event.pointerY()
            };

            /* Update container's size */
            var height = parseFloat(this.container.getStyle('height').gsub('px', ''));
            var width = parseFloat(this.container.getStyle('width').gsub('px', ''));
            var new_h = (!type || type == 'y') ? height + moved.y : height;
            var new_w = (!type || type == 'x') ? width + moved.x : width;
            this.container.setStyle({
                height: new_h + 'px',
                width: new_w + 'px'
            });
        }.bindAsEventListener(this);

        /* Listen for 'mouse down' on handle to start the move listener */
        handle.observe('mousedown',
        function(event) {
            /* Set starting x/y */
            this.container.moveposition = {
                x: event.pointerX(),
                y: event.pointerY()
            };
            /* Start listening for mouse move on body */
            Event.observe(document.body, 'mousemove', this.moveListener);
        }.bindAsEventListener(this));

        /* Listen for 'mouse up' to cancel 'move' listener */
        Event.observe(document.body, 'mouseup',
        function(event) {
            Event.stopObserving(document.body, 'mousemove', this.moveListener);
        }.bindAsEventListener(this));
    }
});

var ToolTipManager = Class.create({

    initialize: function() {
        this.initializeBehaviour();
    },

    initializeBehaviour: function(el) {
        document.observe('mouseover',
        function(e) {
            var element = e.element();
            // if a tooltip exists for this program, hide it and show it only
            // when mouse is over it
            if (element && typeof element.next == 'function' &&
            element.next() &&
            element.next().hasClassName('tooltip')) {
                var tooltipEl = element.next();
                this.showTooltip(this, tooltipEl);
                element.onmouseover = this.showTooltip.bindAsEventListener(this, tooltipEl);
                element.onmouseout = this.hideTooltip.bindAsEventListener(this, tooltipEl, element);
                tooltipEl.onmouseout = this.hideTooltip.bindAsEventListener(this, tooltipEl, element);
                element.onmousemove = this.moveTooltip.bindAsEventListener(this, tooltipEl);
                tooltipEl.onclick = element.onclick;
            }
        }
        .bind(this));
    },

    showTooltip: function(e, tooltipEl) {
        if (this.timeOutTooltipId) {
            clearTimeout(this.timeOutTooltipId);
        }
        this.timeOutTooltipId = setTimeout((function() {
            Element.show(tooltipEl);
        }).bind(this), 500);
    },

    hideTooltip: function(e, tooltipEl, element) {
        if (e.relatedTarget != tooltipEl && e.relatedTarget != element) {
            // do not accept this event if we leave to enter the tooltip element
            if (this.timeOutTooltipId) {
                clearTimeout(this.timeOutTooltipId);
            }
            Element.hide(tooltipEl);
        }
    },

    moveTooltip: function(e, tooltipEl) {
        if (e.currentTarget != null) {
            Element.setStyle(tooltipEl, {
                position: 'absolute',
                top: (Event.pointerY(e) - e.currentTarget.offsetParent.offsetTop) +
                'px',
                left: (Event.pointerX(e) - e.currentTarget.offsetParent.offsetLeft) +
                'px'
            });
        }
    }
});

function readCookieValue(key) {
    var regex = new RegExp(key + '\s*="?(.*?)"?(;|$)');
    var cookie = document.cookie.toString();
    var match = cookie.match(regex);
    return match ? unescape(match[1]) : '';
}

function editCookieValue(key, value) {
    var regex = new RegExp(key + '\s*="?(.*?)"?(;|$)');
    var repl = key + '=' + encodeURIComponent(value) + '';
    var cookie = document.cookie.toString();
    document.cookie = repl;
    return;
}

function readUrlParameterValue(name) {
    name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
    var regexS = '[\\?&]' + name + '=([^&#]*)';
    var regex = new RegExp(regexS);
    var results = regex.exec(window.location.href);
    if (results == null)
    return '';
    else
    return results[1];
}

//firebug logging
function logError(err) {
    console.group('Mobyle error');
    console.dir(err);
    console.trace();
    console.groupEnd();
    gTrack('error', 'report', err);
}

Ajax.Responders.register({
    // this hack forces to avoid caching throughout every ajax request,
    // because IE tends to force cache when using GET requests.
    onCreate: function(o) {
        o.options.requestHeaders = $H({
            'Cache-Control': 'post-check=1,pre-check=2,no-cache,must-revalidate',
            'If-Modified-Since': 'Sat, 1 Jan 2000 00:00:00 GMT',
            'X-MobylePortalName': portalProperties.portal_name
        });
    },
    // this traces any ajax exception that might be raised
    onException: function(req, err) {
        logError(err);
    },
    onComplete: function(response) {
        // because IE does not include dynamically loaded html in its observed code,
        // we need to re-register the form event capture each time HTML is loaded.
        init_observers();
        // refresh user space when modifying session contents...
          switch (response.url) {
          case 'data_bookmark.py':
          case 'data_remove.py':
          case 'data_rename.py':
          case 'data_upload.py':
          case 'session_activate.py':
          case 'session_captcha_check.py':
          case 'session_job_submit.py':
          case 'session_job_remove.py':
          case 'session_job_rename.py':
          case 'session_register.py':
          case 'session_setemail.py':
          case 'session_signin.py':
          case 'session_signout.py':
            user.workspace.get();
            break;
        }
    }
});

var process_autofocus = function() {
  // this function provides a patch that simulates the autofocus for browsers that do not support HTML5 autofocus attribute.
  //if (!("autofocus" in document.createElement("input"))){
      var autofoc_els = $$('*[style~="z-index:"] *[autofocus]');
      if (autofoc_els.length == 0) {
          autofoc_els = $$('*[autofocus]');
      }
       autofoc_els.first().focus();
  //}
};

// this wraps any event handling function to catch raised exceptions
Event.observe = Event.observe.wrap(function(proceed, element, eventName, handler) {
    handler = handler.wrap(function(proceed, e) {
        try {
            return proceed(e);
        } catch (err) {
            logError(err);
        }
    });
    return proceed(element, eventName, handler);
});

Event.observe(window, 'load',
function() {

    options = {
        'anonymousSession': ($F('anonymousSession') == 'True'),
        'authenticatedSession': ($F('authenticatedSession') == 'True')
    };

    user = new User();

    portal = new Portal('mainContainer');
    if (dhtmlHistory.isSafari) {
      dhtmlHistory.add = function(hash) {console.log(hash);};
      // this desactivates rsh altogether in Safari, to avoid the bugs in this specific browser
    }else {
      dhtmlHistory.initialize(historyListener);
    }

    var initialState = document.location.hash.substr(1);
    initialState = initialState.blank() ? 'welcome' : initialState;
    portal.go(initialState);
    new ProgramsListForm();

    init_observers();
    process_autofocus();

    new ResizeHandle('navbar', 'navbarResize', 'x');
});

var ProgramsListForm = Class.create({

    initialize: function(dataEl) {
        this.form = $('programSearch');
        this.displayList();
        this.form.observe('submit',
        function(e) {
            this.displayList();
            e.stop();
        }.bind(this));
        $('listAllSubmit').observe('click', $('searchString').clear.bind($('searchString')));
        var eventtowatch = Prototype.Browser['IE'] == true ? 'click' : 'change';
        $A(this.form.serviceTypeSort).each(function(el) {
            el.observe(eventtowatch, this.displayList.bind(this));
        }.bind(this));
        $A(this.form.classifyBy).each(function(el) {
            el.observe(eventtowatch, this.displayList.bind(this));
        }.bind(this));
    },

    displayList: function() {
        this.form.request({
            onCreate: function(response) {
                this.form.disable();
            }.bind(this),
            onSuccess: function(response) {
                $('programs_list_channel').update(response.responseText);
                var el = $('listAllSubmitBlock');
                if (this.form.searchString.value.blank()) {
                    el.style.visibility = 'hidden';
                }
                else {
                    el.style.visibility = 'visible';
                }
            }.bind(this),
            onComplete: function(response) {
                this.form.enable();
            }.bind(this)
        });
    }


});

var observers = $H({
    'links': {
        'event': 'click',
        'selector': 'body',
        'fn': function(e) {
            var linkEl = e.element();
            var aas = linkEl.ancestors().concat([linkEl]);
            var l = Selector.findElement(aas, '.link, .modalLink');
            if (l && l.hash) {
                portal.go(l.hash.substr(1));
                e.stop();
                return;
            }
            var l = Selector.findElement(aas, '.closeTab');
            if (l && l.hash) {
                $('ms-' + l.hash.substr(1)).removeTab();
                e.stop();
                dhtmlHistory.add(where());
                return;
            }
            var l = Selector.findElement(aas, '.closeModal');
            if (l) {
                portal.hideAllModal();
                e.stop();
                dhtmlHistory.add(where());
                return;
            }
            var l = Selector.findElement(aas, '.detachModal');
            if (l) {
                portal.hideAllModal();
                dhtmlHistory.add(where());
                return;
            }
        }
    },
    'clear_messages': {
        'event': 'change',
        'selector': 'body',
        'fn': function(e) {
            e.element().removeMessage();
        }
    },
    'blinds, examples, refreshs, minimizers': {
        'event': 'click',
        'selector': 'body',
        'fn': function(e) {
            var el = e.element();
            // managing blinds for comments, drawers, programs tree, etc.
            if (el.hasClassName('blindLink')) {
                var tg = $(e.element().readAttribute('href').split('#').last());
                if (tg.visible()) {
                    el.addClassName('closed');
                }
                else {
                    el.removeClassName('closed');
                }
                Effect[Element.visible(tg) ? 'BlindUp' : 'BlindDown'](tg);
                e.stop();
            }
            // managing examples
            if (el.hasClassName('exampleLink')) {
                var tg = $(e.element().readAttribute('href').substr(1));
                var program = tg.up('form').id;
                var param = tg.readAttribute('data-forparameter');
                var formParam = tg.up('form')[tg.readAttribute('data-forparameter')];
                e.stop(); // stop the event before, just in case anything goes wrong...
                if (formParam.value) {
                  //non-databox parameter
                  formParam.value = tg.down('pre').innerHTML.unescapeHTML();
                }else {
                  //databox parameter
                  // switch the databox to "paste" mode
                  $(program).select('[name="' + param + '.mode"][value="paste"]')[0].fire('databox:switch');
                  // load data
                  $(program).select('[name="' + param + '"][class="databox_paste"]')[0].value = tg.down('pre').innerHTML.unescapeHTML();
                }
            }
            // managing refresh links
            if (el.hasClassName('refresh_link') || el.up('a.refresh_link')) {
                user.workspace.get();
                e.stop();
            }
            // managing minimizable fieldsets
            if (el.tagName == 'LEGEND' && el.up('fieldset').hasClassName('minimizable')) {
                var tgA = el.nextSiblings().select(function(el) {el.hasClassName('commentText')});
                tgA.each(function(tg) {
                    Effect[el.up('fieldset').hasClassName('minimized') ? 'BlindDown' : 'BlindUp'](tg, {
                        queue: 'end'
                    });
                });
                Element[el.up('fieldset').hasClassName('minimized') ? 'removeClassName' : 'addClassName'](el.up('fieldset'), 'minimized');
                e.stop();
            }
        }
    },
    'submit': {
        'event': 'submit',
        'selector': 'form',
        'fn': function(e) {
            var form = e.element();
            e.stop();
            if (form.getAMethod() != 'get') {
                portal.startWaiting();
            }
            form._submit();
        }
    },
    'click_download': {
        'event': 'click',
        'selector': 'a button',
        'fn': function(e) {
            // this handler fires the onclick event not triggered in IE when a
            // button is clicked inside an anchor element
            var a = e.element().up('a');
            if (a && a.click) {
                e.element().up('a').click();
                e.stop();
            }
        }
    }
});

var init_observers = function() {
    observers.each(function(pair) {
        $$(pair.value.selector).each(function(el) {
            el.stopObserving(pair.value.event, pair.value.fn);
            el.observe(pair.value.event, pair.value.fn);
        });
    });
};

// RSH initialization
window.dhtmlHistory.create({
    toJSON: function(o) {
        return Object.toJSON(o);
    },
    fromJSON: function(s) {
        return s.evalJSON();
    },
    'blankURL': portalProperties.htbase + 'js/blank.html'
});

var historyListener = function(newLocation, historyData) {
    if (newLocation == 'undefined' || newLocation == 'null' || newLocation.blank()) return; // safari bug
    portal.go(newLocation);
};

Form.Element.disable = Form.Element.disable.wrap(function(proceed, el) {
    el = proceed(el);
    if (el.match('.tabPanel')) {
        var cpnts = el.select('input', 'select', 'textarea').reject(function(el) {
            return el.disabled == true;
        });
        if (cpnts.size() == 0) {
            el.hide();
            $$('.handlesList .link[href="#' + el.id.substr(3) + '"]')[0].up('li').hide();
        }
    }
});

Form.Element.enable = Form.Element.enable.wrap(function(proceed, el) {
    el = proceed(el);
    if (el.match('.tabPanel') && !(Form.serialize(el, false).blank())) {
        $$('.handlesList .link[href="#' + el.id.substr(3) + '"]')[0].up('li').show();
    }
});

//GA event tracking wrapper function
var gTrack = function(category,action,label) {
  if (typeof _gaq != 'undefined') {
     _gaq.push(['_trackEvent', category, action, label]);
  }
};

var ResizeHandle = Class.create({

    initialize: function(container, handle, type) {
        this.container = $(container);
        var handle = $(handle);
        /* Add property to container to store position variables */
        this.container.moveposition = {
            x: 0,
            y: 0
        };
        this.moveListener = function(event) {
            /* Calculate how far the mouse moved */
            var moved = {
                x: (event.pointerX() - this.container.moveposition.x),
                y: (event.pointerY() - this.container.moveposition.y)
            };
            /* Reset container's x/y utility property */
            this.container.moveposition = {
                x: event.pointerX(),
                y: event.pointerY()
            };

            /* Update container's size */
            var height = parseFloat(this.container.getStyle('height').gsub('px', ''));
            var width = parseFloat(this.container.getStyle('width').gsub('px', ''));
            var new_h = (!type || type == 'y') ? height + moved.y : height;
            var new_w = (!type || type == 'x') ? width + moved.x : width;
            this.container.setStyle({
                height: new_h + 'px',
                width: new_w + 'px'
            });
        }.bindAsEventListener(this);

        /* Listen for 'mouse down' on handle to start the move listener */
        handle.observe('mousedown',
        function(event) {
            /* Set starting x/y */
            this.container.moveposition = {
                x: event.pointerX(),
                y: event.pointerY()
            };
            /* Start listening for mouse move on body */
            Event.observe(document.body, 'mousemove', this.moveListener);
        }.bindAsEventListener(this));

        /* Listen for 'mouse up' to cancel 'move' listener */
        Event.observe(document.body, 'mouseup',
        function(event) {
            Event.stopObserving(document.body, 'mousemove');
        }.bindAsEventListener(this));
    }
});

var FormSimpleParametersToggleManager = Class.create({

    initialize: function(formEl) {
      var simpleParamEls = formEl.select('.parameter[data-issimple="true"]');
      var allParamEls = formEl.select('.parameter');
      if (simpleParamEls.size() > 0 && allParamEls.size() != simpleParamEls.size()) {
        this.simple = null; //"simple form" mode toggle
        this.formEl = formEl;
        this.showAdvanced = Builder.node('button', ['advanced options']);
        this.showAdvanced.observe('click',
          function(ev) {
            this.showAll();
            ev.stop();
          }.bindAsEventListener(this));
        this.formEl.select('.formCtrl').first().insert(this.showAdvanced);
        this.showSimple = Builder.node('button', ['only simple options']);
        this.showSimple.observe('click',
          function(ev) {
            this.showOnlySimple();
            ev.stop();
          }.bindAsEventListener(this));
        this.formEl.observe('form:parameter_load', function(event) {
          if (this.simple) {
            // if simple form toggle, reinitialize simple form display
            // to display modified parameters
            this.showAll();
            this.showOnlySimple();
          }
        }.bind(this));
        this.formEl.select('.formCtrl').first().insert(this.showSimple);
        this.showOnlySimple();
      }
    },

    showOnlySimple: function(event) {
      // hide advanced parameters unless value!=default
      this.formEl.select('.parameter').each(
        function(pEl) {
          if (pEl.readAttribute('data-issimple') != 'true' && !pEl.hasClassName('modified')) {
            pEl.hide();
          }
        }.bind(this)
      );
      // hide "hidden parameters" container paragraphs
      this.formEl.select('[data-paragraphname]').each(
        function(pgEl) {
          if (pgEl.select('.parameter.modified').size() == 0 && pgEl.select('.parameter[data-issimple="true"]').size() == 0) {
            pgEl.hide();
          }
        }
      );
      this.showSimple.hide();
      this.showAdvanced.show();
      this.simple = true;
    },

    showAll: function() {
      this.formEl.select('.parameter').each(
        function(pEl) {
          if (pEl.readAttribute('data-issimple') != 'true') {
            pEl.show();
          }
        }
      );
      this.formEl.select('[data-paragraphname]').each(
        function(pgEl) {
          if (pgEl.select('.parameter[data-issimple="true"]').size() == 0) {
            pgEl.show();
          }
        }
      );
      this.showAdvanced.hide();
      this.showSimple.show();
      this.simple = false;
    }
});

var FormParametersManager = Class.create({

    initialize: function(formEl) {
      this.formEl = formEl;
      this.formEl.select('.parameter').each(
        function(pEl) {
          this.createParameter(pEl);
        }.bind(this)
      );
      if(portal.properties.email_results){
          this.email_results_ctrl = new Element('div').update('Email me the results of this job <select name="_email_notify"><option value="auto">if it takes a long time</option><option value="true">yes</option><option value="false">no</option></select>');
          this.formEl.down('.header').insert(this.email_results_ctrl);
          this.updateEmailCtrl();
          document.observe('user:email',this.updateEmailCtrl.bind(this));
      }
      this.formEl.observe('reset',
        function(event) {
          this.formEl.select('.modified').invoke('removeClassName', 'modified');
          this.formEl.select('[data-ismultiple="true"]').invoke('fire', 'form:reset');
      }.bind(this));
    },
    
    updateEmailCtrl: function(){
    	// set visibility for results mail settings
    	if(user.email){
            this.email_results_ctrl.show();
    	}else{
            this.email_results_ctrl.hide();
    	}
    },

    createParameter: function(el, isClone,value) {
      // create a parameter for the form
      // el: "parameter"-level element in the form
      // isClone: boolean that mentions if the parameter already contains a Databox
      var datatype = el.readAttribute('data-datatype');
      var name = el.readAttribute('data-parametername');
      var isMultiple = el.readAttribute('data-ismultiple') == 'true';
      var parameter = null; // parameter is either a Databox object or an input control element
      if (!datatype.blank() && simpleDatatypes.indexOf(datatype) == -1) {
        parameter = this.createDatabox(el, name, isMultiple, value);
      }else {
        parameter = this.createSimpleParameter(el, isMultiple, value);
      }
      el.fire('multiple_parameter:add_remove');
      if (isMultiple & !isClone) {
        var addValues = Builder.node('button', {className: 'databox_add_button', title: 'click here to add new values for this parameter'}, ['+']);
        el.insert({top: addValues});
        addValues.observe('click', function(ev) {
          ev.stop();
          this.createParameter(el, true);
        }.bind(this));
      }
      el.select('input,select,textarea').each(
      function(ctrlEl) {
          ctrlEl.observe('reset', onFormParameterModification);
      });
      if (!isClone) {
        el.observe('parameter:pipe', function(e) {
          try {
          var inputMode = e.memo.inputModes.isArray ? e.memo.inputModes[0] : e.memo.inputModes;
          var resultId = e.memo.safeFileName;
          var databoxToPipe = null;
          var name = el.readAttribute('data-parametername');
          if (isMultiple) {
            var databoxes = this.formEl.select('[data-databoxforparametername="' + name + '"]');
            var firstEmptyDatabox = databoxes.detect(function(dbEl) {
              var dbName = dbEl.readAttribute('data-databoxname');
              var value = dbEl.select('[name="' + dbName + '"]').invoke('getValue').without('') + dbEl.select('[name="' + dbName + '.ref"]').invoke('getValue').without('');
              return value == '';
            });
            if (firstEmptyDatabox) {
              name = name + '['+ firstEmptyDatabox.readAttribute('data-databoxindex') + ']';
            }else {
              name = this.createParameter(el, true).name;
            }
          }
          // switch the databox to "result" mode
          this.formEl.select('[name="' + name + '.mode"][value="' + inputMode + '"]')[0].fire('databox:switch');
          // select appropriate value in results history to load in the databox
          var program = this.formEl.id;
          $(program + '::' + name + '::' + inputMode + '::history').value = resultId;
          // load data
          $(program + '::' + name + '::' + inputMode + '::history').next('input[type="button"]').fire('databox:history_load');
          }catch (e) {console.log(e)}
        }.bind(this));
      }
      return parameter;
    },

    createDatabox: function(el,name,isMultiple,value) {
      var inputs = el.select('[name=' + name + ']');
      var db = new Databox(el);
      el.insert({
          bottom: db.el
      });
      inputs.invoke('remove');
      return db;
    },

    createSimpleParameter: function(el,isMultiple,value) {
      el.select('input,select,textarea').each(
        function(ctrlEl) {
          ctrlEl.observe('change', onFormParameterModification);
      }.bind(this));
      return el;
    }
});

var onFormParameterModification = function(source) {
  // this function tests wether a control value is different from its corresponding
  // parameter value (based on data-defaultvalue attribute), and depending on the
  // result adds or removes the "modified" css class.
  var el = source.findElement ? source.findElement() : source;
  var form = el.up('form');
  var parEl = el.up('[data-parametername]');
  var formNameToTest = parEl.readAttribute('data-parametername');
  //if the parameter is a databox
  if (parEl.select('[data-databoxforparametername]')) {
    if (parEl.readAttribute('data-ismultiple') == 'true') {
      var databoxes = parEl.select('[data-databoxforparametername]');
      var firstNonEmptyDatabox = databoxes.detect(function(dbEl) {
        var dbName = dbEl.readAttribute('data-databoxname');
        var value = dbEl.select('[name="' + dbName + '"]').invoke('getValue').without('') + dbEl.select('[name="' + dbName + '.ref"]').invoke('getValue').without('');
        return value != '';
      });
      if (firstNonEmptyDatabox) {
        formNameToTest = firstNonEmptyDatabox.readAttribute('data-databoxname');
      }
    }
  }
  var value = '';
  if(form[formNameToTest]){
    value = form[formNameToTest].value || $A(form[formNameToTest]).pluck('value').reject(function(val) {return val == ''});
  }
  value = (value == '') ? undefined : value;
  var defaultValue = parEl.readAttribute('data-default-value');
  defaultValue = (defaultValue == '') ? undefined : defaultValue;
  if (value != defaultValue) {
    parEl.addClassName('modified');
  }else {
    parEl.removeClassName('modified');
  }
};
