RemotingProvider.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. /**
  2. * @class Ext.direct.RemotingProvider
  3. *
  4. * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
  5. * server side methods on the client (a remote procedure call (RPC) type of
  6. * connection where the client can initiate a procedure on the server).</p>
  7. *
  8. * <p>This allows for code to be organized in a fashion that is maintainable,
  9. * while providing a clear path between client and server, something that is
  10. * not always apparent when using URLs.</p>
  11. *
  12. * <p>To accomplish this the server-side needs to describe what classes and methods
  13. * are available on the client-side. This configuration will typically be
  14. * outputted by the server-side Ext.Direct stack when the API description is built.</p>
  15. */
  16. Ext.define('Ext.direct.RemotingProvider', {
  17. /* Begin Definitions */
  18. alias: 'direct.remotingprovider',
  19. extend: 'Ext.direct.JsonProvider',
  20. requires: [
  21. 'Ext.util.MixedCollection',
  22. 'Ext.util.DelayedTask',
  23. 'Ext.direct.Transaction',
  24. 'Ext.direct.RemotingMethod'
  25. ],
  26. /* End Definitions */
  27. /**
  28. * @cfg {Object} actions
  29. * Object literal defining the server side actions and methods. For example, if
  30. * the Provider is configured with:
  31. * <pre><code>
  32. "actions":{ // each property within the 'actions' object represents a server side Class
  33. "TestAction":[ // array of methods within each server side Class to be
  34. { // stubbed out on client
  35. "name":"doEcho",
  36. "len":1
  37. },{
  38. "name":"multiply",// name of method
  39. "len":2 // The number of parameters that will be used to create an
  40. // array of data to send to the server side function.
  41. // Ensure the server sends back a Number, not a String.
  42. },{
  43. "name":"doForm",
  44. "formHandler":true, // direct the client to use specialized form handling method
  45. "len":1
  46. }]
  47. }
  48. * </code></pre>
  49. * <p>Note that a Store is not required, a server method can be called at any time.
  50. * In the following example a <b>client side</b> handler is used to call the
  51. * server side method "multiply" in the server-side "TestAction" Class:</p>
  52. * <pre><code>
  53. TestAction.multiply(
  54. 2, 4, // pass two arguments to server, so specify len=2
  55. // callback function after the server is called
  56. // result: the result returned by the server
  57. // e: Ext.direct.RemotingEvent object
  58. function(result, e){
  59. var t = e.getTransaction();
  60. var action = t.action; // server side Class called
  61. var method = t.method; // server side method called
  62. if(e.status){
  63. var answer = Ext.encode(result); // 8
  64. }else{
  65. var msg = e.message; // failure message
  66. }
  67. }
  68. );
  69. * </code></pre>
  70. * In the example above, the server side "multiply" function will be passed two
  71. * arguments (2 and 4). The "multiply" method should return the value 8 which will be
  72. * available as the <tt>result</tt> in the example above.
  73. */
  74. /**
  75. * @cfg {String/Object} namespace
  76. * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
  77. * Explicitly specify the namespace Object, or specify a String to have a
  78. * {@link Ext#namespace namespace created} implicitly.
  79. */
  80. /**
  81. * @cfg {String} url
  82. * <b>Required</b>. The url to connect to the {@link Ext.direct.Manager} server-side router.
  83. */
  84. /**
  85. * @cfg {String} enableUrlEncode
  86. * Specify which param will hold the arguments for the method.
  87. * Defaults to <tt>'data'</tt>.
  88. */
  89. /**
  90. * @cfg {Number/Boolean} enableBuffer
  91. * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
  92. * calls. If a number is specified this is the amount of time in milliseconds
  93. * to wait before sending a batched request.</p>
  94. * <br><p>Calls which are received within the specified timeframe will be
  95. * concatenated together and sent in a single request, optimizing the
  96. * application by reducing the amount of round trips that have to be made
  97. * to the server.</p>
  98. */
  99. enableBuffer: 10,
  100. /**
  101. * @cfg {Number} maxRetries
  102. * Number of times to re-attempt delivery on failure of a call.
  103. */
  104. maxRetries: 1,
  105. /**
  106. * @cfg {Number} timeout
  107. * The timeout to use for each request.
  108. */
  109. timeout: undefined,
  110. constructor : function(config){
  111. var me = this;
  112. me.callParent(arguments);
  113. me.addEvents(
  114. /**
  115. * @event beforecall
  116. * Fires immediately before the client-side sends off the RPC call.
  117. * By returning false from an event handler you can prevent the call from
  118. * executing.
  119. * @param {Ext.direct.RemotingProvider} provider
  120. * @param {Ext.direct.Transaction} transaction
  121. * @param {Object} meta The meta data
  122. */
  123. 'beforecall',
  124. /**
  125. * @event call
  126. * Fires immediately after the request to the server-side is sent. This does
  127. * NOT fire after the response has come back from the call.
  128. * @param {Ext.direct.RemotingProvider} provider
  129. * @param {Ext.direct.Transaction} transaction
  130. * @param {Object} meta The meta data
  131. */
  132. 'call'
  133. );
  134. me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
  135. me.transactions = new Ext.util.MixedCollection();
  136. me.callBuffer = [];
  137. },
  138. /**
  139. * Initialize the API
  140. * @private
  141. */
  142. initAPI : function(){
  143. var actions = this.actions,
  144. namespace = this.namespace,
  145. action,
  146. cls,
  147. methods,
  148. i,
  149. len,
  150. method;
  151. for (action in actions) {
  152. if (actions.hasOwnProperty(action)) {
  153. cls = namespace[action];
  154. if (!cls) {
  155. cls = namespace[action] = {};
  156. }
  157. methods = actions[action];
  158. for (i = 0, len = methods.length; i < len; ++i) {
  159. method = new Ext.direct.RemotingMethod(methods[i]);
  160. cls[method.name] = this.createHandler(action, method);
  161. }
  162. }
  163. }
  164. },
  165. /**
  166. * Create a handler function for a direct call.
  167. * @private
  168. * @param {String} action The action the call is for
  169. * @param {Object} method The details of the method
  170. * @return {Function} A JS function that will kick off the call
  171. */
  172. createHandler : function(action, method){
  173. var me = this,
  174. handler;
  175. if (!method.formHandler) {
  176. handler = function(){
  177. me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
  178. };
  179. } else {
  180. handler = function(form, callback, scope){
  181. me.configureFormRequest(action, method, form, callback, scope);
  182. };
  183. }
  184. handler.directCfg = {
  185. action: action,
  186. method: method
  187. };
  188. return handler;
  189. },
  190. // inherit docs
  191. isConnected: function(){
  192. return !!this.connected;
  193. },
  194. // inherit docs
  195. connect: function(){
  196. var me = this;
  197. if (me.url) {
  198. me.initAPI();
  199. me.connected = true;
  200. me.fireEvent('connect', me);
  201. } else if(!me.url) {
  202. //<debug>
  203. Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
  204. //</debug>
  205. }
  206. },
  207. // inherit docs
  208. disconnect: function(){
  209. var me = this;
  210. if (me.connected) {
  211. me.connected = false;
  212. me.fireEvent('disconnect', me);
  213. }
  214. },
  215. /**
  216. * Run any callbacks related to the transaction.
  217. * @private
  218. * @param {Ext.direct.Transaction} transaction The transaction
  219. * @param {Ext.direct.Event} event The event
  220. */
  221. runCallback: function(transaction, event){
  222. var success = !!event.status,
  223. funcName = success ? 'success' : 'failure',
  224. callback,
  225. result;
  226. if (transaction && transaction.callback) {
  227. callback = transaction.callback;
  228. result = Ext.isDefined(event.result) ? event.result : event.data;
  229. if (Ext.isFunction(callback)) {
  230. callback(result, event, success);
  231. } else {
  232. Ext.callback(callback[funcName], callback.scope, [result, event, success]);
  233. Ext.callback(callback.callback, callback.scope, [result, event, success]);
  234. }
  235. }
  236. },
  237. /**
  238. * React to the ajax request being completed
  239. * @private
  240. */
  241. onData: function(options, success, response){
  242. var me = this,
  243. i = 0,
  244. len,
  245. events,
  246. event,
  247. transaction,
  248. transactions;
  249. if (success) {
  250. events = me.createEvents(response);
  251. for (len = events.length; i < len; ++i) {
  252. event = events[i];
  253. transaction = me.getTransaction(event);
  254. me.fireEvent('data', me, event);
  255. if (transaction) {
  256. me.runCallback(transaction, event, true);
  257. Ext.direct.Manager.removeTransaction(transaction);
  258. }
  259. }
  260. } else {
  261. transactions = [].concat(options.transaction);
  262. for (len = transactions.length; i < len; ++i) {
  263. transaction = me.getTransaction(transactions[i]);
  264. if (transaction && transaction.retryCount < me.maxRetries) {
  265. transaction.retry();
  266. } else {
  267. event = new Ext.direct.ExceptionEvent({
  268. data: null,
  269. transaction: transaction,
  270. code: Ext.direct.Manager.exceptions.TRANSPORT,
  271. message: 'Unable to connect to the server.',
  272. xhr: response
  273. });
  274. me.fireEvent('data', me, event);
  275. if (transaction) {
  276. me.runCallback(transaction, event, false);
  277. Ext.direct.Manager.removeTransaction(transaction);
  278. }
  279. }
  280. }
  281. }
  282. },
  283. /**
  284. * Get transaction from XHR options
  285. * @private
  286. * @param {Object} options The options sent to the Ajax request
  287. * @return {Ext.direct.Transaction} The transaction, null if not found
  288. */
  289. getTransaction: function(options){
  290. return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
  291. },
  292. /**
  293. * Configure a direct request
  294. * @private
  295. * @param {String} action The action being executed
  296. * @param {Object} method The being executed
  297. */
  298. configureRequest: function(action, method, args){
  299. var me = this,
  300. callData = method.getCallData(args),
  301. data = callData.data,
  302. callback = callData.callback,
  303. scope = callData.scope,
  304. transaction;
  305. transaction = new Ext.direct.Transaction({
  306. provider: me,
  307. args: args,
  308. action: action,
  309. method: method.name,
  310. data: data,
  311. callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
  312. });
  313. if (me.fireEvent('beforecall', me, transaction, method) !== false) {
  314. Ext.direct.Manager.addTransaction(transaction);
  315. me.queueTransaction(transaction);
  316. me.fireEvent('call', me, transaction, method);
  317. }
  318. },
  319. /**
  320. * Gets the Ajax call info for a transaction
  321. * @private
  322. * @param {Ext.direct.Transaction} transaction The transaction
  323. * @return {Object} The call params
  324. */
  325. getCallData: function(transaction){
  326. return {
  327. action: transaction.action,
  328. method: transaction.method,
  329. data: transaction.data,
  330. type: 'rpc',
  331. tid: transaction.id
  332. };
  333. },
  334. /**
  335. * Sends a request to the server
  336. * @private
  337. * @param {Object/Array} data The data to send
  338. */
  339. sendRequest : function(data){
  340. var me = this,
  341. request = {
  342. url: me.url,
  343. callback: me.onData,
  344. scope: me,
  345. transaction: data,
  346. timeout: me.timeout
  347. }, callData,
  348. enableUrlEncode = me.enableUrlEncode,
  349. i = 0,
  350. len,
  351. params;
  352. if (Ext.isArray(data)) {
  353. callData = [];
  354. for (len = data.length; i < len; ++i) {
  355. callData.push(me.getCallData(data[i]));
  356. }
  357. } else {
  358. callData = me.getCallData(data);
  359. }
  360. if (enableUrlEncode) {
  361. params = {};
  362. params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
  363. request.params = params;
  364. } else {
  365. request.jsonData = callData;
  366. }
  367. Ext.Ajax.request(request);
  368. },
  369. /**
  370. * Add a new transaction to the queue
  371. * @private
  372. * @param {Ext.direct.Transaction} transaction The transaction
  373. */
  374. queueTransaction: function(transaction){
  375. var me = this,
  376. enableBuffer = me.enableBuffer;
  377. if (transaction.form) {
  378. me.sendFormRequest(transaction);
  379. return;
  380. }
  381. me.callBuffer.push(transaction);
  382. if (enableBuffer) {
  383. if (!me.callTask) {
  384. me.callTask = new Ext.util.DelayedTask(me.combineAndSend, me);
  385. }
  386. me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
  387. } else {
  388. me.combineAndSend();
  389. }
  390. },
  391. /**
  392. * Combine any buffered requests and send them off
  393. * @private
  394. */
  395. combineAndSend : function(){
  396. var buffer = this.callBuffer,
  397. len = buffer.length;
  398. if (len > 0) {
  399. this.sendRequest(len == 1 ? buffer[0] : buffer);
  400. this.callBuffer = [];
  401. }
  402. },
  403. /**
  404. * Configure a form submission request
  405. * @private
  406. * @param {String} action The action being executed
  407. * @param {Object} method The method being executed
  408. * @param {HTMLElement} form The form being submitted
  409. * @param {Function} callback (optional) A callback to run after the form submits
  410. * @param {Object} scope (optional) A scope to execute the callback in
  411. */
  412. configureFormRequest : function(action, method, form, callback, scope){
  413. var me = this,
  414. transaction = new Ext.direct.Transaction({
  415. provider: me,
  416. action: action,
  417. method: method.name,
  418. args: [form, callback, scope],
  419. callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
  420. isForm: true
  421. }),
  422. isUpload,
  423. params;
  424. if (me.fireEvent('beforecall', me, transaction, method) !== false) {
  425. Ext.direct.Manager.addTransaction(transaction);
  426. isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
  427. params = {
  428. extTID: transaction.id,
  429. extAction: action,
  430. extMethod: method.name,
  431. extType: 'rpc',
  432. extUpload: String(isUpload)
  433. };
  434. // change made from typeof callback check to callback.params
  435. // to support addl param passing in DirectSubmit EAC 6/2
  436. Ext.apply(transaction, {
  437. form: Ext.getDom(form),
  438. isUpload: isUpload,
  439. params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
  440. });
  441. me.fireEvent('call', me, transaction, method);
  442. me.sendFormRequest(transaction);
  443. }
  444. },
  445. /**
  446. * Sends a form request
  447. * @private
  448. * @param {Ext.direct.Transaction} transaction The transaction to send
  449. */
  450. sendFormRequest: function(transaction){
  451. Ext.Ajax.request({
  452. url: this.url,
  453. params: transaction.params,
  454. callback: this.onData,
  455. scope: this,
  456. form: transaction.form,
  457. isUpload: transaction.isUpload,
  458. transaction: transaction
  459. });
  460. }
  461. });