1 /*
  2  * Copyright (c) 2012-2013 LabKey Corporation
  3  *
  4  * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
  5  */
  6 
  7 /**
  8  * @name LABKEY.ext4.data.JsonReader
  9  * @-class
 10  */
 11 Ext4.define('LABKEY.ext4.data.JsonReader', {
 12     /**
 13      * @-lends LABKEY.ext4.data.JsonReader
 14      */
 15     extend: 'Ext.data.reader.Json',
 16     alias: 'reader.labkeyjson',
 17     config: {
 18         userFilters: null,
 19         useSimpleAccessors: true
 20     },
 21     mixins: {
 22         observable: 'Ext.util.Observable'
 23     },
 24     constructor: function(){
 25         this.callParent(arguments);
 26         this.addEvents('dataload');
 27     },
 28     readRecords: function(data) {
 29         if (data.metaData){
 30             // NOTE: normalize which field holds the PK.  this is a little unfortunate b/c ext will automatically create this field if it doesnt exist,
 31             // such as a query w/o a PK.  therefore we fall back to a standard name, which we can ignore when drawing grids
 32             this.idProperty = data.metaData.id || this.idProperty || '_internalId';
 33             this.totalProperty = data.metaData.totalProperty; //NOTE: normalize which field holds total rows.
 34             if (this.model){
 35                 this.model.prototype.idProperty = this.idProperty;
 36                 this.model.prototype.totalProperty = this.totalProperty;
 37             }
 38 
 39             //NOTE: it would be interesting to convert this JSON into a more functional object here
 40             //for example, columns w/ lookups could actually reference their target
 41             //we could add methods like getDisplayString(), which accept the ext record and return the appropriate display string
 42             Ext4.each(data.metaData.fields, function(meta){
 43                 if (meta.jsonType == 'int' || meta.jsonType=='float' || meta.jsonType=='boolean')
 44                     meta.useNull = true;  //prevents Ext from assigning 0's to field when record created
 45 
 46                 if (meta.jsonType == 'string')
 47                     meta.sortType = 'asUCString';
 48 
 49                 //convert string into function
 50                 if (meta.extFormatFn){
 51                     try {
 52                         meta.extFormatFn = eval(meta.extFormatFn);
 53                     }
 54                     catch (ex)
 55                     {
 56                         //this is potentially the sort of thing we'd want to log to mothership??
 57                     }
 58                 }
 59 
 60                 if (meta.jsonType) {
 61                     meta.extType = LABKEY.ext4.Util.EXT_TYPE_MAP[meta.jsonType];
 62                 }
 63             });
 64         }
 65 
 66         return this.callParent([data]);
 67     },
 68 
 69     //added event to allow store to modify metadata before it is applied
 70     onMetaChange : function(meta) {
 71         this.fireEvent('datachange', meta);
 72 
 73         this.callParent(arguments);
 74 
 75         //NOTE: Ext.data.Model.onFieldAddReplace() would normally reset the idField; however, we usually end up changing the idProperty since it was not known at time of store creation
 76         if (this.model){
 77             this.model.prototype.idField = this.model.prototype.fields.get(this.model.prototype.idProperty);
 78         }
 79     },
 80 
 81     /*
 82     because our 9.1 API format returns results as objects, we transform them here.  In addition to extracting the values, Ext creates an accessor for the record's ID
 83     this must also be modified to support the 9.1 API.  Because I believe getId() can be called both on initial load (prior to
 84     when we transform the data) and after, I modified the method to test whether the field's value is an object instead of
 85     looking for '.value' exclusively.
 86     */
 87     createFieldAccessExpression: (function() {
 88         var re = /[\[\.]/;
 89 
 90         return function(field, fieldVarName, dataName) {
 91             var me     = this,
 92                 hasMap = (field.mapping !== null),
 93                 map    = hasMap ? field.mapping : field.name,
 94                 result,
 95                 operatorSearch;
 96 
 97             if (typeof map === 'function') {
 98                 result = fieldVarName + '.mapping(' + dataName + ', this)';
 99             } else if (this.useSimpleAccessors === true || ((operatorSearch = String(map).search(re)) < 0)) {
100                 if (!hasMap || isNaN(map)) {
101                     // If we don't provide a mapping, we may have a field name that is numeric
102                     map = '"' + map + '"';
103                 }
104                 //TODO: account for field.notFromServer here...
105                 //also: we should investigate how convert() works and probably use this instead
106                 result = dataName + "[" + map + "] !== undefined ? " + dataName + "[" + map + "].value : ''";
107             } else {
108                 result = dataName + (operatorSearch > 0 ? '.' : '') + map;
109             }
110             return result;
111         };
112     }()),
113 
114     //see note for createFieldAccessExpression()
115     buildExtractors: function(force) {
116         this.callParent(arguments);
117 
118         var me = this,
119             idProp      = me.getIdProperty(),
120             accessor,
121             idField,
122             map;
123 
124         if (idProp) {
125             idField = me.model.prototype.fields.get(idProp);
126             if (idField) {
127                 map = idField.mapping;
128                 idProp = (map !== undefined && map !== null) ? map : idProp;
129             }
130             accessor = me.createAccessor('["' + idProp + '"].value');
131 
132             me.getId = function(record) {
133                 var id = accessor.call(me, record);
134                 return (id === undefined || id === '') ? null : id;
135             };
136         }
137     }
138 });
139