diff --git a/client/galaxy/scripts/mvc/tools/tools-tree.js b/client/galaxy/scripts/mvc/form/form-data.js similarity index 87% rename from client/galaxy/scripts/mvc/tools/tools-tree.js rename to client/galaxy/scripts/mvc/form/form-data.js index fe060ad7bb9..5e1f23ce1fb 100644 --- a/client/galaxy/scripts/mvc/tools/tools-tree.js +++ b/client/galaxy/scripts/mvc/form/form-data.js @@ -1,49 +1,40 @@ /* - This class maps the tool form dom to an api compatible javascript dictionary. + This class maps the form dom to an api compatible javascript dictionary. */ -// dependencies define(['utils/utils'], function(Utils) { - -// tool form tree return Backbone.Model.extend({ // initialize initialize: function(app) { this.app = app; }, - /** Convert dictionary representation into tool api specific flat dictionary format. + /** Creates a checksum. */ - finalize: function(patch) { + checksum: function() { + var sum = ''; + for (var i in this.app.field_list) { + var field = this.app.field_list[i]; + sum += JSON.stringify(field.value && field.value()); + } + return sum; + }, + + /** Convert dom into dictionary. + */ + create: function() { // link this var self = this; - // dictionary with api specific identifiers - this.map_dict = {}; - - // check if section is available - if (!this.app.section) { - return {}; - } - - // ensure that dictionary with patching functions exists - patch = patch || {}; - - // dictionary formatted for job submission or tool form update - var result_dict = {}; - - // prepare full dictionary + // get raw dictionary from dom var dict = {}; - - // fill dictionary from dom this._iterate(this.app.section.$el, dict); - // add identifier and value to job definition + // add to result dictionary + var result_dict = {}; + this.map_dict = {}; function add(job_input_id, input_id, input_value) { - // add entry to result dictionary - result_dict[job_input_id] = input_value; - - // backup id mapping self.map_dict[job_input_id] = input_id; + result_dict[job_input_id] = input_value; }; // converter between raw dictionary and job dictionary @@ -93,12 +84,9 @@ return Backbone.Model.extend({ break; // handle conditionals case 'conditional': - // get and patch conditional value + // get conditional value var value = self.app.field_list[input.id].value(); - if (patch[input.test_param.type]) { - value = patch[input.test_param.type](value); - } - + // add conditional value add (job_input_id + '|' + input.test_param.name, input.id, value); @@ -119,11 +107,6 @@ return Backbone.Model.extend({ // validate field value var value = field.value(); - // get and patch field value - if (patch[input.type]) { - value = patch[input.type](value); - } - // ignore certain values if (input.ignore === undefined || input.ignore != value) { // add value to submission @@ -144,7 +127,7 @@ return Backbone.Model.extend({ // start conversion convert('', dict); - + // return result return result_dict; }, @@ -262,7 +245,7 @@ return Backbone.Model.extend({ return result; }, - /** Iterate through the tool form dom and map it to the dictionary. + /** Iterate through the form dom and map it to the dictionary. */ _iterate: function(parent, dict) { // get child nodes diff --git a/client/galaxy/scripts/mvc/tools/tools-input.js b/client/galaxy/scripts/mvc/form/form-input.js similarity index 97% rename from client/galaxy/scripts/mvc/tools/tools-input.js rename to client/galaxy/scripts/mvc/form/form-input.js index 9eaba877363..302e391595f 100644 --- a/client/galaxy/scripts/mvc/tools/tools-input.js +++ b/client/galaxy/scripts/mvc/form/form-input.js @@ -1,11 +1,8 @@ /** - This class creates a tool form input element wrapper + This class creates a form input element wrapper */ define([], function() { - - // input field element wrapper return Backbone.View.extend({ - // initialize input wrapper initialize: function(app, options) { // link app this.app = app; diff --git a/client/galaxy/scripts/mvc/tools/tools-parameters.js b/client/galaxy/scripts/mvc/form/form-parameters.js similarity index 91% rename from client/galaxy/scripts/mvc/tools/tools-parameters.js rename to client/galaxy/scripts/mvc/form/form-parameters.js index 0724e6d4910..d392c2c3b97 100644 --- a/client/galaxy/scripts/mvc/tools/tools-parameters.js +++ b/client/galaxy/scripts/mvc/form/form-parameters.js @@ -3,7 +3,7 @@ */ define(['utils/utils', 'mvc/ui/ui-misc', - 'mvc/tools/tools-select-content', + 'mvc/form/form-select-content', 'mvc/ui/ui-color-picker'], function(Utils, Ui, SelectContent, ColorPicker) { @@ -24,7 +24,7 @@ define(['utils/utils', 'color' : '_fieldColor', 'hidden' : '_fieldHidden', 'hidden_data' : '_fieldHidden', - 'baseurl' : '_fieldHidden', + 'baseurl' : '_fieldHidden' }, // initialize @@ -81,7 +81,7 @@ define(['utils/utils', /** Data input field */ - _fieldData : function(input_def) { + _fieldData: function(input_def) { if (!this.app.options.is_dynamic) { input_def.info = 'Data input \'' + input_def.name + '\' (' + Utils.textify(input_def.extensions.toString()) + ')'; input_def.value = null; @@ -96,14 +96,14 @@ define(['utils/utils', type : input_def.type, data : input_def.options, onchange : function() { - self.app.trigger('refresh'); + self.app.trigger('change'); } }); }, /** Select/Checkbox/Radio options field */ - _fieldSelect : function (input_def) { + _fieldSelect: function (input_def) { // show text field in if dynamic fields are disabled e.g. in workflow editor if (!this.app.options.is_dynamic && input_def.is_dynamic) { return this._fieldText(input_def); @@ -147,14 +147,14 @@ define(['utils/utils', multiple : input_def.multiple, searchable : input_def.searchable, onchange : function() { - self.app.trigger('refresh'); + self.app.trigger('change'); } }); }, /** Drill down options field */ - _fieldDrilldown : function (input_def) { + _fieldDrilldown: function (input_def) { // show text field in if dynamic fields are disabled e.g. in workflow editor if (!this.app.options.is_dynamic && input_def.is_dynamic) { return this._fieldText(input_def); @@ -167,14 +167,14 @@ define(['utils/utils', data : input_def.options, display : input_def.display, onchange : function() { - self.app.trigger('refresh'); + self.app.trigger('change'); } }); }, /** Text input field */ - _fieldText : function(input_def) { + _fieldText: function(input_def) { // field replaces e.g. a select field if (input_def.options) { // show text area if selecting multiple entries is allowed @@ -201,7 +201,7 @@ define(['utils/utils', id : 'field-' + input_def.id, area : input_def.area, onchange : function() { - self.app.trigger('refresh'); + self.app.trigger('change'); } }); }, @@ -216,14 +216,14 @@ define(['utils/utils', min : input_def.min, max : input_def.max, onchange : function() { - self.app.trigger('refresh'); + self.app.trigger('change'); } }); }, /** Hidden field */ - _fieldHidden : function(input_def) { + _fieldHidden: function(input_def) { return new Ui.Hidden({ id : 'field-' + input_def.id, info : input_def.info @@ -232,26 +232,26 @@ define(['utils/utils', /** Boolean field */ - _fieldBoolean : function(input_def) { + _fieldBoolean: function(input_def) { var self = this; return new Ui.RadioButton.View({ id : 'field-' + input_def.id, data : [ { label : 'Yes', value : 'true' }, { label : 'No', value : 'false' }], onchange : function() { - self.app.trigger('refresh'); + self.app.trigger('change'); } }); }, /** Color picker field */ - _fieldColor : function(input_def) { + _fieldColor: function(input_def) { var self = this; return new ColorPicker({ id : 'field-' + input_def.id, onchange : function() { - self.app.trigger('refresh'); + self.app.trigger('change'); } }); } diff --git a/client/galaxy/scripts/mvc/tools/tools-repeat.js b/client/galaxy/scripts/mvc/form/form-repeat.js similarity index 94% rename from client/galaxy/scripts/mvc/tools/tools-repeat.js rename to client/galaxy/scripts/mvc/form/form-repeat.js index 3f78bffbee9..89d1b4818f3 100644 --- a/client/galaxy/scripts/mvc/tools/tools-repeat.js +++ b/client/galaxy/scripts/mvc/form/form-repeat.js @@ -1,5 +1,6 @@ // dependencies -define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc'], function(Utils, Table, Portlet, Ui) { +define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc'], + function(Utils, Table, Portlet, Ui) { /** This class creates a ui component which enables the dynamic creation of portlets */ @@ -66,7 +67,7 @@ var View = Backbone.View.extend({ add: function(options) { // repeat block already exists if (!options.id || this.list[options.id]) { - console.debug('tools-repeat::add - Duplicate repeat block id.'); + console.debug('form-repeat::add - Duplicate repeat block id.'); return; } @@ -122,7 +123,7 @@ var View = Backbone.View.extend({ del: function(id) { // could not find element if (!this.list[id]) { - console.debug('tools-repeat::del - Invalid repeat block id.'); + console.debug('form-repeat::del - Invalid repeat block id.'); return; } diff --git a/client/galaxy/scripts/mvc/tools/tools-section.js b/client/galaxy/scripts/mvc/form/form-section.js similarity index 95% rename from client/galaxy/scripts/mvc/tools/tools-section.js rename to client/galaxy/scripts/mvc/form/form-section.js index c779d73acdb..83ef3cda5c3 100644 --- a/client/galaxy/scripts/mvc/tools/tools-section.js +++ b/client/galaxy/scripts/mvc/form/form-section.js @@ -1,13 +1,13 @@ /** - This class creates a tool form section and populates it with input elements. It also handles repeat blocks and conditionals by recursively creating new sub sections. + This class creates a form section and populates it with input elements. It also handles repeat blocks and conditionals by recursively creating new sub sections. */ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/ui/ui-portlet', - 'mvc/tools/tools-repeat', - 'mvc/tools/tools-input', - 'mvc/tools/tools-parameters'], + 'mvc/form/form-repeat', + 'mvc/form/form-input', + 'mvc/form/form-parameters'], function(Utils, Table, Ui, Portlet, Repeat, InputElement, Parameters) { // create form view @@ -26,7 +26,7 @@ define(['utils/utils', // add table class for tr tag // this assist in transforming the form into a json structure options.cls_tr = 'section-row'; - + // create table this.table = new Table.View(options); @@ -36,7 +36,7 @@ define(['utils/utils', // configure portlet and form table this.setElement(this.table.$el); - // render tool section + // render section this.render(); }, @@ -103,7 +103,7 @@ define(['utils/utils', // set onchange event for test parameter field.options.onchange = function(value) { // identify the selected case - var selectedCase = self.app.tree.matchCase(input_def, value); + var selectedCase = self.app.data.matchCase(input_def, value); // check value in order to hide/show options for (var i in input_def.cases) { @@ -134,7 +134,7 @@ define(['utils/utils', } // refresh form inputs - self.app.trigger('refresh'); + self.app.trigger('change'); }; // add conditional sub sections @@ -181,7 +181,7 @@ define(['utils/utils', create(input_def.inputs); // trigger refresh - self.app.trigger('refresh'); + self.app.trigger('change'); } }); @@ -204,7 +204,7 @@ define(['utils/utils', repeat.del(sub_section_id); // trigger refresh - self.app.trigger('refresh'); + self.app.trigger('change'); } }); } diff --git a/client/galaxy/scripts/mvc/tools/tools-select-content.js b/client/galaxy/scripts/mvc/form/form-select-content.js similarity index 93% rename from client/galaxy/scripts/mvc/tools/tools-select-content.js rename to client/galaxy/scripts/mvc/form/form-select-content.js index 6f057e8f6c9..28ade6a3a50 100644 --- a/client/galaxy/scripts/mvc/tools/tools-select-content.js +++ b/client/galaxy/scripts/mvc/form/form-select-content.js @@ -1,5 +1,9 @@ // dependencies -define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-tabs', 'mvc/tools/tools-template'], function(Utils, Ui, Tabs, ToolTemplate) { +define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-tabs', 'mvc/tools/tools-template'], + function(Utils, Ui, Tabs, ToolTemplate) { + + +history = {}; var View = Backbone.View.extend({ // initialize @@ -14,7 +18,7 @@ var View = Backbone.View.extend({ // add element this.setElement('
'); - // list of select fields + // list of select fieldsFormSection this.list = {}; // radio button options @@ -119,7 +123,7 @@ var View = Backbone.View.extend({ }); // add batch mode information - this.$batch = $(ToolTemplate.batchMode()); + this.$batch = $(this.template_batch()); // number of radio buttons var n_buttons = _.size(this.list); @@ -191,7 +195,7 @@ var View = Backbone.View.extend({ value: item.id }); // backup to local history - self.app.history[item.id + '_' + item.src] = item; + history[item.id + '_' + item.src] = item; } // update field field.update(select_options); @@ -266,7 +270,7 @@ var View = Backbone.View.extend({ // append to dataset ids for (var i in id_list) { - var details = _.findWhere(this.app.history, { + var details = _.findWhere(history, { id : id_list[i], src : this.list[this.current].type }); @@ -312,7 +316,7 @@ var View = Backbone.View.extend({ /** Assists in identifying the batch mode */ _batch: function() { if (this.current == 'collection') { - var hdca = _.findWhere(this.app.history, { + var hdca = _.findWhere(history, { id : this._select().value(), src : 'hdca' }); @@ -326,6 +330,14 @@ var View = Backbone.View.extend({ } } return false; + }, + + /** Batch message template */ + template_batch: function() { + return '
' + + '' + + 'This is a batch mode input field. A separate job will be triggered for each dataset.' + + '
'; } }); diff --git a/client/galaxy/scripts/mvc/form/form-view.js b/client/galaxy/scripts/mvc/form/form-view.js new file mode 100644 index 00000000000..5cccd7b04b3 --- /dev/null +++ b/client/galaxy/scripts/mvc/form/form-view.js @@ -0,0 +1,199 @@ +/** + This is the main class of the form plugin. It is referenced as 'app' in all lower level modules. +*/ +define(['utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', + 'mvc/form/form-section', 'mvc/form/form-data'], + function(Utils, Portlet, Ui, FormSection, FormData) { + + // create form view + return Backbone.View.extend({ + // initialize + initialize: function(options) { + // options + this.optionsDefault = { + // uses dynamic fields instead of text fields + is_dynamic : true, + // shows form in narrow view mode + narrow : false, + // shows errors on start + initial_errors : false, + // portlet style + cls : 'ui-portlet-limited' + }; + + // configure options + this.options = Utils.merge(options, this.optionsDefault); + + // log options + console.debug(this.options); + + // link galaxy modal or create one + var galaxy = parent.Galaxy; + if (galaxy && galaxy.modal) { + this.modal = galaxy.modal; + } else { + this.modal = new Ui.Modal.View(); + } + + // set element + this.setElement('
'); + + // build this form + this._build(); + }, + + /** Set form into wait mode */ + wait: function(active) { + for (var i in this.input_list) { + var field = this.field_list[i]; + var input = this.input_list[i]; + if (input.is_dynamic && field.wait && field.unwait) { + if (active) { + field.wait(); + } else { + field.unwait(); + } + } + } + }, + + /** Shows the final message (usually upon successful job submission) + */ + reciept: function($el) { + this.$el.empty(); + this.$el.append($el); + }, + + /** Highlight and scroll to input element (currently only used for error notifications) + */ + highlight: function (input_id, message, silent) { + // get input field + var input_element = this.element_list[input_id]; + + // check input element + if (input_element) { + // mark error + input_element.error(message || 'Please verify this parameter.'); + + // scroll to first input element + if (!silent) { + $('html, body').animate({ + scrollTop: input_element.$el.offset().top - 20 + }, 500); + } + } + }, + + /** Highlights errors + */ + errors: function(options) { + // hide previous error statements + this.trigger('reset'); + + // highlight all errors + if (options && options.errors) { + var error_messages = this.data.matchResponse(options.errors); + for (var input_id in this.element_list) { + var input = this.element_list[input_id]; + if (error_messages[input_id]) { + this.highlight(input_id, error_messages[input_id], true); + } + } + } + }, + + /** Main tool form build function. This function is called once a new model is available. + */ + _build: function() { + // link this + var self = this; + + // reset events + this.off('change'); + this.off('reset'); + + // reset field list, which contains the input field elements + this.field_list = {}; + + // reset sequential input definition list, which contains the input definitions as provided from the api + this.input_list = {}; + + // reset input element list, which contains the dom elements of each input element (includes also the input field) + this.element_list = {}; + + // creates a json data structure from the input form + this.data = new FormData(this); + + // create ui elements + this._renderForm(); + + // refresh data + this.data.create(); + + // show errors on startup + if (this.options.initial_errors) { + this.errors(this.options); + } + + // add listener which triggers on checksum change + var current_check = this.data.checksum(); + this.on('change', function() { + var new_check = self.data.checksum(); + if (new_check != current_check) { + current_check = new_check; + self.options.onchange && self.options.onchange(); + } + }); + + // add reset listener + this.on('reset', function() { + for (var i in this.element_list) { + this.element_list[i].reset(); + } + }); + }, + + /** Renders the UI elements required for the form + */ + _renderForm: function() { + // create message view + this.message = new Ui.Message(); + + // create tool form section + this.section = new FormSection.View(this, { + inputs : this.options.inputs + }); + + // create portlet + this.portlet = new Portlet.View({ + icon : 'fa-wrench', + title : this.options.title, + cls : this.options.cls, + operations : this.options.operations, + buttons : this.options.buttons + }); + + // append message + this.portlet.append(this.message.$el.addClass('ui-margin-top')); + + // append tool section + this.portlet.append(this.section.$el); + + // start form + this.$el.empty(); + this.$el.append(this.portlet.$el); + + // show message if available in model + if (this.options.message) { + this.message.update({ + persistent : true, + status : 'warning', + message : this.options.message + }); + } + + // log + console.debug('tools-form-base::initialize() - Completed.'); + } + }); +}); diff --git a/client/galaxy/scripts/mvc/tools/tools-form-base.js b/client/galaxy/scripts/mvc/tools/tools-form-base.js index db802460058..cbfb0ebedf2 100644 --- a/client/galaxy/scripts/mvc/tools/tools-form-base.js +++ b/client/galaxy/scripts/mvc/tools/tools-form-base.js @@ -1,52 +1,22 @@ /** - This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules. + This is the base class of the tool form plugin. It is referenced as 'app' in all lower level modules. */ -define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', - 'mvc/citation/citation-model', 'mvc/citation/citation-view', - 'mvc/tools', 'mvc/tools/tools-template', 'mvc/tools/tools-section', 'mvc/tools/tools-tree'], - function(Utils, Deferred, Portlet, Ui, CitationModel, CitationView, - Tools, ToolTemplate, ToolSection, ToolTree) { +define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view', + 'mvc/tools/tools-template', 'mvc/citation/citation-model', 'mvc/citation/citation-view'], + function(Utils, Deferred, Ui, Form, ToolTemplate, CitationModel, CitationView) { // create form view return Backbone.View.extend({ // initialize initialize: function(options) { - // options - this.optionsDefault = { - // uses dynamic fields instead of text fields - is_dynamic : true, - // shows form in narrow view mode - narrow : false, - // shows errors on start - initial_errors : false, - // portlet style - cls_portlet : 'ui-portlet-limited' - }; - - // configure options - this.options = Utils.merge(options, this.optionsDefault); - - // log options - console.debug(this.options); - - // link galaxy modal or create one - var galaxy = parent.Galaxy; - if (galaxy && galaxy.modal) { - this.modal = galaxy.modal; - } else { - this.modal = new Ui.Modal.View(); - } - // check if the user is an admin + var galaxy = parent.Galaxy; if (galaxy && galaxy.currUser) { this.is_admin = galaxy.currUser.get('is_admin'); } else { this.is_admin = false; } - // link container - this.container = this.options.container || 'body'; - // create deferred processing queue handler // this handler reduces the number of requests to the api by filtering redundant requests this.deferred = new Deferred(); @@ -54,126 +24,52 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', // set element this.setElement('
'); - // add to main element + // append to body + this.container = options.container || 'body'; $(this.container).append(this.$el); - // build this form - this.build(this.options); + // create form + this._buildForm(options); }, - /** Main tool form build function. This function is called once a new model is available. - */ - build: function(options) { + /** Build form */ + _buildForm: function(options) { // link this var self = this; - // reset events - this.off('refresh'); - this.off('reset'); + // configure options + this.options = Utils.merge(options, this.optionsDefault); - // reset field list, which contains the input field elements - this.field_list = {}; - - // reset sequential input definition list, which contains the input definitions as provided from the api - this.input_list = {}; - - // reset input element list, which contains the dom elements of each input element (includes also the input field) - this.element_list = {}; - - // creates a tree/json data structure from the input form - this.tree = new ToolTree(this); - - // keeps track of history items - this.history = {}; - - // update model data - self.options.inputs = options && options.inputs; - - // create ui elements - this._renderForm(options); - - // rebuild the underlying data structure - var current_state = this.tree.finalize(); - - // show errors on startup - if (options.initial_errors) { - this._errors(options); - } - - // add refresh listener - this.on('refresh', function() { - // by using/resetting the deferred ajax queue the number of redundant calls is reduced - self.deferred.reset(); - self.deferred.execute(function(){ - var new_state = self.tree.finalize(); - if (!_.isEqual(new_state, current_state)) { - current_state = new_state; - self._updateModel($.extend(true, {}, current_state)); - } - }); - }); - - // add reset listener - this.on('reset', function() { - for (var i in this.element_list) { - this.element_list[i].reset(); + // merge form options + var form_options = Utils.merge({ + icon : 'fa-wrench', + title : '' + options.name + ' ' + options.description + ' (Galaxy Tool Version ' + options.version + ')', + operations : this._operations(), + onchange : function(current_state) { + // by resetting the deferred ajax queue the number of redundant calls is reduced + self.deferred.reset(); + self.deferred.execute(function(){ + self._updateModel($.extend(true, {}, self.form.data.create())); + }); } - }); + }, options); + + // create form + this.form = new Form(form_options); + + // create footer + this._footer(); + + // create element + this.$el.empty(); + this.$el.append(this.form.$el); }, - /** Shows the final message (usually upon successful job submission) - */ - reciept: function($el) { - $(this.container).empty(); - $(this.container).append($el); - }, - - /** Highlight and scroll to input element (currently only used for error notifications) - */ - highlight: function (input_id, message, silent) { - // get input field - var input_element = this.element_list[input_id]; - - // check input element - if (input_element) { - // mark error - input_element.error(message || 'Please verify this parameter.'); - - // scroll to first input element - if (!silent) { - $('html, body').animate({ - scrollTop: input_element.$el.offset().top - 20 - }, 500); - } - } - }, - - /** Highlights errors - */ - _errors: function(options) { - // hide previous error statements - this.trigger('reset'); - - // highlight all errors - if (options && options.errors) { - var error_messages = this.tree.matchResponse(options.errors); - for (var input_id in this.element_list) { - var input = this.element_list[input_id]; - if (error_messages[input_id]) { - this.highlight(input_id, error_messages[input_id], true); - } - } - } - }, - - /** Renders the UI elements required for the form - */ - _renderForm: function(options) { + // create tool operation menu + _operations: function() { // link this var self = this; - - // create message view - this.message = new Ui.Message(); + var options = self.options; // button for version selection var versions_button = new Ui.ButtonMenu({ @@ -191,8 +87,8 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', icon : 'fa-cube', onclick : function() { // here we update the tool version (some tools encode the version also in the id) - self.options.id = self.options.id.replace(self.options.version, this.version); - self.options.version = this.version; + options.id = options.id.replace(options.version, this.version); + options.version = this.version; // rebuild the model and form self.deferred.reset(); @@ -223,7 +119,7 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', window.open(options.biostar_url + '/p/new/post/'); } }); - + // create search button menu_button.addMenu({ icon : 'fa-search', @@ -246,7 +142,7 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', }); // add admin operations - if (this.is_admin) { + if (self.is_admin) { // create download button menu_button.addMenu({ icon : 'fa-download', @@ -267,14 +163,14 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', onclick : function() { if (!this.visible) { this.visible = true; - self.message.update({ + self.form.message.update({ persistent : true, message : ToolTemplate.requirements(options), status : 'info' }); } else { this.visible = false; - self.message.update({ + self.form.message.update({ message : '' }); } @@ -282,59 +178,36 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', }); } - // add toolshed url - if (this.options.sharable_url) { + if (options.sharable_url) { menu_button.addMenu({ icon : 'fa-external-link', title : 'See in Tool Shed', tooltip : 'Access the repository', onclick : function() { - window.open(self.options.sharable_url); + window.open(options.sharable_url); } }); } - // create tool form section - this.section = new ToolSection.View(self, { - inputs : options.inputs - }); - - // switch to classic tool form mako if the form definition is incompatible - if (this.incompatible) { - this.$el.hide(); - $('#tool-form-classic').show(); - return; + // return operations + return { + menu : menu_button, + versions : versions_button } + }, - // create portlet - this.portlet = new Portlet.View({ - icon : 'fa-wrench', - title : '' + options.name + ' ' + options.description + ' (Galaxy Tool Version ' + options.version + ')', - cls : this.options.cls_portlet, - operations: { - menu : menu_button, - versions : versions_button - }, - buttons : this.buttons - }); - - // append message - this.portlet.append(this.message.$el.addClass('ui-margin-top')); - - // append tool section - this.portlet.append(this.section.$el); - - // start form - this.$el.empty(); - this.$el.append(this.portlet.$el); + // create footer + _footer: function() { + // link options + var options = this.options; // append help if (options.help != '') { - this.$el.append(ToolTemplate.help(options.help)); + this.form.$el.append(ToolTemplate.help(options)); } - // append citations + // create and append tool citations if (options.citations) { var $citations = $('
'); var citations = new CitationModel.ToolCitationCollection(); @@ -342,20 +215,8 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations } ); citation_list_view.render(); citations.fetch(); - this.$el.append($citations); + this.form.$el.append($citations); } - - // show message if available in model - if (options.message) { - this.message.update({ - persistent : true, - status : 'warning', - message : options.message - }); - } - - // log - console.debug('tools-form-base::initialize() - Completed.'); } }); }); diff --git a/client/galaxy/scripts/mvc/tools/tools-form-workflow.js b/client/galaxy/scripts/mvc/tools/tools-form-workflow.js index 649e99119ab..9b9e1db1fd1 100644 --- a/client/galaxy/scripts/mvc/tools/tools-form-workflow.js +++ b/client/galaxy/scripts/mvc/tools/tools-form-workflow.js @@ -278,7 +278,7 @@ define(['utils/utils', 'mvc/tools/tools-form-base'], _updateModel: function(current_state) { // link self var self = this; - + // create the request dictionary current_state = { tool_id : this.options.id, @@ -306,7 +306,7 @@ define(['utils/utils', 'mvc/tools/tools-form-base'], self.node.update_field_data(data); // highlight errors - self._errors(data && data.tool_model); + self.form.errors(data && data.tool_model); // process completed self.deferred.done(process_id); diff --git a/client/galaxy/scripts/mvc/tools/tools-form.js b/client/galaxy/scripts/mvc/tools/tools-form.js index bf27de7bf24..2e94723cd3d 100644 --- a/client/galaxy/scripts/mvc/tools/tools-form.js +++ b/client/galaxy/scripts/mvc/tools/tools-form.js @@ -9,8 +9,7 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools // initialize initialize: function(options) { var self = this; - this.job_handler = new ToolJobs(this); - this.buttons = { + options.buttons = { execute : new Ui.Button({ icon : 'fa-check', tooltip : 'Execute: ' + options.name, @@ -18,11 +17,10 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools cls : 'btn btn-primary', floating : 'clear', onclick : function() { - self.job_handler.submit(); + ToolJobs.submit(self.form, options); } }) - } - options.initial_errors = options.job_id; + }; ToolFormBase.prototype.initialize.call(this, options); }, @@ -58,10 +56,10 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools url : model_url, success : function(response) { // build new tool form - self.build(response); + self._buildForm(response); // notification - self.message.update({ + self.form.message.update({ status : 'success', message : 'Now you are using \'' + self.options.name + '\' version ' + self.options.version + '.', persistent : false @@ -84,12 +82,12 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools // show error var error_message = response.error || 'Uncaught error.'; - self.modal.show({ + self.form.modal.show({ title : 'Tool cannot be executed', body : error_message, buttons : { 'Close' : function() { - self.modal.hide(); + self.form.modal.hide(); } } }); @@ -100,8 +98,11 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools /** Request a new model for an already created tool form and updates the form inputs */ _updateModel: function(current_state) { - // create the request dictionary + // link this var self = this; + + // create the request dictionary + var form = this.form; // create the request dictionary var current_state = { @@ -110,23 +111,8 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools inputs : current_state } - // activates/disables spinner for dynamic fields to indicate that they are currently being updated - function wait(active) { - for (var i in self.input_list) { - var field = self.field_list[i]; - var input = self.input_list[i]; - if (input.is_dynamic && field.wait && field.unwait) { - if (active) { - field.wait(); - } else { - field.unwait(); - } - } - } - } - // set wait mode - wait(true); + form.wait(true); // register process var process_id = this.deferred.register(); @@ -145,15 +131,15 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools data : current_state, success : function(new_model) { // update form - self.tree.matchModel(new_model, function(input_id, node) { - var input = self.input_list[input_id]; + form.data.matchModel(new_model, function(input_id, node) { + var input = form.input_list[input_id]; if (input && input.options) { if (!_.isEqual(input.options, node.options)) { // backup new options input.options = node.options; // get/update field - var field = self.field_list[input_id]; + var field = form.field_list[input_id]; if (field.update) { var new_options = []; if ((['data', 'data_collection', 'drill_down']).indexOf(input.type) != -1) { @@ -178,7 +164,7 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools }); // unset wait mode - wait(false); + form.wait(false); // log success console.debug('tools-form::_refreshForm() - Received new model (see below).'); diff --git a/client/galaxy/scripts/mvc/tools/tools-jobs.js b/client/galaxy/scripts/mvc/tools/tools-jobs.js index de5d574c301..c78c9d41112 100644 --- a/client/galaxy/scripts/mvc/tools/tools-jobs.js +++ b/client/galaxy/scripts/mvc/tools/tools-jobs.js @@ -1,74 +1,67 @@ /** - This class handles job submissions and the error handling. + This class handles job submissions and validations. */ define(['utils/utils', 'mvc/tools/tools-template'], function(Utils, ToolTemplate) { -return Backbone.Model.extend({ - // initialize - initialize: function(app) { - this.app = app; - }, - - /** Creates and submits a job request to the api - */ - submit: function() { +return { + submit: function(form, options) { // link this var self = this; - + // create job definition for submission to tools api var job_def = { - tool_id : this.app.options.id, - tool_version : this.app.options.version, - inputs : this.app.tree.finalize() + tool_id : options.id, + tool_version : options.version, + inputs : form.data.create() } - + // reset - this.app.trigger('reset'); - + form.trigger('reset'); + // validate job definition - if (!this._validation(job_def)) { + if (!this._validation(form, job_def)) { console.debug('tools-jobs::submit - Submission canceled. Validation failed.'); return; } - + // debug console.debug(job_def); - + // show progress modal var user_is_waiting = true; - this.app.modal.show({title: 'Please wait...', body: 'progress', buttons: { + form.modal.show({title: 'Please wait...', body: 'progress', buttons: { 'Close' : function () { - self.app.modal.hide(); + form.modal.hide(); user_is_waiting = false; } }}); - + // post job Utils.request({ type : 'POST', url : galaxy_config.root + 'api/tools', data : job_def, success : function(response) { - self.app.modal.hide(); - self.app.reciept(ToolTemplate.success(response)); + form.modal.hide(); + form.reciept(ToolTemplate.success(response)); self._refreshHdas(); }, error : function(response, response_full) { - self.app.modal.hide(); + form.modal.hide(); if (response && response.message && response.message.data) { - var error_messages = self.app.tree.matchResponse(response.message.data); + var error_messages = form.data.matchResponse(response.message.data); for (var input_id in error_messages) { - self.app.highlight(input_id, error_messages[input_id]); + form.highlight(input_id, error_messages[input_id]); break; } } else { // show error message with details console.debug(response); - user_is_waiting && self.app.modal.show({ + user_is_waiting && form.modal.show({ title : 'Job submission failed', body : ToolTemplate.error(job_def), buttons : { 'Close' : function() { - self.app.modal.hide(); + form.modal.hide(); } } }); @@ -76,78 +69,78 @@ return Backbone.Model.extend({ } }); }, - + /** Validate job definition */ - _validation: function(job_def) { + _validation: function(form, job_def) { // get input parameters var job_inputs = job_def.inputs; - + // counter for values declared in batch mode var batch_n = -1; var batch_src = null; - + // validation for (var job_input_id in job_inputs) { // get current value var input_value = job_inputs[job_input_id]; - + // collect input field properties - var input_id = this.app.tree.match(job_input_id); - var input_field = this.app.field_list[input_id]; - var input_def = this.app.input_list[input_id]; - + var input_id = form.data.match(job_input_id); + var input_field = form.field_list[input_id]; + var input_def = form.input_list[input_id]; + // check if objects where properly identified if (!input_id || !input_def || !input_field) { console.debug('tools-jobs::_validation - Retrieving input objects failed.'); continue; } - + // validate non-optional fields if (!input_def.optional && input_value == null) { - this.app.highlight(input_id); + form.highlight(input_id); return false; } - + // check if input field is in batch mode if (input_value && input_value.batch) { // get values var n = input_value.values.length; - + // get source var src = null; if (n > 0) { src = input_value.values[0] && input_value.values[0].src; } - + // check source type if (src) { if (batch_src === null) { batch_src = src; } else { if (batch_src !== src) { - this.app.highlight(input_id, 'Please select either dataset or dataset list fields for all batch mode fields.'); + form.highlight(input_id, 'Please select either dataset or dataset list fields for all batch mode fields.'); return false; } } } - + // check number of inputs if (batch_n === -1) { batch_n = n; } else { if (batch_n !== n) { - this.app.highlight(input_id, 'Please make sure that you select the same number of inputs for all batch mode fields. This field contains ' + n + ' selection(s) while a previous field contains ' + batch_n + '.'); + form.highlight(input_id, 'Please make sure that you select the same number of inputs for all batch mode fields. This field contains ' + n + ' selection(s) while a previous field contains ' + batch_n + '.'); return false; } } } } - + // return validation result return true; }, - + /** Refreshes the history panel */ _refreshHdas: function() { @@ -155,6 +148,6 @@ return Backbone.Model.extend({ parent.Galaxy.currHistoryPanel.refreshContents(); } } -}); +}; }); \ No newline at end of file diff --git a/client/galaxy/scripts/mvc/tools/tools-template.js b/client/galaxy/scripts/mvc/tools/tools-template.js index 349cb16698c..ad536519d03 100644 --- a/client/galaxy/scripts/mvc/tools/tools-template.js +++ b/client/galaxy/scripts/mvc/tools/tools-template.js @@ -3,14 +3,14 @@ define([], function() { // tool form templates return { - help: function(content) { + help: function(options) { return '
' + '
' + - content + + options.help + '
' + '
'; }, - + success: function(response) { // check if (!response.jobs || !response.jobs.length) { @@ -52,13 +52,6 @@ return { '
'; }, - batchMode: function() { - return '
' + - '' + - 'This is a batch mode input field. A separate job will be triggered for each dataset.' + - '
'; - }, - requirements: function(options) { var requirements_message = 'This tool requires '; for (var i in options.requirements) { diff --git a/static/scripts/mvc/form/form-data.js b/static/scripts/mvc/form/form-data.js new file mode 100644 index 00000000000..5e1f23ce1fb --- /dev/null +++ b/static/scripts/mvc/form/form-data.js @@ -0,0 +1,283 @@ +/* + This class maps the form dom to an api compatible javascript dictionary. +*/ +define(['utils/utils'], function(Utils) { +return Backbone.Model.extend({ + // initialize + initialize: function(app) { + this.app = app; + }, + + /** Creates a checksum. + */ + checksum: function() { + var sum = ''; + for (var i in this.app.field_list) { + var field = this.app.field_list[i]; + sum += JSON.stringify(field.value && field.value()); + } + return sum; + }, + + /** Convert dom into dictionary. + */ + create: function() { + // link this + var self = this; + + // get raw dictionary from dom + var dict = {}; + this._iterate(this.app.section.$el, dict); + + // add to result dictionary + var result_dict = {}; + this.map_dict = {}; + function add(job_input_id, input_id, input_value) { + self.map_dict[job_input_id] = input_id; + result_dict[job_input_id] = input_value; + }; + + // converter between raw dictionary and job dictionary + function convert(identifier, head) { + for (var index in head) { + var node = head[index]; + if (node.input) { + // get node + var input = node.input; + + // create identifier + var job_input_id = identifier; + if (identifier != '') { + job_input_id += '|'; + } + job_input_id += input.name; + + // process input type + switch (input.type) { + // handle repeats + case 'repeat': + // section identifier + var section_label = 'section-'; + + // collect repeat block identifiers + var block_indices = []; + var block_prefix = null; + for (var block_label in node) { + var pos = block_label.indexOf(section_label); + if (pos != -1) { + pos += section_label.length; + block_indices.push(parseInt(block_label.substr(pos))); + if (!block_prefix) { + block_prefix = block_label.substr(0, pos); + } + } + } + + // sort repeat blocks + block_indices.sort(function(a,b) { return a - b; }); + + // add to response dictionary in created order + var index = 0; + for (var i in block_indices) { + convert(job_input_id + '_' + index++, node[block_prefix + block_indices[i]]); + } + break; + // handle conditionals + case 'conditional': + // get conditional value + var value = self.app.field_list[input.id].value(); + + // add conditional value + add (job_input_id + '|' + input.test_param.name, input.id, value); + + // identify selected case + var selectedCase = self.matchCase(input, value); + if (selectedCase != -1) { + convert(job_input_id, head[input.id + '-section-' + selectedCase]); + } + break; + // handle custom sub sections + case 'section': + convert('', node); + break; + default: + // get field + var field = self.app.field_list[input.id]; + if (field && field.value) { + // validate field value + var value = field.value(); + + // ignore certain values + if (input.ignore === undefined || input.ignore != value) { + // add value to submission + add (job_input_id, input.id, value); + + // add payload to submission + if (input.payload) { + for (var p_id in input.payload) { + add (p_id, input.id, input.payload[p_id]); + } + } + } + } + } + } + } + } + + // start conversion + convert('', dict); + + // return result + return result_dict; + }, + + /** Match job definition identifier to input element identifier + */ + match: function (job_input_id) { + return this.map_dict && this.map_dict[job_input_id]; + }, + + /** Match conditional values to selected cases + */ + matchCase: function(input, value) { + // format value for boolean inputs + if (input.test_param.type == 'boolean') { + if (value == 'true') { + value = input.test_param.truevalue || 'true'; + } else { + value = input.test_param.falsevalue || 'false'; + } + } + + // find selected case + for (var i in input.cases) { + if (input.cases[i].value == value) { + return i; + } + } + + // selected case not found + return -1; + }, + + /** Matches identifier from api model to input elements + */ + matchModel: function(model, callback) { + // final result dictionary + var result = {}; + + // link this + var self = this; + + // search throughout response + function search (id, head) { + for (var i in head) { + var node = head[i]; + var index = node.name; + if (id != '') { + index = id + '|' + index; + } + switch (node.type) { + case 'repeat': + for (var j in node.cache) { + search (index + '_' + j, node.cache[j]); + } + break; + case 'conditional': + var value = node.test_param && node.test_param.value; + var selectedCase = self.matchCase(node, value); + if (selectedCase != -1) { + search (index, node.cases[selectedCase].inputs); + } + break; + default: + var input_id = self.map_dict[index]; + if (input_id) { + callback(input_id, node); + } + } + } + } + + // match all ids and return messages + search('', model.inputs); + + // return matched results + return result; + }, + + /** Matches identifier from api response to input elements + */ + matchResponse: function(response) { + // final result dictionary + var result = {}; + + // link this + var self = this; + + // search throughout response + function search (id, head) { + if (typeof head === 'string') { + var input_id = self.map_dict[id]; + if (input_id) { + result[input_id] = head; + } + } else { + for (var i in head) { + var new_id = i; + if (id !== '') { + var separator = '|'; + if (head instanceof Array) { + separator = '_'; + } + new_id = id + separator + new_id; + } + search (new_id, head[i]); + } + } + } + + // match all ids and return messages + search('', response); + + // return matched results + return result; + }, + + /** Iterate through the form dom and map it to the dictionary. + */ + _iterate: function(parent, dict) { + // get child nodes + var self = this; + var children = $(parent).children(); + children.each(function() { + // get child element + var child = this; + + // get id + var id = $(child).attr('id'); + + // create new branch + if ($(child).hasClass('section-row')) { + // create sub dictionary + dict[id] = {}; + + // add input element if it exists + var input = self.app.input_list[id]; + if (input) { + dict[id] = { + input : input + } + } + + // fill sub dictionary + self._iterate(child, dict[id]); + } else { + self._iterate(child, dict); + } + }); + } +}); + +}); \ No newline at end of file diff --git a/static/scripts/mvc/form/form-input.js b/static/scripts/mvc/form/form-input.js new file mode 100644 index 00000000000..302e391595f --- /dev/null +++ b/static/scripts/mvc/form/form-input.js @@ -0,0 +1,144 @@ +/** + This class creates a form input element wrapper +*/ +define([], function() { + return Backbone.View.extend({ + initialize: function(app, options) { + // link app + this.app = app; + + // set text labels and icons for optional button + this.text_enable = app.options.text_enable || 'Enable'; + this.text_disable = app.options.text_disable || 'Disable'; + this.cls_enable = app.options.cls_enable || 'fa fa-plus-square'; + this.cls_disable = app.options.cls_disable || 'fa fa-minus-square'; + + // link field + this.field = options.field; + this.default_value = options.default_value; + + // set element + this.setElement(this._template(options)); + + // link elements + this.$field = this.$el.find('.ui-table-form-field'); + this.$optional = this.$el.find('.ui-table-form-optional'); + this.$optional_icon = this.$el.find('.ui-table-form-optional').find('.icon'); + this.$error_text = this.$el.find('.ui-table-form-error-text'); + this.$error = this.$el.find('.ui-table-form-error'); + + // add field element + this.$field.prepend(this.field.$el); + + // decide wether to expand or collapse optional fields + this.field.skip = false; + var v = this.field.value && this.field.value(); + this.field.skip = Boolean(options.optional && + (v === null || v === '' || v === this.default_value || + String(v) === String(this.default_value) || + Number(v) === Number(this.default_value) || + JSON.stringify(v) === JSON.stringify(this.default_value))); + + // refresh view + this._refresh(); + + // add optional hide/show + var self = this; + this.$optional.on('click', function() { + // flip flag + self.field.skip = !self.field.skip; + + // refresh view + self._refresh(); + + // refresh state + self.app.trigger('refresh'); + }); + }, + + /** Set error text + */ + error: function(text) { + this.$error_text.html(text); + this.$error.show(); + this.$el.addClass('ui-error'); + }, + + /** Reset this view + */ + reset: function() { + this.$error.hide(); + this.$el.removeClass('ui-error'); + }, + + /** Refresh element + */ + _refresh: function() { + // reset optional button + this.$optional_icon.removeClass().addClass('icon'); + + // identify state + if (!this.field.skip) { + // enable input field + this.$field.fadeIn('fast'); + this._tooltip(this.text_disable, this.cls_disable); + + } else { + // disable input field + this.$field.hide(); + this._tooltip(this.text_enable, this.cls_enable); + // reset field value + this.field.value && this.field.value(this.default_value); + } + }, + + /** Set tooltip text + */ + _tooltip: function(title, cls) { + if (this.$optional.length) { + this.$optional_icon.addClass(cls) + .tooltip({ placement: 'bottom' }) + .attr('data-original-title', title) + .tooltip('fixTitle').tooltip('hide'); + } + }, + + /** Main Template + */ + _template: function(options) { + // create table element + var tmp = '
' + + '
' + + '' + + '
' + + '
'; + + // is optional + if (options.optional) { + tmp += '
' + + '' + options.label + + '
'; + } else { + tmp += options.label; + } + + // finalize title + tmp += '
' + + '
'; + + // add help + tmp += '
'; + if (options.help) { + tmp += options.help; + } + tmp += '
'; + + // finalize + tmp += '
' + + '
'; + + // return input element + return tmp; + } + }); +}); \ No newline at end of file diff --git a/static/scripts/mvc/form/form-parameters.js b/static/scripts/mvc/form/form-parameters.js new file mode 100644 index 00000000000..d392c2c3b97 --- /dev/null +++ b/static/scripts/mvc/form/form-parameters.js @@ -0,0 +1,263 @@ +/** + This class creates input elements. New input parameter types should be added to the types dictionary. +*/ +define(['utils/utils', + 'mvc/ui/ui-misc', + 'mvc/form/form-select-content', + 'mvc/ui/ui-color-picker'], + function(Utils, Ui, SelectContent, ColorPicker) { + + // create form view + return Backbone.Model.extend({ + /** Available parameter types */ + types: { + 'text' : '_fieldText', + 'select' : '_fieldSelect', + 'data_column' : '_fieldSelect', + 'genomebuild' : '_fieldSelect', + 'data' : '_fieldData', + 'data_collection' : '_fieldData', + 'integer' : '_fieldSlider', + 'float' : '_fieldSlider', + 'boolean' : '_fieldBoolean', + 'drill_down' : '_fieldDrilldown', + 'color' : '_fieldColor', + 'hidden' : '_fieldHidden', + 'hidden_data' : '_fieldHidden', + 'baseurl' : '_fieldHidden' + }, + + // initialize + initialize: function(app, options) { + this.app = app; + }, + + /** Returns an input field for a given field type + */ + create: function(input_def) { + // add regular/default value if missing + if (input_def.value === undefined) { + input_def.value = null; + } + if (input_def.default_value === undefined) { + input_def.default_value = input_def.value; + } + + // field wrapper + var field = null; + + // get field class + var fieldClass = this.types[input_def.type]; + if (fieldClass && typeof(this[fieldClass]) === 'function') { + field = this[fieldClass].call(this, input_def); + } + + // identify field type + if (!field) { + // flag + this.app.incompatible = true; + + // with or without options + if (input_def.options) { + // assign select field + field = this._fieldSelect(input_def); + } else { + // assign text field + field = this._fieldText(input_def); + } + + // log + console.debug('tools-form::_addRow() : Auto matched field type (' + input_def.type + ').'); + } + + // set field value + if (input_def.value !== undefined) { + field.value(input_def.value); + } + + // return field element + return field; + }, + + /** Data input field + */ + _fieldData: function(input_def) { + if (!this.app.options.is_dynamic) { + input_def.info = 'Data input \'' + input_def.name + '\' (' + Utils.textify(input_def.extensions.toString()) + ')'; + input_def.value = null; + return this._fieldHidden(input_def); + } + var self = this; + return new SelectContent.View(this.app, { + id : 'field-' + input_def.id, + extensions : input_def.extensions, + optional : input_def.optional, + multiple : input_def.multiple, + type : input_def.type, + data : input_def.options, + onchange : function() { + self.app.trigger('change'); + } + }); + }, + + /** Select/Checkbox/Radio options field + */ + _fieldSelect: function (input_def) { + // show text field in if dynamic fields are disabled e.g. in workflow editor + if (!this.app.options.is_dynamic && input_def.is_dynamic) { + return this._fieldText(input_def); + } + + // customize properties + if (input_def.type == 'data_column') { + input_def.error_text = 'Missing columns in referenced dataset.' + } + if (input_def.type == 'genomebuild') { + input_def.searchable = true; + } + + // configure options fields + var options = []; + for (var i in input_def.options) { + var option = input_def.options[i]; + options.push({ + label: option[0], + value: option[1] + }); + } + + // identify display type + var SelectClass = Ui.Select; + switch (input_def.display) { + case 'checkboxes': + SelectClass = Ui.Checkbox; + break; + case 'radio': + SelectClass = Ui.Radio; + break; + } + + // select field + var self = this; + return new SelectClass.View({ + id : 'field-' + input_def.id, + data : options, + error_text : input_def.error_text || 'No options available', + multiple : input_def.multiple, + searchable : input_def.searchable, + onchange : function() { + self.app.trigger('change'); + } + }); + }, + + /** Drill down options field + */ + _fieldDrilldown: function (input_def) { + // show text field in if dynamic fields are disabled e.g. in workflow editor + if (!this.app.options.is_dynamic && input_def.is_dynamic) { + return this._fieldText(input_def); + } + + // create drill down field + var self = this; + return new Ui.Drilldown.View({ + id : 'field-' + input_def.id, + data : input_def.options, + display : input_def.display, + onchange : function() { + self.app.trigger('change'); + } + }); + }, + + /** Text input field + */ + _fieldText: function(input_def) { + // field replaces e.g. a select field + if (input_def.options) { + // show text area if selecting multiple entries is allowed + input_def.area = input_def.multiple; + + // validate value + if (!Utils.validate(input_def.value)) { + input_def.value = ''; + } else { + if (input_def.value instanceof Array) { + input_def.value = value.toString(); + } else { + input_def.value = String(input_def.value).replace(/[\[\]'"\s]/g, ''); + if (input_def.multiple) { + input_def.value = input_def.value.replace(/,/g, '\n'); + } + } + } + } + + // create input element + var self = this; + return new Ui.Input({ + id : 'field-' + input_def.id, + area : input_def.area, + onchange : function() { + self.app.trigger('change'); + } + }); + }, + + /** Slider field + */ + _fieldSlider: function(input_def) { + var self = this; + return new Ui.Slider.View({ + id : 'field-' + input_def.id, + precise : input_def.type == 'float', + min : input_def.min, + max : input_def.max, + onchange : function() { + self.app.trigger('change'); + } + }); + }, + + /** Hidden field + */ + _fieldHidden: function(input_def) { + return new Ui.Hidden({ + id : 'field-' + input_def.id, + info : input_def.info + }); + }, + + /** Boolean field + */ + _fieldBoolean: function(input_def) { + var self = this; + return new Ui.RadioButton.View({ + id : 'field-' + input_def.id, + data : [ { label : 'Yes', value : 'true' }, + { label : 'No', value : 'false' }], + onchange : function() { + self.app.trigger('change'); + } + }); + }, + + /** Color picker field + */ + _fieldColor: function(input_def) { + var self = this; + return new ColorPicker({ + id : 'field-' + input_def.id, + onchange : function() { + self.app.trigger('change'); + } + }); + } + }); + + return { + View: View + }; +}); diff --git a/static/scripts/mvc/form/form-repeat.js b/static/scripts/mvc/form/form-repeat.js new file mode 100644 index 00000000000..89d1b4818f3 --- /dev/null +++ b/static/scripts/mvc/form/form-repeat.js @@ -0,0 +1,167 @@ +// dependencies +define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc'], + function(Utils, Table, Portlet, Ui) { + +/** This class creates a ui component which enables the dynamic creation of portlets +*/ +var View = Backbone.View.extend({ + // default options + optionsDefault : { + title : 'Section', + max : null, + min : null + }, + + /** Initialize + */ + initialize : function(options) { + // configure options + this.options = Utils.merge(options, this.optionsDefault); + + // create new element + this.setElement('
'); + + // link this + var self = this; + + // create button + this.button_new = new Ui.ButtonIcon({ + icon : 'fa-plus', + title : 'Insert ' + options.title_new, + tooltip : 'Add new ' + options.title_new + ' block', + floating: 'clear', + onclick : function() { + if (options.onnew) { + options.onnew(); + } + } + }); + + // create table + this.table = new Table.View({ + cls : 'ui-table-plain', + content : '' + }); + + // append button + this.$el.append(this.table.$el); + + // add button + this.$el.append($('
').append(this.button_new.$el)); + + // clear list + this.list = {}; + + // number of available repeats + this.n = 0; + }, + + /** Number of repeat blocks + */ + size: function() { + return this.n; + }, + + /** Add new repeat block + */ + add: function(options) { + // repeat block already exists + if (!options.id || this.list[options.id]) { + console.debug('form-repeat::add - Duplicate repeat block id.'); + return; + } + + // increase repeat block counter + this.n++; + + // delete button + var button_delete = new Ui.ButtonIcon({ + icon : 'fa-trash-o', + tooltip : 'Delete this repeat block', + cls : 'ui-button-icon-plain', + onclick : function() { + if (options.ondel) { + options.ondel(); + } + } + }); + + // create portlet + var portlet = new Portlet.View({ + id : options.id, + title : 'placeholder', + cls : 'ui-portlet-repeat', + operations : { + button_delete : button_delete + } + }); + + // append content + portlet.append(options.$el); + + // tag as section row + portlet.$el.addClass('section-row'); + + // append to dom + this.list[options.id] = portlet; + + // append to dom + this.table.add(portlet.$el); + this.table.append('row_' + options.id, true); + + // validate maximum + if (this.options.max > 0 && this.n >= this.options.max) { + this.button_new.disable(); + } + + // refresh view + this._refresh(); + }, + + /** Delete repeat block + */ + del: function(id) { + // could not find element + if (!this.list[id]) { + console.debug('form-repeat::del - Invalid repeat block id.'); + return; + } + + // decrease repeat block counter + this.n--; + + // delete table row + var table_row = this.table.get('row_' + id); + table_row.remove(); + + // remove from list + delete this.list[id]; + + // enable new button + this.button_new.enable(); + + // refresh delete button visibility + this._refresh(); + }, + + /** Refresh view + */ + _refresh: function() { + var index = 0; + for (var id in this.list) { + var portlet = this.list[id]; + portlet.title(++index + ': ' + this.options.title); + if (this.n > this.options.min) { + portlet.showOperation('button_delete'); + } else { + portlet.hideOperation('button_delete'); + } + } + } +}); + +return { + View : View +} + +}); diff --git a/static/scripts/mvc/form/form-section.js b/static/scripts/mvc/form/form-section.js new file mode 100644 index 00000000000..83ef3cda5c3 --- /dev/null +++ b/static/scripts/mvc/form/form-section.js @@ -0,0 +1,343 @@ +/** + This class creates a form section and populates it with input elements. It also handles repeat blocks and conditionals by recursively creating new sub sections. +*/ +define(['utils/utils', + 'mvc/ui/ui-table', + 'mvc/ui/ui-misc', + 'mvc/ui/ui-portlet', + 'mvc/form/form-repeat', + 'mvc/form/form-input', + 'mvc/form/form-parameters'], + function(Utils, Table, Ui, Portlet, Repeat, InputElement, Parameters) { + + // create form view + var View = Backbone.View.extend({ + // initialize + initialize: function(app, options) { + // link app + this.app = app; + + // link inputs + this.inputs = options.inputs; + + // fix table style + options.cls = 'ui-table-plain'; + + // add table class for tr tag + // this assist in transforming the form into a json structure + options.cls_tr = 'section-row'; + + // create table + this.table = new Table.View(options); + + // create parameter handler + this.parameters = new Parameters(app, options); + + // configure portlet and form table + this.setElement(this.table.$el); + + // render section + this.render(); + }, + + /** Render section view + */ + render: function() { + // reset table + this.table.delAll(); + + // load settings elements into table + for (var i in this.inputs) { + this.add(this.inputs[i]); + } + }, + + /** Add a new input element + */ + add: function(input) { + // link this + var self = this; + + // clone definition + var input_def = jQuery.extend(true, {}, input); + + // create unique id + input_def.id = input.id = Utils.uuid(); + + // add to sequential list of inputs + this.app.input_list[input_def.id] = input_def; + + // identify field type + var type = input_def.type; + switch(type) { + // conditional field + case 'conditional': + this._addConditional(input_def); + break; + // repeat block + case 'repeat': + this._addRepeat(input_def); + break; + // customized section + case 'section': + this._addSection(input_def); + break; + // default single element row + default: + this._addRow(input_def); + } + }, + + /** Add a conditional block + */ + _addConditional: function(input_def) { + // link this + var self = this; + + // copy identifier + input_def.test_param.id = input_def.id; + + // build test parameter + var field = this._addRow(input_def.test_param); + + // set onchange event for test parameter + field.options.onchange = function(value) { + // identify the selected case + var selectedCase = self.app.data.matchCase(input_def, value); + + // check value in order to hide/show options + for (var i in input_def.cases) { + // get case + var case_def = input_def.cases[i]; + + // identify subsection name + var section_id = input_def.id + '-section-' + i; + + // identify row + var section_row = self.table.get(section_id); + + // check if non-hidden elements exist + var nonhidden = false; + for (var j in case_def.inputs) { + if (!case_def.inputs[j].hidden) { + nonhidden = true; + break; + } + } + + // show/hide sub form + if (i == selectedCase && nonhidden) { + section_row.fadeIn('fast'); + } else { + section_row.hide(); + } + } + + // refresh form inputs + self.app.trigger('change'); + }; + + // add conditional sub sections + for (var i in input_def.cases) { + // create id tag + var sub_section_id = input_def.id + '-section-' + i; + + // create sub section + var sub_section = new View(this.app, { + inputs : input_def.cases[i].inputs + }); + + // displays as grouped subsection + sub_section.$el.addClass('ui-table-section'); + + // create table row + this.table.add(sub_section.$el); + + // append to table + this.table.append(sub_section_id); + } + + // trigger refresh on conditional input field after all input elements have been created + field.trigger('change'); + }, + + /** Add a repeat block + */ + _addRepeat: function(input_def) { + // link this + var self = this; + + // block index + var block_index = 0; + + // create repeat block element + var repeat = new Repeat.View({ + title : input_def.title, + title_new : input_def.title, + min : input_def.min, + max : input_def.max, + onnew : function() { + // create + create(input_def.inputs); + + // trigger refresh + self.app.trigger('change'); + } + }); + + // helper function to create new repeat blocks + function create (inputs) { + // create id tag + var sub_section_id = input_def.id + '-section-' + (block_index++); + + // create sub section + var sub_section = new View(self.app, { + inputs : inputs + }); + + // add tab + repeat.add({ + id : sub_section_id, + $el : sub_section.$el, + ondel : function() { + // delete repeat block + repeat.del(sub_section_id); + + // trigger refresh + self.app.trigger('change'); + } + }); + } + + // + // add parsed/minimum number of repeat blocks + // + var n_min = input_def.min; + var n_cache = _.size(input_def.cache); + for (var i = 0; i < Math.max(n_cache, n_min); i++) { + var inputs = null; + if (i < n_cache) { + inputs = input_def.cache[i]; + } else { + inputs = input_def.inputs; + } + + // create repeat block + create(inputs); + } + + // create input field wrapper + var input_element = new InputElement(this.app, { + label : input_def.title, + help : input_def.help, + field : repeat + }); + + // create table row + this.table.add(input_element.$el); + + // append row to table + this.table.append(input_def.id); + }, + + /** Add a customized section + */ + _addSection: function(input_def) { + // link this + var self = this; + + // create sub section + var sub_section = new View(self.app, { + inputs : input_def.inputs + }); + + // delete button + var button_visible = new Ui.ButtonIcon({ + icon : 'fa-eye-slash', + tooltip : 'Show/hide section', + cls : 'ui-button-icon-plain' + }); + + // create portlet for sub section + var portlet = new Portlet.View({ + title : input_def.label, + cls : 'ui-portlet-section', + operations : { + button_visible: button_visible + } + }); + portlet.append(sub_section.$el); + portlet.append($('
').addClass('ui-table-form-info').html(input_def.help)); + + // add event handler visibility button + var visible = false; + portlet.$content.hide(); + portlet.$header.css('cursor', 'pointer'); + portlet.$header.on('click', function() { + if (visible) { + visible = false; + portlet.$content.hide(); + button_visible.setIcon('fa-eye-slash'); + } else { + visible = true; + portlet.$content.fadeIn('fast'); + button_visible.setIcon('fa-eye'); + } + }); + + // show sub section if requested + if (input_def.expand) { + portlet.$header.trigger('click'); + } + + // create table row + this.table.add(portlet.$el); + + // append row to table + this.table.append(input_def.id); + }, + + /** Add a single input field element + */ + _addRow: function(input_def) { + // get id + var id = input_def.id; + + // create input field + var field = this.parameters.create(input_def); + + // add to field list + this.app.field_list[id] = field; + + // create input field wrapper + var input_element = new InputElement(this.app, { + label : input_def.label, + default_value : input_def.default_value, + optional : input_def.optional, + help : input_def.help, + field : field + }); + + // add to element list + this.app.element_list[id] = input_element; + + // create table row + this.table.add(input_element.$el); + + // append to table + this.table.append(id); + + // hide row if neccessary + if (input_def.hidden) { + this.table.get(id).hide(); + } + + // return created field + return field; + } + }); + + return { + View: View + }; +}); diff --git a/static/scripts/mvc/form/form-select-content.js b/static/scripts/mvc/form/form-select-content.js new file mode 100644 index 00000000000..28ade6a3a50 --- /dev/null +++ b/static/scripts/mvc/form/form-select-content.js @@ -0,0 +1,348 @@ +// dependencies +define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-tabs', 'mvc/tools/tools-template'], + function(Utils, Ui, Tabs, ToolTemplate) { + + +history = {}; + +var View = Backbone.View.extend({ + // initialize + initialize : function(app, options) { + // link app and options + this.app = app; + this.options = options; + + // link this + var self = this; + + // add element + this.setElement('
'); + + // list of select fieldsFormSection + this.list = {}; + + // radio button options + var radio_buttons = []; + + // identify selector type + if (options.type == 'data_collection') { + this.mode = 'collection'; + } else { + if (options.multiple) { + this.mode = 'multiple'; + } else { + this.mode = 'single'; + } + } + + // set initial state + this.current = this.mode; + this.list = {}; + + // error messages + var extensions = Utils.textify(options.extensions); + var hda_error = 'No dataset available.'; + if (extensions) { + hda_error = 'No ' + extensions + ' dataset available.'; + } + var hdca_error = 'No dataset list available.'; + if (extensions) { + hdca_error = 'No ' + extensions + ' dataset collection available.'; + } + + // add single dataset selector + if (this.mode == 'single') { + radio_buttons.push({ + icon : 'fa-file-o', + value : 'single', + tooltip : 'Single dataset' + }); + this.select_single = new Ui.Select.View({ + optional : options.optional, + error_text : hda_error, + onchange : function() { + self.trigger('change'); + } + }); + this.list['single'] = { + field: this.select_single, + type : 'hda' + }; + } + + // add multiple dataset selector + if (this.mode == 'single' || this.mode == 'multiple') { + radio_buttons.push({ + icon : 'fa-files-o', + value : 'multiple', + tooltip : 'Multiple datasets' + }); + this.select_multiple = new Ui.Select.View({ + multiple : true, + searchable : false, + error_text : hda_error, + onchange : function() { + self.trigger('change'); + } + }); + this.list['multiple'] = { + field: this.select_multiple, + type : 'hda' + }; + } + + // add collection selector + if (this.mode == 'single' || this.mode == 'multiple' || this.mode == 'collection') { + radio_buttons.push({ + icon : 'fa-folder-o', + value : 'collection', + tooltip : 'Dataset collection' + }); + this.select_collection = new Ui.Select.View({ + error_text : hdca_error, + optional : options.optional, + onchange : function() { + self.trigger('change'); + } + }); + this.list['collection'] = { + field: this.select_collection, + type : 'hdca' + }; + } + + // create button + this.button_type = new Ui.RadioButton.View({ + value : this.current, + data : radio_buttons, + onchange: function(value) { + self.current = value; + self.refresh(); + self.trigger('change'); + } + }); + + // add batch mode information + this.$batch = $(this.template_batch()); + + // number of radio buttons + var n_buttons = _.size(this.list); + + // add button to dom + var button_width = 0; + if (n_buttons > 1) { + this.$el.append(this.button_type.$el); + button_width = Math.max(0, _.size(this.list) * 35) + 'px'; + } + + // append field elements + for (var i in this.list) { + this.$el.append(this.list[i].field.$el.css({ + 'margin-left': button_width + })); + } + + // append batch message + this.$el.append(this.$batch.css({ + 'margin-left': button_width + })); + + // update options + this.update(options.data); + + // set initial value + if (this.options.value !== undefined) { + this.value(this.options.value); + } + + // refresh view + this.refresh(); + + // add change event. fires on trigger + this.on('change', function() { + if (options.onchange) { + options.onchange(self.value()); + } + }); + }, + + /** Indicate that select fields are being updated */ + wait: function() { + for (var i in this.list) { + this.list[i].field.wait(); + } + }, + + /** Indicate that the options update has been completed */ + unwait: function() { + for (var i in this.list) { + this.list[i].field.unwait(); + } + }, + + /** Update content selector */ + update: function(options) { + // update a particular select field + var self = this; + function _update(field, options) { + if (field) { + // identify available options + var select_options = []; + for (var i in options) { + var item = options[i]; + select_options.push({ + label: item.hid + ': ' + item.name, + value: item.id + }); + // backup to local history + history[item.id + '_' + item.src] = item; + } + // update field + field.update(select_options); + } + } + + // update available options + _update(this.select_single, options.hda); + _update(this.select_multiple, options.hda); + _update(this.select_collection, options.hdca); + }, + + /** Return the currently selected dataset values */ + value : function (new_value) { + // update current value + if (new_value !== undefined) { + if (new_value && new_value.values) { + try { + // create list with values + var list = []; + for (var i in new_value.values) { + list.push(new_value.values[i].id); + } + + // identify suitable select field + if (new_value && new_value.values.length > 0 && new_value.values[0].src == 'hcda') { + this.current = 'collection'; + this.select_collection.value(list[0]); + } else { + if (this.mode == 'multiple') { + this.current = 'multiple'; + this.select_multiple.value(list); + } else { + this.current = 'single'; + this.select_single.value(list[0]); + } + } + } catch (err) { + console.debug('tools-select-content::value() - Skipped.'); + } + } else { + for (var i in this.list) { + this.list[i].field.value(null); + } + } + } + + // refresh view + this.refresh(); + + // validate value + var id_list = this._select().value(); + if (id_list === null) { + return null; + } + + // transform into an array + if (!(id_list instanceof Array)) { + id_list = [id_list]; + } + + // check if value exists + if (id_list.length === 0) { + return null; + } + + // prepare result dict + var result = { + batch : this._batch(), + values : [] + } + + // append to dataset ids + for (var i in id_list) { + var details = _.findWhere(history, { + id : id_list[i], + src : this.list[this.current].type + }); + if (details) { + result.values.push(details); + } else { + return null; + } + } + + // sort by history ids + result.values.sort(function(a, b){ + return a.hid - b.hid; + }); + + // return + return result; + }, + + /** Refreshes data selection view */ + refresh: function() { + this.button_type.value(this.current); + for (var i in this.list) { + var $el = this.list[i].field.$el; + if (this.current == i) { + $el.show(); + } else { + $el.hide(); + } + } + if (this._batch()) { + this.$batch.show(); + } else { + this.$batch.hide(); + } + }, + + /** Assists in selecting the current field */ + _select: function() { + return this.list[this.current].field; + }, + + /** Assists in identifying the batch mode */ + _batch: function() { + if (this.current == 'collection') { + var hdca = _.findWhere(history, { + id : this._select().value(), + src : 'hdca' + }); + if (hdca && hdca.map_over_type) { + return true; + } + } + if (this.current != 'single') { + if (this.mode == 'single') { + return true; + } + } + return false; + }, + + /** Batch message template */ + template_batch: function() { + return '
' + + '' + + 'This is a batch mode input field. A separate job will be triggered for each dataset.' + + '
'; + } +}); + +return { + View: View +} + +}); diff --git a/static/scripts/mvc/form/form-view.js b/static/scripts/mvc/form/form-view.js new file mode 100644 index 00000000000..5cccd7b04b3 --- /dev/null +++ b/static/scripts/mvc/form/form-view.js @@ -0,0 +1,199 @@ +/** + This is the main class of the form plugin. It is referenced as 'app' in all lower level modules. +*/ +define(['utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', + 'mvc/form/form-section', 'mvc/form/form-data'], + function(Utils, Portlet, Ui, FormSection, FormData) { + + // create form view + return Backbone.View.extend({ + // initialize + initialize: function(options) { + // options + this.optionsDefault = { + // uses dynamic fields instead of text fields + is_dynamic : true, + // shows form in narrow view mode + narrow : false, + // shows errors on start + initial_errors : false, + // portlet style + cls : 'ui-portlet-limited' + }; + + // configure options + this.options = Utils.merge(options, this.optionsDefault); + + // log options + console.debug(this.options); + + // link galaxy modal or create one + var galaxy = parent.Galaxy; + if (galaxy && galaxy.modal) { + this.modal = galaxy.modal; + } else { + this.modal = new Ui.Modal.View(); + } + + // set element + this.setElement('
'); + + // build this form + this._build(); + }, + + /** Set form into wait mode */ + wait: function(active) { + for (var i in this.input_list) { + var field = this.field_list[i]; + var input = this.input_list[i]; + if (input.is_dynamic && field.wait && field.unwait) { + if (active) { + field.wait(); + } else { + field.unwait(); + } + } + } + }, + + /** Shows the final message (usually upon successful job submission) + */ + reciept: function($el) { + this.$el.empty(); + this.$el.append($el); + }, + + /** Highlight and scroll to input element (currently only used for error notifications) + */ + highlight: function (input_id, message, silent) { + // get input field + var input_element = this.element_list[input_id]; + + // check input element + if (input_element) { + // mark error + input_element.error(message || 'Please verify this parameter.'); + + // scroll to first input element + if (!silent) { + $('html, body').animate({ + scrollTop: input_element.$el.offset().top - 20 + }, 500); + } + } + }, + + /** Highlights errors + */ + errors: function(options) { + // hide previous error statements + this.trigger('reset'); + + // highlight all errors + if (options && options.errors) { + var error_messages = this.data.matchResponse(options.errors); + for (var input_id in this.element_list) { + var input = this.element_list[input_id]; + if (error_messages[input_id]) { + this.highlight(input_id, error_messages[input_id], true); + } + } + } + }, + + /** Main tool form build function. This function is called once a new model is available. + */ + _build: function() { + // link this + var self = this; + + // reset events + this.off('change'); + this.off('reset'); + + // reset field list, which contains the input field elements + this.field_list = {}; + + // reset sequential input definition list, which contains the input definitions as provided from the api + this.input_list = {}; + + // reset input element list, which contains the dom elements of each input element (includes also the input field) + this.element_list = {}; + + // creates a json data structure from the input form + this.data = new FormData(this); + + // create ui elements + this._renderForm(); + + // refresh data + this.data.create(); + + // show errors on startup + if (this.options.initial_errors) { + this.errors(this.options); + } + + // add listener which triggers on checksum change + var current_check = this.data.checksum(); + this.on('change', function() { + var new_check = self.data.checksum(); + if (new_check != current_check) { + current_check = new_check; + self.options.onchange && self.options.onchange(); + } + }); + + // add reset listener + this.on('reset', function() { + for (var i in this.element_list) { + this.element_list[i].reset(); + } + }); + }, + + /** Renders the UI elements required for the form + */ + _renderForm: function() { + // create message view + this.message = new Ui.Message(); + + // create tool form section + this.section = new FormSection.View(this, { + inputs : this.options.inputs + }); + + // create portlet + this.portlet = new Portlet.View({ + icon : 'fa-wrench', + title : this.options.title, + cls : this.options.cls, + operations : this.options.operations, + buttons : this.options.buttons + }); + + // append message + this.portlet.append(this.message.$el.addClass('ui-margin-top')); + + // append tool section + this.portlet.append(this.section.$el); + + // start form + this.$el.empty(); + this.$el.append(this.portlet.$el); + + // show message if available in model + if (this.options.message) { + this.message.update({ + persistent : true, + status : 'warning', + message : this.options.message + }); + } + + // log + console.debug('tools-form-base::initialize() - Completed.'); + } + }); +}); diff --git a/static/scripts/mvc/tools/tools-form-base.js b/static/scripts/mvc/tools/tools-form-base.js index db802460058..cbfb0ebedf2 100644 --- a/static/scripts/mvc/tools/tools-form-base.js +++ b/static/scripts/mvc/tools/tools-form-base.js @@ -1,52 +1,22 @@ /** - This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules. + This is the base class of the tool form plugin. It is referenced as 'app' in all lower level modules. */ -define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', - 'mvc/citation/citation-model', 'mvc/citation/citation-view', - 'mvc/tools', 'mvc/tools/tools-template', 'mvc/tools/tools-section', 'mvc/tools/tools-tree'], - function(Utils, Deferred, Portlet, Ui, CitationModel, CitationView, - Tools, ToolTemplate, ToolSection, ToolTree) { +define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view', + 'mvc/tools/tools-template', 'mvc/citation/citation-model', 'mvc/citation/citation-view'], + function(Utils, Deferred, Ui, Form, ToolTemplate, CitationModel, CitationView) { // create form view return Backbone.View.extend({ // initialize initialize: function(options) { - // options - this.optionsDefault = { - // uses dynamic fields instead of text fields - is_dynamic : true, - // shows form in narrow view mode - narrow : false, - // shows errors on start - initial_errors : false, - // portlet style - cls_portlet : 'ui-portlet-limited' - }; - - // configure options - this.options = Utils.merge(options, this.optionsDefault); - - // log options - console.debug(this.options); - - // link galaxy modal or create one - var galaxy = parent.Galaxy; - if (galaxy && galaxy.modal) { - this.modal = galaxy.modal; - } else { - this.modal = new Ui.Modal.View(); - } - // check if the user is an admin + var galaxy = parent.Galaxy; if (galaxy && galaxy.currUser) { this.is_admin = galaxy.currUser.get('is_admin'); } else { this.is_admin = false; } - // link container - this.container = this.options.container || 'body'; - // create deferred processing queue handler // this handler reduces the number of requests to the api by filtering redundant requests this.deferred = new Deferred(); @@ -54,126 +24,52 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', // set element this.setElement('
'); - // add to main element + // append to body + this.container = options.container || 'body'; $(this.container).append(this.$el); - // build this form - this.build(this.options); + // create form + this._buildForm(options); }, - /** Main tool form build function. This function is called once a new model is available. - */ - build: function(options) { + /** Build form */ + _buildForm: function(options) { // link this var self = this; - // reset events - this.off('refresh'); - this.off('reset'); + // configure options + this.options = Utils.merge(options, this.optionsDefault); - // reset field list, which contains the input field elements - this.field_list = {}; - - // reset sequential input definition list, which contains the input definitions as provided from the api - this.input_list = {}; - - // reset input element list, which contains the dom elements of each input element (includes also the input field) - this.element_list = {}; - - // creates a tree/json data structure from the input form - this.tree = new ToolTree(this); - - // keeps track of history items - this.history = {}; - - // update model data - self.options.inputs = options && options.inputs; - - // create ui elements - this._renderForm(options); - - // rebuild the underlying data structure - var current_state = this.tree.finalize(); - - // show errors on startup - if (options.initial_errors) { - this._errors(options); - } - - // add refresh listener - this.on('refresh', function() { - // by using/resetting the deferred ajax queue the number of redundant calls is reduced - self.deferred.reset(); - self.deferred.execute(function(){ - var new_state = self.tree.finalize(); - if (!_.isEqual(new_state, current_state)) { - current_state = new_state; - self._updateModel($.extend(true, {}, current_state)); - } - }); - }); - - // add reset listener - this.on('reset', function() { - for (var i in this.element_list) { - this.element_list[i].reset(); + // merge form options + var form_options = Utils.merge({ + icon : 'fa-wrench', + title : '' + options.name + ' ' + options.description + ' (Galaxy Tool Version ' + options.version + ')', + operations : this._operations(), + onchange : function(current_state) { + // by resetting the deferred ajax queue the number of redundant calls is reduced + self.deferred.reset(); + self.deferred.execute(function(){ + self._updateModel($.extend(true, {}, self.form.data.create())); + }); } - }); + }, options); + + // create form + this.form = new Form(form_options); + + // create footer + this._footer(); + + // create element + this.$el.empty(); + this.$el.append(this.form.$el); }, - /** Shows the final message (usually upon successful job submission) - */ - reciept: function($el) { - $(this.container).empty(); - $(this.container).append($el); - }, - - /** Highlight and scroll to input element (currently only used for error notifications) - */ - highlight: function (input_id, message, silent) { - // get input field - var input_element = this.element_list[input_id]; - - // check input element - if (input_element) { - // mark error - input_element.error(message || 'Please verify this parameter.'); - - // scroll to first input element - if (!silent) { - $('html, body').animate({ - scrollTop: input_element.$el.offset().top - 20 - }, 500); - } - } - }, - - /** Highlights errors - */ - _errors: function(options) { - // hide previous error statements - this.trigger('reset'); - - // highlight all errors - if (options && options.errors) { - var error_messages = this.tree.matchResponse(options.errors); - for (var input_id in this.element_list) { - var input = this.element_list[input_id]; - if (error_messages[input_id]) { - this.highlight(input_id, error_messages[input_id], true); - } - } - } - }, - - /** Renders the UI elements required for the form - */ - _renderForm: function(options) { + // create tool operation menu + _operations: function() { // link this var self = this; - - // create message view - this.message = new Ui.Message(); + var options = self.options; // button for version selection var versions_button = new Ui.ButtonMenu({ @@ -191,8 +87,8 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', icon : 'fa-cube', onclick : function() { // here we update the tool version (some tools encode the version also in the id) - self.options.id = self.options.id.replace(self.options.version, this.version); - self.options.version = this.version; + options.id = options.id.replace(options.version, this.version); + options.version = this.version; // rebuild the model and form self.deferred.reset(); @@ -223,7 +119,7 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', window.open(options.biostar_url + '/p/new/post/'); } }); - + // create search button menu_button.addMenu({ icon : 'fa-search', @@ -246,7 +142,7 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', }); // add admin operations - if (this.is_admin) { + if (self.is_admin) { // create download button menu_button.addMenu({ icon : 'fa-download', @@ -267,14 +163,14 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', onclick : function() { if (!this.visible) { this.visible = true; - self.message.update({ + self.form.message.update({ persistent : true, message : ToolTemplate.requirements(options), status : 'info' }); } else { this.visible = false; - self.message.update({ + self.form.message.update({ message : '' }); } @@ -282,59 +178,36 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', }); } - // add toolshed url - if (this.options.sharable_url) { + if (options.sharable_url) { menu_button.addMenu({ icon : 'fa-external-link', title : 'See in Tool Shed', tooltip : 'Access the repository', onclick : function() { - window.open(self.options.sharable_url); + window.open(options.sharable_url); } }); } - // create tool form section - this.section = new ToolSection.View(self, { - inputs : options.inputs - }); - - // switch to classic tool form mako if the form definition is incompatible - if (this.incompatible) { - this.$el.hide(); - $('#tool-form-classic').show(); - return; + // return operations + return { + menu : menu_button, + versions : versions_button } + }, - // create portlet - this.portlet = new Portlet.View({ - icon : 'fa-wrench', - title : '' + options.name + ' ' + options.description + ' (Galaxy Tool Version ' + options.version + ')', - cls : this.options.cls_portlet, - operations: { - menu : menu_button, - versions : versions_button - }, - buttons : this.buttons - }); - - // append message - this.portlet.append(this.message.$el.addClass('ui-margin-top')); - - // append tool section - this.portlet.append(this.section.$el); - - // start form - this.$el.empty(); - this.$el.append(this.portlet.$el); + // create footer + _footer: function() { + // link options + var options = this.options; // append help if (options.help != '') { - this.$el.append(ToolTemplate.help(options.help)); + this.form.$el.append(ToolTemplate.help(options)); } - // append citations + // create and append tool citations if (options.citations) { var $citations = $('
'); var citations = new CitationModel.ToolCitationCollection(); @@ -342,20 +215,8 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations } ); citation_list_view.render(); citations.fetch(); - this.$el.append($citations); + this.form.$el.append($citations); } - - // show message if available in model - if (options.message) { - this.message.update({ - persistent : true, - status : 'warning', - message : options.message - }); - } - - // log - console.debug('tools-form-base::initialize() - Completed.'); } }); }); diff --git a/static/scripts/mvc/tools/tools-form-workflow.js b/static/scripts/mvc/tools/tools-form-workflow.js index 649e99119ab..9b9e1db1fd1 100644 --- a/static/scripts/mvc/tools/tools-form-workflow.js +++ b/static/scripts/mvc/tools/tools-form-workflow.js @@ -278,7 +278,7 @@ define(['utils/utils', 'mvc/tools/tools-form-base'], _updateModel: function(current_state) { // link self var self = this; - + // create the request dictionary current_state = { tool_id : this.options.id, @@ -306,7 +306,7 @@ define(['utils/utils', 'mvc/tools/tools-form-base'], self.node.update_field_data(data); // highlight errors - self._errors(data && data.tool_model); + self.form.errors(data && data.tool_model); // process completed self.deferred.done(process_id); diff --git a/static/scripts/mvc/tools/tools-form.js b/static/scripts/mvc/tools/tools-form.js index bf27de7bf24..2e94723cd3d 100644 --- a/static/scripts/mvc/tools/tools-form.js +++ b/static/scripts/mvc/tools/tools-form.js @@ -9,8 +9,7 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools // initialize initialize: function(options) { var self = this; - this.job_handler = new ToolJobs(this); - this.buttons = { + options.buttons = { execute : new Ui.Button({ icon : 'fa-check', tooltip : 'Execute: ' + options.name, @@ -18,11 +17,10 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools cls : 'btn btn-primary', floating : 'clear', onclick : function() { - self.job_handler.submit(); + ToolJobs.submit(self.form, options); } }) - } - options.initial_errors = options.job_id; + }; ToolFormBase.prototype.initialize.call(this, options); }, @@ -58,10 +56,10 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools url : model_url, success : function(response) { // build new tool form - self.build(response); + self._buildForm(response); // notification - self.message.update({ + self.form.message.update({ status : 'success', message : 'Now you are using \'' + self.options.name + '\' version ' + self.options.version + '.', persistent : false @@ -84,12 +82,12 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools // show error var error_message = response.error || 'Uncaught error.'; - self.modal.show({ + self.form.modal.show({ title : 'Tool cannot be executed', body : error_message, buttons : { 'Close' : function() { - self.modal.hide(); + self.form.modal.hide(); } } }); @@ -100,8 +98,11 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools /** Request a new model for an already created tool form and updates the form inputs */ _updateModel: function(current_state) { - // create the request dictionary + // link this var self = this; + + // create the request dictionary + var form = this.form; // create the request dictionary var current_state = { @@ -110,23 +111,8 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools inputs : current_state } - // activates/disables spinner for dynamic fields to indicate that they are currently being updated - function wait(active) { - for (var i in self.input_list) { - var field = self.field_list[i]; - var input = self.input_list[i]; - if (input.is_dynamic && field.wait && field.unwait) { - if (active) { - field.wait(); - } else { - field.unwait(); - } - } - } - } - // set wait mode - wait(true); + form.wait(true); // register process var process_id = this.deferred.register(); @@ -145,15 +131,15 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools data : current_state, success : function(new_model) { // update form - self.tree.matchModel(new_model, function(input_id, node) { - var input = self.input_list[input_id]; + form.data.matchModel(new_model, function(input_id, node) { + var input = form.input_list[input_id]; if (input && input.options) { if (!_.isEqual(input.options, node.options)) { // backup new options input.options = node.options; // get/update field - var field = self.field_list[input_id]; + var field = form.field_list[input_id]; if (field.update) { var new_options = []; if ((['data', 'data_collection', 'drill_down']).indexOf(input.type) != -1) { @@ -178,7 +164,7 @@ define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools }); // unset wait mode - wait(false); + form.wait(false); // log success console.debug('tools-form::_refreshForm() - Received new model (see below).'); diff --git a/static/scripts/mvc/tools/tools-jobs.js b/static/scripts/mvc/tools/tools-jobs.js index de5d574c301..c78c9d41112 100644 --- a/static/scripts/mvc/tools/tools-jobs.js +++ b/static/scripts/mvc/tools/tools-jobs.js @@ -1,74 +1,67 @@ /** - This class handles job submissions and the error handling. + This class handles job submissions and validations. */ define(['utils/utils', 'mvc/tools/tools-template'], function(Utils, ToolTemplate) { -return Backbone.Model.extend({ - // initialize - initialize: function(app) { - this.app = app; - }, - - /** Creates and submits a job request to the api - */ - submit: function() { +return { + submit: function(form, options) { // link this var self = this; - + // create job definition for submission to tools api var job_def = { - tool_id : this.app.options.id, - tool_version : this.app.options.version, - inputs : this.app.tree.finalize() + tool_id : options.id, + tool_version : options.version, + inputs : form.data.create() } - + // reset - this.app.trigger('reset'); - + form.trigger('reset'); + // validate job definition - if (!this._validation(job_def)) { + if (!this._validation(form, job_def)) { console.debug('tools-jobs::submit - Submission canceled. Validation failed.'); return; } - + // debug console.debug(job_def); - + // show progress modal var user_is_waiting = true; - this.app.modal.show({title: 'Please wait...', body: 'progress', buttons: { + form.modal.show({title: 'Please wait...', body: 'progress', buttons: { 'Close' : function () { - self.app.modal.hide(); + form.modal.hide(); user_is_waiting = false; } }}); - + // post job Utils.request({ type : 'POST', url : galaxy_config.root + 'api/tools', data : job_def, success : function(response) { - self.app.modal.hide(); - self.app.reciept(ToolTemplate.success(response)); + form.modal.hide(); + form.reciept(ToolTemplate.success(response)); self._refreshHdas(); }, error : function(response, response_full) { - self.app.modal.hide(); + form.modal.hide(); if (response && response.message && response.message.data) { - var error_messages = self.app.tree.matchResponse(response.message.data); + var error_messages = form.data.matchResponse(response.message.data); for (var input_id in error_messages) { - self.app.highlight(input_id, error_messages[input_id]); + form.highlight(input_id, error_messages[input_id]); break; } } else { // show error message with details console.debug(response); - user_is_waiting && self.app.modal.show({ + user_is_waiting && form.modal.show({ title : 'Job submission failed', body : ToolTemplate.error(job_def), buttons : { 'Close' : function() { - self.app.modal.hide(); + form.modal.hide(); } } }); @@ -76,78 +69,78 @@ return Backbone.Model.extend({ } }); }, - + /** Validate job definition */ - _validation: function(job_def) { + _validation: function(form, job_def) { // get input parameters var job_inputs = job_def.inputs; - + // counter for values declared in batch mode var batch_n = -1; var batch_src = null; - + // validation for (var job_input_id in job_inputs) { // get current value var input_value = job_inputs[job_input_id]; - + // collect input field properties - var input_id = this.app.tree.match(job_input_id); - var input_field = this.app.field_list[input_id]; - var input_def = this.app.input_list[input_id]; - + var input_id = form.data.match(job_input_id); + var input_field = form.field_list[input_id]; + var input_def = form.input_list[input_id]; + // check if objects where properly identified if (!input_id || !input_def || !input_field) { console.debug('tools-jobs::_validation - Retrieving input objects failed.'); continue; } - + // validate non-optional fields if (!input_def.optional && input_value == null) { - this.app.highlight(input_id); + form.highlight(input_id); return false; } - + // check if input field is in batch mode if (input_value && input_value.batch) { // get values var n = input_value.values.length; - + // get source var src = null; if (n > 0) { src = input_value.values[0] && input_value.values[0].src; } - + // check source type if (src) { if (batch_src === null) { batch_src = src; } else { if (batch_src !== src) { - this.app.highlight(input_id, 'Please select either dataset or dataset list fields for all batch mode fields.'); + form.highlight(input_id, 'Please select either dataset or dataset list fields for all batch mode fields.'); return false; } } } - + // check number of inputs if (batch_n === -1) { batch_n = n; } else { if (batch_n !== n) { - this.app.highlight(input_id, 'Please make sure that you select the same number of inputs for all batch mode fields. This field contains ' + n + ' selection(s) while a previous field contains ' + batch_n + '.'); + form.highlight(input_id, 'Please make sure that you select the same number of inputs for all batch mode fields. This field contains ' + n + ' selection(s) while a previous field contains ' + batch_n + '.'); return false; } } } } - + // return validation result return true; }, - + /** Refreshes the history panel */ _refreshHdas: function() { @@ -155,6 +148,6 @@ return Backbone.Model.extend({ parent.Galaxy.currHistoryPanel.refreshContents(); } } -}); +}; }); \ No newline at end of file diff --git a/static/scripts/mvc/tools/tools-template.js b/static/scripts/mvc/tools/tools-template.js index 349cb16698c..ad536519d03 100644 --- a/static/scripts/mvc/tools/tools-template.js +++ b/static/scripts/mvc/tools/tools-template.js @@ -3,14 +3,14 @@ define([], function() { // tool form templates return { - help: function(content) { + help: function(options) { return '
' + '
' + - content + + options.help + '
' + '
'; }, - + success: function(response) { // check if (!response.jobs || !response.jobs.length) { @@ -52,13 +52,6 @@ return { '
'; }, - batchMode: function() { - return '
' + - '' + - 'This is a batch mode input field. A separate job will be triggered for each dataset.' + - '
'; - }, - requirements: function(options) { var requirements_message = 'This tool requires '; for (var i in options.requirements) { diff --git a/static/scripts/packed/mvc/form/form-data.js b/static/scripts/packed/mvc/form/form-data.js new file mode 100644 index 00000000000..43c0ea390ed --- /dev/null +++ b/static/scripts/packed/mvc/form/form-data.js @@ -0,0 +1 @@ +define(["utils/utils"],function(a){return Backbone.Model.extend({initialize:function(b){this.app=b},checksum:function(){var c="";for(var b in this.app.field_list){var d=this.app.field_list[b];c+=JSON.stringify(d.value&&d.value())}return c},create:function(){var b=this;var f={};this._iterate(this.app.section.$el,f);var e={};this.map_dict={};function d(i,h,g){b.map_dict[i]=h;e[i]=g}function c(o,r){for(var m in r){var j=r[m];if(j.input){var t=j.input;var n=o;if(o!=""){n+="|"}n+=t.name;switch(t.type){case"repeat":var h="section-";var w=[];var q=null;for(var v in j){var p=v.indexOf(h);if(p!=-1){p+=h.length;w.push(parseInt(v.substr(p)));if(!q){q=v.substr(0,p)}}}w.sort(function(x,i){return x-i});var m=0;for(var k in w){c(n+"_"+m++,j[q+w[k]])}break;case"conditional":var u=b.app.field_list[t.id].value();d(n+"|"+t.test_param.name,t.id,u);var g=b.matchCase(t,u);if(g!=-1){c(n,r[t.id+"-section-"+g])}break;case"section":c("",j);break;default:var s=b.app.field_list[t.id];if(s&&s.value){var u=s.value();if(t.ignore===undefined||t.ignore!=u){d(n,t.id,u);if(t.payload){for(var l in t.payload){d(l,t.id,t.payload[l])}}}}}}}}c("",f);return e},match:function(b){return this.map_dict&&this.map_dict[b]},matchCase:function(b,d){if(b.test_param.type=="boolean"){if(d=="true"){d=b.test_param.truevalue||"true"}else{d=b.test_param.falsevalue||"false"}}for(var c in b.cases){if(b.cases[c].value==d){return c}}return -1},matchModel:function(d,f){var b={};var c=this;function e(g,p){for(var m in p){var k=p[m];var n=k.name;if(g!=""){n=g+"|"+n}switch(k.type){case"repeat":for(var l in k.cache){e(n+"_"+l,k.cache[l])}break;case"conditional":var q=k.test_param&&k.test_param.value;var h=c.matchCase(k,q);if(h!=-1){e(n,k.cases[h].inputs)}break;default:var o=c.map_dict[n];if(o){f(o,k)}}}}e("",d.inputs);return b},matchResponse:function(d){var b={};var c=this;function e(l,j){if(typeof j==="string"){var g=c.map_dict[l];if(g){b[g]=j}}else{for(var h in j){var f=h;if(l!==""){var k="|";if(j instanceof Array){k="_"}f=l+k+f}e(f,j[h])}}}e("",d);return b},_iterate:function(d,e){var b=this;var c=$(d).children();c.each(function(){var h=this;var g=$(h).attr("id");if($(h).hasClass("section-row")){e[g]={};var f=b.app.input_list[g];if(f){e[g]={input:f}}b._iterate(h,e[g])}else{b._iterate(h,e)}})}})}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/form/form-input.js b/static/scripts/packed/mvc/form/form-input.js new file mode 100644 index 00000000000..0c4bb1fdce3 --- /dev/null +++ b/static/scripts/packed/mvc/form/form-input.js @@ -0,0 +1 @@ +define([],function(){return Backbone.View.extend({initialize:function(d,c){this.app=d;this.text_enable=d.options.text_enable||"Enable";this.text_disable=d.options.text_disable||"Disable";this.cls_enable=d.options.cls_enable||"fa fa-plus-square";this.cls_disable=d.options.cls_disable||"fa fa-minus-square";this.field=c.field;this.default_value=c.default_value;this.setElement(this._template(c));this.$field=this.$el.find(".ui-table-form-field");this.$optional=this.$el.find(".ui-table-form-optional");this.$optional_icon=this.$el.find(".ui-table-form-optional").find(".icon");this.$error_text=this.$el.find(".ui-table-form-error-text");this.$error=this.$el.find(".ui-table-form-error");this.$field.prepend(this.field.$el);this.field.skip=false;var b=this.field.value&&this.field.value();this.field.skip=Boolean(c.optional&&(b===null||b===""||b===this.default_value||String(b)===String(this.default_value)||Number(b)===Number(this.default_value)||JSON.stringify(b)===JSON.stringify(this.default_value)));this._refresh();var a=this;this.$optional.on("click",function(){a.field.skip=!a.field.skip;a._refresh();a.app.trigger("refresh")})},error:function(a){this.$error_text.html(a);this.$error.show();this.$el.addClass("ui-error")},reset:function(){this.$error.hide();this.$el.removeClass("ui-error")},_refresh:function(){this.$optional_icon.removeClass().addClass("icon");if(!this.field.skip){this.$field.fadeIn("fast");this._tooltip(this.text_disable,this.cls_disable)}else{this.$field.hide();this._tooltip(this.text_enable,this.cls_enable);this.field.value&&this.field.value(this.default_value)}},_tooltip:function(b,a){if(this.$optional.length){this.$optional_icon.addClass(a).tooltip({placement:"bottom"}).attr("data-original-title",b).tooltip("fixTitle").tooltip("hide")}},_template:function(a){var b='
';if(a.optional){b+='
'+a.label+"
"}else{b+=a.label}b+='
';b+='
';if(a.help){b+=a.help}b+="
";b+="
";return b}})}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/form/form-parameters.js b/static/scripts/packed/mvc/form/form-parameters.js new file mode 100644 index 00000000000..69f736b7159 --- /dev/null +++ b/static/scripts/packed/mvc/form/form-parameters.js @@ -0,0 +1 @@ +define(["utils/utils","mvc/ui/ui-misc","mvc/form/form-select-content","mvc/ui/ui-color-picker"],function(c,d,a,b){return Backbone.Model.extend({types:{text:"_fieldText",select:"_fieldSelect",data_column:"_fieldSelect",genomebuild:"_fieldSelect",data:"_fieldData",data_collection:"_fieldData",integer:"_fieldSlider","float":"_fieldSlider","boolean":"_fieldBoolean",drill_down:"_fieldDrilldown",color:"_fieldColor",hidden:"_fieldHidden",hidden_data:"_fieldHidden",baseurl:"_fieldHidden"},initialize:function(f,e){this.app=f},create:function(e){if(e.value===undefined){e.value=null}if(e.default_value===undefined){e.default_value=e.value}var g=null;var f=this.types[e.type];if(f&&typeof(this[f])==="function"){g=this[f].call(this,e)}if(!g){this.app.incompatible=true;if(e.options){g=this._fieldSelect(e)}else{g=this._fieldText(e)}console.debug("tools-form::_addRow() : Auto matched field type ("+e.type+").")}if(e.value!==undefined){g.value(e.value)}return g},_fieldData:function(e){if(!this.app.options.is_dynamic){e.info="Data input '"+e.name+"' ("+c.textify(e.extensions.toString())+")";e.value=null;return this._fieldHidden(e)}var f=this;return new a.View(this.app,{id:"field-"+e.id,extensions:e.extensions,optional:e.optional,multiple:e.multiple,type:e.type,data:e.options,onchange:function(){f.app.trigger("change")}})},_fieldSelect:function(e){if(!this.app.options.is_dynamic&&e.is_dynamic){return this._fieldText(e)}if(e.type=="data_column"){e.error_text="Missing columns in referenced dataset."}if(e.type=="genomebuild"){e.searchable=true}var g=[];for(var h in e.options){var j=e.options[h];g.push({label:j[0],value:j[1]})}var k=d.Select;switch(e.display){case"checkboxes":k=d.Checkbox;break;case"radio":k=d.Radio;break}var f=this;return new k.View({id:"field-"+e.id,data:g,error_text:e.error_text||"No options available",multiple:e.multiple,searchable:e.searchable,onchange:function(){f.app.trigger("change")}})},_fieldDrilldown:function(e){if(!this.app.options.is_dynamic&&e.is_dynamic){return this._fieldText(e)}var f=this;return new d.Drilldown.View({id:"field-"+e.id,data:e.options,display:e.display,onchange:function(){f.app.trigger("change")}})},_fieldText:function(e){if(e.options){e.area=e.multiple;if(!c.validate(e.value)){e.value=""}else{if(e.value instanceof Array){e.value=value.toString()}else{e.value=String(e.value).replace(/[\[\]'"\s]/g,"");if(e.multiple){e.value=e.value.replace(/,/g,"\n")}}}}var f=this;return new d.Input({id:"field-"+e.id,area:e.area,onchange:function(){f.app.trigger("change")}})},_fieldSlider:function(e){var f=this;return new d.Slider.View({id:"field-"+e.id,precise:e.type=="float",min:e.min,max:e.max,onchange:function(){f.app.trigger("change")}})},_fieldHidden:function(e){return new d.Hidden({id:"field-"+e.id,info:e.info})},_fieldBoolean:function(e){var f=this;return new d.RadioButton.View({id:"field-"+e.id,data:[{label:"Yes",value:"true"},{label:"No",value:"false"}],onchange:function(){f.app.trigger("change")}})},_fieldColor:function(e){var f=this;return new b({id:"field-"+e.id,onchange:function(){f.app.trigger("change")}})}});return{View:View}}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/form/form-repeat.js b/static/scripts/packed/mvc/form/form-repeat.js new file mode 100644 index 00000000000..b503bcb8c7d --- /dev/null +++ b/static/scripts/packed/mvc/form/form-repeat.js @@ -0,0 +1 @@ +define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-portlet","mvc/ui/ui-misc"],function(c,a,b,e){var d=Backbone.View.extend({optionsDefault:{title:"Section",max:null,min:null},initialize:function(g){this.options=c.merge(g,this.optionsDefault);this.setElement("
");var f=this;this.button_new=new e.ButtonIcon({icon:"fa-plus",title:"Insert "+g.title_new,tooltip:"Add new "+g.title_new+" block",floating:"clear",onclick:function(){if(g.onnew){g.onnew()}}});this.table=new a.View({cls:"ui-table-plain",content:""});this.$el.append(this.table.$el);this.$el.append($("
").append(this.button_new.$el));this.list={};this.n=0},size:function(){return this.n},add:function(g){if(!g.id||this.list[g.id]){console.debug("form-repeat::add - Duplicate repeat block id.");return}this.n++;var f=new e.ButtonIcon({icon:"fa-trash-o",tooltip:"Delete this repeat block",cls:"ui-button-icon-plain",onclick:function(){if(g.ondel){g.ondel()}}});var h=new b.View({id:g.id,title:"placeholder",cls:"ui-portlet-repeat",operations:{button_delete:f}});h.append(g.$el);h.$el.addClass("section-row");this.list[g.id]=h;this.table.add(h.$el);this.table.append("row_"+g.id,true);if(this.options.max>0&&this.n>=this.options.max){this.button_new.disable()}this._refresh()},del:function(g){if(!this.list[g]){console.debug("form-repeat::del - Invalid repeat block id.");return}this.n--;var f=this.table.get("row_"+g);f.remove();delete this.list[g];this.button_new.enable();this._refresh()},_refresh:function(){var f=0;for(var h in this.list){var g=this.list[h];g.title(++f+": "+this.options.title);if(this.n>this.options.min){g.showOperation("button_delete")}else{g.hideOperation("button_delete")}}}});return{View:d}}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/form/form-section.js b/static/scripts/packed/mvc/form/form-section.js new file mode 100644 index 00000000000..a60f463dac5 --- /dev/null +++ b/static/scripts/packed/mvc/form/form-section.js @@ -0,0 +1 @@ +define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-portlet","mvc/form/form-repeat","mvc/form/form-input","mvc/form/form-parameters"],function(e,a,h,d,c,f,b){var g=Backbone.View.extend({initialize:function(j,i){this.app=j;this.inputs=i.inputs;i.cls="ui-table-plain";i.cls_tr="section-row";this.table=new a.View(i);this.parameters=new b(j,i);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var j in this.inputs){this.add(this.inputs[j])}},add:function(k){var j=this;var i=jQuery.extend(true,{},k);i.id=k.id=e.uuid();this.app.input_list[i.id]=i;var l=i.type;switch(l){case"conditional":this._addConditional(i);break;case"repeat":this._addRepeat(i);break;case"section":this._addSection(i);break;default:this._addRow(i)}},_addConditional:function(j){var k=this;j.test_param.id=j.id;var n=this._addRow(j.test_param);n.options.onchange=function(w){var v=k.app.data.matchCase(j,w);for(var u in j.cases){var q=j.cases[u];var t=j.id+"-section-"+u;var p=k.table.get(t);var s=false;for(var r in q.inputs){if(!q.inputs[r].hidden){s=true;break}}if(u==v&&s){p.fadeIn("fast")}else{p.hide()}}k.app.trigger("change")};for(var m in j.cases){var l=j.id+"-section-"+m;var o=new g(this.app,{inputs:j.cases[m].inputs});o.$el.addClass("ui-table-section");this.table.add(o.$el);this.table.append(l)}n.trigger("change")},_addRepeat:function(p){var s=this;var q=0;var l=new c.View({title:p.title,title_new:p.title,min:p.min,max:p.max,onnew:function(){n(p.inputs);s.app.trigger("change")}});function n(i){var t=p.id+"-section-"+(q++);var u=new g(s.app,{inputs:i});l.add({id:t,$el:u.$el,ondel:function(){l.del(t);s.app.trigger("change")}})}var j=p.min;var r=_.size(p.cache);for(var m=0;m").addClass("ui-table-form-info").html(i.help));var k=false;l.$content.hide();l.$header.css("cursor","pointer");l.$header.on("click",function(){if(k){k=false;l.$content.hide();m.setIcon("fa-eye-slash")}else{k=true;l.$content.fadeIn("fast");m.setIcon("fa-eye")}});if(i.expand){l.$header.trigger("click")}this.table.add(l.$el);this.table.append(i.id)},_addRow:function(i){var l=i.id;var j=this.parameters.create(i);this.app.field_list[l]=j;var k=new f(this.app,{label:i.label,default_value:i.default_value,optional:i.optional,help:i.help,field:j});this.app.element_list[l]=k;this.table.add(k.$el);this.table.append(l);if(i.hidden){this.table.get(l).hide()}return j}});return{View:g}}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/form/form-select-content.js b/static/scripts/packed/mvc/form/form-select-content.js new file mode 100644 index 00000000000..bfa92e57183 --- /dev/null +++ b/static/scripts/packed/mvc/form/form-select-content.js @@ -0,0 +1 @@ +define(["utils/utils","mvc/ui/ui-misc","mvc/ui/ui-tabs","mvc/tools/tools-template"],function(c,e,b,a){history={};var d=Backbone.View.extend({initialize:function(g,p){this.app=g;this.options=p;var o=this;this.setElement('
');this.list={};var m=[];if(p.type=="data_collection"){this.mode="collection"}else{if(p.multiple){this.mode="multiple"}else{this.mode="single"}}this.current=this.mode;this.list={};var k=c.textify(p.extensions);var j="No dataset available.";if(k){j="No "+k+" dataset available."}var l="No dataset list available.";if(k){l="No "+k+" dataset collection available."}if(this.mode=="single"){m.push({icon:"fa-file-o",value:"single",tooltip:"Single dataset"});this.select_single=new e.Select.View({optional:p.optional,error_text:j,onchange:function(){o.trigger("change")}});this.list.single={field:this.select_single,type:"hda"}}if(this.mode=="single"||this.mode=="multiple"){m.push({icon:"fa-files-o",value:"multiple",tooltip:"Multiple datasets"});this.select_multiple=new e.Select.View({multiple:true,searchable:false,error_text:j,onchange:function(){o.trigger("change")}});this.list.multiple={field:this.select_multiple,type:"hda"}}if(this.mode=="single"||this.mode=="multiple"||this.mode=="collection"){m.push({icon:"fa-folder-o",value:"collection",tooltip:"Dataset collection"});this.select_collection=new e.Select.View({error_text:l,optional:p.optional,onchange:function(){o.trigger("change")}});this.list.collection={field:this.select_collection,type:"hdca"}}this.button_type=new e.RadioButton.View({value:this.current,data:m,onchange:function(i){o.current=i;o.refresh();o.trigger("change")}});this.$batch=$(this.template_batch());var f=_.size(this.list);var n=0;if(f>1){this.$el.append(this.button_type.$el);n=Math.max(0,_.size(this.list)*35)+"px"}for(var h in this.list){this.$el.append(this.list[h].field.$el.css({"margin-left":n}))}this.$el.append(this.$batch.css({"margin-left":n}));this.update(p.data);if(this.options.value!==undefined){this.value(this.options.value)}this.refresh();this.on("change",function(){if(p.onchange){p.onchange(o.value())}})},wait:function(){for(var f in this.list){this.list[f].field.wait()}},unwait:function(){for(var f in this.list){this.list[f].field.unwait()}},update:function(h){var f=this;function g(m,j){if(m){var n=[];for(var k in j){var l=j[k];n.push({label:l.hid+": "+l.name,value:l.id});history[l.id+"_"+l.src]=l}m.update(n)}}g(this.select_single,h.hda);g(this.select_multiple,h.hda);g(this.select_collection,h.hdca)},value:function(j){if(j!==undefined){if(j&&j.values){try{var m=[];for(var h in j.values){m.push(j.values[h].id)}if(j&&j.values.length>0&&j.values[0].src=="hcda"){this.current="collection";this.select_collection.value(m[0])}else{if(this.mode=="multiple"){this.current="multiple";this.select_multiple.value(m)}else{this.current="single";this.select_single.value(m[0])}}}catch(l){console.debug("tools-select-content::value() - Skipped.")}}else{for(var h in this.list){this.list[h].field.value(null)}}}this.refresh();var k=this._select().value();if(k===null){return null}if(!(k instanceof Array)){k=[k]}if(k.length===0){return null}var f={batch:this._batch(),values:[]};for(var h in k){var g=_.findWhere(history,{id:k[h],src:this.list[this.current].type});if(g){f.values.push(g)}else{return null}}f.values.sort(function(n,i){return n.hid-i.hid});return f},refresh:function(){this.button_type.value(this.current);for(var g in this.list){var f=this.list[g].field.$el;if(this.current==g){f.show()}else{f.hide()}}if(this._batch()){this.$batch.show()}else{this.$batch.hide()}},_select:function(){return this.list[this.current].field},_batch:function(){if(this.current=="collection"){var f=_.findWhere(history,{id:this._select().value(),src:"hdca"});if(f&&f.map_over_type){return true}}if(this.current!="single"){if(this.mode=="single"){return true}}return false},template_batch:function(){return'
This is a batch mode input field. A separate job will be triggered for each dataset.
'}});return{View:d}}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/form/form-view.js b/static/scripts/packed/mvc/form/form-view.js new file mode 100644 index 00000000000..ed9ba59b4a5 --- /dev/null +++ b/static/scripts/packed/mvc/form/form-view.js @@ -0,0 +1 @@ +define(["utils/utils","mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/form/form-section","mvc/form/form-data"],function(c,b,e,a,d){return Backbone.View.extend({initialize:function(f){this.optionsDefault={is_dynamic:true,narrow:false,initial_errors:false,cls:"ui-portlet-limited"};this.options=c.merge(f,this.optionsDefault);console.debug(this.options);var g=parent.Galaxy;if(g&&g.modal){this.modal=g.modal}else{this.modal=new e.Modal.View()}this.setElement("
");this._build()},wait:function(j){for(var g in this.input_list){var h=this.field_list[g];var f=this.input_list[g];if(f.is_dynamic&&h.wait&&h.unwait){if(j){h.wait()}else{h.unwait()}}}},reciept:function(f){this.$el.empty();this.$el.append(f)},highlight:function(g,h,f){var i=this.element_list[g];if(i){i.error(h||"Please verify this parameter.");if(!f){$("html, body").animate({scrollTop:i.$el.offset().top-20},500)}}},errors:function(h){this.trigger("reset");if(h&&h.errors){var i=this.data.matchResponse(h.errors);for(var g in this.element_list){var f=this.element_list[g];if(i[g]){this.highlight(g,i[g],true)}}}},_build:function(){var f=this;this.off("change");this.off("reset");this.field_list={};this.input_list={};this.element_list={};this.data=new d(this);this._renderForm();this.data.create();if(this.options.initial_errors){this.errors(this.options)}var g=this.data.checksum();this.on("change",function(){var h=f.data.checksum();if(h!=g){g=h;f.options.onchange&&f.options.onchange()}});this.on("reset",function(){for(var h in this.element_list){this.element_list[h].reset()}})},_renderForm:function(){this.message=new e.Message();this.section=new a.View(this,{inputs:this.options.inputs});this.portlet=new b.View({icon:"fa-wrench",title:this.options.title,cls:this.options.cls,operations:this.options.operations,buttons:this.options.buttons});this.portlet.append(this.message.$el.addClass("ui-margin-top"));this.portlet.append(this.section.$el);this.$el.empty();this.$el.append(this.portlet.$el);if(this.options.message){this.message.update({persistent:true,status:"warning",message:this.options.message})}console.debug("tools-form-base::initialize() - Completed.")}})}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/tools/tools-form-base.js b/static/scripts/packed/mvc/tools/tools-form-base.js index c47d32cea9b..26214149b9c 100644 --- a/static/scripts/packed/mvc/tools/tools-form-base.js +++ b/static/scripts/packed/mvc/tools/tools-form-base.js @@ -1 +1 @@ -define(["utils/utils","utils/deferred","mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-section","mvc/tools/tools-tree"],function(f,g,e,j,h,a,d,c,i,b){return Backbone.View.extend({initialize:function(k){this.optionsDefault={is_dynamic:true,narrow:false,initial_errors:false,cls_portlet:"ui-portlet-limited"};this.options=f.merge(k,this.optionsDefault);console.debug(this.options);var l=parent.Galaxy;if(l&&l.modal){this.modal=l.modal}else{this.modal=new j.Modal.View()}if(l&&l.currUser){this.is_admin=l.currUser.get("is_admin")}else{this.is_admin=false}this.container=this.options.container||"body";this.deferred=new g();this.setElement("
");$(this.container).append(this.$el);this.build(this.options)},build:function(m){var k=this;this.off("refresh");this.off("reset");this.field_list={};this.input_list={};this.element_list={};this.tree=new b(this);this.history={};k.options.inputs=m&&m.inputs;this._renderForm(m);var l=this.tree.finalize();if(m.initial_errors){this._errors(m)}this.on("refresh",function(){k.deferred.reset();k.deferred.execute(function(){var n=k.tree.finalize();if(!_.isEqual(n,l)){l=n;k._updateModel($.extend(true,{},l))}})});this.on("reset",function(){for(var n in this.element_list){this.element_list[n].reset()}})},reciept:function(k){$(this.container).empty();$(this.container).append(k)},highlight:function(l,m,k){var n=this.element_list[l];if(n){n.error(m||"Please verify this parameter.");if(!k){$("html, body").animate({scrollTop:n.$el.offset().top-20},500)}}},_errors:function(m){this.trigger("reset");if(m&&m.errors){var n=this.tree.matchResponse(m.errors);for(var l in this.element_list){var k=this.element_list[l];if(n[l]){this.highlight(l,n[l],true)}}}},_renderForm:function(s){var r=this;this.message=new j.Message();var l=new j.ButtonMenu({icon:"fa-cubes",title:(!s.narrow&&"Versions")||null,tooltip:"Select another tool version"});if(s.versions&&s.versions.length>1){for(var n in s.versions){var p=s.versions[n];if(p!=s.version){l.addMenu({title:"Switch to "+p,version:p,icon:"fa-cube",onclick:function(){r.options.id=r.options.id.replace(r.options.version,this.version);r.options.version=this.version;r.deferred.reset();r.deferred.execute(function(){r._buildModel()})}})}}}else{l.$el.hide()}var o=new j.ButtonMenu({icon:"fa-caret-down",title:(!s.narrow&&"Options")||null,tooltip:"View available options"});if(s.biostar_url){o.addMenu({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(s.biostar_url+"/p/new/post/")}});o.addMenu({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(s.biostar_url+"/local/search/page/?q="+s.name)}})}o.addMenu({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",window.location.origin+galaxy_config.root+"root?tool_id="+s.id)}});if(this.is_admin){o.addMenu({icon:"fa-download",title:"Download",tooltip:"Download this tool",onclick:function(){window.location.href=galaxy_config.root+"api/tools/"+s.id+"/download"}})}if(s.requirements&&s.requirements.length>0){o.addMenu({icon:"fa-info-circle",title:"Requirements",tooltip:"Display tool requirements",onclick:function(){if(!this.visible){this.visible=true;r.message.update({persistent:true,message:c.requirements(s),status:"info"})}else{this.visible=false;r.message.update({message:""})}}})}if(this.options.sharable_url){o.addMenu({icon:"fa-external-link",title:"See in Tool Shed",tooltip:"Access the repository",onclick:function(){window.open(r.options.sharable_url)}})}this.section=new i.View(r,{inputs:s.inputs});if(this.incompatible){this.$el.hide();$("#tool-form-classic").show();return}this.portlet=new e.View({icon:"fa-wrench",title:""+s.name+" "+s.description+" (Galaxy Tool Version "+s.version+")",cls:this.options.cls_portlet,operations:{menu:o,versions:l},buttons:this.buttons});this.portlet.append(this.message.$el.addClass("ui-margin-top"));this.portlet.append(this.section.$el);this.$el.empty();this.$el.append(this.portlet.$el);if(s.help!=""){this.$el.append(c.help(s.help))}if(s.citations){var q=$("
");var k=new h.ToolCitationCollection();k.tool_id=s.id;var m=new a.CitationListView({el:q,collection:k});m.render();k.fetch();this.$el.append(q)}if(s.message){this.message.update({persistent:true,status:"warning",message:s.message})}console.debug("tools-form-base::initialize() - Completed.")}})}); \ No newline at end of file +define(["utils/utils","utils/deferred","mvc/ui/ui-misc","mvc/form/form-view","mvc/tools/tools-template","mvc/citation/citation-model","mvc/citation/citation-view"],function(f,d,g,c,a,e,b){return Backbone.View.extend({initialize:function(h){var i=parent.Galaxy;if(i&&i.currUser){this.is_admin=i.currUser.get("is_admin")}else{this.is_admin=false}this.deferred=new d();this.setElement("
");this.container=h.container||"body";$(this.container).append(this.$el);this._buildForm(h)},_buildForm:function(i){var h=this;this.options=f.merge(i,this.optionsDefault);var j=f.merge({icon:"fa-wrench",title:""+i.name+" "+i.description+" (Galaxy Tool Version "+i.version+")",operations:this._operations(),onchange:function(k){h.deferred.reset();h.deferred.execute(function(){h._updateModel($.extend(true,{},h.form.data.create()))})}},i);this.form=new c(j);this._footer();this.$el.empty();this.$el.append(this.form.$el)},_operations:function(){var j=this;var k=j.options;var n=new g.ButtonMenu({icon:"fa-cubes",title:(!k.narrow&&"Versions")||null,tooltip:"Select another tool version"});if(k.versions&&k.versions.length>1){for(var m in k.versions){var h=k.versions[m];if(h!=k.version){n.addMenu({title:"Switch to "+h,version:h,icon:"fa-cube",onclick:function(){k.id=k.id.replace(k.version,this.version);k.version=this.version;j.deferred.reset();j.deferred.execute(function(){j._buildModel()})}})}}}else{n.$el.hide()}var l=new g.ButtonMenu({icon:"fa-caret-down",title:(!k.narrow&&"Options")||null,tooltip:"View available options"});if(k.biostar_url){l.addMenu({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(k.biostar_url+"/p/new/post/")}});l.addMenu({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(k.biostar_url+"/local/search/page/?q="+k.name)}})}l.addMenu({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",window.location.origin+galaxy_config.root+"root?tool_id="+k.id)}});if(j.is_admin){l.addMenu({icon:"fa-download",title:"Download",tooltip:"Download this tool",onclick:function(){window.location.href=galaxy_config.root+"api/tools/"+k.id+"/download"}})}if(k.requirements&&k.requirements.length>0){l.addMenu({icon:"fa-info-circle",title:"Requirements",tooltip:"Display tool requirements",onclick:function(){if(!this.visible){this.visible=true;j.form.message.update({persistent:true,message:a.requirements(k),status:"info"})}else{this.visible=false;j.form.message.update({message:""})}}})}if(k.sharable_url){l.addMenu({icon:"fa-external-link",title:"See in Tool Shed",tooltip:"Access the repository",onclick:function(){window.open(k.sharable_url)}})}return{menu:l,versions:n}},_footer:function(){var i=this.options;if(i.help!=""){this.form.$el.append(a.help(i))}if(i.citations){var k=$("
");var h=new e.ToolCitationCollection();h.tool_id=i.id;var j=new b.CitationListView({el:k,collection:h});j.render();h.fetch();this.form.$el.append(k)}}})}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/tools/tools-form-workflow.js b/static/scripts/packed/mvc/tools/tools-form-workflow.js index fe8b6febde7..c336f62e9ac 100644 --- a/static/scripts/packed/mvc/tools/tools-form-workflow.js +++ b/static/scripts/packed/mvc/tools/tools-form-workflow.js @@ -1 +1 @@ -define(["utils/utils","mvc/tools/tools-form-base"],function(b,a){var c=a.extend({initialize:function(e){this.node=workflow.active_node;if(!this.node){console.debug("FAILED - tools-form-workflow:initialize() - Node not found in workflow.");return}this.post_job_actions=this.node.post_job_actions||{};this.options=e;this.options.text_enable="Set in Advance";this.options.text_disable="Set at Runtime";this.options.is_dynamic=false;this.options.narrow=true;this.options.initial_errors=true;this.options.cls_portlet="ui-portlet-narrow";b.deepeach(e.inputs,function(f){if(f.type){f.optional=(["data","data_hidden","hidden","repeat","conditional"]).indexOf(f.type)==-1}});b.deepeach(e.inputs,function(f){if(f.type){if(f.type=="conditional"){f.test_param.optional=false}}});var d=this;b.get({url:galaxy_config.root+"api/datatypes",cache:true,success:function(f){d.datatypes=f;d._makeSections(e.inputs);a.prototype.initialize.call(d,e)}})},_makeSections:function(d){d[b.uuid()]={label:"Annotation / Notes",name:"annotation",type:"text",area:true,help:"Add an annotation or note for this step. It will be shown with the workflow.",value:this.node.annotation};var f=this.node.output_terminals&&Object.keys(this.node.output_terminals)[0];if(f){d[b.uuid()]={name:"pja__"+f+"__EmailAction",label:"Email notification",type:"boolean",value:String(Boolean(this.post_job_actions["EmailAction"+f])),ignore:"false",help:"An email notification will be send when the job has completed.",payload:{host:window.location.host}};d[b.uuid()]={name:"pja__"+f+"__DeleteIntermediatesAction",label:"Output cleanup",type:"boolean",value:String(Boolean(this.post_job_actions["DeleteIntermediatesAction"+f])),ignore:"false",help:"Delete intermediate outputs if they are not used as input for another job."};for(var e in this.node.output_terminals){d[b.uuid()]=this._makeSection(e)}}},_makeSection:function(h){var g=[];for(key in this.datatypes){g.push({0:this.datatypes[key],1:this.datatypes[key]})}g.sort(function(j,i){return j.label>i.label?1:j.labelhere for more information.'},{action:"ChangeDatatypeAction",argument:"newtype",label:"Change datatype",type:"select",ignore:"__empty__",value:"__empty__",options:g,help:"This action will change the datatype of the output to the indicated value."},{action:"TagDatasetAction",argument:"tags",label:"Tags",type:"text",value:"",ignore:"",help:"This action will set tags for the dataset."},{label:"Assign columns",type:"section",inputs:[{action:"ColumnSetAction",argument:"chromCol",label:"Chrom column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"startCol",label:"Start column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"endCol",label:"End column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"strandCol",label:"Strand column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"nameCol",label:"Name column",type:"integer",value:"",ignore:""}],help:"This action will set column assignments in the output dataset. Blank fields are ignored."}]};var d=this;function e(n,o){o=o||[];o.push(n);for(var m in n.inputs){var k=n.inputs[m];if(k.action){k.name="pja__"+h+"__"+k.action;if(k.argument){k.name+="__"+k.argument}if(k.payload){for(var s in k.payload){var q=k.payload[s];k.payload[k.name+"__"+s]=q;delete q}}var r=d.post_job_actions[k.action+h];if(r){for(var l in o){o[l].expand=true}if(k.argument){k.value=r.action_arguments&&r.action_arguments[k.argument]||k.value}else{k.value="true"}}}if(k.inputs){e(k,o.slice(0))}}}e(f);return f},_buildModel:function(){Galaxy.modal.show({title:"Coming soon...",body:"This feature has not been implemented yet.",buttons:{Close:function(){Galaxy.modal.hide()}}})},_updateModel:function(e){var d=this;e={tool_id:this.options.id,tool_version:this.options.version,inputs:e};console.debug("tools-form-workflow::_refreshForm() - Refreshing states.");console.debug(e);var g=this.deferred.register();var f=galaxy_config.root+"api/workflows/build_module";b.request({type:"POST",url:f,data:e,success:function(h){d.node.update_field_data(h);d._errors(h&&h.tool_model);d.deferred.done(g);console.debug("tools-form::_refreshForm() - States refreshed.");console.debug(h)},error:function(h){d.deferred.done(g);console.debug("tools-form::_refreshForm() - Refresh request failed.");console.debug(h)}})}});return{View:c}}); \ No newline at end of file +define(["utils/utils","mvc/tools/tools-form-base"],function(b,a){var c=a.extend({initialize:function(e){this.node=workflow.active_node;if(!this.node){console.debug("FAILED - tools-form-workflow:initialize() - Node not found in workflow.");return}this.post_job_actions=this.node.post_job_actions||{};this.options=e;this.options.text_enable="Set in Advance";this.options.text_disable="Set at Runtime";this.options.is_dynamic=false;this.options.narrow=true;this.options.initial_errors=true;this.options.cls_portlet="ui-portlet-narrow";b.deepeach(e.inputs,function(f){if(f.type){f.optional=(["data","data_hidden","hidden","repeat","conditional"]).indexOf(f.type)==-1}});b.deepeach(e.inputs,function(f){if(f.type){if(f.type=="conditional"){f.test_param.optional=false}}});var d=this;b.get({url:galaxy_config.root+"api/datatypes",cache:true,success:function(f){d.datatypes=f;d._makeSections(e.inputs);a.prototype.initialize.call(d,e)}})},_makeSections:function(d){d[b.uuid()]={label:"Annotation / Notes",name:"annotation",type:"text",area:true,help:"Add an annotation or note for this step. It will be shown with the workflow.",value:this.node.annotation};var f=this.node.output_terminals&&Object.keys(this.node.output_terminals)[0];if(f){d[b.uuid()]={name:"pja__"+f+"__EmailAction",label:"Email notification",type:"boolean",value:String(Boolean(this.post_job_actions["EmailAction"+f])),ignore:"false",help:"An email notification will be send when the job has completed.",payload:{host:window.location.host}};d[b.uuid()]={name:"pja__"+f+"__DeleteIntermediatesAction",label:"Output cleanup",type:"boolean",value:String(Boolean(this.post_job_actions["DeleteIntermediatesAction"+f])),ignore:"false",help:"Delete intermediate outputs if they are not used as input for another job."};for(var e in this.node.output_terminals){d[b.uuid()]=this._makeSection(e)}}},_makeSection:function(h){var g=[];for(key in this.datatypes){g.push({0:this.datatypes[key],1:this.datatypes[key]})}g.sort(function(j,i){return j.label>i.label?1:j.labelhere for more information.'},{action:"ChangeDatatypeAction",argument:"newtype",label:"Change datatype",type:"select",ignore:"__empty__",value:"__empty__",options:g,help:"This action will change the datatype of the output to the indicated value."},{action:"TagDatasetAction",argument:"tags",label:"Tags",type:"text",value:"",ignore:"",help:"This action will set tags for the dataset."},{label:"Assign columns",type:"section",inputs:[{action:"ColumnSetAction",argument:"chromCol",label:"Chrom column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"startCol",label:"Start column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"endCol",label:"End column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"strandCol",label:"Strand column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"nameCol",label:"Name column",type:"integer",value:"",ignore:""}],help:"This action will set column assignments in the output dataset. Blank fields are ignored."}]};var d=this;function e(n,o){o=o||[];o.push(n);for(var m in n.inputs){var k=n.inputs[m];if(k.action){k.name="pja__"+h+"__"+k.action;if(k.argument){k.name+="__"+k.argument}if(k.payload){for(var s in k.payload){var q=k.payload[s];k.payload[k.name+"__"+s]=q;delete q}}var r=d.post_job_actions[k.action+h];if(r){for(var l in o){o[l].expand=true}if(k.argument){k.value=r.action_arguments&&r.action_arguments[k.argument]||k.value}else{k.value="true"}}}if(k.inputs){e(k,o.slice(0))}}}e(f);return f},_buildModel:function(){Galaxy.modal.show({title:"Coming soon...",body:"This feature has not been implemented yet.",buttons:{Close:function(){Galaxy.modal.hide()}}})},_updateModel:function(e){var d=this;e={tool_id:this.options.id,tool_version:this.options.version,inputs:e};console.debug("tools-form-workflow::_refreshForm() - Refreshing states.");console.debug(e);var g=this.deferred.register();var f=galaxy_config.root+"api/workflows/build_module";b.request({type:"POST",url:f,data:e,success:function(h){d.node.update_field_data(h);d.form.errors(h&&h.tool_model);d.deferred.done(g);console.debug("tools-form::_refreshForm() - States refreshed.");console.debug(h)},error:function(h){d.deferred.done(g);console.debug("tools-form::_refreshForm() - Refresh request failed.");console.debug(h)}})}});return{View:c}}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/tools/tools-form.js b/static/scripts/packed/mvc/tools/tools-form.js index 34ce8886033..d9d894ce5f4 100644 --- a/static/scripts/packed/mvc/tools/tools-form.js +++ b/static/scripts/packed/mvc/tools/tools-form.js @@ -1 +1 @@ -define(["utils/utils","mvc/ui/ui-misc","mvc/tools/tools-form-base","mvc/tools/tools-jobs"],function(c,e,b,a){var d=b.extend({initialize:function(g){var f=this;this.job_handler=new a(this);this.buttons={execute:new e.Button({icon:"fa-check",tooltip:"Execute: "+g.name,title:"Execute",cls:"btn btn-primary",floating:"clear",onclick:function(){f.job_handler.submit()}})};g.initial_errors=g.job_id;b.prototype.initialize.call(this,g)},_buildModel:function(){var f=this;var g=galaxy_config.root+"api/tools/"+this.options.id+"/build?";if(this.options.job_id){g+="job_id="+this.options.job_id}else{if(this.options.dataset_id){g+="dataset_id="+this.options.dataset_id}else{g+="tool_version="+this.options.version+"&";var i=top.location.href;var j=i.indexOf("?");if(i.indexOf("tool_id=")!=-1&&j!==-1){g+=i.slice(j+1)}}}var h=this.deferred.register();c.request({type:"GET",url:g,success:function(k){f.build(k);f.message.update({status:"success",message:"Now you are using '"+f.options.name+"' version "+f.options.version+".",persistent:false});f.deferred.done(h);console.debug("tools-form::initialize() - Initial tool model ready.");console.debug(k)},error:function(k){f.deferred.done(h);console.debug("tools-form::initialize() - Initial tool model request failed.");console.debug(k);var l=k.error||"Uncaught error.";f.modal.show({title:"Tool cannot be executed",body:l,buttons:{Close:function(){f.modal.hide()}}})}})},_updateModel:function(g){var f=this;var g={tool_id:this.options.id,tool_version:this.options.version,inputs:g};function j(n){for(var l in f.input_list){var m=f.field_list[l];var k=f.input_list[l];if(k.is_dynamic&&m.wait&&m.unwait){if(n){m.wait()}else{m.unwait()}}}}j(true);var i=this.deferred.register();console.debug("tools-form::_refreshForm() - Sending current state (see below).");console.debug(g);var h=galaxy_config.root+"api/tools/"+this.options.id+"/build";c.request({type:"POST",url:h,data:g,success:function(k){f.tree.matchModel(k,function(m,q){var l=f.input_list[m];if(l&&l.options){if(!_.isEqual(l.options,q.options)){l.options=q.options;var r=f.field_list[m];if(r.update){var p=[];if((["data","data_collection","drill_down"]).indexOf(l.type)!=-1){p=l.options}else{for(var o in q.options){var n=q.options[o];if(n.length>2){p.push({label:n[0],value:n[1]})}}}r.update(p);r.trigger("change");console.debug("Updating options for "+m)}}}});j(false);console.debug("tools-form::_refreshForm() - Received new model (see below).");console.debug(k);f.deferred.done(i)},error:function(k){f.deferred.done(i);console.debug("tools-form::_refreshForm() - Refresh request failed.");console.debug(k)}})}});return{View:d}}); \ No newline at end of file +define(["utils/utils","mvc/ui/ui-misc","mvc/tools/tools-form-base","mvc/tools/tools-jobs"],function(c,e,b,a){var d=b.extend({initialize:function(g){var f=this;g.buttons={execute:new e.Button({icon:"fa-check",tooltip:"Execute: "+g.name,title:"Execute",cls:"btn btn-primary",floating:"clear",onclick:function(){a.submit(f.form,g)}})};b.prototype.initialize.call(this,g)},_buildModel:function(){var f=this;var g=galaxy_config.root+"api/tools/"+this.options.id+"/build?";if(this.options.job_id){g+="job_id="+this.options.job_id}else{if(this.options.dataset_id){g+="dataset_id="+this.options.dataset_id}else{g+="tool_version="+this.options.version+"&";var i=top.location.href;var j=i.indexOf("?");if(i.indexOf("tool_id=")!=-1&&j!==-1){g+=i.slice(j+1)}}}var h=this.deferred.register();c.request({type:"GET",url:g,success:function(k){f._buildForm(k);f.form.message.update({status:"success",message:"Now you are using '"+f.options.name+"' version "+f.options.version+".",persistent:false});f.deferred.done(h);console.debug("tools-form::initialize() - Initial tool model ready.");console.debug(k)},error:function(k){f.deferred.done(h);console.debug("tools-form::initialize() - Initial tool model request failed.");console.debug(k);var l=k.error||"Uncaught error.";f.form.modal.show({title:"Tool cannot be executed",body:l,buttons:{Close:function(){f.form.modal.hide()}}})}})},_updateModel:function(g){var f=this;var i=this.form;var g={tool_id:this.options.id,tool_version:this.options.version,inputs:g};i.wait(true);var j=this.deferred.register();console.debug("tools-form::_refreshForm() - Sending current state (see below).");console.debug(g);var h=galaxy_config.root+"api/tools/"+this.options.id+"/build";c.request({type:"POST",url:h,data:g,success:function(k){i.data.matchModel(k,function(m,q){var l=i.input_list[m];if(l&&l.options){if(!_.isEqual(l.options,q.options)){l.options=q.options;var r=i.field_list[m];if(r.update){var p=[];if((["data","data_collection","drill_down"]).indexOf(l.type)!=-1){p=l.options}else{for(var o in q.options){var n=q.options[o];if(n.length>2){p.push({label:n[0],value:n[1]})}}}r.update(p);r.trigger("change");console.debug("Updating options for "+m)}}}});i.wait(false);console.debug("tools-form::_refreshForm() - Received new model (see below).");console.debug(k);f.deferred.done(j)},error:function(k){f.deferred.done(j);console.debug("tools-form::_refreshForm() - Refresh request failed.");console.debug(k)}})}});return{View:d}}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/tools/tools-jobs.js b/static/scripts/packed/mvc/tools/tools-jobs.js index f948d18dd3d..453a86c4828 100644 --- a/static/scripts/packed/mvc/tools/tools-jobs.js +++ b/static/scripts/packed/mvc/tools/tools-jobs.js @@ -1 +1 @@ -define(["utils/utils","mvc/tools/tools-template"],function(b,a){return Backbone.Model.extend({initialize:function(c){this.app=c},submit:function(){var c=this;var e={tool_id:this.app.options.id,tool_version:this.app.options.version,inputs:this.app.tree.finalize()};this.app.trigger("reset");if(!this._validation(e)){console.debug("tools-jobs::submit - Submission canceled. Validation failed.");return}console.debug(e);var d=true;this.app.modal.show({title:"Please wait...",body:"progress",buttons:{Close:function(){c.app.modal.hide();d=false}}});b.request({type:"POST",url:galaxy_config.root+"api/tools",data:e,success:function(f){c.app.modal.hide();c.app.reciept(a.success(f));c._refreshHdas()},error:function(f,h){c.app.modal.hide();if(f&&f.message&&f.message.data){var i=c.app.tree.matchResponse(f.message.data);for(var g in i){c.app.highlight(g,i[g]);break}}else{console.debug(f);d&&c.app.modal.show({title:"Job submission failed",body:a.error(e),buttons:{Close:function(){c.app.modal.hide()}}})}}})},_validation:function(h){var d=h.inputs;var m=-1;var i=null;for(var k in d){var f=d[k];var l=this.app.tree.match(k);var e=this.app.field_list[l];var j=this.app.input_list[l];if(!l||!j||!e){console.debug("tools-jobs::_validation - Retrieving input objects failed.");continue}if(!j.optional&&f==null){this.app.highlight(l);return false}if(f&&f.batch){var g=f.values.length;var c=null;if(g>0){c=f.values[0]&&f.values[0].src}if(c){if(i===null){i=c}else{if(i!==c){this.app.highlight(l,"Please select either dataset or dataset list fields for all batch mode fields.");return false}}}if(m===-1){m=g}else{if(m!==g){this.app.highlight(l,"Please make sure that you select the same number of inputs for all batch mode fields. This field contains "+g+" selection(s) while a previous field contains "+m+".");return false}}}}return true},_refreshHdas:function(){if(parent.Galaxy&&parent.Galaxy.currHistoryPanel){parent.Galaxy.currHistoryPanel.refreshContents()}}})}); \ No newline at end of file +define(["utils/utils","mvc/tools/tools-template"],function(b,a){return{submit:function(g,e){var c=this;var f={tool_id:e.id,tool_version:e.version,inputs:g.data.create()};g.trigger("reset");if(!this._validation(g,f)){console.debug("tools-jobs::submit - Submission canceled. Validation failed.");return}console.debug(f);var d=true;g.modal.show({title:"Please wait...",body:"progress",buttons:{Close:function(){g.modal.hide();d=false}}});b.request({type:"POST",url:galaxy_config.root+"api/tools",data:f,success:function(h){g.modal.hide();g.reciept(a.success(h));c._refreshHdas()},error:function(h,j){g.modal.hide();if(h&&h.message&&h.message.data){var k=g.data.matchResponse(h.message.data);for(var i in k){g.highlight(i,k[i]);break}}else{console.debug(h);d&&g.modal.show({title:"Job submission failed",body:a.error(f),buttons:{Close:function(){g.modal.hide()}}})}}})},_validation:function(e,i){var f=i.inputs;var o=-1;var j=null;for(var l in f){var g=f[l];var m=e.data.match(l);var d=e.field_list[m];var k=e.input_list[m];if(!m||!k||!d){console.debug("tools-jobs::_validation - Retrieving input objects failed.");continue}if(!k.optional&&g==null){e.highlight(m);return false}if(g&&g.batch){var h=g.values.length;var c=null;if(h>0){c=g.values[0]&&g.values[0].src}if(c){if(j===null){j=c}else{if(j!==c){e.highlight(m,"Please select either dataset or dataset list fields for all batch mode fields.");return false}}}if(o===-1){o=h}else{if(o!==h){e.highlight(m,"Please make sure that you select the same number of inputs for all batch mode fields. This field contains "+h+" selection(s) while a previous field contains "+o+".");return false}}}}return true},_refreshHdas:function(){if(parent.Galaxy&&parent.Galaxy.currHistoryPanel){parent.Galaxy.currHistoryPanel.refreshContents()}}}}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/tools/tools-template.js b/static/scripts/packed/mvc/tools/tools-template.js index ef76945eea9..d8821035f8b 100644 --- a/static/scripts/packed/mvc/tools/tools-template.js +++ b/static/scripts/packed/mvc/tools/tools-template.js @@ -1 +1 @@ -define([],function(){return{help:function(a){return'
'+a+"
"},success:function(c){if(!c.jobs||!c.jobs.length){return this.error(c)}var a=c.jobs.length;var d="";if(a==1){d="1 job has"}else{d=a+" jobs have"}var b='

'+d+" been successfully added to the queue - resulting in the following datasets:

";for(var e in c.outputs){b+='

'+c.outputs[e].hid+": "+c.outputs[e].name+"

"}b+="

You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from 'running' to 'finished' if completed successfully or 'error' if problems were encountered.

";return b},error:function(a){return'

The server could not complete the request. Please contact the Galaxy Team if this error persists.

"},batchMode:function(){return'
This is a batch mode input field. A separate job will be triggered for each dataset.
'},requirements:function(a){var d="This tool requires ";for(var b in a.requirements){var c=a.requirements[b];d+=c.name;if(c.version){d+=" (Version "+c.version+")"}if(bhere for more information.'}}}); \ No newline at end of file +define([],function(){return{help:function(a){return'
'+a.help+"
"},success:function(c){if(!c.jobs||!c.jobs.length){return this.error(c)}var a=c.jobs.length;var d="";if(a==1){d="1 job has"}else{d=a+" jobs have"}var b='

'+d+" been successfully added to the queue - resulting in the following datasets:

";for(var e in c.outputs){b+='

'+c.outputs[e].hid+": "+c.outputs[e].name+"

"}b+="

You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from 'running' to 'finished' if completed successfully or 'error' if problems were encountered.

";return b},error:function(a){return'

The server could not complete the request. Please contact the Galaxy Team if this error persists.

"},requirements:function(a){var d="This tool requires ";for(var b in a.requirements){var c=a.requirements[b];d+=c.name;if(c.version){d+=" (Version "+c.version+")"}if(bhere for more information.'}}}); \ No newline at end of file