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.AjaxProxy
  9  * @-class
 10  * The primary reason to create a custom proxy is to support batching CRUD requests into a single request to saveRows().  Otherwise Ext
 11  * would perform each action as a separate request.
 12  */
 13 Ext4.define('LABKEY.ext4.data.AjaxProxy', {
 14     /**
 15      * @-lends LABKEY.ext4.data.AjaxProxy
 16      */
 17     extend: 'Ext.data.proxy.Ajax',
 18     alias: 'proxy.labkeyajax',
 19     constructor: function(config){
 20         config = config || {};
 21 
 22         Ext4.apply(config, {
 23             api: {
 24                 create: "saveRows.view",
 25                 read: "selectRows.api",
 26                 update: "saveRows.view",
 27                 destroy: "saveRows.view",
 28                 //NOTE: added in order to batch create/update/destroy into 1 request
 29                 saveRows: "saveRows.view"
 30             },
 31             actionMethods: {
 32                 create: "POST",
 33                 read: "POST",
 34                 update: "POST",
 35                 destroy: "POST",
 36                 saveRows: "POST"
 37             }
 38         });
 39         this.addEvents('exception');
 40         this.callParent(arguments);
 41     },
 42 
 43     saveRows: function(operation, callback, scope){
 44         var request = operation.request;
 45         Ext4.apply(request, {
 46             timeout       : this.timeout,
 47             scope         : this,
 48             callback      : this.createRequestCallback(request, operation, callback, scope),
 49             method        : this.getMethod(request),
 50             disableCaching: false // explicitly set it to false, ServerProxy handles caching
 51         });
 52 
 53         Ext4.Ajax.request(request);
 54 
 55         return request;
 56     },
 57     reader: 'labkeyjson',
 58     writer: {
 59         type: 'json',
 60         write: function(request){
 61             return request;
 62         }
 63     },
 64     headers: {
 65         'Content-Type' : 'application/json'
 66     },
 67 
 68     /**
 69      * @Override Ext.data.proxy.Proxy (4.1.0)
 70      * Overridden so we can batch insert/update/deletes into a single request using saveRows, rather than submitting 3 sequential ones
 71      */
 72     batch: function(options, listeners) {
 73         var me = this,
 74             useBatch = me.batchActions,
 75             batch,
 76             records,
 77             actions, aLen, action, a, r, rLen, record;
 78 
 79         var commands = []; //this array is not from Ext
 80 
 81         if (options.operations === undefined) {
 82             // the old-style (operations, listeners) signature was called
 83             // so convert to the single options argument syntax
 84             options = {
 85                 operations: options,
 86                 listeners: listeners
 87             };
 88         }
 89 
 90         if (options.batch) {
 91             if (Ext4.isDefined(options.batch.runOperation)) {
 92                 batch = Ext4.applyIf(options.batch, {
 93                     proxy: me,
 94                     listeners: {}
 95                 });
 96             }
 97         } else {
 98             options.batch = {
 99                 proxy: me,
100                 listeners: options.listeners || {}
101             };
102         }
103 
104         if (!batch) {
105             batch = new Ext4.data.Batch(options.batch);
106         }
107 
108         batch.on('complete', Ext4.bind(me.onBatchComplete, me, [options], 0));
109 
110         actions = me.batchOrder.split(',');
111         aLen    = actions.length;
112 
113         for (a = 0; a < aLen; a++) {
114             action  = actions[a];
115             records = options.operations[action];
116             if (records) {
117                 //the body of this is changed compared to Ext4.1
118                 this.processRecord(action, batch, records, commands);
119             }
120         }
121 
122         //this is added compared to Ext 4.1
123         if (commands.length){
124             var request = Ext4.create('Ext.data.Request', {
125                 action: 'saveRows',
126                 url: LABKEY.ActionURL.buildURL("query", 'saveRows', this.extraParams.containerPath),
127                 jsonData: Ext4.apply(this.extraParams, {
128                     commands: commands
129                 })
130             });
131 
132             var b = Ext4.create('Ext.data.Operation', {
133                 action: 'saveRows',
134                 request: request
135             });
136             batch.add(b);
137         }
138 
139         batch.start();
140         return batch;
141     },
142 
143     /**
144      * Not an Ext method.
145      */
146     processRecord: function(action, batch, records, commands){
147         var operation = Ext4.create('Ext.data.Operation', {
148             action: action,
149             records: records
150         });
151 
152         if (action == 'read'){
153             batch.add(operation);
154         }
155         else {
156             commands.push(this.buildCommand(operation));
157         }
158     },
159 
160     /**
161      * @Override Ext.data.proxy.Server (4.1.0)
162      */
163     buildRequest: function(operation) {
164         if (this.extraParams.sql){
165             this.api.read = "executeSql.api";
166         }
167         else {
168             this.api.read = "selectRows.api";
169         }
170 
171         var request = this.callParent(arguments);
172         request.jsonData = request.jsonData || {};
173         Ext4.apply(request.jsonData, request.params);
174         if (request.method == 'POST' || request.url.indexOf('selectRows') > -1 || request.url.indexOf('saveRows') > -1) {
175             delete request.params;  //would be applied to the URL
176         }
177 
178         //morph request into the commands expected by saverows:
179         request.jsonData.commands = request.jsonData.commands || [];
180 
181         var command = this.buildCommand(operation);
182         if (command && command.rows.length){
183             request.jsonData.commands.push(command);
184         }
185 
186         return request;
187     },
188 
189     //does not override an Ext method - used internally
190     buildCommand: function(operation){
191         if (operation.action!='read'){
192             var command = {
193                 schemaName: this.extraParams.schemaName,
194                 queryName: this.extraParams['query.queryName'],
195                 rows: [],
196                 extraContext: {
197                     storeId: this.storeId,
198                     queryName: this.extraParams['query.queryName'],
199                     schemaName: this.extraParams.schemaName,
200                     keyField: this.reader.getIdProperty()
201                 }
202             };
203 
204             if (operation.action=='create')
205                 command.command = "insertWithKeys";
206             else if (operation.action=='update')
207                 command.command = "updateChangingKeys";
208             else if (operation.action=='destroy')
209                 command.command = "delete";
210 
211             for (var i=0;i<operation.records.length;i++){
212                 var record = operation.records[i];
213                 var oldKeys = {};
214 
215                 //NOTE: if the PK of this table is editable (like a string), then we need to submit
216                 //this record using the unmodified value as the PK
217                 var id = record.getId();
218                 if (record.modified && !Ext4.isEmpty(record.modified[this.reader.getIdProperty()])){
219                     id = record.modified[this.reader.getIdProperty()];
220                 }
221 
222                 oldKeys[this.reader.getIdProperty()] = id;
223                 oldKeys['_internalId'] = record.internalId;  //NOTE: also include internalId for records that do not have a server-assigned PK yet
224 
225                 if (command.command == 'delete'){
226                     command.rows.push(this.getRowData(record));
227                 }
228                 else {
229                     command.rows.push({
230                         values: this.getRowData(record),
231                         oldKeys : oldKeys
232                     });
233                 }
234             }
235 
236             return command;
237         }
238     },
239 
240     /**
241      * @Override Ext.data.proxy.Server (4.1.0)
242      */
243     getUrl: function(request) {
244         var url = this.callParent(arguments);
245         return LABKEY.ActionURL.buildURL("query", url, request.params.containerPath);
246     },
247 
248     //does not override an Ext method - used internally
249     getRowData : function(record) {
250         //convert empty strings to null before posting
251         var data = {};
252         Ext4.apply(data, record.data);
253         for (var field in data)
254         {
255             if (Ext4.isEmpty(data[field]))
256                 data[field] = null;
257         }
258         return data;
259     },
260 
261     /**
262      * @Override Ext.data.proxy.Server (4.1.0)
263      */
264     getParams: function(operation){
265         var params = this.callParent(arguments);
266 
267         //this is a little funny.  Ext expects to encode filters into a filter 'filter' param.
268         //if present, we split it apart here.
269         if (params.filter && params.filter.length){
270             var val;
271             Ext4.each(params.filter, function(f){
272                 val = f.split('=');
273                 params[val[0]] = val[1];
274             }, this);
275             delete params.filter;
276         }
277         return params;
278     },
279 
280     /**
281      * @Override Ext.data.proxy.Server (4.1.0)
282      */
283     sortParam: 'query.sort',
284 
285     /**
286      * @Override Ext.data.proxy.Server (4.1.0)
287      * @name encodeSorters
288      * @function
289      * @memberOf LABKEY.ext4.data.AjaxProxy#
290      *
291      */
292     encodeSorters: function(sorters){
293          var length   = sorters.length,
294              sortStrs = [],
295              sorter, i;
296 
297          for (i = 0; i < length; i++) {
298              sorter = sorters[i];
299 
300              sortStrs[i] = (sorter.direction=='DESC' ? '-' : '') + sorter.property
301          }
302 
303          return sortStrs.join(",");
304     },
305 
306     /**
307      * @Override Ext.data.proxy.Server (4.1.0)
308      * See note in body of getParams()
309      */
310     encodeFilters: function(filters){
311         var result = [];
312         if (filters && filters.length){
313             Ext4.each(filters, function(filter){
314                 if (filter.filterType)
315                     result.push(Ext4.htmlEncode('query.' + filter.property + '~' + filter.filterType.getURLSuffix()) + '=' + Ext4.htmlEncode(filter.value));
316             }, this);
317         }
318         return result;
319     }
320 });
321