/**
* 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) {
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getCurrentAcceleration');
}
//
return config;
},
watchAcceleration: function(config) {
var defaultConfig = Ext.device.accelerometer.Abstract.prototype.config;
config = Ext.applyIf(config, {
frequency: defaultConfig.frequency
});
//
if (!config.callback) {
Ext.Logger.warn('You need to specify a `callback` function for #watchAcceleration');
}
//
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) {
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #captureAudio');
}
//
var options = {
limit: config.limit,
duration: config.maximumDuration
};
navigator.device.capture.captureAudio(config.success, config.failure, options);
},
captureVideo: function(config) {
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #captureVideo');
}
//
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) {
//
if (!config.callback) {
Ext.Logger.warn('You need to specify a `callback` function for #getHeadingAvailable');
}
//
return config;
},
getCurrentHeading: function(config) {
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getCurrentHeading');
}
//
return config;
},
watchHeading: function(config) {
var defaultConfig = Ext.device.compass.Abstract.prototype.config;
config = Ext.applyIf(config, {
frequency: defaultConfig.frequency
});
//
if (!config.callback) {
Ext.Logger.warn('You need to specify a `callback` function for #watchHeading');
}
//
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) {
* console.log('Heading: ' + heading.magneticHeading);
* },
* failure: function() {
* console.log('something went wrong!');
* }
* });
*
* @mixins Ext.device.compass.Abstract
*/
Ext.define('Ext.device.Compass', {
singleton: true,
requires: [
'Ext.device.compass.Cordova',
'Ext.device.compass.Simulator'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView && browserEnv.Cordova) {
return Ext.create('Ext.device.compass.Cordova');
}
return Ext.create('Ext.device.compass.Simulator');
}
});
/**
* @private
*/
Ext.define('Ext.device.connection.Abstract', {
extend: 'Ext.Evented',
mixins: [
'Ext.mixin.Observable'
],
config: {
online: false,
type: null
},
/**
* @event online
* Fires when the device goes online
*/
/**
* @event offline
* Fires when the device goes offline
*/
/**
* @property {String} UNKNOWN
* Text label for a connection type.
*/
UNKNOWN: 'Unknown connection',
/**
* @property {String} ETHERNET
* Text label for a connection type.
*/
ETHERNET: 'Ethernet connection',
/**
* @property {String} WIFI
* Text label for a connection type.
*/
WIFI: 'WiFi connection',
/**
* @property {String} CELL_2G
* Text label for a connection type.
*/
CELL_2G: 'Cell 2G connection',
/**
* @property {String} CELL_3G
* Text label for a connection type.
*/
CELL_3G: 'Cell 3G connection',
/**
* @property {String} CELL_4G
* Text label for a connection type.
*/
CELL_4G: 'Cell 4G connection',
/**
* @property {String} NONE
* Text label for a connection type.
*/
NONE: 'No network connection',
/**
* True if the device is currently online
* @return {Boolean} online
*/
isOnline: function() {
return this.getOnline();
}
});
/**
* @method getType
* Returns the current connection type.
* @return {String} type
*/
/**
* @private
*/
Ext.define('Ext.device.connection.Cordova', {
alternateClassName: 'Ext.device.connection.PhoneGap',
extend: 'Ext.device.connection.Abstract',
constructor: function() {
var me = this;
document.addEventListener('online', function() {
me.fireEvent('online', me);
});
document.addEventListener('offline', function() {
me.fireEvent('offline', me);
});
},
syncOnline: function() {
var type = navigator.connection.type;
this._type = type;
this._online = type != Connection.NONE;
},
getOnline: function() {
this.syncOnline();
return this._online;
},
getType: function() {
this.syncOnline();
return this._type;
}
});
/**
* @private
*/
Ext.define('Ext.device.connection.Simulator', {
extend: 'Ext.device.connection.Abstract',
getOnline: function() {
this._online = navigator.onLine;
this._type = Ext.device.Connection.UNKNOWN;
return this._online;
}
});
/**
* This class is used to check if the current device is currently online or not. It has three different implementations:
*
* - Sencha Packager
* - Cordova
* - Simulator
*
* Both the Sencha Packager and Cordova implementations will use the native functionality to determine if the current
* device is online. The Simulator version will simply use `navigator.onLine`.
*
* When this singleton ({@link Ext.device.Connection}) is instantiated, it will automatically decide which version to
* use based on the current platform.
*
* ## Examples
*
* Determining if the current device is online:
*
* alert(Ext.device.Connection.isOnline());
*
* Checking the type of connection the device has:
*
* alert('Your connection type is: ' + Ext.device.Connection.getType());
*
* The available connection types are:
*
* - {@link Ext.device.Connection#UNKNOWN UNKNOWN} - Unknown connection
* - {@link Ext.device.Connection#ETHERNET ETHERNET} - Ethernet connection
* - {@link Ext.device.Connection#WIFI WIFI} - WiFi connection
* - {@link Ext.device.Connection#CELL_2G CELL_2G} - Cell 2G connection
* - {@link Ext.device.Connection#CELL_3G CELL_3G} - Cell 3G connection
* - {@link Ext.device.Connection#CELL_4G CELL_4G} - Cell 4G connection
* - {@link Ext.device.Connection#NONE NONE} - No network connection
*
* @mixins Ext.device.connection.Abstract
*/
Ext.define('Ext.device.Connection', {
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.connection.Cordova',
'Ext.device.connection.Simulator'
],
/**
* @event onlinechange
* @inheritdoc Ext.device.connection.Sencha#onlinechange
*/
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.connection.Cordova');
}
}
return Ext.create('Ext.device.connection.Simulator');
}
});
/**
* @private
*/
Ext.define('Ext.device.contacts.Abstract', {
mixins: [
'Ext.mixin.Observable'
],
config: {
/**
* @cfg {Boolean} includeImages
* True to include images when you get the contacts store. Please beware that this can be very slow.
*/
includeImages: false
},
/**
* Returns an Array of contact objects.
* @return {Object[]} An array of contact objects.
*/
getContacts: function(config) {
if (!this._store) {
this._store = [
{
first: 'Peter',
last: 'Venkman',
emails: {
work: 'peter.venkman@gb.com'
}
},
{
first: 'Egon',
last: 'Spengler',
emails: {
work: 'egon.spengler@gb.com'
}
}
];
}
config.success.call(config.scope || this, this._store);
},
/**
* Returns base64 encoded image thumbnail for a contact specified in config.id
* **This method is for Sencha Native Packager only**
*
* @return {String} base64 string
*/
getThumbnail: function(config) {
config.callback.call(config.scope || this, "");
},
/**
* Returns localized, user readable label for a contact field (i.e. "Mobile", "Home")
* **This method is for Sencha Native Packager only**
*
* @return {String} user readable string
*/
getLocalizedLabel: function(config) {
config.callback.call(config.scope || this, config.label.toUpperCase(), config.label);
}
});
/**
* @private
*/
Ext.define('Ext.device.contacts.Cordova', {
alternateClassName: 'Ext.device.contacts.PhoneGap',
extend: 'Ext.device.contacts.Abstract',
getContacts: function(config) {
if (!config) {
Ext.Logger.warn('Ext.device.Contacts#getContacts: You must specify a `config` object.');
return false;
}
if (!config.success) {
Ext.Logger.warn('Ext.device.Contacts#getContacts: You must specify a `success` method.');
return false;
}
if (!config.fields) {
config.fields = [
"*"
];
}
if (!Ext.isArray(config.fields)) {
config.fields = [
config.fields
];
}
if (Ext.isEmpty(config.multiple)) {
config.multiple = true;
}
navigator.contacts.find(config.fields, config.success, config.failure, config);
}
});
/**
* This device API allows you to access a users contacts using a {@link Ext.data.Store}. This allows you to search, filter
* and sort through all the contacts using its methods.
*
* To use this API, all you need to do is require this class (`Ext.device.Contacts`) and then use `Ext.device.Contacts.getContacts()`
* to retrieve an array of contacts.
*
* **Please note that getThumbnail and getLocalizedLabel are *only* for the Sencha Native Packager.**
* **Both Cordova/PhoneGap and Sencha Native Packager can access the find method though properties of returned contacts will differ.**
*
* # Example
*
* Ext.application({
* name: 'Sencha',
* requires: 'Ext.device.Contacts',
*
* launch: function() {
* Ext.Viewport.add({
* xtype: 'list',
* itemTpl: '{First} {Last}',
* store: {
* fields: ['First', 'Last'],
* data: Ext.device.Contacts.getContacts()
* }
* });
* }
* });
*
* @mixins Ext.device.contacts.Abstract
* @mixins Ext.device.contacts.Sencha
* @mixins Ext.device.contacts.Cordova
*/
Ext.define('Ext.device.Contacts', {
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.contacts.Cordova'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.contacts.Cordova');
}
}
return Ext.create('Ext.device.contacts.Abstract');
}
});
/**
* @private
*/
Ext.define('Ext.device.device.Abstract', {
mixins: [
'Ext.mixin.Observable'
],
/**
* @event schemeupdate
* Event which is fired when your Sencha Native packaged application is opened from another application using a custom URL scheme.
*
* This event will only fire if the application was already open (in other words; `onReady` was already fired). This means you should check
* if {@link Ext.device.Device#scheme} is set in your Application `launch`/`onReady` method, and perform any needed changes for that URL (if defined).
* Then listen to this event for future changed.
*
* ## Example
*
* Ext.application({
* name: 'Sencha',
* requires: ['Ext.device.Device'],
* launch: function() {
* if (Ext.device.Device.scheme) {
* // the application was opened via another application. Do something:
* console.log('Applicaton opened via another application: ' + Ext.device.Device.scheme.url);
* }
*
* // Listen for future changes
* Ext.device.Device.on('schemeupdate', function(device, scheme) {
* // the application was launched, closed, and then launched another from another application
* // this means onReady wont be called again ('cause the application is already running in the
* // background) - but this event will be fired
* console.log('Applicated reopened via another application: ' + scheme.url);
* }, this);
* }
* });
*
* __Note:__ This currently only works with the Sencha Native Packager. If you attempt to listen to this event when packaged with
* PhoneGap or simply in the browser, it will never fire.**
*
* @param {Ext.device.Device} this The instance of Ext.device.Device
* @param {Object/Boolean} scheme The scheme information, if opened via another application
* @param {String} scheme.url The URL that was opened, if this application was opened via another application. Example: `sencha:`
* @param {String} scheme.sourceApplication The source application that opened this application. Example: `com.apple.safari`.
*/
/**
* @property {String} name
* Returns the name of the current device. If the current device does not have a name (for example, in a browser), it will
* default to `not available`.
*
* alert('Device name: ' + Ext.device.Device.name);
*/
name: 'not available',
/**
* @property {String} uuid
* Returns a unique identifier for the current device. If the current device does not have a unique identifier (for example,
* in a browser), it will default to `anonymous`.
*
* alert('Device UUID: ' + Ext.device.Device.uuid);
*/
uuid: 'anonymous',
/**
* @property {String} platform
* The current platform the device is running on.
*
* alert('Device platform: ' + Ext.device.Device.platform);
*/
platform: Ext.os.name,
/**
* @property {Object/Boolean} scheme
*
*/
scheme: false,
/**
* Opens a specified URL. The URL can contain a custom URL Scheme for another app or service:
*
* // Safari
* Ext.device.Device.openURL('http://sencha.com');
*
* // Telephone
* Ext.device.Device.openURL('tel:6501231234');
*
* // SMS with a default number
* Ext.device.Device.openURL('sms:+12345678901');
*
* // Email client
* Ext.device.Device.openURL('mailto:rob@sencha.com');
*
* You can find a full list of available URL schemes here: [http://wiki.akosma.com/IPhone_URL_Schemes](http://wiki.akosma.com/IPhone_URL_Schemes).
*
* __Note:__ This currently only works with the Sencha Native Packager. Attempting to use this on PhoneGap, iOS Simulator
* or the browser will simply result in the current window location changing.**
*
* If successful, this will close the application (as another one opens).
*
* @param {String} url The URL to open
*/
openURL: function(url) {
window.location = url;
}
});
/**
* @private
*/
Ext.define('Ext.device.device.Cordova', {
alternateClassName: 'Ext.device.device.PhoneGap',
extend: 'Ext.device.device.Abstract',
availableListeners: [
'pause',
'resume',
'backbutton',
'batterycritical',
'batterylow',
'batterystatus',
'menubutton',
'searchbutton',
'startcallbutton',
'endcallbutton',
'volumeupbutton',
'volumedownbutton'
],
constructor: function() {
// We can't get the device details until the device is ready, so lets wait.
if (Ext.isReady) {
this.onReady();
} else {
Ext.onReady(this.onReady, this, {
single: true
});
}
},
/**
* @property {String} cordova
* Returns the version of Cordova running on the device.
*
* alert('Device cordova: ' + Ext.device.Device.cordova);
*/
/**
* @property {String} version
* Returns the operating system version.
*
* alert('Device Version: ' + Ext.device.Device.version);
*/
/**
* @property {String} model
* Returns the device's model name.
*
* alert('Device Model: ' + Ext.device.Device.model);
*/
/**
* @event pause
* Fires when the application goes into the background
*/
/**
* @event resume
* Fires when the application goes into the foreground
*/
/**
* @event batterycritical
* This event that fires when a Cordova application detects the percentage of battery
* has reached the critical battery threshold.
*/
/**
* @event batterylow
* This event that fires when a Cordova application detects the percentage of battery
* has reached the low battery threshold.
*/
/**
* @event batterystatus
* This event that fires when a Cordova application detects the percentage of battery
* has changed by at least 1 percent.
*/
/**
* @event backbutton
* This is an event that fires when the user presses the back button.
*/
/**
* @event menubutton
* This is an event that fires when the user presses the menu button.
*/
/**
* @event searchbutton
* This is an event that fires when the user presses the search button.
*/
/**
* @event startcallbutton
* This is an event that fires when the user presses the start call button.
*/
/**
* @event endcallbutton
* This is an event that fires when the user presses the end call button.
*/
/**
* @event volumeupbutton
* This is an event that fires when the user presses the volume up button.
*/
/**
* @event volumedownbutton
* This is an event that fires when the user presses the volume down button.
*/
onReady: function() {
var me = this,
device = window.device;
me.name = device.name || device.model;
me.cordova = device.cordova;
me.platform = device.platform || Ext.os.name;
me.uuid = device.uuid;
me.version = device.version;
me.model = device.model;
},
privates: {
doAddListener: function(name) {
var me = this;
if (!me.addedListeners) {
me.addedListeners = [];
}
if (me.availableListeners.indexOf(name) != -1 && me.addedListeners.indexOf(name) == -1) {
// Add the listeners
me.addedListeners.push(name);
document.addEventListener(name, function() {
me.fireEvent(name, me);
});
}
Ext.device.Device.mixins.observable.doAddListener.apply(Ext.device.Device.mixins.observable, arguments);
}
}
});
/**
* @private
*/
Ext.define('Ext.device.device.Simulator', {
extend: 'Ext.device.device.Abstract'
});
/**
* Provides a cross device way to get information about the device your application is running on. There are 3 different implementations:
*
* - Sencha Packager
* - [Cordova](http://cordova.apache.org/docs/en/2.5.0/cordova_device_device.md.html#Device)
* - Simulator
*
* ## Examples
*
* #### Device Information
*
* Getting the device information:
*
* Ext.application({
* name: 'Sencha',
*
* // Remember that the Ext.device.Device class *must* be required
* requires: ['Ext.device.Device'],
*
* launch: function() {
* alert([
* 'Device name: ' + Ext.device.Device.name,
* 'Device platform: ' + Ext.device.Device.platform,
* 'Device UUID: ' + Ext.device.Device.uuid
* ].join('\n'));
* }
* });
*
* ### Custom Scheme URL
*
* Using custom scheme URL to application your application from other applications:
*
* Ext.application({
* name: 'Sencha',
* requires: ['Ext.device.Device'],
* launch: function() {
* if (Ext.device.Device.scheme) {
* // the application was opened via another application. Do something:
* alert('Applicaton pened via another application: ' + Ext.device.Device.scheme.url);
* }
*
* // Listen for future changes
* Ext.device.Device.on('schemeupdate', function(device, scheme) {
* // the application was launched, closed, and then launched another from another application
* // this means onReady wont be called again ('cause the application is already running in the
* // background) - but this event will be fired
* alert('Applicated reopened via another application: ' + scheme.url);
* }, this);
* }
* });
*
* Of course, you must add the custom scheme URL you would like to use when packaging your application.
* You can do this by setting the `URLScheme` property inside your `package.json` file (Sencha Native Packager configuration file):
*
* {
* ...
* "URLScheme": "sencha",
* ...
* }
*
* You can change the available URL scheme.
*
* You can then test it by packaging and installing the application onto a device/iOS Simulator, opening Safari and typing: `sencha:testing`.
* The application will launch and it will `alert` the URL you specified.
*
* **PLEASE NOTE: This currently only works with the Sencha Native Packager. If you attempt to listen to this event when packaged with
* PhoneGap or simply in the browser, it will not function.**
*
* @mixins Ext.device.device.Abstract
*/
Ext.define('Ext.device.Device', {
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.device.Cordova',
'Ext.device.device.Simulator'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.device.Cordova');
}
}
return Ext.create('Ext.device.device.Simulator');
}
});
/**
* @private
*/
Ext.define('Ext.device.filesystem.Abstract', {
config: {
fileSystemType: 1,
fileSystemSize: 0,
readerType: "text",
stringEncoding: "UTF8"
},
requestFileSystem: function(config) {
var defaultConfig = Ext.device.filesystem.Abstract.prototype.config;
config = Ext.applyIf(config, {
type: defaultConfig.fileSystemType,
size: defaultConfig.fileSystemSize,
success: Ext.emptyFn,
failure: Ext.emptyFn
});
return config;
}
});
/**
* @private
*/
Ext.define('Ext.device.filesystem.HTML5', {
extend: 'Ext.device.filesystem.Abstract',
/**
* Requests a {@link Ext.device.filesystem.FileSystem} instance.
*
* var me = this;
* var fs = Ext.create("Ext.device.FileSystem", {});
* fs.requestFileSystem({
* type: window.PERSISTENT,
* size: 1024 * 1024,
* success: function(fileSystem) {
* me.fs = fileSystem;
* },
* failure: function(err) {
* console.log("FileSystem Failure: " + err.code);
* }
* });
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Number} config.type
* window.TEMPORARY (0) or window.PERSISTENT (1)
*
* @param {Number} config.size
* Storage space, in Bytes, needed by the application
*
* @param {Function} config.success This is required.
* The callback to be called when the file system has been successfully created.
*
* @param {Ext.device.filesystem.FileSystem} config.success.fileSystem
* The created file system.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
requestFileSystem: function(config) {
if (!config.success) {
Ext.Logger.error('Ext.device.filesystem#requestFileSystem: You must specify a `success` callback.');
return null;
}
var me = this;
var successCallback = function(fs) {
var fileSystem = Ext.create('Ext.device.filesystem.FileSystem', fs);
config.success.call(config.scope || me, fileSystem);
};
window.requestFileSystem(config.type, config.size, successCallback, config.failure || Ext.emptyFn);
}
}, function() {
/**
* The FileSystem class which is used to represent a file system.
*/
Ext.define('Ext.device.filesystem.FileSystem', {
fs: null,
root: null,
constructor: function(fs) {
this.fs = fs;
this.root = Ext.create('Ext.device.filesystem.DirectoryEntry', '/', this);
},
/**
* Returns a {@link Ext.device.filesystem.DirectoryEntry} instance for the root of the file system.
*
* @return {Ext.device.filesystem.DirectoryEntry}
* The file system root directory.
*/
getRoot: function() {
return this.root;
}
}, function() {
/**
* The Entry class which is used to represent entries in a file system,
* each of which may be a {@link Ext.device.filesystem.FileEntry} or a {@link Ext.device.filesystem.DirectoryEntry}.
*
* This is an abstract class.
* @abstract
*/
Ext.define('Ext.device.filesystem.Entry', {
directory: false,
path: 0,
fileSystem: null,
entry: null,
constructor: function(directory, path, fileSystem) {
this.directory = directory;
this.path = path;
this.fileSystem = fileSystem;
},
/**
* Returns whether the entry is a file.
*
* @return {Boolean}
* The entry is a file.
*/
isFile: function() {
return !this.directory;
},
/**
* Returns whether the entry is a directory.
*
* @return {Boolean}
* The entry is a directory.
*/
isDirectory: function() {
return this.directory;
},
/**
* Returns the name of the entry, excluding the path leading to it.
*
* @return {String}
* The entry name.
*/
getName: function() {
var components = this.path.split('/');
for (var i = components.length - 1; i >= 0; --i) {
if (components[i].length > 0) {
return components[i];
}
}
return '/';
},
/**
* Returns the full absolute path from the root to the entry.
*
* @return {String}
* The entry full path.
*/
getFullPath: function() {
return this.path;
},
/**
* Returns the file system on which the entry resides.
*
* @return {Ext.device.filesystem.FileSystem}
* The entry file system.
*/
getFileSystem: function() {
return this.fileSystem;
},
getEntry: function() {
return null;
},
/**
* Moves the entry to a different location on the file system.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Ext.device.filesystem.DirectoryEntry} config.parent This is required.
* The directory to which to move the entry.
*
* @param {String} config.newName This is optional.
* The new name of the entry to move. Defaults to the entry's current name if unspecified.
*
* @param {Function} config.success This is optional.
* The callback to be called when the entry has been successfully moved.
*
* @param {Ext.device.filesystem.Entry} config.success.entry
* The entry for the new location.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
moveTo: function(config) {
if (config.parent == null) {
Ext.Logger.error('Ext.device.filesystem.Entry#moveTo: You must specify a new `parent` of the entry.');
return null;
}
var me = this;
this.getEntry({
options: config.options || {},
success: function(sourceEntry) {
config.parent.getEntry({
options: config.options || {},
success: function(destinationEntry) {
if (config.copy) {
sourceEntry.copyTo(destinationEntry, config.newName, function(entry) {
config.success.call(config.scope || me, entry.isDirectory ? Ext.create('Ext.device.filesystem.DirectoryEntry', entry.fullPath, me.fileSystem) : Ext.create('Ext.device.filesystem.FileEntry', entry.fullPath, me.fileSystem));
}, config.failure);
} else {
sourceEntry.moveTo(destinationEntry, config.newName, function(entry) {
config.success.call(config.scope || me, entry.isDirectory ? Ext.create('Ext.device.filesystem.DirectoryEntry', entry.fullPath, me.fileSystem) : Ext.create('Ext.device.filesystem.FileEntry', entry.fullPath, me.fileSystem));
}, config.failure);
}
},
failure: config.failure
});
},
failure: config.failure
});
},
/**
* Works the same way as {@link Ext.device.filesystem.Entry#moveTo}, but copies the entry.
*/
copyTo: function(config) {
this.moveTo(Ext.apply(config, {
copy: true
}));
},
/**
* Removes the entry from the file system.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Boolean} config.recursively This is optional
* Deletes a directory and all of its contents
*
* @param {Function} config.success This is optional.
* The callback to be called when the entry has been successfully removed.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
remove: function(config) {
this.getEntry({
success: function(entry) {
if (config.recursively && this.directory) {
entry.removeRecursively(config.success, config.failure);
} else {
entry.remove(config.success, config.failure);
}
},
failure: config.failure
});
},
/**
* Looks up the parent directory containing the entry.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Function} config.success This is required.
* The callback to be called when the parent directory has been successfully selected.
*
* @param {Ext.device.filesystem.DirectoryEntry} config.success.entry
* The parent directory of the entry.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
getParent: function(config) {
if (!config.success) {
Ext.Logger.error('Ext.device.filesystem.Entry#getParent: You must specify a `success` callback.');
return null;
}
var me = this;
this.getEntry({
options: config.options || {},
success: function(entry) {
entry.getParent(function(parentEntry) {
config.success.call(config.scope || me, parentEntry.isDirectory ? Ext.create('Ext.device.filesystem.DirectoryEntry', parentEntry.fullPath, me.fileSystem) : Ext.create('Ext.device.filesystem.FileEntry', parentEntry.fullPath, me.fileSystem));
}, config.failure);
},
failure: config.failure
});
}
});
/**
* The DirectoryEntry class which is used to represent a directory on a file system.
*/
Ext.define('Ext.device.filesystem.DirectoryEntry', {
extend: 'Ext.device.filesystem.Entry',
cachedDirectory: null,
constructor: function(path, fileSystem) {
this.callParent([
true,
path,
fileSystem
]);
},
/**
* Requests a Directory from the Local File System
*
* @param {Object} config
*
* @param {Object} config.options
* File creation options {create:true, exclusive:false}
*
* @param {Boolean} config.options.create
* Indicates if the directory should be created if it doesn't exist
*
* @param {Boolean} config.options.exclusive
* Used with the create option only indicates whether a creation causes an error if the directory already exists
*
* @param {Function} config.success
* The function called when the Directory is returned successfully
*
* @param {Ext.device.filesystem.DirectoryEntry} config.success.directory
* DirectoryEntry Object
*
* @param {Function} config.failure
* The function called when the Directory request causes an error
*
* @param {FileError} config.failure.error
*/
getEntry: function(config) {
var me = this;
var callback = config.success;
if ((config.options && config.options.create) && this.path) {
var folders = this.path.split("/");
if (folders[0] == '.' || folders[0] == '') {
folders = folders.slice(1);
}
var recursiveCreation = function(dirEntry) {
if (folders.length) {
dirEntry.getDirectory(folders.shift(), config.options, recursiveCreation, config.failure);
} else {
callback(dirEntry);
}
};
recursiveCreation(this.fileSystem.fs.root);
} else {
this.fileSystem.fs.root.getDirectory(this.path, config.options, function(directory) {
config.success.call(config.scope || me, directory);
}, config.failure);
}
},
/**
* Lists all the entries in the directory.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Function} config.success This is required.
* The callback to be called when the entries has been successfully read.
*
* @param {Ext.device.filesystem.Entry[]} config.success.entries
* The array of entries of the directory.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
readEntries: function(config) {
if (!config.success) {
Ext.Logger.error('Ext.device.filesystem.DirectoryEntry#readEntries: You must specify a `success` callback.');
return null;
}
var me = this;
this.getEntry({
success: function(dirEntry) {
var directoryReader = dirEntry.createReader();
directoryReader.readEntries(function(entryInfos) {
var entries = [],
i = 0,
len = entryInfos.length;
for (; i < len; i++) {
entryInfo = entryInfos[i];
entries[i] = entryInfo.isDirectory ? Ext.create('Ext.device.filesystem.DirectoryEntry', entryInfo.fullPath, me.fileSystem) : Ext.create('Ext.device.filesystem.FileEntry', entryInfo.fullPath, me.fileSystem);
}
config.success.call(config.scope || this, entries);
}, function(error) {
if (config.failure) {
config.failure.call(config.scope || this, error);
}
});
},
failure: config.failure
});
},
/**
* Creates or looks up a file.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {String} config.path This is required.
* The absolute path or relative path from the entry to the file to create or select.
*
* @param {Object} config.options This is optional.
* The object which contains the following options:
*
* @param {Boolean} config.options.create This is optional.
* Indicates whether to create a file, if path does not exist.
*
* @param {Boolean} config.options.exclusive This is optional. Used with 'create', by itself has no effect.
* Indicates that method should fail, if path already exists.
*
* @param {Function} config.success This is optional.
* The callback to be called when the file has been successfully created or selected.
*
* @param {Ext.device.filesystem.Entry} config.success.entry
* The created or selected file.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
getFile: function(config) {
if (config.path == null) {
Ext.Logger.error('Ext.device.filesystem.DirectoryEntry#getFile: You must specify a `path` of the file.');
return null;
}
var me = this;
var fullPath = this.path + config.path;
var fileEntry = Ext.create('Ext.device.filesystem.FileEntry', fullPath, this.fileSystem);
fileEntry.getEntry({
success: function() {
config.success.call(config.scope || me, fileEntry);
},
options: config.options || {},
failure: config.failure
});
},
/**
* Works the same way as {@link Ext.device.filesystem.DirectoryEntry#getFile},
* but creates or looks up a directory.
*/
getDirectory: function(config) {
if (config.path == null) {
Ext.Logger.error('Ext.device.filesystem.DirectoryEntry#getFile: You must specify a `path` of the file.');
return null;
}
var me = this;
var fullPath = this.path + config.path;
var directoryEntry = Ext.create('Ext.device.filesystem.DirectoryEntry', fullPath, this.fileSystem);
directoryEntry.getEntry({
success: function() {
config.success.call(config.scope || me, directoryEntry);
},
options: config.options || {},
failure: config.failure
});
},
/**
* Works the same way as {@link Ext.device.filesystem.Entry#remove},
* but removes the directory and all of its contents, if any.
*/
removeRecursively: function(config) {
this.remove(Ext.apply(config, {
recursively: true
}));
}
});
/**
* The FileEntry class which is used to represent a file on a file system.
*/
Ext.define('Ext.device.filesystem.FileEntry', {
extend: 'Ext.device.filesystem.Entry',
length: 0,
offset: 0,
constructor: function(path, fileSystem) {
this.callParent([
false,
path,
fileSystem
]);
this.offset = 0;
this.length = 0;
},
/**
* Requests a File Handle from the Local File System
*
* @param {Object} config
*
* @param {String} config.file
* Filename optionally including path in string format '/tmp/debug.txt' or a File Object
*
* @param {Object} config.options
* File creation options {create:true, exclusive:false}
*
* @param {Boolean} config.options.create
* Indicates if the file should be created if it doesn't exist
*
* @param {Boolean} config.options.exclusive
* Used with the create option only indicates whether a creation causes an error if the file already exists
*
* @param {Function} config.success
* The function called when the filesystem is returned successfully
*
* @param {FileSystem} config.success.entry
*
* @param {Function} config.failure
* The function called when the filesystem request causes and error
*
* @param {FileError} config.failure.error
*
*/
getEntry: function(config) {
var me = this;
var originalConfig = Ext.applyIf({}, config);
if (this.fileSystem) {
var failure = function(evt) {
if ((config.options && config.options.create) && Ext.isString(this.path)) {
var folders = this.path.split("/");
if (folders[0] == '.' || folders[0] == '') {
folders = folders.slice(1);
}
if (folders.length > 1 && !config.recursive === true) {
folders.pop();
var dirEntry = Ext.create('Ext.device.filesystem.DirectoryEntry', folders.join("/"), me.fileSystem);
dirEntry.getEntry({
options: config.options,
success: function() {
originalConfig.recursive = true;
me.getEntry(originalConfig);
},
failure: config.failure
});
} else {
if (config.failure) {
config.failure.call(config.scope || me, evt);
}
}
} else {
if (config.failure) {
config.failure.call(config.scope || me, evt);
}
}
};
this.fileSystem.fs.root.getFile(this.path, config.options || null, function(fileEntry) {
fileEntry.file(function(file) {
me.length = file.size;
originalConfig.success.call(config.scope || me, fileEntry);
}, function(error) {
failure.call(config.scope || me, error);
});
}, function(error) {
failure.call(config.scope || me, error);
});
} else {
config.failure({
code: -1,
message: "FileSystem not Initialized"
});
}
},
/**
* Returns the byte offset into the file at which the next read/write will occur.
*
* @return {Number}
* The file offset.
*/
getOffset: function() {
return this.offset;
},
/**
* Sets the byte offset into the file at which the next read/write will occur.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Number} config.offset This is required.
* The file offset to set. If negative, the offset back from the end of the file.
*
* @param {Function} config.success This is optional.
* The callback to be called when the file offset has been successfully set.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
seek: function(config) {
if (config.offset == null) {
Ext.Logger.error('Ext.device.filesystem.FileEntry#seek: You must specify an `offset` in the file.');
return null;
}
this.offset = config.offset || 0;
if (config.success) {
config.success.call(config.scope || this);
}
},
/**
* Reads the data from the file starting at the file offset.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Number} config.length This is optional.
* The length of bytes to read from the file. Defaults to the file's current size if unspecified.
*
* @param {String} config.encoding
* Optional encoding type used only for reading as Text
*
* @param {String} config.type
* Type of reading to use options are "text" (default), "dataURL", "binaryString" and "arrayBuffer"
*
* @param {Object} config.reader
* Optional config params to be applied to a File Reader
*
* @param {Function} config.reader.onloadstart
* @param {Function} config.reader.onloadprogress
* @param {Function} config.reader.onload
* @param {Function} config.reader.onabort
* @param {Function} config.reader.onerror
* @param {Function} config.reader.onloadend
*
* @param {Function} config.success This is optional.
* The callback to be called when the data has been successfully read.
*
* @param {Object} config.success.data
* The read data.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
read: function(config) {
var me = this;
this.getEntry({
success: function(fileEntry) {
fileEntry.file(function(file) {
if (Ext.isNumber(config.length)) {
if (Ext.isFunction(file.slice)) {
file = file.slice(me.offset, config.length);
} else {
if (config.failure) {
config.failure.call(config.scope || me, {
code: -2,
message: "File missing slice functionality"
});
}
return;
}
}
var reader = new FileReader();
reader.onloadend = function(evt) {
config.success.call(config.scope || me, evt.target.result);
};
reader.onerror = function(error) {
config.failure.call(config.scope || me, error);
};
if (config.reader) {
reader = Ext.applyIf(reader, config.reader);
}
config.encoding = config.encoding || "UTF8";
switch (config.type) {
default:
case "text":
reader.readAsText(file, config.encoding);
break;
case "dataURL":
reader.readAsDataURL(file);
break;
case "binaryString":
reader.readAsBinaryString(file);
break;
case "arrayBuffer":
reader.readAsArrayBuffer(file);
break;
}
}, function(error) {
config.failure.call(config.scope || me, error);
});
},
failure: function(error) {
config.failure.call(config.scope || me, error);
}
});
},
/**
* Writes the data to the file starting at the file offset.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Object} config.data This is required.
* The data to write to the file.
*
* @param {Boolean} config.append This is optional.
* Append to the end of the file
*
* @param {Object} config.writer
* Optional config params to be applied to a File Reader
*
* @param {Function} config.writer.onwritestart
* @param {Function} config.writer.onprogress
* @param {Function} config.writer.onwrite
* @param {Function} config.writer.onabort
* @param {Function} config.writer.onerror
* @param {Function} config.writer.onwriteend
*
* @param {Function} config.success This is optional.
* The callback to be called when the data has been successfully written.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
write: function(config) {
if (config.data == null) {
Ext.Logger.error('Ext.device.filesystem.FileEntry#write: You must specify `data` to write into the file.');
return null;
}
var me = this;
this.getEntry({
options: config.options || {},
success: function(fileEntry) {
fileEntry.createWriter(function(writer) {
writer.onwriteend = function(evt) {
me.length = evt.target.length;
config.success.call(config.scope || me, evt.result);
};
writer.onerror = function(error) {
config.failure.call(config.scope || me, error);
};
if (config.writer) {
writer = Ext.applyIf(writer, config.writer);
}
if (me.offset) {
writer.seek(me.offset);
} else if (config.append) {
writer.seek(me.length);
}
me.writeData(writer, config.data);
}, function(error) {
config.failure.call(config.scope || me, error);
});
},
failure: function(error) {
config.failure.call(config.scope || me, error);
}
});
},
writeData: function(writer, data) {
writer.write(new Blob([
data
]));
},
/**
* Truncates or extends the file to the specified size in bytes.
* If the file is extended, the added bytes are null bytes.
*
* @param {Object} config
* The object which contains the following config options:
*
* @param {Number} config.size This is required.
* The new file size.
*
* @param {Function} config.success This is optional.
* The callback to be called when the file size has been successfully changed.
*
* @param {Function} config.failure This is optional.
* The callback to be called when an error occurred.
*
* @param {Object} config.failure.error
* The occurred error.
*
* @param {Object} config.scope
* The scope object
*/
truncate: function(config) {
if (config.size == null) {
Ext.Logger.error('Ext.device.filesystem.FileEntry#write: You must specify a `size` of the file.');
return null;
}
var me = this;
//noinspection JSValidateTypes
this.getEntry({
success: function(fileEntry) {
fileEntry.createWriter(function(writer) {
writer.truncate(config.size);
config.success.call(config.scope || me, me);
}, function(error) {
config.failure.call(config.scope || me, error);
});
},
failure: function(error) {
config.failure.call(config.scope || me, error);
}
});
}
});
});
});
/**
* Cordova File APi Abstraction
*
* For more documentation see
* http://docs.phonegap.com/en/2.7.0/cordova_file_file.md.html#File
*/
Ext.define('Ext.device.filesystem.Cordova', {
alternateClassName: 'Ext.device.filesystem.PhoneGap',
extend: 'Ext.device.filesystem.HTML5',
constructor: function() {
Ext.override(Ext.device.filesystem.Entry, {
/**
*
* @param {Object} config
*
* @param {Object} config.metadata
* Metadata to add to the file or directory
*
* @param {Object} config.options
* File creation options {create:true, exclusive:false}
*
* @param {Boolean} config.options.create
* Indicates if the file should be created if it doesn't exist
*
* @param {Boolean} config.options.exclusive
* Used with the create option only indicates whether a creation causes an error if the file already exists
*
* @param {Function} config.success
* The function called when the File's Metadata is written successfully
*
* @param {Function} config.failure
* The function called when the File request causes an error
*
* @param {FileError} config.failure.error
*
*/
writeMetadata: function(config) {
var me = this;
this.getEntry({
options: config.options,
success: function(entry) {
entry.setMetadata(function() {
config.success.call(config.scope || me);
}, function(error) {
config.failure.call(config.scope || me, error);
}, config.metadata);
},
failure: function(error) {
config.failure.call(config.scope || me, error);
}
});
},
/**
*
* @param {Object} config
*
* @param {Object} config.options
* File creation options {create:true, exclusive:false}
*
* @param {Boolean} config.options.create
* Indicates if the file should be created if it doesn't exist
*
* @param {Boolean} config.options.exclusive
* Used with the create option only indicates whether a creation causes an error if the file already exists
*
* @param {Function} config.success
* The function called when the File's Metadata is written successfully
*
* @param {Function} config.failure
* The function called when the File request causes an error
*
* @param {FileError} config.failure.error
*
*/
readMetadata: function(config) {
var me = this;
this.getEntry({
options: config.options,
success: function(entry) {
entry.getMetadata(function(metadata) {
config.success.call(config.scope || me, metadata);
}, function(error) {
config.failure.call(config.scope || me, error);
});
},
failure: function(error) {
config.failure.call(config.scope || me, error);
}
});
}
});
Ext.override(Ext.device.filesystem.FileEntry, {
writeData: function(writer, data) {
writer.write(data.toString());
},
/**
* Send a file to a server
*
* @param {Object} config
*
* @param {String} config.url
* URL of server to receive the file
*
* @param {Boolean} config.trustAllHosts
* (Optional) If true it will accept all security certificates. Defaults to false
*
* @param {String} config.fileKey
* Name of the form element. Defaults to "file"
*
* @param {String} config.fileName
* Name of the file on the server
*
* @param {String} config.mimeType
* mime type of the data being uploaded. defaults to "image/jpeg"
*
* @param {Object} config.params
* (Optional) set of key/value pairs to be passed along with the request
*
* @param {Boolean} config.chunkMode
* Should the data be uploaded in a chunked streaming mode. defaults to true
*
* @param {Object} config.headers
* Map of header name => header values. Multiple values should be specified an array of values
* var headers={'headerParam':'headerValue'};
*
* @param {Function} config.success
* The function called when the File is uploaded successfully
*
* @param {Function} config.success.metadata
*
* @param {Function} config.failure
* The function called when the File upload fails
*
* @param {FileError} config.failure.error
*
* @return {FileTransfer}
*/
upload: function(config) {
var options = new FileUploadOptions();
options.fileKey = config.fileKey || "file";
options.fileName = this.path.substr(this.path.lastIndexOf('/') + 1);
options.mimeType = config.mimeType || "image/jpeg";
options.params = config.params || {};
options.headers = config.headers || {};
options.chunkMode = config.chunkMode || true;
var fileTransfer = new FileTransfer();
fileTransfer.upload(this.path, encodeURI(config.url), config.success, config.failure, options, config.trustAllHosts || false);
return fileTransfer;
},
/**
* Downloads a file from the server saving it into the Local File System
*
* @param {Object} config
*
* @param {String} config.source
* URL of file to download
*
* @param {Boolean} config.trustAllHosts
* if true it will accept all security certificates. Defaults to false
*
* @param {Object} config.options
* Header parameters (Auth, etc)
*
* {
* headers: {
* "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
* }
* }
*
* @param {Function} config.success
* The function called when the File is downloaded successfully
*
* @param {Function} config.success.entry
* File Entry object of the downloaded file
*
* @param {Function} config.failure
* The function called when the File download fails
*
* @param {FileError} config.failure.error
*
* @return {FileTransfer}
*/
download: function(config) {
var fileTransfer = new FileTransfer();
fileTransfer.download(encodeURI(config.source), this.path, config.success, config.failure, config.trustAllHosts || false, config.options || {});
return fileTransfer;
}
});
}
});
/**
* @private
*/
Ext.define('Ext.device.filesystem.Chrome', {
extend: 'Ext.device.filesystem.HTML5',
/**
* Requests access to the Local File System
*
* var me = this;
* var fs = Ext.create("Ext.device.File", {});
* fs.requestFileSystem({
* type: window.PERSISTENT,
* size: 1024 * 1024,
* success: function(fileSystem) {
* me.fs = fileSystem;
* },
* failure: function(err) {
* console.log("FileSystem Failure: " + err.code);
* }
* });
*
*
* @param {Object} config An object which contains the follow options
* @param {Number} config.type
* window.TEMPORARY (0) or window.PERSISTENT (1)
*
* @param {Number} config.size
* Storage space, in Bytes, needed by the application
*
* @param {Function} config.success
* The function called when the filesystem is returned successfully
*
* @param {FileSystem} config.success.fs
*
* @param {Function} config.failure
* The function called when the filesystem request causes and error
*
* @param {FileError} config.failure.error
*
*/
requestFileSystem: function(config) {
var me = this;
config = Ext.device.filesystem.Abstract.prototype.requestFileSystem(config);
var successCallback = function(fs) {
var fileSystem = Ext.create('Ext.device.filesystem.FileSystem', fs);
config.success.call(config.scope || me, fileSystem);
};
if (config.type == window.PERSISTENT) {
if (navigator.webkitPersistentStorage) {
navigator.webkitPersistentStorage.requestQuota(config.size, function(grantedBytes) {
window.webkitRequestFileSystem(config.type, grantedBytes, successCallback, config.failure);
});
} else {
window.webkitStorageInfo.requestQuota(window.PERSISTENT, config.size, function(grantedBytes) {
window.webkitRequestFileSystem(config.type, grantedBytes, successCallback, config.failure);
});
}
} else {
window.webkitRequestFileSystem(config.type, config.size, successCallback, config.failure);
}
}
});
/**
* @private
*/
Ext.define('Ext.device.filesystem.Simulator', {
extend: 'Ext.device.filesystem.HTML5'
});
/**
* Provides an API to navigate file system hierarchies.
*
* @mixins Ext.device.filesystem.Sencha
*/
Ext.define('Ext.device.FileSystem', {
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.filesystem.Cordova',
'Ext.device.filesystem.Chrome',
'Ext.device.filesystem.Simulator'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.filesystem.Cordova');
}
} else if (browserEnv.Chrome) {
return Ext.create('Ext.device.filesystem.Chrome');
}
return Ext.create('Ext.device.filesystem.Simulator');
}
});
/**
* @private
*/
Ext.define('Ext.device.geolocation.Abstract', {
config: {
/**
* @cfg {Number} maximumAge
* This option indicates that the application is willing to accept cached location information whose age
* is no greater than the specified time in milliseconds. If maximumAge is set to 0, an attempt to retrieve
* new location information is made immediately.
*/
maximumAge: 0,
/**
* @cfg {Number} frequency The default frequency to get the current position when using {@link Ext.device.Geolocation#watchPosition}.
*/
frequency: 10000,
/**
* @cfg {Boolean} allowHighAccuracy True to allow high accuracy when getting the current position.
*/
allowHighAccuracy: false,
/**
* @cfg {Number} timeout
* The maximum number of milliseconds allowed to elapse between a location update operation.
*/
timeout: Infinity
},
/**
* Attempts to get the current position of this device.
*
* Ext.device.Geolocation.getCurrentPosition({
* success: function(position) {
* console.log(position);
* },
* failure: function() {
* Ext.Msg.alert('Geolocation', 'Something went wrong!');
* }
* });
*
* *Note:* If you want to watch the current position, you could use {@link Ext.device.Geolocation#watchPosition} instead.
*
* @param {Object} config An object which contains the following config options:
*
* @param {Function} config.success
* The function to call when the location of the current device has been received.
*
* @param {Object} config.success.position
*
* @param {Function} config.failure
* The function that is called when something goes wrong.
*
* @param {Object} config.scope
* The scope of the `success` and `failure` functions.
*
* @param {Number} config.maximumAge
* The maximum age of a cached location. If you do not enter a value for this, the value of {@link #maximumAge}
* will be used.
*
* @param {Number} config.timeout
* The timeout for this request. If you do not specify a value, it will default to {@link #timeout}.
*
* @param {Boolean} config.allowHighAccuracy
* True to enable allow accuracy detection of the location of the current device. If you do not specify a value, it will
* default to {@link #allowHighAccuracy}.
*/
getCurrentPosition: function(config) {
var defaultConfig = Ext.device.geolocation.Abstract.prototype.config;
config = Ext.applyIf(config, {
maximumAge: defaultConfig.maximumAge,
frequency: defaultConfig.frequency,
allowHighAccuracy: defaultConfig.allowHighAccuracy,
timeout: defaultConfig.timeout
});
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getCurrentPosition');
}
//
return config;
},
/**
* Watches for the current position and calls the callback when successful depending on the specified {@link #frequency}.
*
* Ext.device.Geolocation.watchPosition({
* callback: function(position) {
* console.log(position);
* },
* failure: function() {
* Ext.Msg.alert('Geolocation', 'Something went wrong!');
* }
* });
*
* @param {Object} config An object which contains the following config options:
*
* @param {Function} config.callback
* The function to be called when the position has been updated.
*
* @param {Function} config.failure
* The function that is called when something goes wrong.
*
* @param {Object} config.scope
* The scope of the `success` and `failure` functions.
*
* @param {Boolean} config.frequency
* The frequency in which to call the supplied callback. Defaults to {@link #frequency} if you do not specify a value.
*
* @param {Boolean} config.allowHighAccuracy
* True to enable allow accuracy detection of the location of the current device. If you do not specify a value, it will
* default to {@link #allowHighAccuracy}.
*/
watchPosition: function(config) {
var defaultConfig = Ext.device.geolocation.Abstract.prototype.config;
config = Ext.applyIf(config, {
maximumAge: defaultConfig.maximumAge,
frequency: defaultConfig.frequency,
allowHighAccuracy: defaultConfig.allowHighAccuracy,
timeout: defaultConfig.timeout
});
//
if (!config.callback) {
Ext.Logger.warn('You need to specify a `callback` function for #watchPosition');
}
//
return config;
},
/**
* If you are currently watching for the current position, this will stop that task.
*/
clearWatch: function() {}
});
/**
* @private
*/
Ext.define('Ext.device.geolocation.Cordova', {
alternateClassName: 'Ext.device.geolocation.PhoneGap',
extend: 'Ext.device.geolocation.Abstract',
activeWatchID: null,
getCurrentPosition: function(config) {
config = this.callParent(arguments);
navigator.geolocation.getCurrentPosition(config.success, config.failure, config);
return config;
},
watchPosition: function(config) {
config = this.callParent(arguments);
if (this.activeWatchID) {
this.clearWatch();
}
this.activeWatchID = navigator.geolocation.watchPosition(config.callback, config.failure, config);
return config;
},
clearWatch: function() {
if (this.activeWatchID) {
navigator.geolocation.clearWatch(this.activeWatchID);
this.activeWatchID = null;
}
}
});
/**
* @private
*/
Ext.define('Ext.device.geolocation.Simulator', {
extend: 'Ext.device.geolocation.Abstract',
requires: [
'Ext.util.Geolocation'
],
getCurrentPosition: function(config) {
config = this.callParent([
config
]);
Ext.apply(config, {
autoUpdate: false,
listeners: {
scope: this,
locationupdate: function(geolocation) {
if (config.success) {
config.success.call(config.scope || this, geolocation.position);
}
},
locationerror: function() {
if (config.failure) {
config.failure.call(config.scope || this);
}
}
}
});
this.geolocation = Ext.create('Ext.util.Geolocation', config);
this.geolocation.updateLocation();
return config;
},
watchPosition: function(config) {
config = this.callParent([
config
]);
Ext.apply(config, {
listeners: {
scope: this,
locationupdate: function(geolocation) {
if (config.callback) {
config.callback.call(config.scope || this, geolocation.position);
}
},
locationerror: function() {
if (config.failure) {
config.failure.call(config.scope || this);
}
}
}
});
this.geolocation = Ext.create('Ext.util.Geolocation', config);
return config;
},
clearWatch: function() {
if (this.geolocation) {
this.geolocation.destroy();
}
this.geolocation = null;
}
});
/**
* Provides access to the native Geolocation API when running on a device. There are three implementations of this API:
*
* - Sencha Packager
* - [PhoneGap](http://docs.phonegap.com/en/1.4.1/phonegap_device_device.md.html)
* - Browser
*
* This class will automatically select the correct implementation depending on the device your application is running on.
*
* ## Examples
*
* Getting the current location:
*
* Ext.device.Geolocation.getCurrentPosition({
* success: function(position) {
* console.log(position.coords);
* },
* failure: function() {
* console.log('something went wrong!');
* }
* });
*
* Watching the current location:
*
* Ext.device.Geolocation.watchPosition({
* frequency: 3000, // Update every 3 seconds
* callback: function(position) {
* console.log('Position updated!', position.coords);
* },
* failure: function() {
* console.log('something went wrong!');
* }
* });
*
* @mixins Ext.device.geolocation.Abstract
*/
Ext.define('Ext.device.Geolocation', {
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.geolocation.Cordova',
'Ext.device.geolocation.Simulator'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.geolocation.Cordova');
}
}
return Ext.create('Ext.device.geolocation.Simulator');
}
});
/**
* @private
*/
Ext.define('Ext.device.globalization.Abstract', {
mixins: [
'Ext.mixin.Observable'
],
config: {
formatLength: 'full',
selector: 'date and time',
dateType: 'wide',
items: 'months',
numberType: 'decimal',
currencyCode: "USD"
},
getPreferredLanguage: function(config) {
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getPreferredLanguage');
}
//
return config;
},
getLocaleName: function(config) {
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getLocaleName');
}
//
return config;
},
dateToString: function(config) {
var defaultConfig = Ext.device.globalization.Abstract.prototype.config;
config = Ext.applyIf(config, {
date: new Date(),
formatLength: defaultConfig.formatLength,
selector: defaultConfig.selector
});
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #dateToString');
}
//
return config;
},
stringToDate: function(config) {
var defaultConfig = Ext.device.globalization.Abstract.prototype.config;
config = Ext.applyIf(config, {
dateString: Ext.util.Format.date(new Date(), 'm/d/Y'),
formatLength: defaultConfig.formatLength,
selector: defaultConfig.selector
});
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #stringToDate');
}
//
return config;
},
getDatePattern: function(config) {
var defaultConfig = Ext.device.globalization.Abstract.prototype.config;
config = Ext.applyIf(config, {
formatLength: defaultConfig.formatLength,
selector: defaultConfig.selector
});
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getDatePattern');
}
//
return config;
},
getDateNames: function(config) {
var defaultConfig = Ext.device.globalization.Abstract.prototype.config;
config = Ext.applyIf(config, {
type: defaultConfig.dateType,
items: defaultConfig.items
});
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getDateNames');
}
//
return config;
},
isDayLightSavingsTime: function(config) {
config = Ext.applyIf(config, {
date: new Date()
});
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #isDayLightSavingsTime');
}
//
return config;
},
getFirstDayOfWeek: function(config) {
//
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getFirstDayOfWeek');
}
//
return config;
},
numberToString: function(config) {
var defaultConfig = Ext.device.globalization.Abstract.prototype.config;
config = Ext.applyIf(config, {
number: defaultConfig.number,
type: defaultConfig.numberType
});
//
if (!config.number) {
Ext.Logger.warn('You need to specify a `number` for #numberToString');
}
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #numberToString');
}
//
return config;
},
stringToNumber: function(config) {
var defaultConfig = Ext.device.globalization.Abstract.prototype.config;
config = Ext.applyIf(config, {
type: defaultConfig.numberType
});
//
if (!config.number) {
Ext.Logger.warn('You need to specify a `string` for #stringToNumber');
}
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #stringToNumber');
}
//
return config;
},
getNumberPattern: function(config) {
var defaultConfig = Ext.device.globalization.Abstract.prototype.config;
config = Ext.applyIf(config, {
type: defaultConfig.numberType
});
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getNumberPattern');
}
//
return config;
},
getCurrencyPattern: function(config) {
var defaultConfig = Ext.device.globalization.Abstract.prototype.config;
config = Ext.applyIf(config, {
currencyCode: defaultConfig.currencyCode
});
if (!config.success) {
Ext.Logger.warn('You need to specify a `success` function for #getCurrency');
}
//
return config;
}
});
/**
* @private
*/
Ext.define('Ext.device.globalization.Cordova', {
alternateClassName: 'Ext.device.globalization.PhoneGap',
extend: 'Ext.device.globalization.Abstract',
getPreferredLanguage: function(config) {
config = this.callParent(arguments);
navigator.globalization.getPreferredLanguage(config.success, config.error);
},
getLocaleName: function(config) {
config = this.callParent(arguments);
navigator.globalization.getLocaleName(config.success, config.error);
},
dateToString: function(config) {
config = this.callParent(arguments);
navigator.globalization.dateToString(config.date, config.success, config.error, config);
},
stringToDate: function(config) {
config = this.callParent(arguments);
navigator.globalization.stringToDate(config.dateString, config.success, config.error, config);
},
getDatePattern: function(config) {
config = this.callParent(arguments);
navigator.globalization.getDatePattern(config.success, config.error, config);
},
getDateNames: function(config) {
config = this.callParent(arguments);
navigator.globalization.getDateNames(config.success, config.error, config);
},
isDayLightSavingsTime: function(config) {
config = this.callParent(arguments);
navigator.globalization.isDayLightSavingsTime(config.date, config.success, config.error, config);
},
getFirstDayOfWeek: function(config) {
config = this.callParent(arguments);
navigator.globalization.getFirstDayOfWeek(config.success, config.error);
},
numberToString: function(config) {
config = this.callParent(arguments);
navigator.globalization.numberToString(config.number, config.success, config.error, config);
},
stringToNumber: function(config) {
config = this.callParent(arguments);
navigator.globalization.stringToNumber(config.string, config.success, config.error, config);
},
getNumberPattern: function(config) {
config = this.callParent(arguments);
navigator.globalization.getNumberPattern(config.success, config.error, config);
},
getCurrencyPattern: function(config) {
config = this.callParent(arguments);
navigator.globalization.getCurrencyPattern(config.currencyCode, config.success, config.error);
}
});
/**
* @private
*/
Ext.define('Ext.device.globalization.Simulator', {
extend: 'Ext.device.globalization.Abstract'
});
/**
* Provides access to the native Globalization API
*
* - [PhoneGap](http://docs.phonegap.com/en/2.6.0/cordova_globalization_globalization.md.html)
*
* Class currently only works with Cordova and does not have a simulated HTML counter part.
* Please see notes on Cordova Docs for more information.
*
* http://docs.phonegap.com/en/2.6.0/cordova_globalization_globalization.md.html
*/
Ext.define('Ext.device.Globalization', {
singleton: true,
requires: [
'Ext.device.globalization.Cordova',
'Ext.device.globalization.Simulator'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.globalization.Cordova');
}
}
return Ext.create('Ext.device.globalization.Simulator');
}
});
/**
* @private
*/
Ext.define('Ext.device.media.Abstract', {
mixins: [
'Ext.mixin.Observable'
],
config: {
src: null
},
play: Ext.emptyFn,
pause: Ext.emptyFn,
stop: Ext.emptyFn,
release: Ext.emptyFn,
seekTo: Ext.emptyFn,
getCurrentPosition: Ext.emptyFn,
getDuration: Ext.emptyFn,
startRecord: Ext.emptyFn,
stopRecord: Ext.emptyFn
});
/**
* @private
*/
Ext.define('Ext.device.media.Cordova', {
alternateClassName: 'Ext.device.media.PhoneGap',
extend: 'Ext.device.media.Abstract',
config: {
/**
* A URI containing the audio content.
* @type {String}
*/
src: null,
/**
* @private
*/
media: null
},
updateSrc: function(newSrc, oldSrc) {
this.setMedia(new Media(newSrc));
},
play: function() {
var media = this.getMedia();
if (media) {
media.play();
}
},
pause: function() {
var media = this.getMedia();
if (media) {
media.pause();
}
},
stop: function() {
var media = this.getMedia();
if (media) {
media.stop();
}
},
release: function() {
var media = this.getMedia();
if (media) {
media.release();
}
},
seekTo: function(miliseconds) {
var media = this.getMedia();
if (media) {
media.seekTo(miliseconds);
}
},
getDuration: function() {
var media = this.getMedia();
if (media) {
media.getDuration();
}
},
startRecord: function() {
var media = this.getMedia();
if (!media) {
this.setSrc(null);
}
media.startRecord();
},
stopRecord: function() {
var media = this.getMedia();
if (media) {
media.stopRecord();
}
}
});
/**
* @mixins Ext.device.media.Abstract
*/
Ext.define('Ext.device.Media', {
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.media.Cordova'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView && browserEnv.Cordova) {
return Ext.create('Ext.device.media.Cordova');
}
return Ext.create('Ext.device.media.Abstract');
}
});
/**
* @private
*/
Ext.define('Ext.device.notification.Abstract', {
/**
* A simple way to show a notification.
*
* Ext.device.Notification.show({
* title: 'Verification',
* message: 'Is your email address is: test@sencha.com',
* buttons: Ext.MessageBox.OKCANCEL,
* callback: function(button) {
* if (button == "ok") {
* console.log('Verified');
* } else {
* console.log('Nope.');
* }
* }
* });
*
* @param {Object} config An object which contains the following config options:
*
* @param {String} config.title The title of the notification
*
* @param {String} config.message The message to be displayed on the notification
*
* @param {String/String[]} [config.buttons="OK"]
* The buttons to be displayed on the notification. It can be a string, which is the title of the button, or an array of multiple strings.
* Please not that you should not use more than 2 buttons, as they may not be displayed correct on all devices.
*
* @param {Function} config.callback
* A callback function which is called when the notification is dismissed by clicking on the configured buttons.
* @param {String} config.callback.buttonId The id of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
*
* @param {Object} config.scope The scope of the callback function
*/
show: function(config) {
if (!config.message) {
throw ('[Ext.device.Notification#show] You passed no message');
}
if (!config.buttons) {
config.buttons = [
"OK",
"Cancel"
];
}
if (!Ext.isArray(config.buttons)) {
config.buttons = [
config.buttons
];
}
if (!config.scope) {
config.scope = this;
}
return config;
},
alert: function(config) {
if (!config.message) {
throw ('[Ext.device.Notification#alert] You passed no message');
}
if (!config.scope) {
config.scope = this;
}
return config;
},
confirm: function(config) {
if (!config.message) {
throw ('[Ext.device.Notification#confirm] You passed no message');
}
if (!config.buttons) {
config.buttons = [
"OK",
"Cancel"
];
}
if (!Ext.isArray(config.buttons)) {
config.buttons = [
config.buttons
];
}
if (!config.scope) {
config.scope = this;
}
return config;
},
prompt: function(config) {
if (!config.message) {
throw ('[Ext.device.Notification#prompt] You passed no message');
}
if (!config.buttons) {
config.buttons = [
"OK",
"Cancel"
];
}
if (!Ext.isArray(config.buttons)) {
config.buttons = [
config.buttons
];
}
if (!config.scope) {
config.scope = this;
}
return config;
},
/**
* Vibrates the device.
*/
vibrate: Ext.emptyFn,
beep: Ext.emptyFn
});
/**
* @private
*/
Ext.define('Ext.device.notification.Cordova', {
alternateClassName: 'Ext.device.notification.PhoneGap',
extend: 'Ext.device.notification.Abstract',
requires: [
'Ext.device.Communicator'
],
show: function(config) {
config = this.callParent(arguments);
this.confirm(config);
},
confirm: function(config) {
config = this.callParent(arguments);
var buttons = config.buttons,
ln = config.buttons.length;
if (ln && typeof buttons[0] != "string") {
var newButtons = [],
i;
for (i = 0; i < ln; i++) {
newButtons.push(buttons[i].text);
}
buttons = newButtons;
}
var callback = function(index) {
if (config.callback) {
config.callback.apply(config.scope, (buttons) ? [
buttons[index - 1].toLowerCase()
] : []);
}
};
navigator.notification.confirm(config.message, callback, config.title, buttons);
},
alert: function(config) {
navigator.notification.alert(config.message, config.callback, config.title, config.buttonName);
},
prompt: function(config) {
config = this.callParent(arguments);
var buttons = config.buttons,
ln = config.buttons.length;
if (ln && typeof buttons[0] != "string") {
var newButtons = [],
i;
for (i = 0; i < ln; i++) {
newButtons.push(buttons[i].text);
}
buttons = newButtons;
}
var callback = function(result) {
if (config.callback) {
config.callback.call(config.scope, (buttons) ? buttons[result.buttonIndex - 1].toLowerCase() : null, result.input1);
}
};
navigator.notification.prompt(config.message, callback, config.title, buttons);
},
vibrate: function(time) {
navigator.notification.vibrate(time);
},
beep: function(times) {
navigator.notification.vibrate(times);
}
});
/**
* @private
*/
Ext.define('Ext.device.notification.Simulator', {
extend: 'Ext.device.notification.Abstract',
requires: [
'Ext.MessageBox',
'Ext.util.Audio'
],
/**
* @private
*/
msg: null,
show: function() {
var config = this.callParent(arguments),
buttons = [],
ln = config.buttons.length,
button, i, callback;
//buttons
for (i = 0; i < ln; i++) {
button = config.buttons[i];
if (Ext.isString(button)) {
button = {
text: config.buttons[i],
itemId: config.buttons[i].toLowerCase()
};
}
buttons.push(button);
}
this.msg = Ext.create('Ext.MessageBox');
callback = function(itemId) {
if (config.callback) {
config.callback.apply(config.scope, [
itemId
]);
}
};
this.msg.show({
title: config.title,
message: config.message,
scope: this.msg,
buttons: buttons,
fn: callback
});
},
alert: function() {
var config = this.callParent(arguments);
if (config.buttonName) {
config.buttons = [
config.buttonName
];
}
this.show(config);
},
confirm: function() {
var config = this.callParent(arguments);
this.show(config);
},
prompt: function() {
var config = this.callParent(arguments),
buttons = [],
ln = config.buttons.length,
button, i, callback;
//buttons
for (i = 0; i < ln; i++) {
button = config.buttons[i];
if (Ext.isString(button)) {
button = {
text: config.buttons[i],
itemId: config.buttons[i].toLowerCase()
};
}
buttons.push(button);
}
this.msg = Ext.create('Ext.MessageBox');
callback = function(buttonText, value) {
if (config.callback) {
config.callback.apply(config.scope, [
buttonText,
value
]);
}
};
this.msg.prompt(config.title, config.message, callback, this.msg, config.multiLine, config.value, config.prompt);
},
beep: function(times) {
if (!Ext.isNumber(times)) {
times = 1;
}
var count = 0;
var callback = function() {
if (count < times) {
Ext.defer(function() {
Ext.util.Audio.beep(callback);
}, 50);
}
count++;
};
callback();
},
vibrate: function() {
//nice animation to fake vibration
var animation = [
"@-webkit-keyframes vibrate{",
" from {",
" -webkit-transform: rotate(-2deg);",
" }",
" to{",
" -webkit-transform: rotate(2deg);",
" }",
"}",
"body {",
" -webkit-animation: vibrate 50ms linear 10 alternate;",
"}"
];
var head = document.getElementsByTagName("head")[0];
var cssNode = document.createElement('style');
cssNode.innerHTML = animation.join('\n');
head.appendChild(cssNode);
Ext.defer(function() {
head.removeChild(cssNode);
}, 400);
}
});
/**
* Provides a cross device way to show notifications. There are three different implementations:
*
* - Sencha Packager
* - Cordova
* - Simulator
*
* When this singleton is instantiated, it will automatically use the correct implementation depending on the current device.
*
* Both the Sencha Packager and Cordova versions will use the native implementations to display the notification. The
* Simulator implementation will use {@link Ext.MessageBox} for {@link #show} and a simply animation when you call {@link #vibrate}.
*
* ## Examples
*
* To show a simple notification:
*
* Ext.device.Notification.show({
* title: 'Verification',
* message: 'Is your email address: test@sencha.com',
* buttons: Ext.MessageBox.OKCANCEL,
* callback: function(button) {
* if (button === "ok") {
* console.log('Verified');
* } else {
* console.log('Nope');
* }
* }
* });
*
* To make the device vibrate:
*
* Ext.device.Notification.vibrate();
*
* @mixins Ext.device.notification.Abstract
*/
Ext.define('Ext.device.Notification', {
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.notification.Cordova',
'Ext.device.notification.Simulator'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.notification.Cordova');
}
}
return Ext.create('Ext.device.notification.Simulator');
}
});
/**
* @private
*/
Ext.define('Ext.device.orientation.Abstract', {
mixins: [
'Ext.mixin.Observable'
],
/**
* @event orientationchange
* Fires when the orientation has been changed on this device.
*
* Ext.device.Orientation.on({
* scope: this,
* orientationchange: function(e) {
* console.log('Alpha: ', e.alpha);
* console.log('Beta: ', e.beta);
* console.log('Gamma: ', e.gamma);
* }
* });
*
* @param {Object} event The event object
* @param {Object} event.alpha The alpha value of the orientation event
* @param {Object} event.beta The beta value of the orientation event
* @param {Object} event.gamma The gamma value of the orientation event
*/
onDeviceOrientation: function(e) {
this.doFireEvent('orientationchange', [
e
]);
}
});
/**
* Provides the HTML5 implementation for the orientation API.
* @private
*/
Ext.define('Ext.device.orientation.HTML5', {
extend: 'Ext.device.orientation.Abstract',
constructor: function() {
this.callParent(arguments);
this.onDeviceOrientation = Ext.Function.bind(this.onDeviceOrientation, this);
window.addEventListener('deviceorientation', this.onDeviceOrientation, true);
}
});
/**
* This class provides you with a cross platform way of listening to when the the orientation changes on the
* device your application is running on.
*
* The {@link Ext.device.Orientation#orientationchange orientationchange} event gets passes the `alpha`, `beta` and
* `gamma` values. ** These properties only exist when packaging with the Sencha Native Packager. **
*
* You can find more information about these values and how to use them on the [W3C device orientation specification](http://dev.w3.org/geo/api/spec-source-orientation.html#deviceorientation).
*
* ## Example
*
* To listen to the device orientation, you can do the following:
*
* Ext.device.Orientation.on({
* scope: this,
* orientationchange: function(e) {
* console.log('Alpha: ', e.alpha);
* console.log('Beta: ', e.beta);
* console.log('Gamma: ', e.gamma);
* }
* });
*
* @mixins Ext.device.orientation.Abstract
*/
Ext.define('Ext.device.Orientation', {
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.orientation.HTML5'
],
constructor: function() {
return Ext.create('Ext.device.orientation.HTML5');
}
});
/**
* @private
*/
Ext.define('Ext.device.push.Abstract', {
/**
* @property
* Notification type: alert.
*/
ALERT: 1,
/**
* @property
* Notification type: badge.
*/
BADGE: 2,
/**
* @property
* Notification type: sound.
*/
SOUND: 4,
/**
* @method getInitialConfig
* @hide
*/
/**
* Registers a push notification.
*
* Ext.device.Push.register({
* type: Ext.device.Push.ALERT|Ext.device.Push.BADGE|Ext.device.Push.SOUND,
* success: function(token) {
* console.log('# Push notification registration successful:');
* console.log(' token: ' + token);
* },
* failure: function(error) {
* console.log('# Push notification registration unsuccessful:');
* console.log(' error: ' + error);
* },
* received: function(notifications) {
* console.log('# Push notification received:');
* console.log(' ' + JSON.stringify(notifications));
* }
* });
*
* @param {Object} config
* The configuration for to pass when registering this push notification service.
*
* @param {Number} config.type
* The type(s) of notifications to enable. Available options are:
*
* - {@link Ext.device.Push#ALERT}
* - {@link Ext.device.Push#BADGE}
* - {@link Ext.device.Push#SOUND}
*
* **Usage**
*
* Enable alerts and badges:
*
* Ext.device.Push.register({
* type: Ext.device.Push.ALERT|Ext.device.Push.BADGE
* // ...
* });
*
* Enable alerts, badges and sounds:
*
* Ext.device.Push.register({
* type: Ext.device.Push.ALERT|Ext.device.Push.BADGE|Ext.device.Push.SOUND
* // ...
* });
*
* Enable only sounds:
*
* Ext.device.Push.register({
* type: Ext.device.Push.SOUND
* // ...
* });
*
* @param {Function} config.success
* The callback to be called when registration is complete.
*
* @param {String} config.success.token
* A unique token for this push notification service.
*
* @param {Function} config.failure
* The callback to be called when registration fails.
*
* @param {String} config.failure.error
* The error message.
*
* @param {Function} config.received
* The callback to be called when a push notification is received on this device.
*
* @param {Object} config.received.notifications
* The notifications that have been received.
*/
register: function(config) {
var me = this;
if (!config.received) {
Ext.Logger.error('Failed to pass a received callback. This is required.');
}
if (config.type == null) {
Ext.Logger.error('Failed to pass a type. This is required.');
}
return {
success: function(token) {
me.onSuccess(token, config.success, config.scope || me);
},
failure: function(error) {
me.onFailure(error, config.failure, config.scope || me);
},
received: function(notifications) {
me.onReceived(notifications, config.received, config.scope || me);
},
type: config.type
};
},
onSuccess: function(token, callback, scope) {
if (callback) {
callback.call(scope, token);
}
},
onFailure: function(error, callback, scope) {
if (callback) {
callback.call(scope, error);
}
},
onReceived: function(notifications, callback, scope) {
if (callback) {
callback.call(scope, notifications);
}
}
});
/**
* @private
* Interfaces with Cordova PushPlugin: https://github.com/phonegap-build/PushPlugin
*/
Ext.define('Ext.device.push.Cordova', {
extend: 'Ext.device.push.Abstract',
statics: {
/**
* @private
* A collection of callback methods that can be globally called by the Cordova PushPlugin
*/
callbacks: {}
},
setPushConfig: function(config) {
var methodName = Ext.id(null, 'callback');
//Cordova's PushPlugin needs a static method to call when notifications are received
Ext.device.push.Cordova.callbacks[methodName] = config.callbacks.received;
return {
"badge": (config.callbacks.type === Ext.device.Push.BADGE) ? "true" : "false",
"sound": (config.callbacks.type === Ext.device.Push.SOUND) ? "true" : "false",
"alert": (config.callbacks.type === Ext.device.Push.ALERT) ? "true" : "false",
"ecb": 'Ext.device.push.Cordova.callbacks.' + methodName,
"senderID": config.senderID
};
},
register: function() {
var config = arguments[0];
config.callbacks = this.callParent(arguments);
var pushConfig = this.setPushConfig(config),
plugin = window.plugins.pushNotification;
plugin.register(config.callbacks.success, config.callbacks.failure, pushConfig);
}
});
/**
* Provides a way to send push notifications to a device.
*
* # Example
*
* Ext.device.Push.register({
* type: Ext.device.Push.ALERT|Ext.device.Push.BADGE|Ext.device.Push.SOUND,
* success: function(token) {
* console.log('# Push notification registration successful:');
* console.log(' token: ' + token);
* },
* failure: function(error) {
* console.log('# Push notification registration unsuccessful:');
* console.log(' error: ' + error);
* },
* received: function(notifications) {
* console.log('# Push notification received:');
* console.log(' ' + JSON.stringify(notifications));
* }
* });
*
*
* ## Sencha Cmd
*
* Currently only available on iOS for apps packaged with Sencha Cmd.
*
* ## Cordova / PhoneGap
*
* For apps packaged with Cordova or PhoneGap, Ext.device.Push currently supports iOS and
* Android via the [PushPlugin](https://github.com/phonegap-build/PushPlugin).
*
* Be sure to include that plugin in your project; Ext.device.Push simply normalizes the
* interface for using notifications in your application.
*
* @mixins Ext.device.push.Abstract
*/
Ext.define('Ext.device.Push', {
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.push.Cordova'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.push.Cordova');
}
}
return Ext.create('Ext.device.push.Abstract');
}
});
/**
* @private
*/
Ext.define('Ext.device.splashscreen.Abstract', {
show: Ext.emptyFn,
hide: Ext.emptyFn
});
/**
* @private
*/
Ext.define('Ext.device.splashscreen.Cordova', {
alternateClassName: 'Ext.device.splashscreen.PhoneGap',
extend: 'Ext.device.splashscreen.Abstract',
show: function() {
navigator.splashscreen.show();
},
hide: function() {
navigator.splashscreen.hide();
}
});
/**
* @private
*/
Ext.define('Ext.device.splashscreen.Simulator', {
extend: 'Ext.device.splashscreen.Abstract'
});
/**
* Provides access to the native Splashscreen API
*
* - [PhoneGap](http://docs.phonegap.com/en/2.6.0/cordova_splashscreen_splashscreen.md.html#Splashscreen)
*
* Class currently only works with Cordova and does not have a simulated HTML counter part.
* Please see notes on Cordova Docs for proper Native project code changes that
* will need to be made to use this plugin.
*
* http://docs.phonegap.com/en/2.6.0/cordova_splashscreen_splashscreen.md.html#Splashscreen
*/
Ext.define('Ext.device.Splashscreen', {
singleton: true,
requires: [
'Ext.device.splashscreen.Cordova',
'Ext.device.splashscreen.Simulator'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.splashscreen.Cordova');
}
}
return Ext.create('Ext.device.splashscreen.Simulator');
}
});
/**
* @private
*/
Ext.define('Ext.device.storage.Abstract', {
config: {
databaseName: "Sencha",
databaseVersion: '1.0',
databaseDisplayName: 'Sencha Database',
databaseSize: 5 * 1024 * 1024
},
openDatabase: function(config) {
var defaultConfig = Ext.device.storage.Abstract.prototype.config;
config = Ext.applyIf(config, {
name: defaultConfig.databaseName,
version: defaultConfig.databaseVersion,
displayName: defaultConfig.databaseDisplayName,
size: defaultConfig.databaseSize
});
return config;
},
numKeys: Ext.emptyFn,
getKey: Ext.emptyFn,
getItem: Ext.emptyFn,
setItem: Ext.emptyFn,
removeItem: Ext.emptyFn,
clear: Ext.emptyFn
});
/**
* @private
*/
Ext.define("Ext.device.storage.HTML5.SQLStatement", {
extend: 'Ext.Base',
sql: null,
"arguments": null,
success: Ext.emptyFn,
failure: Ext.emptyFn,
constructor: function(config) {
this.sql = config.sql;
this.arguments = config.arguments;
this.success = config.success;
this.failure = config.failure;
}
});
/**
* @private
*/
Ext.define('Ext.device.storage.HTML5.Database', {
requires: [
"Ext.device.storage.HTML5.SQLStatement"
],
db: null,
constructor: function(config) {
this.db = window.openDatabase(config.name, config.version, config.displayName, config.size);
},
getVersion: function() {
if (this.db) {
return this.db.version;
}
//
Ext.Logger.warn('Database has not been opened before calling function #getVersion');
//
return null;
},
/**
* @param {String/String[]/Object/Object[]/SQLStatement/SQLStatement[]} sql SQL Command to run with optional arguments and callbacks
* @param {Function} success callback for successful transaction
* @param {Function} failure callback for failed transaction
*/
transaction: function(sql, success, failure) {
if (!this.db) {
//
Ext.Logger.warn('Database has not been opened before calling function #transaction');
//
return;
}
if (!Ext.isArray(sql)) {
sql = [
sql
];
}
var txFn = function(tx) {
Ext.each(sql, function(sqlStatement) {
if (Ext.isString(sqlStatement)) {
tx.executeSql(sqlStatement);
} else if (Ext.isObject(sqlStatement)) {
tx.executeSql(sqlStatement.sql, sqlStatement.arguments, sqlStatement.success, sqlStatement.failure);
}
});
};
this.db.transaction(txFn, failure, success);
}
});
/**
* @private
*/
Ext.define('Ext.device.storage.HTML5.HTML5', {
extend: 'Ext.device.storage.Abstract',
requires: [
'Ext.device.storage.HTML5.Database'
],
dbCache: {},
openDatabase: function(config) {
config = this.callParent(arguments);
if (!this.dbCache[config.name] || config.noCache) {
this.dbCache[config.name] = Ext.create('Ext.device.storage.HTML5.Database', config);
}
return this.dbCache[config.name];
},
numKeys: function() {
return window.localStorage.length;
},
getKey: function(index) {
return window.localStorage.key(index);
},
getItem: function(key) {
return window.localStorage.getItem(key);
},
setItem: function(key, value) {
return window.localStorage.setItem(key, value);
},
removeItem: function(key) {
return window.localStorage.removeItem(key);
},
clear: function() {
return window.localStorage.clear();
}
});
/**
* @private
*/
Ext.define('Ext.device.storage.Cordova', {
alternateClassName: 'Ext.device.storage.PhoneGap',
extend: 'Ext.device.storage.HTML5.HTML5'
});
/**
* @private
*/
Ext.define('Ext.device.storage.Simulator', {
extend: 'Ext.device.storage.HTML5.HTML5'
});
/**
*
*/
Ext.define('Ext.device.Storage', {
singleton: true,
requires: [
'Ext.device.storage.Cordova',
'Ext.device.storage.Simulator'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView) {
if (browserEnv.Cordova) {
return Ext.create('Ext.device.storage.Cordova');
}
}
return Ext.create('Ext.device.storage.Simulator');
}
});
/**
* @private
*/
Ext.define('Ext.device.twitter.Abstract', {
/**
* Pops up a Twitter compose sheet view with your specified tweet.
*
* @param {Object} config An object which contains the following config options:
*
* @param {String} config.tweet The default tweet text to add to the compose window.
*
* @param {String} config.url An optional URL to attatch to the Tweet.
*
* @param {String} config.image An optional image URL to attatch to the Tweet.
*
* @param {Function} config.success The callback when the Tweet is successfully posted.
*
* @param {Function} config.failure The callback when the Tweet is unsuccessfully posted.
*/
compose: Ext.emptyFn,
/**
* Gets Tweets from Twitter Timeline
*
* @param {Object} config An object which contains the following config options:
*
* @param {Function} config.success callback
* @param {Object[]} config.success.response Tweet objects, see [Twitter Timeline Doc]
*
* @param {Function} config.failure callback
* @param {String} config.failure.error reason for failure
*
* [Twitter Timeline Doc]: https://dev.twitter.com/docs/api/1/get/statuses/public_timeline
*/
getPublicTimeline: Ext.emptyFn,
/**
* Gets Tweets from Twitter Mentions
*
* @param {Object} config An object which contains the following config options:
*
* @param {Function} config.success callback
* @param {Object[]} config.success.response Tweet objects, see [Twitter Mentions Doc]
*
* @param {Function} config.failure callback
* @param {String} config.failure.error reason for failure
*
* [Twitter Timeline Doc]: https://dev.twitter.com/docs/api/1/get/statuses/public_timeline
*/
getMentions: Ext.emptyFn,
/**
* Gets a specific Twitter user info
*
* @param {Object} config An object which contains the following config options:
*
* @param {Function} config.success callback
* @param {Object[]} config.success.response The JSON response form twitter
*
* @param {Function} config.failure callback
* @param {String} config.failure.error reason for failure
*/
getTwitterUsername: Ext.emptyFn,
/**
* Gets a specific Twitter user info
*
* @param {Object} config An object which contains the following config options:
*
* @param {String} config.url of [Twitter API Endpoint]
*
* @param {Object} config.params key-value map, matching [Twitter API Endpoint]
*
* @param {Object} config.options (optional) other options for the HTTP request
* @param {String} config.options.requestMethod HTTP Request type, ex: "POST"
*
* @param {Function} config.success callback
* @param {Object[]} config.success.response objects returned from Twitter API (Tweets, Users,...)
*
* @param {Function} config.failure callback
* @param {String} config.failure.error reason for failure
*
* [Twitter API Endpoint]: https://dev.twitter.com/docs/api
*/
getTwitterRequest: Ext.emptyFn
});
/**
* @private
*/
Ext.define('Ext.device.twitter.Cordova', {
compose: function(config) {
window.plugins.twitter.composeTweet(config.success, config.failure, config.tweet, {
urlAttach: config.url,
imageAttach: config.image
});
},
getPublicTimeline: function(config) {
window.plugins.twitter.getPublicTimeline(config.success, config.failure);
},
getMentions: function(config) {
window.plugins.twitter.getMentions(config.success, config.failure);
},
getTwitterUsername: function(config) {
window.plugins.twitter.getTwitterUsername(config.success, config.failure);
},
getTwitterRequest: function(config) {
window.plugins.twitter.getTWRequest(config.url, config.params, config.success, config.failure, config.options);
}
});
/**
* Allows you to interact with the Twitter API on iOS devices from within your Cordova application.
*
* For setup information, please read the [plugin guide](https://github.com/phonegap/phonegap-plugins/tree/master/iOS/Twitter).
*
* @mixins Ext.device.twitter.Abstract
*/
Ext.define('Ext.device.Twitter', {
alternateClassName: 'Ext.ux.device.Twitter',
singleton: true,
requires: [
'Ext.device.Communicator',
'Ext.device.twitter.*'
],
constructor: function() {
var browserEnv = Ext.browser.is;
if (browserEnv.WebView && browserEnv.Cordova) {
return Ext.create('Ext.device.twitter.Cordova');
} else {
return Ext.create('Ext.device.twitter.Abstract');
}
}
});
/**
* @private
*/
Ext.define('Ext.device.browser.Window', {
extend: 'Ext.Evented',
open: function(config) {
var me = this;
this._window = window.open(config.url, config.showToolbar ? '_blank' : '_self', config.options || null);
// Add events
this._window.addEventListener('loadstart', function() {
me.fireEvent('loadstart', me);
});
this._window.addEventListener('loadstop', function() {
me.fireEvent('loadstop', me);
});
this._window.addEventListener('loaderror', function() {
me.fireEvent('loaderror', me);
});
this._window.addEventListener('exit', function() {
me.fireEvent('close', me);
});
},
close: function() {
if (!this._window) {
return;
}
this._window.close();
}
});