extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
1,505 lines (1,478 loc) • 172 kB
JavaScript
/**
* 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