1 /**
  2  * @fileOverview
  3  * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>)
  4  * @license Copyright (c) 2012-2019 LabKey Corporation
  5  * <p/>
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  * <p/>
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  * <p/>
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * <p/>
 18  */
 19 
 20 /**
 21  * @namespace The Experiment static class allows you to create hidden run groups and other experiment-related functionality.
 22  *            <p>Additional Documentation:
 23  *              <ul>
 24  *                  <li><a href='https://www.labkey.org/Documentation/wiki-page.view?name=moduleassay'>LabKey File-Based Assays</a></li>
 25  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=experiment">LabKey Experiment</a></li>
 26  *              </ul>
 27  *           </p>
 28  */
 29 LABKEY.Experiment = new function()
 30 {
 31     function getSuccessCallbackWrapper(createExpFn, fn, scope)
 32     {
 33         return function(response, options)
 34         {
 35             //ensure response is JSON before trying to decode
 36             var json = null;
 37             var experiment = null;
 38             if (response && response.getResponseHeader && response.getResponseHeader('Content-Type')
 39                     && response.getResponseHeader('Content-Type').indexOf('application/json') >= 0)
 40             {
 41                 json = LABKEY.Utils.decode(response.responseText);
 42                 experiment = createExpFn(json);
 43             }
 44 
 45             if(fn)
 46                 fn.call(scope || this, experiment, response);
 47         };
 48     }
 49 
 50     function _saveBatches(config, createExps)
 51     {
 52         LABKEY.Ajax.request({
 53             url: LABKEY.ActionURL.buildURL("assay", "saveAssayBatch", LABKEY.ActionURL.getContainer()),
 54             method: 'POST',
 55             jsonData: {
 56                 assayId: config.assayId,
 57                 assayName: config.assayName,
 58                 providerName: config.providerName,
 59                 protocolName : config.protocolName,
 60                 batches: config.batches
 61             },
 62             success: getSuccessCallbackWrapper(createExps, config.success, config.scope),
 63             failure: LABKEY.Utils.getCallbackWrapper(config.failure, config.scope, true),
 64             scope: config.scope,
 65             headers: {
 66                 'Content-Type' : 'application/json'
 67             }
 68         });
 69     }
 70 
 71     // normalize the different config object passed in for saveBatch and saveBatches into one config
 72     // appropriate for _saveBatches call above
 73     function getSaveBatchesConfig(config)
 74     {
 75         var wrapConfig = {};
 76 
 77         if (config.batches)
 78         {
 79             wrapConfig.batches = config.batches;
 80         }
 81         else
 82         {
 83             wrapConfig.batches=[];
 84             wrapConfig.batches.push(config.batch);
 85         }
 86         wrapConfig.assayId = config.assayId;
 87         wrapConfig.assayName = config.assayName;
 88         wrapConfig.providerName = config.providerName;
 89         wrapConfig.protocolName = config.protocolName;
 90         wrapConfig.scope = config.scope;
 91         wrapConfig.success = LABKEY.Utils.getOnSuccess(config);
 92         wrapConfig.failure = LABKEY.Utils.getOnFailure(config);
 93         return wrapConfig;
 94     }
 95 
 96     /** @scope LABKEY.Experiment */
 97     return {
 98 
 99         SAMPLE_DERIVATION_PROTOCOL : "Sample Derivation Protocol",
100 
101         /**
102          * Create or recycle an existing run group. Run groups are the basis for some operations, like comparing
103          * MS2 runs to one another.
104          * @param config A configuration object with the following properties:
105          * @param {function} config.success A reference to a function to call with the API results. This
106          * function will be passed the following parameters:
107          * <ul>
108          * <li><b>runGroup:</b> a {@link LABKEY.Exp.RunGroup} object containing properties about the run group</li>
109          * <li><b>response:</b> The XMLHttpResponse object</li>
110          * </ul>
111          * @param {Integer[]} [config.runIds] An array of integer ids for the runs to be members of the group. Either
112          * runIds or selectionKey must be specified.
113          * @param {string} [config.selectionKey] The DataRegion's selectionKey to be used to resolve the runs to be
114          * members of the group. Either runIds or selectionKey must be specified.
115          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
116          * function will be passed the following parameters:
117          * <ul>
118          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
119          * <li><b>response:</b> The XMLHttpResponse object</li>
120          * </ul>
121          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
122          * the current container path will be used.
123          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
124          * @static
125          */
126         createHiddenRunGroup : function (config)
127         {
128             function createExp(json)
129             {
130                 return new LABKEY.Exp.RunGroup(json);
131             }
132 
133             var jsonData = {};
134             if (config.runIds && config.selectionKey)
135             {
136                 throw "Only one of runIds or selectionKey config parameter is allowed for a single call.";
137             }
138             else if (config.runIds)
139             {
140                 jsonData.runIds = config.runIds;
141             }
142             else if (config.selectionKey)
143             {
144                 jsonData.selectionKey = config.selectionKey;
145             }
146             else
147             {
148                 throw "Either the runIds or the selectionKey config parameter is required.";
149             }
150             LABKEY.Ajax.request(
151             {
152                 url : LABKEY.ActionURL.buildURL("experiment", "createHiddenRunGroup", config.containerPath),
153                 method : 'POST',
154                 jsonData : jsonData,
155                 success: getSuccessCallbackWrapper(createExp, LABKEY.Utils.getOnSuccess(config), config.scope),
156                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
157                 headers :
158                 {
159                     'Content-Type' : 'application/json'
160                 }
161             });
162         },
163 
164         /**
165          * Loads a batch from the server.
166          * @param config An object that contains the following configuration parameters
167          * @param {Number} config.assayId The assay protocol id.
168          * @param {Number} config.batchId The batch id.
169          * @param {function} config.success The function to call when the function finishes successfully.
170          * This function will be called with a the parameters:
171          * <ul>
172          * <li><b>batch</b> A new {@link LABKEY.Exp.RunGroup} object.
173          * <li><b>response</b> The original response
174          * </ul>
175          * @param {function} [config.failure] The function to call if this function encounters an error.
176          * This function will be called with the following parameters:
177          * <ul>
178          * <li><b>response</b> The original response
179          * </ul>
180          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
181          * @see The <a href='https://www.labkey.org/Documentation/wiki-page.view?name=moduleassay'>Module Assay</a> documentation for more information.
182          * @example Load batch:
183 LABKEY.Experiment.loadBatch({
184     protocolName: LABKEY.Experiment.SAMPLE_DERIVATION_PROTOCOL,
185     batchId: 12
186 });
187          */
188         loadBatch : function (config)
189         {
190             function createExp(json)
191             {
192                 return new LABKEY.Exp.RunGroup(json.batch);
193             }
194 
195             LABKEY.Ajax.request({
196                 url: LABKEY.ActionURL.buildURL("assay", "getAssayBatch", LABKEY.ActionURL.getContainer()),
197                 method: 'POST',
198                 success: getSuccessCallbackWrapper(createExp, LABKEY.Utils.getOnSuccess(config), config.scope),
199                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
200                 scope: config.scope,
201                 jsonData : {
202                     assayId: config.assayId,
203                     assayName: config.assayName,
204                     protocolName: config.protocolName,
205                     providerName: config.providerName,
206                     batchId: config.batchId
207                 },
208                 headers : {
209                     'Content-Type' : 'application/json'
210                 }
211             });
212         },
213 
214         /**
215          * Loads batches from the server.
216          * @param config An object that contains the following configuration parameters
217          * @param {Number} config.assayId The assay protocol id.
218          * @param {Number} config.batchIds The list of batch ids.
219          * @param {function} config.success The function to call when the function finishes successfully.
220          * This function will be called with a the parameters:
221          * <ul>
222          * <li><b>batches</b> The list of {@link LABKEY.Exp.RunGroup} objects.
223          * <li><b>response</b> The original response
224          * </ul>
225          * @param {function} [config.failure] The function to call if this function encounters an error.
226          * This function will be called with the following parameters:
227          * <ul>
228          * <li><b>response</b> The original response
229          * </ul>
230          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
231          * @see The <a href='https://www.labkey.org/Documentation/wiki-page.view?name=moduleassay'>Module Assay</a> documentation for more information.
232          * @static
233          */
234         loadBatches : function (config)
235         {
236             function createExp(json)
237             {
238                 var batches = [];
239                 if (json.batches) {
240                     for (var i = 0; i < json.batches.length; i++) {
241                         batches.push(new LABKEY.Exp.RunGroup(json.batches[i]));
242                     }
243                 }
244                 return batches;
245             }
246 
247             LABKEY.Ajax.request({
248                 url: LABKEY.ActionURL.buildURL("assay", "getAssayBatches", LABKEY.ActionURL.getContainer()),
249                 method: 'POST',
250                 success: getSuccessCallbackWrapper(createExp, LABKEY.Utils.getOnSuccess(config), config.scope),
251                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
252                 scope: config.scope,
253                 jsonData : {
254                     assayId: config.assayId,
255                     assayName: config.assayName,
256                     protocolName: config.protocolName,
257                     providerName: config.providerName,
258                     batchIds: config.batchIds
259                 },
260                 headers : {
261                     'Content-Type' : 'application/json'
262                 }
263             });
264         },
265 
266         /**
267          * Loads runs from the server.
268          * @param config An object that contains the following configuration parameters
269          * @param {Array} config.lsids. The list of run lsids.
270          * @param {Array} config.runIds The list of run ids.
271          * @param {function} config.success The function to call when the function finishes successfully.
272          * This function will be called with a the parameters:
273          * <ul>
274          * <li><b>runs</b> The list of {@link LABKEY.Exp.Run} objects.
275          * <li><b>response</b> The original response
276          * </ul>
277          * @param {function} [config.failure] The function to call if this function encounters an error.
278          * This function will be called with the following parameters:
279          * <ul>
280          * <li><b>response</b> The original response
281          * </ul>
282          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
283          * @see The <a href='https://www.labkey.org/Documentation/wiki-page.view?name=moduleassay'>Module Assay</a> documentation for more information.
284          * @static
285          */
286         loadRuns : function (config)
287         {
288             function createExp(json)
289             {
290                 var runs = [];
291                 if (json.runs) {
292                     for (var i = 0; i < json.runs.length; i++) {
293                         runs.push(new LABKEY.Exp.Run(json.runs[i]));
294                     }
295                 }
296                 return runs;
297             }
298 
299             LABKEY.Ajax.request({
300                 url: LABKEY.ActionURL.buildURL("assay", "getAssayRuns.api", LABKEY.ActionURL.getContainer()),
301                 method: 'POST',
302                 success: getSuccessCallbackWrapper(createExp, LABKEY.Utils.getOnSuccess(config), config.scope),
303                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
304                 scope: config.scope,
305                 jsonData : {
306                     runIds: config.runIds,
307                     lsids: config.lsids
308                 },
309                 headers : {
310                     'Content-Type' : 'application/json'
311                 }
312             });
313         },
314 
315         /**
316          * Saves a modified batch.
317          * Runs within the batch may refer to existing data and material objects, either inputs or outputs, by ID or LSID.
318          * Runs may also define new data and materials objects by not specifying an ID or LSID in their properties.
319          * @param config An object that contains the following configuration parameters
320          * @param {Number} [config.assayId] Optional assay protocol id, either assayId or protocolName must be specified.
321          * @param {String} [config.protocolName] Optional protocol name to be used for non-assay backed runs. Currently only
322          * SAMPLE_DERIVATION_PROTOCOL is supported.
323          * @param {LABKEY.Exp.RunGroup} config.batch The modified batch object.
324          * @param {function} config.success The function to call when the function finishes successfully.
325          * This function will be called with the following parameters:
326          * <ul>
327          * <li><b>batch</b> A new {@link LABKEY.Exp.RunGroup} object.  Some values (such as IDs and LSIDs) will be filled in by the server.
328          * <li><b>response</b> The original response
329          * </ul>
330          * @param {function} [config.failure] The function to call if this function encounters an error.
331          * This function will be called with the following parameters:
332          * <ul>
333          * <li><b>response</b> The original response
334          * </ul>
335          * @see The <a href='https://www.labkey.org/Documentation/wiki-page.view?name=moduleassay'>Module Assay</a> documentation for more information.
336          * @example Save batch:
337 LABKEY.Experiment.saveBatch({
338     protocolName: LABKEY.Experiment.SAMPLE_DERIVATION_PROTOCOL,
339     batch: {
340         properties: {
341             // property URI from a Vocabulary
342             'urn:lsid:labkey.com:Vocabulary.Folder-114:MyVocab#field1': '123'
343         },
344         runs: [{
345             name: 'two',
346             properties: {
347                 // property URI from a Vocabulary
348                 'urn:lsid:labkey.com:Vocabulary.Folder-114:MyVocab#field1': '123'
349             }
350         }]
351     }
352 });
353          */
354         saveBatch : function (config)
355         {
356             _saveBatches(getSaveBatchesConfig(config), function(json) {
357                 if (json.batches) {
358                     return new LABKEY.Exp.RunGroup(json.batches[0])
359                 }
360              });
361         },
362 
363         saveRuns: function (config)
364         {
365             function createExp(json)
366             {
367                 var runs = [];
368                 if (json.runs) {
369                     for (var i = 0; i < json.runs.length; i++) {
370                         runs.push(new LABKEY.Exp.Run(json.runs[i]));
371                     }
372                 }
373                 return runs;
374             }
375 
376             LABKEY.Ajax.request({
377                 url: LABKEY.ActionURL.buildURL("assay", "saveAssayRuns.api", LABKEY.ActionURL.getContainer()),
378                 method: 'POST',
379                 success: getSuccessCallbackWrapper(createExp, LABKEY.Utils.getOnSuccess(config), config.scope),
380                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
381                 scope: config.scope,
382                 jsonData : {
383                     assayId: config.assayId,
384                     assayName: config.assayName,
385                     providerName: config.providerName,
386                     protocolName: config.protocolName,
387                     runs: config.runs,
388                 },
389                 scope: config.scope,
390                 headers: {
391                     'Content-Type' : 'application/json'
392                 }
393             });
394         },
395 
396         /**
397          * Saves an array of modified batches.
398          * Runs within the batches may refer to existing data and material objects, either inputs or outputs, by ID or LSID.
399          * Runs may also define new data and materials objects by not specifying an ID or LSID in their properties.
400          * @param config An object that contains the following configuration parameters
401          * @param {Number} config.assayId The assay protocol id.
402          * @param {LABKEY.Exp.RunGroup[]} config.batches The modified batch objects.
403          * @param {function} config.success The function to call when the function finishes successfully.
404          * This function will be called with the following parameters:
405          * <ul>
406          * <li><b>batches</b> An array of new {@link LABKEY.Exp.RunGroup} objects.  Some values (such as IDs and LSIDs) will be filled in by the server.
407          * <li><b>response</b> The original response
408          * </ul>
409          * @param {function} [config.failure] The function to call if this function encounters an error.
410          * This function will be called with the following parameters:
411          * <ul>
412          * <li><b>response</b> The original response
413          * </ul>
414          * @see The <a href='https://www.labkey.org/Documentation/wiki-page.view?name=moduleassay'>Module Assay</a> documentation for more information.
415          * @static
416          */
417         saveBatches : function (config)
418         {
419             _saveBatches(getSaveBatchesConfig(config), function(json){
420                 var batches = [];
421                 if (json.batches) {
422                     for (var i = 0; i < json.batches.length; i++) {
423                         batches.push(new LABKEY.Exp.RunGroup(json.batches[i]));
424                     }
425                 }
426                 return batches;
427             });
428         },
429 
430         /**
431          * Saves materials.
432          * @deprecated Use LABKEY.Query.insertRows({schemaName: 'Samples', queryName: '<sample set name>', ...});
433          *
434          * @param config An object that contains the following configuration parameters
435          * @param config.name name of the sample set
436          * @param config.materials An array of LABKEY.Exp.Material objects to be saved.
437          * @param {function} config.success The function to call when the function finishes successfully.
438          * This function will be called with the following parameters:
439          * <ul>
440          * <li><b>batch</b> A new {@link LABKEY.Exp.RunGroup} object.  Some values will be filled in by the server.
441          * <li><b>response</b> The original response
442          * </ul>
443          * @param {function} [config.failure] The function to call if this function encounters an error.
444          * This function will be called with the following parameters:
445          * <ul>
446          * <li><b>response</b> The original response
447          * </ul>
448          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
449          * @static
450          */
451         saveMaterials : function (config)
452         {
453             LABKEY.Query.insertRows({
454                 schemaName: 'Samples',
455                 queryName: config.name,
456                 rows: config.materials,
457                 success: LABKEY.Utils.getOnSuccess(config),
458                 failure: LABKEY.Utils.getOnFailure(config),
459                 scope: config.scope
460             });
461         },
462 
463         /**
464          * Get parent/child relationships of ExpData, ExpMaterial, or ExpRun.
465          * @param config
466          * @param config.lsids Array of LSIDs for the seed ExpData, ExpMaterials, or ExpRun.
467          * @param {Number} [config.depth] An optional depth argument.  Defaults to include all.
468          * @param {Boolean} [config.parents] Include parents in the lineage response.  Defaults to true.
469          * @param {Boolean} [config.children] Include children in the lineage response.  Defaults to true.
470          * @param {String} [config.expType] Optional experiment type to filter response -- either "Data", "Material", or "ExperimentRun".  Defaults to include all.
471          * @param {String} [config.cpasType] Optional LSID of a SampleSet or DataClass to filter the response.  Defaults to include all.
472          * @static
473          */
474         lineage : function (config)
475         {
476             var params = {};
477             if (config.lsids) {
478                 params.lsids = config.lsids;
479             }
480             else if (config.lsid) {
481                 // Allow singluar 'lsid' for backwards compatibility with <19.3.
482                 // Response will include a top-level 'seed' instead of 'seeds' property.
483                 params.lsid = config.lsid;
484             }
485 
486             if (config.hasOwnProperty('parents'))
487                 params.parents = config.parents;
488             if (config.hasOwnProperty('children'))
489                 params.children = config.children;
490             if (config.hasOwnProperty('depth'))
491                 params.depth = config.depth;
492 
493             if (config.expType)
494                 params.expType = config.expType;
495             if (config.cpasType)
496                 params.cpasType = config.cpasType;
497 
498             LABKEY.Ajax.request({
499                 method: 'GET',
500                 url: LABKEY.ActionURL.buildURL("experiment", "lineage.api"),
501                 params: params,
502                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
503                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
504                 scope: config.scope
505             });
506         }
507     };
508 };
509 
510 if (typeof LABKEY.Exp == "undefined")
511     LABKEY.Exp = {};
512 
513 /**
514  * This constructor isn't called directly, but is used by derived classes.
515  * @class The experiment object base class describes basic
516  * characteristics of a protocol or an experimental run.  Many experiment classes (such as {@link LABKEY.Exp.Run},
517  * {@link LABKEY.Exp.Data} and {@link LABKEY.Exp.Material}) are subclasses
518  * of ExpObject, so they provide the fields defined by this object (e.g., name, lsid, etc).
519  * In a Java representation of these same classes, ExpObject is an abstract class.
520  *            <p>Additional Documentation:
521  *              <ul>
522  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=experiment">LabKey Experiment</a></li>
523  *              </ul>
524  *           </p>
525  * @memberOf LABKEY.Exp
526  *
527  * @param {Object} [config] Configuration object.
528  *
529  * @param {String} config.lsid The LSID of the ExpObject.
530  * @param {String} config.name The name of the ExpObject.
531  * @param {number} config.id The id of the ExpObject.
532  * @param {number} config.rowId The id of the ExpObject (alias of id property)
533  * @param {String} config.comment User editable comment.
534  * @param {Date} config.created When the ExpObject was created.
535  * @param {String} config.createdBy The person who created the ExpObject.
536  * @param {Date} config.modified When the ExpObject was last modified.
537  * @param {String} config.modifiedBy The person who last modified the ExpObject.
538  * @param {Object} config.properties Map of property descriptor names to values. Most types, such as strings and
539  * numbers, are just stored as simple properties. Properties of type FileLink will be returned by the server in the
540  * same format as {@link LABKEY.Exp.Data} objects (missing many properties such as id and createdBy if they exist on disk but
541  * have no row with metadata in the database). FileLink values are accepted from the client in the same way, or a simple value of the
542  * following three types:  the data's RowId, the data's LSID, or the full path on the server's file system.
543  */
544 LABKEY.Exp.ExpObject = function (config) {
545     config = config || {};
546     this.lsid = config.lsid;
547     this.name = config.name;
548     this.id = config.id || config.rowId;
549     this.rowId = this.id;
550     this.comment = config.comment;
551     this.created = config.created;
552     this.createdBy = config.createdBy;
553     this.modified = config.modified;
554     this.modifiedBy = config.modifiedBy;
555     this.properties = config.properties || {};
556 };
557 
558 /**
559  * Constructs a new experiment run object.
560  * @class The Exp.Run class describes an experiment run.  An experiment run is an application of an experimental
561  * protocol to concrete inputs, producing concrete outputs. In object-oriented terminology, a protocol would be a class
562  * while a run would be an instance.
563  *            <p>Additional Documentation:
564  *              <ul>
565  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=experiment">LabKey Experiment</a></li>
566  *              </ul>
567  *           </p>
568  * @extends LABKEY.Exp.ExpObject
569  * @memberOf LABKEY.Exp
570  *
571  * @param {Object} [config] The configuration object.  Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
572  * @param {Object[]} config.dataInputs Array of {@link LABKEY.Exp.Data} objects that are the inputs to this run. Datas typically represent a file on the server's file system.
573  * @param {Object[]} config.dataOutputs Array of {@link LABKEY.Exp.Data} objects that are the outputs from this run. Datas typically represent a file on the server's file system.
574  * @param {Object[]} config.dataRows Array of Objects where each Object corresponds to a row in the results domain.
575  * @param {Object[]} config.materialInputs Array of {@link LABKEY.Exp.Material} objects that are material/sample inputs to the run.
576  * @param {Object[]} config.materialOutputs Array of {@link LABKEY.Exp.Material} objects that are material/sample outputs from the run.
577  *
578  * @see LABKEY.Exp.Data#getContent
579  *
580  * @example
581  * var result = // ... result of uploading a new assay results file
582  * var data = new LABKEY.Exp.Data(result);
583  *
584  * var run = new LABKEY.Exp.Run();
585  * run.name = data.name;
586  * run.properties = { "MyRunProperty" : 3 };
587  * run.dataInputs = [ data ];
588  *
589  * data.getContent({
590  *   format: 'jsonTSV',
591  *   success: function (content, format) {
592  *     data.content = content;
593  *     var sheet = content.sheets[0];
594  *     var filedata = sheet.data;
595  *
596  *     // transform the file content into the dataRows array used by the run
597  *     run.dataRows = [];
598  *     for (var i = 1; i < filedata.length; i++) {
599  *       var row = filedata[i];
600  *       run.dataRows.push({
601  *         "SampleId": row[0],
602  *         "DataValue": row[1],
603  *         // ... other columns
604  *       });
605  *     }
606  *
607  *     var batch = // ... the LABKEY.Exp.RunGroup object
608  *     batch.runs.push(run);
609  *   },
610  *   failure: function (error, format) {
611  *     alert("error: " + error);
612  *   }
613  * });
614  */
615 LABKEY.Exp.Run = function (config) {
616     LABKEY.Exp.ExpObject.call(this, config);
617     config = config || {};
618 
619     this.experiments = config.experiments || [];
620     this.protocol = config.protocol;
621     this.filePathRoot = config.filePathRoot;
622 
623     this.dataInputs = [];
624     if (config.dataInputs) {
625         for (var i = 0; i < config.dataInputs.length; i++) {
626             this.dataInputs.push(new LABKEY.Exp.Data(config.dataInputs[i]));
627         }
628     }
629 
630     this.dataOutputs = config.dataOutputs || [];
631     this.dataRows = config.dataRows || [];
632     this.materialInputs = config.materialInputs || [];
633     this.materialOutputs = config.materialOutputs || [];
634     this.objectProperties = config.objectProperties || {};
635 };
636 LABKEY.Exp.Run.prototype = new LABKEY.Exp.ExpObject;
637 LABKEY.Exp.Run.prototype.constructor = LABKEY.Exp.Run;
638 
639 /**
640  * Deletes the run from the database.
641  * @param config An object that contains the following configuration parameters
642  * @param {Function} config.success A reference to a function to call with the API results. This
643  * function will be passed the following parameters:
644  * <ul>
645  * <li><b>data:</b> a simple object with one property called 'success' which will be set to true.</li>
646  * <li><b>response:</b> The XMLHttpResponse object</li>
647  * </ul>
648  * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
649  * function will be passed the following parameters:
650  * <ul>
651  * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
652  * <li><b>response:</b> The XMLHttpResponse object</li>
653  * </ul>
654  */
655 LABKEY.Exp.Run.prototype.deleteRun = function(config)
656 {
657     LABKEY.Ajax.request(
658     {
659         url : LABKEY.ActionURL.buildURL("experiment", "deleteRun"),
660         method : 'POST',
661         params : { runId : this.id },
662         success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), this, false),
663         failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), this, true)
664     });
665 };
666 
667 
668 
669 /**
670  * The Protocol constructor is private.
671  * @class Experiment protocol.
672  * @extends LABKEY.Exp.ExpObject
673  * @memberOf LABKEY.Exp
674  *
675  * @param {Object} [config] private constructor argument.  Inherits config properties of {@link LABKEY.Exp.ExpObject}.
676  *
677  * @ignore hide from JsDoc for now
678  */
679 LABKEY.Exp.Protocol = function (config) {
680     LABKEY.Exp.ExpObject.call(this, config);
681     config = config || {};
682 
683     this.instrument = config.instrument;
684     this.software = config.software;
685     this.contact = config.contact;
686     this.childProtocols = config.childProtocols || [];
687     this.steps = config.steps || [];
688     this.applicationType = config.applicationType;
689     this.description = config.description;
690     this.runs = [];
691     if (config.runs) {
692         for (var i = 0; i < config.runs.length; i++) {
693             this.runs.push(new LABKEY.Exp.Run(config.runs[i]));
694         }
695     }
696 };
697 LABKEY.Exp.Protocol.prototype = new LABKEY.Exp.ExpObject;
698 LABKEY.Exp.Protocol.prototype.constructor = LABKEY.Exp.Protocol;
699 
700 /**
701  * The RunGroup constructor is private.  To retrieve a batch RunGroup
702  * from the server, see {@link LABKEY.Experiment.loadBatch}.
703  * @class An experiment run group contains an array of
704  * {@link LABKEY.Exp.Run}s.  If all runs have the same assay protocol, the run group
705  * is considered a batch.  To add runs to a batch, insert new {@link LABKEY.Exp.Run}
706  * instances into to the 'runs' Array and save the batch.
707  * <p>
708  * Use {@link LABKEY.Experiment.loadBatch} and {@link LABKEY.Experiment.saveBatch} to
709  * load and save a RunGroup.
710  * </p>
711  *            <p>Additional Documentation:
712  *              <ul>
713  *                  <li><a href='https://www.labkey.org/Documentation/wiki-page.view?name=moduleassay'>LabKey File-Based Assays</a></li>
714  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=experiment">LabKey Experiment</a></li>
715  *              </ul>
716  *           </p>
717  * @extends LABKEY.Exp.ExpObject
718  * @memberOf LABKEY.Exp
719  *
720  * @param {Object} [config] Private configuration object. Inherits config properties of {@link LABKEY.Exp.ExpObject}.
721  * @param {LABKEY.Exp.Run[]} config.runs Array of {@link LABKEY.Exp.Run}s in this run group.
722  * @param {Boolean} config.hidden Determines whether the RunGroup is hidden.
723  */
724 LABKEY.Exp.RunGroup = function (config) {
725     LABKEY.Exp.ExpObject.call(this, config);
726     config = config || {};
727 
728     this.batchProtocolId = config.batchProtocolId || 0;
729     this.runs = [];
730     if (config.runs) {
731         for (var i = 0; i < config.runs.length; i++) {
732             this.runs.push(new LABKEY.Exp.Run(config.runs[i]));
733         }
734     }
735     //this.protocols = config.protocols || [];
736     //this.batchProtocol = config.batchProtocol;
737     this.hidden = config.hidden;
738 };
739 LABKEY.Exp.RunGroup.prototype = new LABKEY.Exp.ExpObject;
740 LABKEY.Exp.RunGroup.prototype.constructor = LABKEY.Exp.RunGroup;
741 
742 /**
743  * The ProtocolApplication constructor is private.
744  * @class Experiment ProtocolApplication.
745  * @extends LABKEY.Exp.ExpObject
746  * @memberOf LABKEY.Exp
747  *
748  * @param {Object} [config] Private configuration object. Inherits config properties of {@link LABKEY.Exp.ExpObject}.
749  *
750  * @ignore hide from JsDoc for now
751  */
752 LABKEY.Exp.ProtocolApplication = function (config) {
753     LABKEY.Exp.ExpObject.call(this, config);
754     config = config || {};
755 
756 };
757 LABKEY.Exp.ProtocolApplication.prototype = new LABKEY.Exp.ExpObject;
758 LABKEY.Exp.ProtocolApplication.prototype.constructor = LABKEY.Exp.ProtocolApplication;
759 
760 /**
761  * @class The SampleSet class describes a collection of experimental samples, which are
762  * also known as materials (see {@link LABKEY.Exp.Material}). This class defines the set of fields that
763  * you you wish to attach to all samples in the group. These fields supply characteristics of the sample
764  * (e.g., its volume, number of cells, color, etc.).
765  *            <p>Additional Documentation:
766  *              <ul>
767  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=experiment">LabKey Experiment</a></li>
768  *              </ul>
769  *           </p>
770  * @extends LABKEY.Exp.ExpObject
771  * @memberOf LABKEY.Exp
772  *
773  * @param {Object} [config] Describes the SampleSet's properties.  Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
774  * @param {Object[]} config.samples Array of {@link LABKEY.Exp.Material} config objects.
775  * @param {String} config.description Description of the SampleSet
776  */
777 
778 LABKEY.Exp.SampleSet = function (config) {
779     LABKEY.Exp.ExpObject.call(this, config);
780     config = config || {};
781     this.samples = config.samples;
782     this.description = config.description;
783 };
784 LABKEY.Exp.SampleSet.prototype = new LABKEY.Exp.ExpObject;
785 LABKEY.Exp.SampleSet.prototype.constructor = LABKEY.Exp.SampleSet;
786 
787 /**
788  * Get a domain design for the SampleSet.
789  *
790  * @param {Function} config.success Required. Function called if the
791  *	"getDomain" function executes successfully. Will be called with the argument {@link LABKEY.Domain.DomainDesign},
792  *    which describes the fields of a domain.
793  * @param {Function} [config.failure] Function called if execution of the "getDomain" function fails.
794  * @param {String} [config.containerPath] The container path in which the requested Domain is defined.
795  *       If not supplied, the current container path will be used.
796  *
797  * @ignore hide from JsDoc for now
798  *
799  * @example
800  * <script type="text/javascript">
801  *   Ext.onReady(function() {
802  *     var ss = new LABKEY.Exp.SampleSet({name: 'MySampleSet'});
803  *     ss.getDomain({
804  *       success : function (domain) {
805  *         console.log(domain);
806  *       }
807  *     });
808  *   }
809  * </script>
810  */
811 LABKEY.Exp.SampleSet.prototype.getDomain = function (config)
812 {
813     LABKEY.Domain.get(LABKEY.Utils.getOnSuccess(config), LABKEY.Utils.getOnFailure(config), "Samples", this.name, config.containerPath);
814 };
815 
816 /**
817  * Create a new Sample Set definition.
818  * @param {Function} config.success Required callback function.
819  * @param {Function} [config.failure] Failure callback function.
820  * @param {LABKEY.Domain.DomainDesign} config.domainDesign The domain design to save.
821  * @param {Object} [config.options] Set of extra options used when creating the SampleSet:
822  * <ul>
823  *   <li>idCols: Optional. Array of indexes into the domain design fields.  If the domain design contains a 'Name' field, no idCols are allowed.  Either a 'Name' field must be present or at least one idCol must be supplied..
824  *   <li>parentCol: Optional. Index of the parent id column.
825  * </ul>
826  * @param {String} [config.containerPath] The container path in which to create the domain.
827  * @static
828  *
829  * @ignore hide from JsDoc for now
830  *
831  * @example
832  * var domainDesign = {
833  *   name: "BoyHowdy",
834  *   description: "A client api created sample set",
835  *   fields: [{
836  *     name: 'TestName',
837  *     label: 'The First Field',
838  *     rangeURI: 'http://www.w3.org/2001/XMLSchema#string'
839  *   },{
840  *     name: 'Num',
841  *     rangeURI: 'http://www.w3.org/2001/XMLSchema#int'
842  *   },{
843  *     name: 'Parent',
844  *     rangeURI: 'http://www.w3.org/2001/XMLSchema#string'
845  *   }]
846  * };
847  *
848  * LABKEY.Exp.SampleSet.create({
849  *   success: function () { alert("success!"); },
850  *   failure: function () { alert("failure!"); },
851  *   domainDesign: domainDesign,
852  *   options: { idCols: [0, 1], parentCol: 2 }
853  * });
854  */
855 LABKEY.Exp.SampleSet.create = function (config)
856 {
857     LABKEY.Domain.create(LABKEY.Utils.getOnSuccess(config), LABKEY.Utils.getOnFailure(config), "SampleSet", config.domainDesign, config.options, config.containerPath);
858 };
859 
860 /**
861  * DataClass represents a set of ExpData objects that share a set of properties.
862  *
863  * @class DataClass describes a collection of Data objects.
864  * This class defines the set of fields that you you wish to attach to all datas in the group.
865  * Within the DataClass, each Data has a unique name.
866  *
867  * @extends LABKEY.Exp.ExpObject
868  * @memberOf LABKEY.Exp
869  *
870  * @param config
871  * @param {String} config.description Description of the DataClass.
872  * @param {String} [config.nameExpression] Optional name expression used to generate unique names for ExpData inserted into the DataClass.
873  * @param {Object} [config.sampleSet] The optional SampleSet the DataClass is associated with.  With the following properties:
874  * @param {Integer} [config.sampleSet.id] The row id of the SampleSet.
875  * @param {String} [config.sampleSet.name] The name of the SampleSet.
876  * @constructor
877  */
878 LABKEY.Exp.DataClass = function (config)
879 {
880     "use strict";
881 
882     LABKEY.Exp.ExpObject.call(this, config);
883     config = config || {};
884     this.description = config.description;
885     this.sampleSet = config.sampleSet;
886 };
887 LABKEY.Exp.DataClass.prototype = new LABKEY.Exp.ExpObject;
888 LABKEY.Exp.DataClass.prototype.constructor = LABKEY.Exp.DataClass;
889 
890 LABKEY.Exp.DataClass.prototype.getDomain = function (config)
891 {
892     "use strict";
893     LABKEY.Domain.get({
894         success: LABKEY.Utils.getOnSuccess(config),
895         failure: LABKEY.Utils.getOnFailure(config),
896         schemaName: "exp.data",
897         queryName: this.name,
898         containerPath: config.containerPath
899     });
900 };
901 
902 LABKEY.Exp.DataClass.create = function (config)
903 {
904     "use strict";
905     LABKEY.Domain.create({
906         success: LABKEY.Utils.getOnSuccess(config),
907         failure: LABKEY.Utils.getOnFailure(config),
908         type: "dataclass",
909         domainDesign: config.domainDesign,
910         options: config.options,
911         containerPath: config.containerPath
912     });
913 };
914 
915 /**
916  * The ChildObject constructor is private.
917  * @class Experiment Child
918  * @extends LABKEY.Exp.ExpObject
919  * @memberOf LABKEY.Exp
920  *
921  * @param {Object} [config] Private configuration object. Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
922  *
923  * @ignore hide from JsDoc for now
924  */
925 LABKEY.Exp.ChildObject = function (config) {
926     LABKEY.Exp.ExpObject.call(this, config);
927     config = config || {};
928     // property holder
929 };
930 LABKEY.Exp.ChildObject.prototype = new LABKEY.Exp.ExpObject;
931 LABKEY.Exp.ChildObject.prototype.constructor = LABKEY.Exp.ChildObject;
932 
933 /**
934  * The RunItem constructor is private.
935  * @class Experiment Run Item.  Base class for {@link LABKEY.Exp.Data}
936  * and {@link LABKEY.Exp.Material}.
937  * @extends LABKEY.Exp.ExpObject
938  * @memberOf LABKEY.Exp
939  * @param {Object} [config] Private configuration object. Inherits config properties of {@link LABKEY.Exp.ExpObject}.
940  * @ignore hide from JsDoc for now
941  */
942 LABKEY.Exp.RunItem = function (config) {
943     LABKEY.Exp.ExpObject.call(this, config);
944     config = config || {};
945 
946     this.sourceProtocol = config.sourceProtocol;
947     this.run = config.run;
948     this.targetApplications = config.targetApplications;
949     this.sourceApplications = config.sourceApplications;
950     this.sucessorRuns = config.sucessorRuns;
951     this.cpasType = config.cpasType;
952 };
953 LABKEY.Exp.RunItem.prototype = new LABKEY.Exp.ExpObject;
954 LABKEY.Exp.RunItem.prototype.constructor = LABKEY.Exp.RunItem;
955 
956 /**
957  * Constructs a new experiment material object.
958  * @class The Exp.Material class describes an experiment material.  "Material" is a synonym for both
959  * "sample" and "specimen."  Thus, for example, the input to an assay could be called a material.
960  * The fields of this class are inherited from the {@link LABKEY.Exp.ExpObject} object and
961  * the private LABKEY.Exp.RunItem object.
962  *            <p>Additional Documentation:
963  *              <ul>
964  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=experiment">LabKey Experiment</a></li>
965  *              </ul>
966  *           </p>
967  * @extends LABKEY.Exp.RunItem
968  * @extends LABKEY.Exp.ExpObject
969  * @memberOf LABKEY.Exp
970  *
971  * @param {Object} [config] Configuration object.  Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
972  * @param {Object} [config.sampleSet] The SampleSet the material belongs to.  With the following properties:
973  * @param {Integer} [config.sampleSet.id] The row id of the SampleSet.
974  * @param {String} [config.sampleSet.name] The name of the SampleSet.
975  */
976 LABKEY.Exp.Material = function (config) {
977     LABKEY.Exp.RunItem.call(this, config);
978     config = config || {};
979 
980     this.sampleSet = config.sampleSet;
981 };
982 LABKEY.Exp.Material.prototype = new LABKEY.Exp.RunItem;
983 LABKEY.Exp.Material.prototype.constructor = LABKEY.Exp.Material;
984 
985 /**
986  * The Data constructor is private.
987  * To create a LABKEY.Exp.Data object, upload a file using to the "assayFileUpload" action of
988  * the "assay" controller.
989  *
990  * @class The Experiment Data class describes the data input or output of a {@link LABKEY.Exp.Run}.  This typically
991  * corresponds to an assay results file uploaded to the LabKey server.
992  * <p>
993  * To create a LABKEY.Exp.Data object, upload a file using to the "assayFileUpload" action of
994  * the "assay" controller.
995  * </p>
996  *            <p>Additional Documentation:
997  *              <ul>
998  *                  <li><a href='https://www.labkey.org/Documentation/wiki-page.view?name=moduleassay'>LabKey File-Based Assays</a></li>
999  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=experiment">LabKey Experiment</a></li>
1000  *              </ul>
1001  *           </p>
1002  *
1003  * @extends LABKEY.Exp.RunItem
1004  * @extends LABKEY.Exp.ExpObject
1005  * @memberOf LABKEY.Exp
1006  *
1007  * @param {Object} [config] Private configuration object.  Inherits the config properties of {@link LABKEY.Exp.ExpObject}.
1008  * @param {String} config.dataFileURL The local file url of the uploaded file.
1009  * @param {Object} [config.dataClass] The DataClass the data belongs to.  With the following properties:
1010  * @param {Integer} [config.dataClass.id] The row id of the DataClass.
1011  * @param {String} [config.dataClass.name] The name of the DataClass.
1012  *
1013  * @example
1014  * // To perform a file upload over HTTP:
1015  * <form id="upload-run-form" enctype="multipart/form-data" method="POST">
1016  *   <div id="upload-run-button"></div>
1017  * </form>
1018  * <script type="text/javascript">
1019  *    LABKEY.Utils.requiresScript("FileUploadField.js");
1020  *    // Optional - specify a protocolId so that the Exp.Data object is assigned the related LSID namespace.
1021  *    var url = LABKEY.ActionURL.buildURL("assay", "assayFileUpload", LABKEY.ActionURL.getContainer(), { protocolId: 50 });
1022  *    Ext.onReady(function() {
1023  *       var form = new Ext.form.BasicForm(
1024  *       Ext.get("upload-run-form"), {
1025  *          fileUpload: true,
1026  *          frame: false,
1027  *          url: url,
1028  *          listeners: {
1029  *             actioncomplete : function (form, action) {
1030  *                alert('Upload successful!');
1031  *                var data = new LABKEY.Exp.Data(action.result);
1032  *
1033  *                // now add the data as a dataInput to a LABKEY.Exp.Run
1034  *                var run = new LABKEY.Exp.Run();
1035  *                run.name = data.name;
1036  *                run.dataInputs = [ data ];
1037  *
1038  *                // add the new run to a LABKEY.Exp.Batch object and
1039  *                // fetch the parsed file contents from the data object
1040  *                // using the LABKEY.Exp.Data#getContent() method.
1041  *             },
1042  *             actionfailed: function (form, action) {
1043  *                alert('Upload failed!');
1044  *             }
1045  *          }
1046  *       });
1047  *
1048  *       var uploadField = new Ext.form.FileUploadField({
1049  *          id: "upload-run-field",
1050  *          renderTo: "upload-run-button",
1051  *          buttonText: "Upload Data...",
1052  *          buttonOnly: true,
1053  *          buttonCfg: { cls: "labkey-button" },
1054  *          listeners: {
1055  *             "fileselected": function (fb, v) {
1056  *                form.submit();
1057  *             }
1058  *          }
1059  *       });
1060  *    });
1061  * </script>
1062  *
1063  * // Or, to upload the contents of a JavaScript string as a file:
1064  * <script type="text/javascript">
1065  * Ext.onReady(function() {
1066  *    LABKEY.Ajax.request({
1067  *      url: LABKEY.ActionURL.buildURL("assay", "assayFileUpload"),
1068  *      params: { fileName: 'test.txt', fileContent: 'Some text!' },
1069  *      success: function(response, options) {
1070  *         var data = new LABKEY.Exp.Data(Ext.util.JSON.decode(response.responseText));
1071  *
1072  *         // now add the data as a dataInput to a LABKEY.Exp.Run
1073  *         var run = new LABKEY.Exp.Run();
1074  *         run.name = data.name;
1075  *         run.dataInputs = [ data ];
1076  *
1077  *         // add the new run to a LABKEY.Exp.Batch object here
1078  *      }
1079  *    });
1080  *  });
1081  *
1082  * </script>
1083  */
1084 LABKEY.Exp.Data = function (config) {
1085     LABKEY.Exp.RunItem.call(this, config);
1086     config = config || {};
1087 
1088     this.dataType = config.dataType;
1089     this.dataFileURL = config.dataFileURL;
1090     this.dataClass = config.dataClass;
1091     if (config.pipelinePath)
1092         this.pipelinePath = config.pipelinePath;
1093     if (config.role)
1094         this.role = config.role;
1095 };
1096 LABKEY.Exp.Data.prototype = new LABKEY.Exp.RunItem;
1097 LABKEY.Exp.Data.prototype.constructor = LABKEY.Exp.Data;
1098 
1099 /**
1100  * Retrieves the contents of the data object from the server.
1101  * @param config An object that contains the following configuration parameters
1102  * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
1103  * @param {function} config.success The function to call when the function finishes successfully.
1104  * This function will be called with the parameters:
1105  * <ul>
1106  * <li><b>content</b> The type of the content varies based on the format requested.
1107  * <li><b>format</b> The format used in the request
1108  * <li><b>response</b> The original response
1109  * </ul>
1110  * @param {function} [config.failure] The function to call if this function encounters an error.
1111  * This function will be called with the following parameters:
1112  * <ul>
1113  * <li><b>errorInfo:</b> An object with a property called "exception," which contains the error message.</li>
1114  * <li><b>format</b> The format used in the request
1115  * <li><b>response</b> The original response
1116  * </ul>
1117  * @param {String} [config.format] How to format the content. Defaults to plaintext, supported for text/* MIME types,
1118  * including .html, .xml, .tsv, .txt, and .csv. Use 'jsonTSV' to get a JSON version of the .xls, .tsv, .or .csv
1119  * files, the structure of which matches the argument to convertToExcel in {@link LABKEY.Utils}.
1120  * <ul>
1121  * <li><b>fileName:</b> the name of the file</li>
1122  * <li><b>sheets:</b> an array of the sheets in the file. Text file types will have a single sheet named 'flat'.
1123  * <ul><li><b>name:</b> the name of the sheet</li>
1124  *     <li><b>values:</b> two-dimensional array of all the cells in the worksheet. First array index is row, second is column</li>
1125  * </ul>
1126  * </ul>
1127  * <br/>Use 'jsonTSVExtended' to get include metadata in the 2D array of cells.
1128  * Text file types will not supply additional metadata but populate the 'value' attribute in the map.
1129  * Excel files will include:
1130  * <ul>
1131  * <li><b>value:</b> the string, boolean, date, or number in the cell</li>
1132  * <li><b>timeOnly:</b> whether the date part should be ignored for dates</li>
1133  * <li><b>formatString:</b> the Java format string to be used to render the value for dates and numbers</li>
1134  * <li><b>formattedValue:</b> the formatted string for that value for all value types</li>
1135  * <li><b>error:</b> true if this cell has an error</li>
1136  * <li><b>formula:</b> if the cell's value is specified by a formula, the text of the formula</li>
1137  * </ul>
1138  * <br/>Use 'jsonTSVIgnoreTypes' to always return string values for all cells, regardless of type.
1139  * <br/>
1140  * An example of the results for a request for 'jsonTsv' format:
1141  * <pre>
1142  * {
1143 "sheets": [
1144     {
1145         "name": "Sheet1",
1146         "data": [
1147             [
1148                 "StringColumn",
1149                 "DateColumn"
1150             ],
1151             [
1152                 "Hello",
1153                 "16 May 2009 17:00:00"
1154             ],
1155             [
1156                 "world",
1157                 "12/21/2008 08:45AM"
1158             ]
1159         ]
1160     },
1161     {
1162         "name": "Sheet2",
1163         "data": [
1164             ["NumberColumn"],
1165             [55.44],
1166             [100.34],
1167             [-1]
1168         ]
1169     },
1170     {
1171         "name": "Sheet3",
1172         "data": []
1173     }
1174 ],
1175 "fileName": "SimpleExcelFile.xls"
1176 }</pre>
1177  <br/>
1178  An example of the same file in the 'jsonTSVExtended' format:
1179  <pre>
1180  * {
1181 "sheets": [
1182     {
1183         "name": "Sheet1",
1184         "data": [
1185             [
1186                 {
1187                     "value": "StringColumn",
1188                     "formattedValue": "StringColumn"
1189                 },
1190                 {
1191                     "value": "DateColumn",
1192                     "formattedValue": "DateColumn"
1193                 }
1194             ],
1195             [
1196                 {
1197                     "value": "Hello",
1198                     "formattedValue": "Hello"
1199                 },
1200                 {
1201                     "formatString": "MMMM d, yyyy",
1202                     "value": "16 May 2009 17:00:00",
1203                     "timeOnly": false,
1204                     "formattedValue": "May 17, 2009"
1205                 }
1206             ],
1207             [
1208                 {
1209                     "value": "world",
1210                     "formattedValue": "world"
1211                 },
1212                  {
1213                      "formatString": "M/d/yy h:mm a",
1214                      "value": "21 Dec 2008 19:31:00",
1215                      "timeOnly": false,
1216                      "formattedValue": "12/21/08 7:31 PM"
1217                  }
1218             ]
1219         ]
1220     },
1221     {
1222         "name": "Sheet2",
1223         "data": [
1224             [{
1225                 "value": "NumberColumn",
1226                 "formattedValue": "NumberColumn"
1227             }],
1228             [{
1229                 "formatString": "$#,##0.00",
1230                 "value": 55.44,
1231                 "formattedValue": "$55.44"
1232             }],
1233             [{
1234                 "value": 100.34,
1235                 "formattedValue": "100.34"
1236             }],
1237             [{
1238                 "value": -1,
1239                 "formattedValue": "-1"
1240             }]
1241         ]
1242     },
1243     {
1244         "name": "Sheet3",
1245         "data": []
1246     }
1247 ],
1248 "fileName": "SimpleExcelFile.xls"
1249 }
1250  </pre>
1251  *
1252  */
1253 LABKEY.Exp.Data.prototype.getContent = function(config)
1254 {
1255     if(!LABKEY.Utils.getOnSuccess(config))
1256     {
1257         alert("You must specify a callback function in config.success when calling LABKEY.Exp.Data.getContent()!");
1258         return;
1259     }
1260 
1261     function getSuccessCallbackWrapper(fn, format, scope)
1262     {
1263         return function(response)
1264         {
1265             //ensure response is JSON before trying to decode
1266             var content = null;
1267             if(response && response.getResponseHeader && response.getResponseHeader('Content-Type')
1268                     && response.getResponseHeader('Content-Type').indexOf('application/json') >= 0)
1269             {
1270                 content = LABKEY.Utils.decode(response.responseText);
1271             }
1272             else
1273             {
1274                 content = response.responseText;
1275             }
1276 
1277             if(fn)
1278                 fn.call(scope || this, content, format, response);
1279         };
1280     }
1281 
1282     LABKEY.Ajax.request(
1283     {
1284         url : LABKEY.ActionURL.buildURL("experiment", "showFile"),
1285         method : 'GET',
1286         params : { rowId : this.id, format: config.format },
1287         success: getSuccessCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.format, config.scope),
1288         failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
1289     });
1290 
1291 };
1292 
1293 
1294