devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
472 lines (407 loc) • 13 kB
JavaScript
"use strict";
var $ = require("../core/renderer"),
windowUtils = require("./utils/window"),
navigator = windowUtils.getNavigator(),
window = windowUtils.getWindow(),
extend = require("./utils/extend").extend,
isPlainObject = require("./utils/type").isPlainObject,
each = require("./utils/iterator").each,
Class = require("./class"),
errors = require("./errors"),
Callbacks = require("./utils/callbacks"),
resizeCallbacks = require("./utils/resize_callbacks"),
EventsMixin = require("./events_mixin"),
SessionStorage = require("./utils/storage").sessionStorage,
viewPort = require("./utils/view_port"),
Config = require("./config");
var KNOWN_UA_TABLE = {
"iPhone": "iPhone",
"iPhone5": "iPhone",
"iPhone6": "iPhone",
"iPhone6plus": "iPhone",
"iPad": "iPad",
"iPadMini": "iPad Mini",
"androidPhone": "Android Mobile",
"androidTablet": "Android",
"win8": "MSAppHost",
"win8Phone": "Windows Phone 8.0",
"msSurface": "Windows ARM Tablet PC",
"desktop": "desktop",
"win10Phone": "Windows Phone 10.0",
"win10": "MSAppHost/3.0"
};
/**
* @name device
* @section commonObjectStructures
* @publicName Device
* @type object
* @namespace DevExpress
* @module core/devices
* @export default
*/
var DEFAULT_DEVICE = {
/**
* @name device.devicetype
* @publicName deviceType
* @type string
* @acceptValues 'phone'|'tablet'|'desktop'
*/
deviceType: "desktop",
/**
* @name device.platform
* @publicName platform
* @type string
* @acceptValues 'android'|'ios'|'win'|'generic'
*/
platform: "generic",
/**
* @name device.version
* @publicName version
* @type Array<number>
*/
version: [],
/**
* @name device.phone
* @publicName phone
* @type boolean
*/
phone: false,
/**
* @name device.tablet
* @publicName tablet
* @type boolean
*/
tablet: false,
/**
* @name device.android
* @publicName android
* @type boolean
*/
android: false,
/**
* @name device.ios
* @publicName ios
* @type boolean
*/
ios: false,
/**
* @name device.win
* @publicName win
* @type boolean
*/
win: false,
/**
* @name device.generic
* @publicName generic
* @type boolean
*/
generic: true,
/**
* @name device.grade
* @publicName grade
* @type string
* @acceptValues 'A'|'B'|'C'
*/
grade: "A",
// TODO: For internal use (draft, do not document these options!)
mac: false
};
var uaParsers = {
win: function win(userAgent) {
var isPhone = /windows phone/i.test(userAgent) || userAgent.match(/WPDesktop/),
isTablet = !isPhone && /Windows(.*)arm(.*)Tablet PC/i.test(userAgent),
isDesktop = !isPhone && !isTablet && /msapphost/i.test(userAgent);
if (!(isPhone || isTablet || isDesktop)) {
return;
}
var matches = userAgent.match(/windows phone (\d+).(\d+)/i) || userAgent.match(/windows nt (\d+).(\d+)/i),
version = [];
if (matches) {
version.push(parseInt(matches[1], 10), parseInt(matches[2], 10));
} else {
matches = userAgent.match(/msapphost(\/(\d+).(\d+))?/i);
matches && version.push(parseInt(matches[2], 10) === 3 ? 10 : 8);
}
return {
deviceType: isPhone ? "phone" : isTablet ? "tablet" : "desktop",
platform: "win",
version: version,
grade: "A"
};
},
ios: function ios(userAgent) {
if (!/ip(hone|od|ad)/i.test(userAgent)) {
return;
}
var isPhone = /ip(hone|od)/i.test(userAgent),
matches = userAgent.match(/os (\d+)_(\d+)_?(\d+)?/i),
version = matches ? [parseInt(matches[1], 10), parseInt(matches[2], 10), parseInt(matches[3] || 0, 10)] : [],
isIPhone4 = window.screen.height === 960 / 2,
grade = isIPhone4 ? "B" : "A";
return {
deviceType: isPhone ? "phone" : "tablet",
platform: "ios",
version: version,
grade: grade
};
},
android: function android(userAgent) {
if (!/android|htc_|silk/i.test(userAgent)) {
return;
}
var isPhone = /mobile/i.test(userAgent),
matches = userAgent.match(/android (\d+)\.(\d+)\.?(\d+)?/i),
version = matches ? [parseInt(matches[1], 10), parseInt(matches[2], 10), parseInt(matches[3] || 0, 10)] : [],
worseThan4_4 = version.length > 1 && (version[0] < 4 || version[0] === 4 && version[1] < 4),
grade = worseThan4_4 ? "B" : "A";
return {
deviceType: isPhone ? "phone" : "tablet",
platform: "android",
version: version,
grade: grade
};
}
};
/**
* @name DevicesObject
* @section Utils
* @publicName devices
* @inherits EventsMixin
* @namespace DevExpress
*/
var Devices = Class.inherit({
/**
* @name DevicesObjectevents.orientationChanged
* @publicName orientationChanged
* @type classEventType
* @type_function_param1 e:object
* @type_function_param1_field1 orientation:String
*/
/**
* @name DevicesObjectMethods.ctor
* @publicName ctor(options)
* @param1 options:object
* @param1_field1 window:Window
* @hidden
*/
ctor: function ctor(options) {
this._window = options && options.window || window;
this._realDevice = this._getDevice();
this._currentDevice = undefined;
this._currentOrientation = undefined;
this.changed = Callbacks();
if (windowUtils.hasWindow()) {
this._recalculateOrientation();
resizeCallbacks.add(this._recalculateOrientation.bind(this));
}
},
/**
* @name DevicesObjectmethods.current
* @publicName current()
* @return Device
*/
/**
* @name DevicesObjectmethods.current
* @publicName current(deviceName)
* @param1 deviceName:string|Device
*/
current: function current(deviceOrName) {
if (deviceOrName) {
this._currentDevice = this._getDevice(deviceOrName);
this._forced = true;
this.changed.fire();
return;
}
if (!this._currentDevice) {
deviceOrName = undefined;
try {
deviceOrName = this._getDeviceOrNameFromWindowScope();
} catch (e) {
deviceOrName = this._getDeviceNameFromSessionStorage();
} finally {
if (!deviceOrName) {
deviceOrName = this._getDeviceNameFromSessionStorage();
}
if (deviceOrName) {
this._forced = true;
}
}
this._currentDevice = this._getDevice(deviceOrName);
}
return this._currentDevice;
},
/**
* @name DevicesObjectmethods.real
* @publicName real()
* @return Device
*/
real: function real() {
///#DEBUG
var forceDevice = arguments[0];
if (isPlainObject(forceDevice)) {
extend(this._realDevice, forceDevice);
return;
}
///#ENDDEBUG
return extend({}, this._realDevice);
},
/**
* @name DevicesObjectmethods.orientation
* @publicName orientation()
* @return String
*/
orientation: function orientation() {
return this._currentOrientation;
},
isForced: function isForced() {
return this._forced;
},
isRippleEmulator: function isRippleEmulator() {
return !!this._window.tinyHippos;
},
_getCssClasses: function _getCssClasses(device) {
var result = [];
var realDevice = this._realDevice;
device = device || this.current();
// TODO: use real device here?
if (device.deviceType) {
result.push("dx-device-" + device.deviceType);
if (device.deviceType !== "desktop") {
result.push("dx-device-mobile");
}
}
result.push("dx-device-" + realDevice.platform);
if (realDevice.version && realDevice.version.length) {
result.push("dx-device-" + realDevice.platform + "-" + realDevice.version[0]);
}
if (devices.isSimulator()) {
result.push("dx-simulator");
}
if (Config().rtlEnabled) {
result.push("dx-rtl");
}
return result;
},
attachCssClasses: function attachCssClasses(element, device) {
this._deviceClasses = this._getCssClasses(device).join(" ");
$(element).addClass(this._deviceClasses);
},
detachCssClasses: function detachCssClasses(element) {
$(element).removeClass(this._deviceClasses);
},
isSimulator: function isSimulator() {
// NOTE: error may happen due to same-origin policy
try {
return this._isSimulator || windowUtils.hasWindow() && this._window.top !== this._window.self && this._window.top["dx-force-device"] || this.isRippleEmulator();
} catch (e) {
return false;
}
},
forceSimulator: function forceSimulator() {
this._isSimulator = true;
},
_getDevice: function _getDevice(deviceName) {
if (deviceName === "genericPhone") {
deviceName = {
deviceType: "phone",
platform: "generic",
generic: true
};
}
if (isPlainObject(deviceName)) {
return this._fromConfig(deviceName);
} else {
var ua;
if (deviceName) {
ua = KNOWN_UA_TABLE[deviceName];
if (!ua) {
throw errors.Error("E0005");
}
} else {
ua = navigator.userAgent;
}
return this._fromUA(ua);
}
},
_getDeviceOrNameFromWindowScope: function _getDeviceOrNameFromWindowScope() {
var result;
if (windowUtils.hasWindow() && (this._window.top["dx-force-device-object"] || this._window.top["dx-force-device"])) {
result = this._window.top["dx-force-device-object"] || this._window.top["dx-force-device"];
}
return result;
},
_getDeviceNameFromSessionStorage: function _getDeviceNameFromSessionStorage() {
var sessionStorage = SessionStorage();
if (!sessionStorage) {
return;
}
var deviceOrName = sessionStorage.getItem("dx-force-device");
try {
return JSON.parse(deviceOrName);
} catch (ex) {
return deviceOrName;
}
},
_fromConfig: function _fromConfig(config) {
var result = extend({}, DEFAULT_DEVICE, this._currentDevice, config),
shortcuts = {
phone: result.deviceType === "phone",
tablet: result.deviceType === "tablet",
android: result.platform === "android",
ios: result.platform === "ios",
win: result.platform === "win",
generic: result.platform === "generic"
};
return extend(result, shortcuts);
},
_fromUA: function _fromUA(ua) {
var config;
each(uaParsers, function (platform, parser) {
config = parser(ua);
return !config;
});
if (config) {
return this._fromConfig(config);
}
var isMac = /(mac os)/.test(ua.toLowerCase()),
deviceWithOS = DEFAULT_DEVICE;
deviceWithOS.mac = isMac;
return deviceWithOS;
},
_changeOrientation: function _changeOrientation() {
var $window = $(this._window),
orientation = $window.height() > $window.width() ? "portrait" : "landscape";
if (this._currentOrientation === orientation) {
return;
}
this._currentOrientation = orientation;
this.fireEvent("orientationChanged", [{
orientation: orientation
}]);
},
_recalculateOrientation: function _recalculateOrientation() {
var windowWidth = $(this._window).width();
if (this._currentWidth === windowWidth) {
return;
}
this._currentWidth = windowWidth;
this._changeOrientation();
}
}).include(EventsMixin);
var devices = new Devices();
viewPort.changeCallback.add(function (viewPort, prevViewport) {
devices.detachCssClasses(prevViewport);
devices.attachCssClasses(viewPort);
});
// TODO: remove with win8 theme
if (!devices.isForced() && devices.current().platform === "win") {
devices.current({ version: [10] });
}
/**
* @name devices
* @publicName devices
* @type DevicesObject
* @namespace DevExpress
* @hidden
*/
module.exports = devices;