UNPKG

extjs-gpl

Version:

GPL licensed version of Sencha Ext JS

1,505 lines (1,478 loc) 172 kB
/** * SQL proxy lets you store data in a SQL database. * The Sencha Touch SQL proxy outputs model data into an HTML5 * local database using WebSQL. * * You can create a Store for the proxy, for example: * * Ext.require(["Ext.data.proxy.SQL"]); * * Ext.define("User", { * extend: "Ext.data.Model", * config: { * fields: [ "firstName", "lastName" ] * } * }); * * Ext.create("Ext.data.Store", { * model: "User", * storeId: "Users", * proxy: { * type: "sql" * } * }); * * Ext.getStore("Users").add({ * firstName: "Polly", * lastName: "Hedra" * }); * * Ext.getStore("Users").sync(); * * To destroy a table use: * * Ext.getStore("Users").getProxy().dropTable(); * * To recreate a table use: * * Ext.data.Store.sync() or Ext.data.Model.save() */ Ext.define('Ext.data.proxy.Sql', { alias: 'proxy.sql', extend: 'Ext.data.proxy.Client', alternateClassName: 'Ext.data.proxy.SQL', isSQLProxy: true, config: { /** * @cfg {Object} reader * @hide */ reader: null, /** * @cfg {Object} writer * @hide */ writer: null, /** * @cfg {String} table * Optional Table name to use if not provided ModelName will be used */ table: null, /** * @cfg {String} database * Database name to access tables from */ database: 'Sencha' }, _createOptions: { silent: true, dirty: false }, updateModel: function(model) { var me = this, modelName, len, i, columns, quoted; if (model) { me.uniqueIdStrategy = model.identifier.isUnique; if (!me.getTable()) { modelName = model.entityName; me.setTable(modelName.slice(modelName.lastIndexOf('.') + 1)); } me.columns = columns = me.getPersistedModelColumns(model); me.quotedColumns = quoted = []; for (i = 0 , len = columns.length; i < len; ++i) { quoted.push('"' + columns[i] + '"'); } } me.callParent([ model ]); }, setException: function(operation, error) { operation.setException(error); }, create: function(operation) { var me = this, records = operation.getRecords(), result, error; operation.setStarted(); me.executeTransaction(function(transaction) { me.insertRecords(records, transaction, function(resultSet, statementError) { result = resultSet; error = statementError; }); }, function(transactionError) { operation.setException(transactionError); }, function() { if (error) { operation.setException(statementError); } else { operation.process(result); } }); }, read: function(operation) { var me = this, model = me.getModel(), records = operation.getRecords(), record = records ? records[0] : null, result, error, id, params; if (record && !record.phantom) { id = record.getId(); } else { id = operation.getId(); } if (id !== undefined) { params = { idOnly: true, id: id }; } else { params = { page: operation.getPage(), start: operation.getStart(), limit: operation.getLimit(), sorters: operation.getSorters(), filters: operation.getFilters() }; } operation.setStarted(); me.executeTransaction(function(transaction) { me.selectRecords(transaction, params, function(resultSet, statementError) { result = resultSet; error = statementError; }); }, function(transactionError) { operation.setException(transactionError); }, function() { if (error) { operation.setException(statementError); } else { operation.process(result); } }); }, update: function(operation) { var me = this, records = operation.getRecords(), result, error; operation.setStarted(); me.executeTransaction(function(transaction) { me.updateRecords(transaction, records, function(resultSet, statementError) { result = resultSet; error = statementError; }); }, function(transactionError) { operation.setException(transactionError); }, function() { if (error) { operation.setException(statementError); } else { operation.process(result); } }); }, erase: function(operation) { var me = this, records = operation.getRecords(), result, error; operation.setStarted(); me.executeTransaction(function(transaction) { me.destroyRecords(transaction, records, function(resultSet, statementError) { result = resultSet; error = statementError; }); }, function(transactionError) { operation.setException(transactionError); }, function() { if (error) { operation.setException(error); } else { operation.process(result); } }); }, createTable: function(transaction) { var me = this; if (!transaction) { me.executeTransaction(function(transaction) { me.createTable(transaction); }); return; } me.executeStatement(transaction, 'CREATE TABLE IF NOT EXISTS "' + me.getTable() + '" (' + me.getSchemaString() + ')', function() { me.tableExists = true; }); }, insertRecords: function(records, transaction, callback) { var me = this, columns = me.columns, totalRecords = records.length, executed = 0, uniqueIdStrategy = me.uniqueIdStrategy, setOptions = me._createOptions, len = records.length, i, record, placeholders, sql, data, values, errors, completeIf; completeIf = function(transaction) { ++executed; if (executed === totalRecords) { callback.call(me, new Ext.data.ResultSet({ success: !errors }), errors); } }; placeholders = Ext.String.repeat('?', columns.length, ','); sql = 'INSERT INTO "' + me.getTable() + '" (' + me.quotedColumns.join(',') + ') VALUES (' + placeholders + ')'; for (i = 0; i < len; ++i) { record = records[i]; data = me.getRecordData(record); values = me.getColumnValues(columns, data); // Capture the record in closure scope so we can access it later (function(record) { me.executeStatement(transaction, sql, values, function(transaction, resultSet) { if (!uniqueIdStrategy) { record.setId(resultSet.insertId, setOptions); } completeIf(); }, function(transaction, error) { if (!errors) { errors = []; } errors.push(error); completeIf(); }); })(record); } }, selectRecords: function(transaction, params, callback, scope) { var me = this, Model = me.getModel(), idProperty = Model.idProperty, sql = 'SELECT * FROM "' + me.getTable() + '"', filterStatement = ' WHERE ', sortStatement = ' ORDER BY ', values = [], sorters, filters, placeholder, i, len, result, filter, sorter, property, operator, value; if (params.idOnly) { sql += filterStatement + '"' + idProperty + '" = ?'; values.push(params); } else { filters = params.filters; len = filters && filters.length; if (len) { for (i = 0; i < len; i++) { filter = filters[i]; property = filter.getProperty(); value = me.toSqlValue(filter.getValue(), Model.getField(property)); operator = filter.getOperator(); if (property !== null) { operator = operator || '='; placeholder = '?'; if (operator === 'like' || (operator === '=' && filter.getAnyMatch())) { operator = 'LIKE'; value = '%' + value + '%'; } if (operator === 'in' || operator === 'notin') { if (operator === 'notin') { operator = 'not in'; } placeholder = '(' + Ext.String.repeat('?', value.length, ',') + ')'; values = values.concat(value); } else { values.push(value); } sql += filterStatement + '"' + property + '" ' + operator + ' ' + placeholder; filterStatement = ' AND '; } } } sorters = params.sorters; len = sorters && sorters.length; if (len) { for (i = 0; i < len; i++) { sorter = sorters[i]; property = sorter.getProperty(); if (property !== null) { sql += sortStatement + '"' + property + '" ' + sorter.getDirection(); sortStatement = ', '; } } } // handle start, limit, sort, filter and group params if (params.page !== undefined) { sql += ' LIMIT ' + parseInt(params.start, 10) + ', ' + parseInt(params.limit, 10); } } me.executeStatement(transaction, sql, values, function(transaction, resultSet) { var rows = resultSet.rows, count = rows.length, records = [], fields = Model.fields, fieldsLen = fields.length, raw, data, i, len, j, field, name; for (i = 0 , len = count; i < len; ++i) { raw = rows.item(i); data = {}; for (j = 0; j < fieldsLen; ++j) { field = fields[j]; name = field.name; data[name] = me.fromSqlValue(raw[name], field); } records.push(new Model(data)); } callback.call(me, new Ext.data.ResultSet({ records: records, success: true, total: count, count: count })); }, function(transaction, error) { callback.call(me, new Ext.data.ResultSet({ success: false, total: 0, count: 0 }), error); }); }, updateRecords: function(transaction, records, callback) { var me = this, columns = me.columns, quotedColumns = me.quotedColumns, totalRecords = records.length, executed = 0, updates = [], setOptions = me._createOptions, len, i, record, placeholders, sql, data, values, errors, completeIf; completeIf = function(transaction) { ++executed; if (executed === totalRecords) { callback.call(me, new Ext.data.ResultSet({ success: !errors }), errors); } }; for (i = 0 , len = quotedColumns.length; i < len; i++) { updates.push(quotedColumns[i] + ' = ?'); } sql = 'UPDATE "' + me.getTable() + '" SET ' + updates.join(', ') + ' WHERE "' + me.getModel().idProperty + '" = ?'; for (i = 0 , len = records.length; i < len; ++i) { record = records[i]; data = me.getRecordData(record); values = me.getColumnValues(columns, data); values.push(record.getId()); // Capture the record in closure scope so we can access it later (function(record) { me.executeStatement(transaction, sql, values, function(transaction, resultSet) { completeIf(); }, function(transaction, error) { if (!errors) { errors = []; } errors.push(error); completeIf(); }); })(record); } }, destroyRecords: function(transaction, records, callback) { var me = this, table = me.getTable(), idProperty = me.getModel().idProperty, ids = [], values = [], destroyedRecords = [], len = records.length, idStr = '"' + idProperty + '" = ?', i, result, record, sql; for (i = 0; i < len; i++) { ids.push(idStr); values.push(records[i].getId()); } sql = 'DELETE FROM "' + me.getTable() + '" WHERE ' + ids.join(' OR '); me.executeStatement(transaction, sql, values, function(transaction, resultSet) { callback.call(me, new Ext.data.ResultSet({ success: true })); }, function(transaction, error) { callback.call(me, new Ext.data.ResultSet({ success: false }), error); }); }, /** * Formats the data for each record before sending it to the server. This * method should be overridden to format the data in a way that differs from the default. * @param {Object} record The record that we are writing to the server. * @return {Object} An object literal of name/value keys to be written to the server. * By default this method returns the data property on the record. */ getRecordData: function(record) { var me = this, fields = record.fields, idProperty = record.idProperty, uniqueIdStrategy = me.uniqueIdStrategy, data = {}, len = fields.length, recordData = record.data, i, name, value, field; for (i = 0; i < len; ++i) { field = fields[i]; if (field.persist !== false) { name = field.name; if (name === idProperty && !uniqueIdStrategy) { continue; } data[name] = me.toSqlValue(recordData[name], field); } } return data; }, getColumnValues: function(columns, data) { var len = columns.length, values = [], i, column, value; for (i = 0; i < len; i++) { column = columns[i]; value = data[column]; if (value !== undefined) { values.push(value); } } return values; }, getSchemaString: function() { var me = this, schema = [], model = me.getModel(), idProperty = model.idProperty, fields = model.fields, uniqueIdStrategy = me.uniqueIdStrategy, len = fields.length, i, field, type, name; for (i = 0; i < len; i++) { field = fields[i]; type = field.getType(); name = field.name; if (name === idProperty) { if (uniqueIdStrategy) { type = me.convertToSqlType(type); schema.unshift('"' + idProperty + '" ' + type); } else { schema.unshift('"' + idProperty + '" INTEGER PRIMARY KEY AUTOINCREMENT'); } } else { type = me.convertToSqlType(type); schema.push('"' + name + '" ' + type); } } return schema.join(', '); }, convertToSqlType: function(type) { switch (type.toLowerCase()) { case 'string': case 'auto': return 'TEXT'; case 'int': case 'date': return 'INTEGER'; case 'float': return 'REAL'; case 'bool': return 'NUMERIC'; } }, dropTable: function() { var me = this; me.executeTransaction(function(transaction) { me.executeStatement(transaction, 'DROP TABLE "' + me.getTable() + '"', function() { me.tableExists = false; }); }, null, null, false); }, getDatabaseObject: function() { return window.openDatabase(this.getDatabase(), '1.0', 'Sencha Database', 5 * 1024 * 1024); }, privates: { executeStatement: function(transaction, sql, values, success, failure) { var me = this; transaction.executeSql(sql, values, success ? function() { success.apply(me, arguments); } : null, failure ? function() { failure.apply(me, arguments); } : null); }, executeTransaction: function(runner, failure, success, autoCreateTable) { var me = this; autoCreateTable = autoCreateTable !== false; me.getDatabaseObject().transaction(runner ? function(transaction) { if (autoCreateTable && !me.tableExists) { me.createTable(transaction); } runner.apply(me, arguments); } : null, failure ? function() { failure.apply(me, arguments); } : null, success ? function() { success.apply(me, arguments); } : null); }, fromSqlValue: function(value, field) { if (field.isDateField) { value = value ? new Date(value) : null; } else if (field.isBooleanField) { value = value === 1; } return value; }, getPersistedModelColumns: function(model) { var fields = model.fields, uniqueIdStrategy = this.uniqueIdStrategy, idProperty = model.idProperty, columns = [], len = fields.length, i, field, name; for (i = 0; i < len; ++i) { field = fields[i]; name = field.name; if (name === idProperty && !uniqueIdStrategy) { continue; } if (field.persist !== false) { columns.push(field.name); } } return columns; }, toSqlValue: function(value, field) { if (field.isDateField) { value = value ? value.getTime() : null; } else if (field.isBooleanField) { value = value ? 1 : 0; } return value; } } }); /** * @private */ Ext.define('Ext.device.accelerometer.Abstract', { config: { /** * @cfg {Number} frequency The default frequency to get the current acceleration when using {@link Ext.device.Accelerometer#watchAcceleration}. */ frequency: 10000 }, getCurrentAcceleration: function(config) { // <debug> if (!config.success) { Ext.Logger.warn('You need to specify a `success` function for #getCurrentAcceleration'); } // </debug> return config; }, watchAcceleration: function(config) { var defaultConfig = Ext.device.accelerometer.Abstract.prototype.config; config = Ext.applyIf(config, { frequency: defaultConfig.frequency }); // <debug> if (!config.callback) { Ext.Logger.warn('You need to specify a `callback` function for #watchAcceleration'); } // </debug> return config; }, clearWatch: Ext.emptyFn }); /** * @private */ Ext.define('Ext.device.accelerometer.Cordova', { alternateClassName: 'Ext.device.accelerometer.PhoneGap', extend: 'Ext.device.accelerometer.Abstract', activeWatchID: null, getCurrentAcceleration: function(config) { config = this.callParent(arguments); navigator.accelerometer.getCurrentAcceleration(config.success, config.failure); return config; }, watchAcceleration: function(config) { config = this.callParent(arguments); if (this.activeWatchID) { this.clearWatch(); } this.activeWatchID = navigator.accelerometer.watchAcceleration(config.callback, config.failure, config); return config; }, clearWatch: function() { if (this.activeWatchID) { navigator.accelerometer.clearWatch(this.activeWatchID); this.activeWatchID = null; } } }); /** * @private */ Ext.define('Ext.device.accelerometer.Simulator', { extend: 'Ext.device.accelerometer.Abstract' }); /** * Provides access to the native Accelerometer API when running on a device. There are three implementations of this API: * * - [PhoneGap](http://docs.phonegap.com/en/2.6.0/cordova_accelerometer_accelerometer.md.html#Accelerometer) * * This class will automatically select the correct implementation depending on the device your application is running on. * * ## Examples * * Getting the current location: * * Ext.device.Accelerometer.getCurrentAcceleration({ * success: function(acceleration) { * alert('Acceleration X: ' + acceleration.x + '\n' + * 'Acceleration Y: ' + acceleration.y + '\n' + * 'Acceleration Z: ' + acceleration.z + '\n' + * 'Timestamp: ' + acceleration.timestamp + '\n'); * }, * failure: function() { * console.log('something went wrong!'); * } * }); * * Watching the current acceleration: * * Ext.device.Accelerometer.watchAcceleration({ * frequency: 500, // Update every 1/2 second * callback: function(acceleration) { * console.log('Acceleration X: ' + acceleration.x + '\n' + * 'Acceleration Y: ' + acceleration.y + '\n' + * 'Acceleration Z: ' + acceleration.z + '\n' + * 'Timestamp: ' + acceleration.timestamp + '\n'); * }, * failure: function() { * console.log('something went wrong!'); * } * }); * * @mixins Ext.device.accelerometer.Abstract */ Ext.define('Ext.device.Accelerometer', { singleton: true, requires: [ 'Ext.device.accelerometer.Cordova', 'Ext.device.accelerometer.Simulator' ], constructor: function() { var browserEnv = Ext.browser.is; if (browserEnv.WebView && browserEnv.Cordova) { return Ext.create('Ext.device.accelerometer.Cordova'); } return Ext.create('Ext.device.accelerometer.Simulator'); } }); /** * @private * * This object handles communication between the WebView and Sencha's native shell. * Currently it has two primary responsibilities: * * 1. Maintaining unique string ids for callback functions, together with their scope objects * 2. Serializing given object data into HTTP GET request parameters * * As an example, to capture a photo from the device's camera, we use `Ext.device.Camera.capture()` like: * * Ext.device.Camera.capture( * function(dataUri){ * // Do something with the base64-encoded `dataUri` string * }, * function(errorMessage) { * * }, * callbackScope, * { * quality: 75, * width: 500, * height: 500 * } * ); * * Internally, `Ext.device.Communicator.send()` will then be invoked with the following argument: * * Ext.device.Communicator.send({ * command: 'Camera#capture', * callbacks: { * onSuccess: function() { * // ... * }, * onError: function() { * // ... * } * }, * scope: callbackScope, * quality: 75, * width: 500, * height: 500 * }); * * Which will then be transformed into a HTTP GET request, sent to native shell's local * HTTP server with the following parameters: * * ?quality=75&width=500&height=500&command=Camera%23capture&onSuccess=3&onError=5 * * Notice that `onSuccess` and `onError` have been converted into string ids (`3` and `5` * respectively) and maintained by `Ext.device.Communicator`. * * Whenever the requested operation finishes, `Ext.device.Communicator.invoke()` simply needs * to be executed from the native shell with the corresponding ids given before. For example: * * Ext.device.Communicator.invoke('3', ['DATA_URI_OF_THE_CAPTURED_IMAGE_HERE']); * * will invoke the original `onSuccess` callback under the given scope. (`callbackScope`), with * the first argument of 'DATA_URI_OF_THE_CAPTURED_IMAGE_HERE' * * Note that `Ext.device.Communicator` maintains the uniqueness of each function callback and * its scope object. If subsequent calls to `Ext.device.Communicator.send()` have the same * callback references, the same old ids will simply be reused, which guarantee the best possible * performance for a large amount of repetitive calls. */ Ext.define('Ext.device.communicator.Default', { SERVER_URL: 'http://localhost:3000', // Change this to the correct server URL callbackDataMap: {}, callbackIdMap: {}, idSeed: 0, globalScopeId: '0', generateId: function() { return String(++this.idSeed); }, getId: function(object) { var id = object.$callbackId; if (!id) { object.$callbackId = id = this.generateId(); } return id; }, getCallbackId: function(callback, scope) { var idMap = this.callbackIdMap, dataMap = this.callbackDataMap, id, scopeId, callbackId, data; if (!scope) { scopeId = this.globalScopeId; } else if (scope.isIdentifiable) { scopeId = scope.getId(); } else { scopeId = this.getId(scope); } callbackId = this.getId(callback); if (!idMap[scopeId]) { idMap[scopeId] = {}; } if (!idMap[scopeId][callbackId]) { id = this.generateId(); data = { callback: callback, scope: scope }; idMap[scopeId][callbackId] = id; dataMap[id] = data; } return idMap[scopeId][callbackId]; }, getCallbackData: function(id) { return this.callbackDataMap[id]; }, invoke: function(id, args) { var data = this.getCallbackData(id); data.callback.apply(data.scope, args); }, send: function(args) { var callbacks, scope, name, callback; if (!args) { args = {}; } else if (args.callbacks) { callbacks = args.callbacks; scope = args.scope; delete args.callbacks; delete args.scope; for (name in callbacks) { if (callbacks.hasOwnProperty(name)) { callback = callbacks[name]; if (typeof callback == 'function') { args[name] = this.getCallbackId(callback, scope); } } } } args.__source = document.location.href; var result = this.doSend(args); return (result && result.length > 0) ? JSON.parse(result) : null; }, doSend: function(args) { var xhr = new XMLHttpRequest(); xhr.open('GET', this.SERVER_URL + '?' + Ext.Object.toQueryString(args) + '&_dc=' + new Date().getTime(), false); // wrap the request in a try/catch block so we can check if any errors are thrown and attempt to call any // failure/callback functions if defined try { xhr.send(null); return xhr.responseText; } catch (e) { if (args.failure) { this.invoke(args.failure); } else if (args.callback) { this.invoke(args.callback); } } } }); /** * @private */ Ext.define('Ext.device.communicator.Android', { extend: 'Ext.device.communicator.Default', doSend: function(args) { return window.Sencha.action(JSON.stringify(args)); } }); /** * @private */ Ext.define('Ext.device.Communicator', { requires: [ 'Ext.device.communicator.Default', 'Ext.device.communicator.Android' ], singleton: true, constructor: function() { if (Ext.os.is.Android) { return new Ext.device.communicator.Android(); } return new Ext.device.communicator.Default(); } }); /** * @private */ Ext.define('Ext.device.analytics.Abstract', { config: { accountID: null }, updateAccountID: function(newID) { if (newID) { window.plugins.googleAnalyticsPlugin.startTrackerWithAccountID(newID); } }, /** * Registers yur Google Analytics account. * * @param {String} accountID Your Google Analytics account ID */ registerAccount: function(accountID) { this.setAccountID(accountID); }, /** * Track an event in your application. * * More information here: http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html * * @param {Object} config * * @param {String} config.category The name you supply for the group of objects you want to track * * @param {String} config.action A string that is uniquely paired with each category, and commonly * used to define the type of user interaction for the web object. * * @param {String} config.label An optional string to provide additional dimensions to the event data. * * @param {String} config.value An integer that you can use to provide numerical data about the user event * * @param {Boolean} config.nonInteraction A boolean that when set to true, indicates that the event hit will * not be used in bounce-rate calculation. */ trackEvent: Ext.emptyFn, /** * Track an pageview in your application. * * @param {String} config.page The page you want to track (must start with a slash). */ trackPageview: Ext.emptyFn }); /** * @private */ Ext.define('Ext.device.analytics.Cordova', { extend: 'Ext.device.analytics.Abstract', trackEvent: function(config) { if (!this.getAccountID()) { return; } window.plugins.googleAnalyticsPlugin.trackEvent(config.category, config.action, config.label, config.value, config.nonInteraction); }, trackPageview: function(page) { if (!this.getAccountID()) { return; } window.plugins.googleAnalyticsPlugin.trackPageview(page); } }); /** * Allows you to use Google Analytics within your Cordova application. * * For setup information, please read the [plugin documentation](https://github.com/phonegap/phonegap-facebook-plugin). * * @mixins Ext.device.analytics.Abstract */ Ext.define('Ext.device.Analytics', { alternateClassName: 'Ext.ux.device.Analytics', singleton: true, requires: [ 'Ext.device.Communicator', 'Ext.device.analytics.*' ], constructor: function() { var browserEnv = Ext.browser.is; if (browserEnv.WebView && browserEnv.Cordova) { return Ext.create('Ext.device.analytics.Cordova'); } else { return Ext.create('Ext.device.analytics.Abstract'); } } }); /** * @private */ Ext.define('Ext.device.browser.Abstract', { /** * Used to open a new browser window. * * When used with Cordova, a new InAppBrowser window opens. With Cordova, you also have the ability * to listen when the window starts loading, is finished loading, fails to load, and when it is closed. * You can also use the {@link #close} method to close the window, if opened. * * @param {Object} options * The options to use when opening a new browser window. * * @param {String} options.url * The URL to open. * * @param {Object} options.listeners * The listeners you want to add onto the window. Available events are: * * - `loadstart` - when the window starts loading the URL * - `loadstop` - when the window is finished loading the URL * - `loaderror` - when the window encounters an error loading the URL * - `close` - when the window is closed * * @param {Boolean} options.showToolbar * True to show the toolbar in the browser window. * * @param {String} options.options * A string of options which are used when using Cordova. For a full list of options, visit the * [PhoneGap documention](http://docs.phonegap.com/en/2.6.0/cordova_inappbrowser_inappbrowser.md.html#window.open). */ open: Ext.emptyFn, /** * Used to close the browser, if one is opened. */ close: Ext.emptyFn }); /** * @private */ Ext.define('Ext.device.browser.Cordova', { extend: 'Ext.device.browser.Abstract', open: function(config) { if (!this._window) { this._window = Ext.create('Ext.device.browser.Window'); } this._window.open(config); return this._window; }, close: function() { if (!this._window) { return; } this._window.close(); } }); /** * @private */ Ext.define('Ext.device.browser.Simulator', { open: function(config) { window.open(config.url, '_blank'); }, close: Ext.emptyFn }); /** * @mixins Ext.device.browser.Abstract */ Ext.define('Ext.device.Browser', { singleton: true, requires: [ 'Ext.device.Communicator', 'Ext.device.browser.Cordova', 'Ext.device.browser.Simulator' ], constructor: function() { var browserEnv = Ext.browser.is; if (browserEnv.WebView && browserEnv.Cordova) { return Ext.create('Ext.device.browser.Cordova'); } return Ext.create('Ext.device.browser.Simulator'); } }); /** * @private */ Ext.define('Ext.device.camera.Abstract', { source: { library: 0, camera: 1, album: 2 }, destination: { data: 0, // Returns base64-encoded string file: 1, // Returns file's URI 'native': 2 }, encoding: { jpeg: 0, jpg: 0, png: 1 }, media: { picture: 0, video: 1, all: 2 }, direction: { back: 0, front: 1 }, /** * Allows you to capture a photo. * * @param {Object} options * The options to use when taking a photo. * * @param {Function} options.success * The success callback which is called when the photo has been taken. * * @param {String} options.success.image * The image which was just taken, either a base64 encoded string or a URI depending on which * option you chose (destination). * * @param {Function} options.failure * The function which is called when something goes wrong. * * @param {Object} scope * The scope in which to call the `success` and `failure` functions, if specified. * * @param {Number} options.quality * The quality of the image which is returned in the callback. This should be a percentage. * * @param {String} options.source * The source of where the image should be taken. Available options are: * * - **album** - prompts the user to choose an image from an album * - **camera** - prompts the user to take a new photo * - **library** - prompts the user to choose an image from the library * * @param {String} destination * The destination of the image which is returned. Available options are: * * - **data** - returns a base64 encoded string * - **file** - returns the file's URI * * @param {String} encoding * The encoding of the returned image. Available options are: * * - **jpg** * - **png** * * @param {Number} width * The width of the image to return * * @param {Number} height * The height of the image to return */ capture: Ext.emptyFn, getPicture: Ext.emptyFn, cleanup: Ext.emptyFn }); /** * @private */ Ext.define('Ext.device.camera.Cordova', { alternateClassName: 'Ext.device.camera.PhoneGap', extend: 'Ext.device.camera.Abstract', getPicture: function(onSuccess, onError, options) { try { navigator.camera.getPicture(onSuccess, onError, options); } catch (e) { alert(e); } }, cleanup: function(onSuccess, onError) { try { navigator.camera.cleanup(onSuccess, onError); } catch (e) { alert(e); } }, capture: function(args) { var onSuccess = args.success, onError = args.failure, scope = args.scope, sources = this.source, destinations = this.destination, encodings = this.encoding, source = args.source, destination = args.destination, encoding = args.encoding, options = {}; if (scope) { onSuccess = Ext.Function.bind(onSuccess, scope); onError = Ext.Function.bind(onError, scope); } if (source !== undefined) { options.sourceType = sources.hasOwnProperty(source) ? sources[source] : source; } if (destination !== undefined) { options.destinationType = destinations.hasOwnProperty(destination) ? destinations[destination] : destination; } if (encoding !== undefined) { options.encodingType = encodings.hasOwnProperty(encoding) ? encodings[encoding] : encoding; } if ('quality' in args) { options.quality = args.quality; } if ('width' in args) { options.targetWidth = args.width; } if ('height' in args) { options.targetHeight = args.height; } this.getPicture(onSuccess, onError, options); } }); /** * @private */ Ext.define('Ext.device.camera.Simulator', { extend: 'Ext.device.camera.Abstract', config: { samples: [ { success: 'http://www.sencha.com/img/sencha-large.png' } ] }, constructor: function(config) { this.initConfig(config); }, updateSamples: function(samples) { this.sampleIndex = 0; }, capture: function(options) { var index = this.sampleIndex, samples = this.getSamples(), samplesCount = samples.length, sample = samples[index], scope = options.scope, success = options.success, failure = options.failure; if ('success' in sample) { if (success) { success.call(scope, sample.success); } } else { if (failure) { failure.call(scope, sample.failure); } } if (++index > samplesCount - 1) { index = 0; } this.sampleIndex = index; } }); /** * This class allows you to use native APIs to take photos using the device camera. * * When this singleton is instantiated, it will automatically select the correct implementation depending on the * current device: * * - Sencha Packager * - Cordova * - Simulator * * Both the Sencha Packager and Cordova implementations will use the native camera functionality to take or select * a photo. The Simulator implementation will simply return fake images. * * ## Example * * You can use the {@link Ext.device.Camera#capture} function to take a photo: * * Ext.device.Camera.capture({ * success: function(image) { * imageView.setSrc(image); * }, * quality: 75, * width: 200, * height: 200, * destination: 'data' * }); * * See the documentation for {@link Ext.device.Camera#capture} all available configurations. * * @mixins Ext.device.camera.Abstract */ Ext.define('Ext.device.Camera', { singleton: true, requires: [ 'Ext.device.Communicator', 'Ext.device.camera.Cordova', 'Ext.device.camera.Simulator' ], constructor: function() { var browserEnv = Ext.browser.is; if (browserEnv.WebView) { if (browserEnv.Cordova) { return Ext.create('Ext.device.camera.Cordova'); } } return Ext.create('Ext.device.camera.Simulator'); } }); /** * @private */ Ext.define('Ext.device.capture.Cordova', { captureAudio: function(config) { // <debug> if (!config.success) { Ext.Logger.warn('You need to specify a `success` function for #captureAudio'); } // </debug> var options = { limit: config.limit, duration: config.maximumDuration }; navigator.device.capture.captureAudio(config.success, config.failure, options); }, captureVideo: function(config) { // <debug> if (!config.success) { Ext.Logger.warn('You need to specify a `success` function for #captureVideo'); } // </debug> var options = { limit: config.limit, duration: config.maximumDuration }; navigator.device.capture.captureVideo(config.success, config.failure, options); } }); /** * @private */ Ext.define('Ext.device.capture.Abstract', { alternateClassName: 'Ext.device.capture.Simulator', /** * Start the audio recorder application and return information about captured audio clip file(s). * * @example * Ext.device.Capture.captureAudio({ * limit: 2, // limit to 2 recordings * maximumDuration: 10, // limit to 10 seconds per recording * success: function(files) { * for (var i = 0; i < files.length; i++) { * console.log('Captured audio path: ', files[i].fullPath); * }; * }, * failure: function() { * console.log('Something went wrong!'); * } * }); * * @param {Object} config The configuration object to be passed: * * @param {Number} config.limit The maximum number of recordings allowed (defaults to 1). * * @param {Number} config.maximumDuration The maximum duration of the capture, in seconds. * * @param {Number} config.duration The maximum duration of the capture, in seconds. * * @param {Function} config.success Called if the capture is successful. * @param {Array} config.success.files An array of objects containing information about the captured audio. * * @param {Function} config.failure Called if the capture is unsuccessful. */ captureAudio: Ext.emptyFn, /** * Start the video recorder application and return information about captured video clip file(s). * * @example * Ext.device.Capture.captureVideo({ * limit: 2, // limit to 2 recordings * maximumDuration: 10, // limit to 10 seconds per recording * success: function(files) { * for (var i = 0; i < files.length; i++) { * console.log('Captured video path: ', files[i].fullPath); * }; * }, * failure: function() { * console.log('Something went wrong!'); * } * }); * * @param {Object} config The configuration object to be passed: * * @param {Number} config.limit The maximum number of recordings allowed (defaults to 1). * * @param {Number} config.maximumDuration The maximum duration of the capture, in seconds. * * @param {Number} config.duration The maximum duration of the capture, in seconds. * * @param {Function} config.success Called if the capture is successful. * @param {Array} config.success.files An array of objects containing information about the captured video. * * @param {Function} config.failure Called if the capture is unsuccessful. */ captureVideo: Ext.emptyFn }); /** * Provides access to the audio and video capture capabilities of the device. * * @mixins Ext.device.capture.Abstract */ Ext.define('Ext.device.Capture', { singleton: true, requires: [ 'Ext.device.Communicator', 'Ext.device.capture.Cordova', 'Ext.device.capture.Simulator' ], constructor: function() { var browserEnv = Ext.browser.is; if (browserEnv.WebView && browserEnv.Cordova) { return Ext.create('Ext.device.capture.Cordova'); } return Ext.create('Ext.device.capture.Simulator'); } }); /** * @private */ Ext.define('Ext.device.compass.Abstract', { config: { /** * @cfg {Number} frequency The default frequency to get the current heading when using {@link Ext.device.Compass#watchHeading}. */ frequency: 100 }, getHeadingAvailable: function(config) { // <debug> if (!config.callback) { Ext.Logger.warn('You need to specify a `callback` function for #getHeadingAvailable'); } // </debug> return config; }, getCurrentHeading: function(config) { // <debug> if (!config.success) { Ext.Logger.warn('You need to specify a `success` function for #getCurrentHeading'); } // </debug> return config; }, watchHeading: function(config) { var defaultConfig = Ext.device.compass.Abstract.prototype.config; config = Ext.applyIf(config, { frequency: defaultConfig.frequency }); // <debug> if (!config.callback) { Ext.Logger.warn('You need to specify a `callback` function for #watchHeading'); } // </debug> return config; }, clearWatch: Ext.emptyFn }); /** * @private */ Ext.define('Ext.device.compass.Cordova', { alternateClassName: 'Ext.device.compass.PhoneGap', extend: 'Ext.device.compass.Abstract', activeWatchID: null, getHeadingAvailable: function(config) { var callback = function(result) { if (result.hasOwnProperty("code")) { config.callback.call(config.scope || this, false); } else { config.callback.call(config.scope || this, true); } }; this.getCurrentHeading({ success: callback, failure: callback }); }, getCurrentHeading: function(config) { config = this.callParent(arguments); navigator.compass.getCurrentHeading(config.success, config.failure); return config; }, watchHeading: function(config) { config = this.callParent(arguments); if (this.activeWatchID) { this.clearWatch(); } this.activeWatchID = navigator.compass.watchHeading(config.callback, config.failure, config); return config; }, clearWatch: function() { if (this.activeWatchID) { navigator.compass.clearWatch(this.activeWatchID); this.activeWatchID = null; } } }); /** * @private */ Ext.define('Ext.device.compass.Simulator', { extend: 'Ext.device.compass.Abstract' }); /** * Provides access to the native Compass API when running on a device. There are three implementations of this API: * * - [PhoneGap](http://docs.phonegap.com/en/2.6.0/cordova_compass_compass.md.html#Compass) * * This class will automatically select the correct implementation depending on the device your application is running on. * * ## Examples * * Getting the current location: * * Ext.device.Compass.getCurrentHeading({ * success: function(heading) { * alert('Heading: ' + heading.magneticHeading); * }, * failure: function() { * console.log('something went wrong!'); * } * }); * * Watching the current compass: * * Ext.device.Compass.watchHeading({ * frequency: 500, // Update every 1/2 second * callback: function(heading) { * con