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