hangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegram
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
16 KiB
510 lines
16 KiB
/** |
|
* Ext Direct aims to streamline communication between the client and server |
|
* by providing a single interface that reduces the amount of common code |
|
* typically required to validate data and handle returned data packets |
|
* (reading data, error conditions, etc). |
|
* |
|
* The Ext.direct namespace includes several classes for a closer integration |
|
* with the server-side. The Ext.data namespace also includes classes for working |
|
* with Ext.data.Stores which are backed by data from an Ext Direct method. |
|
* |
|
* # Specification |
|
* |
|
* For additional information consult the [Ext Direct Specification][1]. |
|
* |
|
* # Providers |
|
* |
|
* Ext Direct uses a provider architecture, where one or more providers are used |
|
* to transport data to and from the server. There are several providers that exist |
|
* in the core at the moment: |
|
* |
|
* - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations |
|
* - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests |
|
* - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side to the client. |
|
* |
|
* A provider does not need to be invoked directly, providers are added via |
|
* {@link Ext.direct.Manager #addProvider}. RemotingProviders' API declarations |
|
* can also be loaded with {@link Ext.direct.Manager #loadProvider}, with |
|
* Provider instance created automatically after successful retrieval. |
|
* |
|
* # Router |
|
* |
|
* Ext Direct RemotingProviders utilize a "router" on the server to direct |
|
* requests from the client to the appropriate server-side method. Because |
|
* the Ext Direct API is platform-agnostic, you could completely swap out |
|
* a Java based server solution and replace it with one that uses C# |
|
* without changing the client side JavaScript at all, or vice versa. |
|
* |
|
* # Server side events |
|
* |
|
* Custom events from the server may be handled by the client by adding listeners, for example: |
|
* |
|
* {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"} |
|
* |
|
* // add a handler for a 'message' event sent by the server |
|
* Ext.direct.Manager.on('message', function(e){ |
|
* out.append(String.format('<p><i>{0}</i></p>', e.data)); |
|
* out.el.scrollTo('t', 100000, true); |
|
* }); |
|
* |
|
* [1]: http://sencha.com/products/extjs/extdirect |
|
* |
|
* @singleton |
|
* @alternateClassName Ext.Direct |
|
*/ |
|
|
|
Ext.define('Ext.direct.Manager', { |
|
singleton: true, |
|
|
|
requires: [ |
|
'Ext.util.MixedCollection' |
|
], |
|
|
|
mixins: [ |
|
'Ext.mixin.Observable' |
|
], |
|
|
|
/** |
|
* Exception types. |
|
*/ |
|
exceptions: { |
|
TRANSPORT: 'xhr', |
|
PARSE: 'parse', |
|
DATA: 'data', |
|
LOGIN: 'login', |
|
SERVER: 'exception' |
|
}, |
|
|
|
// Classes of Providers available to the application |
|
providerClasses: {}, |
|
|
|
// Remoting Methods registered with the Manager |
|
remotingMethods: {}, |
|
|
|
config: { |
|
/** |
|
* @cfg {String} [varName="Ext.app.REMOTING_API"] |
|
* Default variable name to use for Ext.Direct API declaration. |
|
*/ |
|
varName: 'Ext.app.REMOTING_API' |
|
}, |
|
|
|
apiNotFoundError: 'Ext Direct API was not found at {0}', |
|
|
|
/** |
|
* @event event |
|
* |
|
* Fires after an event. |
|
* |
|
* @param {Ext.direct.Event} event The {@link Ext.direct.Event Event} that occurred. |
|
* @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider} |
|
* that provided the event. |
|
*/ |
|
|
|
/** |
|
* @event exception |
|
* |
|
* Fires after an event exception. |
|
* |
|
* @param {Ext.direct.Event} event The {@link Ext.direct.Event Event} that occurred. |
|
* @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider} |
|
* that provided the event. |
|
*/ |
|
|
|
/** |
|
* @event providerload |
|
* |
|
* Fired by {@link #loadProvider} after successfully loading RemotingProvider API |
|
* declaration and creating a new Provider instance. |
|
* |
|
* @param {String} url The URL used to retrieve remoting API. |
|
* @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider} |
|
* instance that was created. |
|
*/ |
|
|
|
/** |
|
* @event providerloaderror |
|
* |
|
* Fired by {@link #loadProvider} when remoting API could not be loaded, or |
|
* Provider instance could not be created. |
|
* |
|
* @param {String} url The URL used to retrieve remoting API. |
|
* @param {String} error The error that occured. |
|
*/ |
|
|
|
constructor: function() { |
|
var me = this; |
|
|
|
me.mixins.observable.constructor.call(me); |
|
|
|
me.transactions = new Ext.util.MixedCollection(); |
|
me.providers = new Ext.util.MixedCollection(); |
|
}, |
|
|
|
/** |
|
* Adds an Ext Direct Provider and creates the proxy or stub methods to execute |
|
* server-side methods for RemotingProviders. If the provider is not already connected, |
|
* it will auto-connect. |
|
* |
|
* var pollProv = new Ext.direct.PollingProvider({ |
|
* url: 'php/poll2.php' |
|
* }); |
|
* |
|
* Ext.direct.Manager.addProvider({ |
|
* type: 'remoting', // create a Ext.direct.RemotingProvider |
|
* url: 'php/router.php', // url to connect to the Ext Direct server-side router. |
|
* actions: { // each property within the actions object represents an Action |
|
* TestAction: [{ // array of Methods within each server side Action |
|
* name: 'doEcho', // name of method |
|
* len: 1 |
|
* }, { |
|
* name: 'multiply', |
|
* len: 1 |
|
* }, { |
|
* name: 'doForm', |
|
* formHandler: true // handle form on server with Ext.direct.Transaction |
|
* }] |
|
* }, |
|
* namespace: 'myApplication', // namespace to create the Remoting Provider in |
|
* }, { |
|
* type: 'polling', // create an Ext.direct.PollingProvider |
|
* url: 'php/poll.php' |
|
* }, |
|
* pollProv); // reference to previously created instance |
|
* |
|
* @param {Ext.direct.Provider/Object...} provider |
|
* |
|
* Accepts any number of Provider descriptions (an instance or config object for |
|
* a Provider). Each Provider description instructs Ext Direct how to create |
|
* client-side stub methods. |
|
*/ |
|
addProvider: function(provider) { |
|
var me = this, |
|
args = arguments, |
|
relayers = me.relayers || (me.relayers = {}), |
|
i, len; |
|
|
|
if (args.length > 1) { |
|
for (i = 0, len = args.length; i < len; ++i) { |
|
me.addProvider(args[i]); |
|
} |
|
|
|
return; |
|
} |
|
|
|
// if provider has not already been instantiated |
|
if (!provider.isProvider) { |
|
provider = Ext.create('direct.' + provider.type + 'provider', provider); |
|
} |
|
|
|
me.providers.add(provider); |
|
provider.on('data', me.onProviderData, me); |
|
|
|
if (provider.relayedEvents) { |
|
relayers[provider.id] = me.relayEvents(provider, provider.relayedEvents); |
|
} |
|
|
|
if (!provider.isConnected()) { |
|
provider.connect(); |
|
} |
|
|
|
return provider; |
|
}, |
|
|
|
/** |
|
* Load Ext Direct Provider API declaration from the server and construct |
|
* a new Provider instance. The new Provider will then auto-connect and |
|
* create stub functions for the methods exposed by the server side. See |
|
* {@link #addProvider}. |
|
* |
|
* Ext.direct.Manager.loadProvider({ |
|
* url: 'php/api.php', |
|
* varName: 'MY_REMOTING_API' // defaults to 'Ext.app.REMOTING_API' |
|
* }); |
|
* |
|
* @param {Object} config Remoting API configuration. |
|
* @param {String} config.url URL to retrieve remoting API declaration from. |
|
* @param {String} config.varName Name of the variable that will hold |
|
* RemotingProvider configuration block, including its Actions. |
|
* @param {Function} [callback] Optional callback to execute when |
|
* Provider is created, or when an error has occured. |
|
* @param {Object} [scope] Optional scope to execute callback function in. |
|
* |
|
* For additional information see the [Ext Direct specification][1]. |
|
*/ |
|
loadProvider: function(config, callback, scope) { |
|
var me = this, |
|
classes = me.providerClasses, |
|
type, url, varName, provider, i, len; |
|
|
|
if (Ext.isArray(config)) { |
|
for (i = 0, len = config.length; i < len; i++) { |
|
me.loadProvider(config[i], callback, scope); |
|
} |
|
|
|
return; |
|
} |
|
|
|
// We may have been passed config object containing enough |
|
// information to create a Provider without further ado. |
|
type = config.type; |
|
url = config.url; |
|
|
|
if (classes[type] && classes[type].checkConfig(config)) { |
|
provider = me.addProvider(config); |
|
|
|
me.fireEventArgs('providerload', [url, provider]); |
|
Ext.callback(callback, scope, [url, provider]); |
|
|
|
// We're deliberately not returning the provider here |
|
// to make way for the future Promises based implementation |
|
// that should be consistent with the remote API declaration |
|
// retrieval below. |
|
return; |
|
} |
|
|
|
// For remote API declaration retrieval we need to know the |
|
// service discovery URL and variable name, at the minimum. |
|
// We have a default for the variable name but not for URL. |
|
varName = config.varName || me.getVarName(); |
|
delete config.varName; |
|
|
|
//<debug> |
|
if (!url) { |
|
Ext.Error.raise("Need API discovery URL to load a Remoting provider!"); |
|
} |
|
//</debug> |
|
|
|
// The URL we are requesting API from is not the same as the |
|
// service URL, and we don't need them to mix. |
|
delete config.url; |
|
|
|
// Have to use closures here as Loader does not allow passing |
|
// options object from caller to callback. |
|
Ext.Loader.loadScript({ |
|
url: url, |
|
scope: me, |
|
|
|
onLoad: function() { |
|
this.onApiLoadSuccess({ |
|
url: url, |
|
varName: varName, |
|
config: config, |
|
callback: callback, |
|
scope: scope |
|
}); |
|
}, |
|
|
|
onError: function() { |
|
this.onApiLoadFailure({ |
|
url: url, |
|
callback: callback, |
|
scope: scope |
|
}); |
|
} |
|
}); |
|
}, |
|
|
|
/** |
|
* Retrieves a {@link Ext.direct.Provider provider} by the id specified when the |
|
* provider is added. |
|
* |
|
* @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance. |
|
*/ |
|
getProvider: function(id) { |
|
return id.isProvider ? id : this.providers.get(id); |
|
}, |
|
|
|
/** |
|
* Removes the provider. |
|
* |
|
* @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider. |
|
* |
|
* @return {Ext.direct.Provider} The provider, null if not found. |
|
*/ |
|
removeProvider: function(provider) { |
|
var me = this, |
|
providers = me.providers, |
|
relayers = me.relayers, |
|
id; |
|
|
|
provider = provider.isProvider ? provider : providers.get(provider); |
|
|
|
if (provider) { |
|
provider.un('data', me.onProviderData, me); |
|
|
|
id = provider.id; |
|
|
|
if (relayers[id]) { |
|
relayers[id].destroy(); |
|
delete relayers[id]; |
|
} |
|
|
|
providers.remove(provider); |
|
|
|
return provider; |
|
} |
|
|
|
return null; |
|
}, |
|
|
|
/** |
|
* Adds a transaction to the manager. |
|
* |
|
* @param {Ext.direct.Transaction} transaction The transaction to add |
|
* |
|
* @return {Ext.direct.Transaction} transaction |
|
* |
|
* @private |
|
*/ |
|
addTransaction: function(transaction) { |
|
this.transactions.add(transaction); |
|
|
|
return transaction; |
|
}, |
|
|
|
/** |
|
* Removes a transaction from the manager. |
|
* |
|
* @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove |
|
* |
|
* @return {Ext.direct.Transaction} transaction |
|
* |
|
* @private |
|
*/ |
|
removeTransaction: function(transaction) { |
|
var me = this; |
|
|
|
transaction = me.getTransaction(transaction); |
|
me.transactions.remove(transaction); |
|
|
|
return transaction; |
|
}, |
|
|
|
/** |
|
* Gets a transaction |
|
* |
|
* @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get |
|
* |
|
* @return {Ext.direct.Transaction} |
|
* |
|
* @private |
|
*/ |
|
getTransaction: function(transaction) { |
|
return typeof transaction === 'object' ? transaction : this.transactions.get(transaction); |
|
}, |
|
|
|
onProviderData: function(provider, event) { |
|
var me = this, |
|
i, len; |
|
|
|
if (Ext.isArray(event)) { |
|
for (i = 0, len = event.length; i < len; ++i) { |
|
me.onProviderData(provider, event[i]); |
|
} |
|
|
|
return; |
|
} |
|
|
|
if (event.name && event.name !== 'event' && event.name !== 'exception') { |
|
me.fireEvent(event.name, event); |
|
} |
|
else if (event.status === false) { |
|
me.fireEvent('exception', event); |
|
} |
|
|
|
me.fireEvent('event', event, provider); |
|
}, |
|
|
|
/** |
|
* Parses a direct function. It may be passed in a string format, for example: |
|
* "MyApp.Person.read". |
|
* |
|
* @param {String/Function} fn The direct function |
|
* |
|
* @return {Function} The function to use in the direct call. Null if not found |
|
*/ |
|
parseMethod: function(fn) { |
|
var current = Ext.global, |
|
i = 0, |
|
resolved, parts, len; |
|
|
|
if (Ext.isFunction(fn)) { |
|
resolved = fn; |
|
} |
|
else if (Ext.isString(fn)) { |
|
resolved = this.remotingMethods[fn]; |
|
|
|
// Support legacy resolution as top-down lookup |
|
// from the window scope |
|
if (!resolved) { |
|
parts = fn.split('.'); |
|
len = parts.length; |
|
|
|
while (current && i < len) { |
|
current = current[parts[i]]; |
|
++i; |
|
} |
|
|
|
resolved = Ext.isFunction(current) ? current : null; |
|
} |
|
} |
|
|
|
return resolved || null; |
|
}, |
|
|
|
privates: { |
|
addProviderClass: function(type, cls) { |
|
this.providerClasses[type] = cls; |
|
}, |
|
|
|
onApiLoadSuccess: function(options) { |
|
var me = this, |
|
url = options.url, |
|
varName = options.varName, |
|
api, provider, error; |
|
|
|
try { |
|
// Variable name could be nested (default is Ext.app.REMOTING_API), |
|
// so we use eval() to get the actual value. |
|
api = Ext.apply(options.config, eval(varName)); |
|
|
|
provider = me.addProvider(api); |
|
} |
|
catch (e) { |
|
error = e + ''; |
|
} |
|
|
|
if (error) { |
|
me.fireEventArgs('providerloaderror', [url, error]); |
|
Ext.callback(options.callback, options.scope, [url, error]); |
|
} |
|
else { |
|
me.fireEventArgs('providerload', [url, provider]); |
|
Ext.callback(options.callback, options.scope, [url, provider]); |
|
} |
|
}, |
|
|
|
onApiLoadFailure: function(options) { |
|
var url = options.url, |
|
error; |
|
|
|
error = Ext.String.format(this.apiNotFoundError, url); |
|
|
|
this.fireEventArgs('providerloaderror', [url, error]); |
|
Ext.callback(options.callback, options.scope, [url, error]); |
|
}, |
|
|
|
registerMethod: function(name, method) { |
|
this.remotingMethods[name] = method; |
|
}, |
|
|
|
// Used for testing |
|
clearAllMethods: function() { |
|
this.remotingMethods = {}; |
|
} |
|
} |
|
}, function() { |
|
// Backwards compatibility |
|
Ext.Direct = Ext.direct.Manager; |
|
});
|
|
|