/** @namespace Starlet @desc Handles loading of Starlet-specific content, ensuring all necessary information is present. Provide starlet identifier (directory) to html counterpart via hash, e.g. "starlet.html#/starletname". Ghost-chasing note: if seeing errors emanating from deep within jQuery (particularly its append method), it's likely a syntax error within the starlet/widget index.html/js. Starlet-specific content is loaded via ajax and errors in it will surface when the html/js is parsed. Logs won't indicate the actual script and line number because the content is "anonymous". */ var Starlet = Starlet || { /* properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // internal 'lastEventTime': true, /** @member {Array} Starlet~loadArguments @private @desc Temporary array of args passed to synchronizer from Framework (name only). @deprecated since version 2.0.2 @see {@link Starlet~synchronizer} */ 'loadArguments': [], /** @member {Object} Starlet~loadSpinner @private @desc Initial load spinner instance. A curtain to hide the page layout until onRun. @see {@link Starlet~synchronizer} */ 'loadSpinner': null, /** @member {Object} Starlet~loadTimer @private @desc Instance of TimerClass, measures time from Starlet creation to load event. @see {@link Starlet~synchronizer} */ 'loadTimer': new TimerClass(), /** @member {Boolean} Starlet~flagDefined @private @desc Starlet synchronizer flag, indicates readiness of starlet behaviors to run. @see {@link Starlet~synchronizer} */ 'flagDefined': false, /** @member {Boolean} Starlet~flagDom @private @desc Starlet synchronizer flag, indicates (safe) dom alterability. @see {@link Starlet~synchronizer} */ 'flagDom': false, /** @member {Boolean} Starlet~flagFramework @private @desc Starlet synchronizer flag, indicates receipt of framework args. @see {@link Starlet~synchronizer} */ 'flagFramework': false, /** @member {Boolean} Starlet~flagLoaded @private @desc Starlet synchronizer flag, indicates presence of essential assets (structure/style/behavior, !media). @see {@link Starlet~synchronizer} */ 'flagLoaded': false, /** @member {Boolean} Starlet~flagCovered @private @desc Starlet synchronizer flag, indicates whether content has been hidden from view for setup. @see {@link Starlet~synchronizer} */ 'flagCovered': false, /** @member {Number} Starlet~near1 @private @desc Threshold of first overlay handle mouse proximity effect (px). @see {@link Starlet~onProximity} */ 'near1': 30, /** @member {Number} Starlet~near2 @private @desc Threshold of second overlay handle mouse proximity effect (px). @see {@link Starlet~onProximity} */ 'near2': 8, /** @member {Boolean} Starlet~nearSb1 @private @desc Whether mouse is within first mouse proximity threshold for shoebox. @see {@link Starlet~onProximity} */ 'nearSb1': false, /** @member {Boolean} Starlet~nearSb2 @private @desc Whether mouse is within second mouse proximity threshold for shoebox. @see {@link Starlet~onProximity} */ 'nearSb2': false, /** @member {Boolean} Starlet~nearNav1 @private @desc Whether mouse is within first mouse proximity threshold for navigation. @see {@link Starlet~onProximity} */ 'nearNav1': false, /** @member {Boolean} Starlet~nearNav2 @private @desc Whether mouse is within second mouse proximity threshold for navigation. @see {@link Starlet~onProximity} */ 'nearNav2': false, /** @member {Number} Starlet~pageViewTimeout @private @desc Google Analytics page tracking timeout identifier. @see {@link Starlet~onPageView} */ 'pageViewTimeout': null, // read only /** @member {Object} Starlet.connectivity @readonly @desc Hashlike object of connectivity states. */ 'connectivity': {'xmpp': true, 'internet': true, 'exiting': false}, /** @member {Number} Starlet.loaded @readonly @desc Time that starlet controller was loaded (ms timestamp). */ 'loaded': new Date().getTime(), /** @member {String} Starlet.type @readonly @desc Represents Framework-given permissions and behavior of WebView. */ 'type': 'trueted_starlet', /** @member {String} Starlet.url @readonly @desc Location of WebView. */ 'url': location.href, /** @member {String} Starlet.root @readonly @desc Server path to current starlet directory. */ 'root': PATH.substring(0, PATH.lastIndexOf('/') + 1) + HASH.substring(1), /** @member {String} Starlet.name @readonly @desc Framework's WebView handle. */ 'name': null, /** @member {String} Starlet.identifier @readonly @desc Current starlet directory. */ 'identifier': HASH.substring(1), /** @member {String} Starlet.title @readonly @desc Display name of starlet shown in header. Override in custom starlet definition. @see {@link Starlet.define} */ 'title': '(Untitled Starlet)', /** @member {Number} Starlet.width @readonly @desc Width of starlet's WebView (px). */ 'width': 0, /** @member {Number} Starlet.height @readonly @desc Height of starlet's WebView (px). */ 'height': 0, /** @member {Number} Starlet.order @readonly @desc Order of last use. Used internally for restoring Framework state. */ 'order': -1, /** @member {Number} Starlet.state @readonly @desc Represents the successful loading, registration, and readiness of starlet to receive instructions and data. */ 'state': S_OPENED, /** @member {Boolean} Starlet.header @readonly @desc Whether the starlet layout includes a header, override in custom starlet definition. @see {@link Starlet.define} */ 'header': true, /** @member {Object} Starlet.footerData @readonly @desc Whether the starlet layout includes a footer, and what variables were passed to the footer template. Override in custom starlet definition. @see {@link Starlet.define} */ 'footerData': {}, /** @member {Array} Starlet.widgets @readonly @desc References to all Widget controller instances within Starlet. */ 'widgets': [], /** @member {Boolean} Starlet.production @readonly @deprecated since version 2.0.2, use constant {@link PRODUCTION} instead. @desc Indicates whether resources are to be loaded from Test server. */ 'production': !!~document.location.hostname.indexOf('.com'), /** @member {String} Starlet.mode @readonly @desc Will be 'prod', 'qa', or 'test', depending on the environment. @todo Create QA constant. */ 'mode': !!~OrionFramework.apiURL.indexOf('.com') ? 'prod' : ( !!~OrionFramework.apiURL.indexOf('web1-test') ? 'qa' : 'test' ), /** @member {String} Starlet.apiServerName @readonly @desc API server name. @todo Create API_HOST constant. @see {@link OrionFramework.apiURL} */ 'apiServerName': OrionFramework.apiURL.replace(/http[s]*:\/\//i, '').replace(/:[0-9]{1,4}/i, ''), // event based states /** @member {Object} Starlet.states @readonly @desc Hashlike grouping of flags relating to user interactions. @property {Boolean} main - Whether current starlet is anchored in the main application window. @property {Boolean} overlay - Whether current starlet is an overlay with specific behavior and extended permissions (shoebox, navigation, notification). @property {Boolean} lightbox - Whether current starlet is anchored in a lightbox. See {@link Starlet.lightbox}. @property {Boolean} ordered - Whether current starlet is part of the normal navigation cycle (anchored in main window, not an overlay or lightbox). @property {Boolean} tearoff - Whether current starlet has been switched from being anchored in the main window to a dedicated one. See {@link Starlet.tearoff}. @property {Boolean} minitearoff - Whether current starlet was initially loaded in a dedicated window. @property {Boolean} visible - Whether current starlet is considered visible to the user. See {@link Starlet.onVisible}, {@link Starlet~onVisibleWrapper}. @property {Boolean} active - Whether current starlet is in a frontmost window and is the primary focus. See {@link Starlet.onActive}, {@link Starlet~onActiveWrapper}. @property {Boolean} focused - Whether current starlet's anchoring window has focus. See {@link Starlet.onFocused}, {@link Starlet~onFocusedWrapper}. @property {Boolean} minimized - Whether current starlet's anchoring window is minimized. See {@link Starlet.onMinimized}, {@link Starlet~onMinimizedWrapper}. @property {Boolean} hidden - Whether current starlet's anchoring window is hidden. See {@link Starlet.onHidden}, {@link Starlet~onHiddenWrapper}. @property {Boolean} mouse - Whether current starlet's WebView contains the mouse pointer. See {@link Starlet.onMouse}, {@link Starlet~onMouseWrapper}. */ 'states': { 'main' : false, 'overlay' : false, 'lightbox' : false, 'ordered' : false, 'tearoff' : false, 'minitearoff': false, 'visible' : false, 'active' : false, 'focused' : false, 'minimized' : false, 'hidden' : false, 'mouse' : false }, // element references /** @member {jQuery} Starlet.$html @readonly @desc Reference to html element (root node). */ '$html': $(), /** @member {jQuery} Starlet.$title @readonly @desc Reference to title element. */ '$title': $(), /** @member {jQuery} Starlet.$body @readonly @desc Reference to body element. */ '$body': $(), /** @member {jQuery} Starlet.$content @readonly @desc Reference to main content area (section#content element). */ '$content': $(), /** @member {jQuery} Starlet.$header @readonly @desc Reference to header element. */ '$header': $(), /** @member {jQuery} Starlet.$footer @readonly @desc Reference to footer element. */ '$footer': $(), /** @member {jQuery} Starlet.$spinner @readonly @desc Reference to initial loading spinner (div.spinner element). */ '$spinner': $(), /** @member {jQuery} Starlet.$tint @readonly @desc Reference to overlay tint (div#tint element). @see {@link Starlet.tint} */ '$tint': $(), /** @member {jQuery} Starlet.$modal @readonly @desc Reference to modal dialog container (div#modal) element. @see {@link Starlet.modal} */ '$modal': $(), /** @member {jQuery} Starlet.$minishoebox @readonly @desc Reference to minishoebox container (div#minishoebox element). @see {@link Starlet.minishoebox} */ '$minishoebox': $(), /** @member {jQuery} Starlet.$menus @readonly @desc Reference to dropdown menu container (div#menus element). @see {@link Starlet.menu} */ '$menus': $(), /* developer callbacks - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** @member {callbackGeneric} Starlet.onRun @abstract @desc Developer callback. Behavioral starting point, fired on successful initialization of Starlet controller. This includes complete loading of templates and scripts, but not multimedia or styles. Will not be retriggered by network reconnection, see {@link Starlet.onReconnect} for this functionality. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onRun} @see {@link Starlet.define} @see {@link Starlet.onDisconnect} @see {@link Starlet.onReconnect} @see {@link Starlet.onUnload} @see {@link Starlet.onExit} */ 'onRun': function () {}, /** @member {callbackGeneric} Starlet.onDisconnect @abstract @desc Developer callback. Fired on termination of User Server XMPP connection. Use this event to disable user interfaces that are not usable when offline. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onDisconnect} @see {@link Starlet.define} @see {@link Starlet.onRun} @see {@link Starlet.onReconnect} @see {@link Starlet.onUnload} @see {@link Starlet.onExit} */ 'onDisconnect': function () {}, /** @member {callbackGeneric} Starlet.onReconnect @abstract @desc Developer callback. Fired on reestablishment of User Server XMPP connection. Use this event to refresh lists of data, and to reenable user interfaces that were disabled while offline. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onReconnect} @see {@link Starlet.define} @see {@link Starlet.onRun} @see {@link Starlet.onDisconnect} @see {@link Starlet.onUnload} @see {@link Starlet.onExit} */ 'onReconnect': function () {}, /** @member {callbackGeneric} Starlet.onUnload @abstract @desc Developer callback. Fired before unloading Starlet, expect no more than ~200ms before WebView stops. Use this event to save state or preferences, and to perform cleanup. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onUnload} @see {@link Starlet.define} @see {@link Starlet.onRun} @see {@link Starlet.onDisconnect} @see {@link Starlet.onReconnect} @see {@link Starlet.onExit} */ 'onUnload': function () {}, /** @member {callbackGeneric} Starlet.onExit @abstract @desc Developer callback. Fired before unloading Starlet, but only when Framework is about to exit. Expect no more than ~200ms before WebView stops. Use this event to save state or preferences, and to perform cleanup. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onExit} @see {@link Starlet.define} @see {@link Starlet.onRun} @see {@link Starlet.onDisconnect} @see {@link Starlet.onReconnect} @see {@link Starlet.onUnload} */ 'onExit': function () {}, /** @member {callbackFrameworkEvent} Starlet.onVisible @abstract @desc Developer callback. Fired on change of visibility of Starlet to the user. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onStarletVisible} @see {@link Starlet.define} @see {@link Starlet.onActive} @see {@link Starlet.onFocused} @see {@link Starlet.onMinimized} @see {@link Starlet.onHidden} @see {@link Starlet.onMouse} */ 'onVisible': function (state) {}, /** @member {callbackFrameworkEvent} Starlet.onActive @abstract @desc Developer callback. Fired on change of whether Starlet has primary focus. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onStarletActive} @see {@link Starlet.define} @see {@link Starlet.onVisible} @see {@link Starlet.onFocused} @see {@link Starlet.onMinimized} @see {@link Starlet.onHidden} @see {@link Starlet.onMouse} */ 'onActive': function (state) {}, /** @member {callbackFrameworkEvent} Starlet.onFocused @abstract @desc Developer callback. Fired on change of whether Starlet's application window has focus. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onStarletFocused} @see {@link Starlet.define} @see {@link Starlet.onVisible} @see {@link Starlet.onActive} @see {@link Starlet.onMinimized} @see {@link Starlet.onHidden} @see {@link Starlet.onMouse} */ 'onFocused': function (state) {}, /** @member {callbackFrameworkEvent} Starlet.onMinimized @abstract @desc Developer callback. Fired on change of whether Starlet's application window is minimized. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onStarletMinimized} @see {@link Starlet.define} @see {@link Starlet.onVisible} @see {@link Starlet.onActive} @see {@link Starlet.onFocused} @see {@link Starlet.onHidden} @see {@link Starlet.onMouse} */ 'onMinimized': function (state) {}, /** @member {callbackFrameworkEvent} Starlet.onHidden @abstract @desc Developer callback. Fired on change of whether Starlet's application window is hidden. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onStarletHidden} @see {@link Starlet.define} @see {@link Starlet.onVisible} @see {@link Starlet.onActive} @see {@link Starlet.onFocused} @see {@link Starlet.onMinimized} @see {@link Starlet.onMouse} */ 'onHidden': function (state) {}, /** @member {callbackFrameworkEvent} Starlet.onMouse @abstract @desc Developer callback. Fired on change of whether Starlet's WebView contains the mouse pointer. Override in custom Starlet definition. @see {@link Widget.ControllerClass#onStarletMouse} @see {@link Starlet.define} @see {@link Starlet.onVisible} @see {@link Starlet.onActive} @see {@link Starlet.onFocused} @see {@link Starlet.onMinimized} @see {@link Starlet.onHidden} */ 'onMouse': function (state) {}, /* callbacks - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** @member {callbackData} Starlet~onConnectivity @private @desc Fired on change of connectivity status, fires appropriate developer callbacks. */ 'onConnectivity': function (packet) { var now = packet.data[0]; if (now.exiting === true) { Starlet.connectivity.exiting = true; Starlet.$html.addClass('exiting'); Starlet.onExit && Starlet.onExit(); Widget && Widget.onExit(); } else if (Starlet.connectivity.xmpp !== now.xmpp) { Starlet.connectivity.xmpp = now.xmpp; if (!Starlet.connectivity.exiting) { if (now.xmpp) { Starlet.$html.removeClass('disconnected').addClass('connected'); Starlet.onReconnect && Starlet.onReconnect(); Widget && Widget.onReconnect(); } else { Starlet.$html.removeClass('connected').addClass('disconnected'); Starlet.onDisconnect && Starlet.onDisconnect(); Widget && Widget.onDisconnect(); } } } }, /** @member {callbackData} Starlet~onStarletData @private @desc Fired on change of any Starlet properties. */ 'onStarletData': function (packet) { var data = dataArrayToObject(packet.data, 'name')[Starlet.name]; //console.log('onStarletData', packet); //data && console.log('onStarletData', data); if (data) { switch (packet.action) { case 'read' : case 'update': //console.log('onStarletData', packet); //return false; $.extend(Starlet, { 'type' : data.type, //'url' : data.url, //'name' : data.name, //'identifier': data.identifier, 'title' : data.title, 'width' : data.width, 'height' : data.height, 'order' : data.order, 'state' : data.state }); $.extend(Starlet.states, { 'main' : data.main, 'overlay' : data.overlay, 'lightbox' : data.lightbox, 'ordered' : data.ordered, 'tearoff' : data.tearoff, 'minitearoff': data.minitearoff }); Starlet.onActiveWrapper (data.active , 1); Starlet.onFocusedWrapper (data.focused , 1); Starlet.onMinimizedWrapper(data.minimized, 1); Starlet.onHiddenWrapper (data.hidden , 1); Starlet.onMouseWrapper (data.mouse , 1); break; default: break; }} }, /** @member {callbackData} Starlet~onOffset @private @desc Fired on change of frontmost WebView's horizontal position during transition animation. Currently unused due to synchronization issue. */ 'onOffset': function (packet) { log(L_FUNC, 'Starlet.onOffset', arguments); var position = packet.data[0], x = position.x * 100, y = position.y * 100; //console.log('onOffset', x, y); Starlet.$header.css({'left': x + '%', 'top': y + '%'}); }, /** @member {callbackData} Starlet~onOverlayState @private @desc Fired on change of overlay visibility states (shoebox|navigation|lightbox), controls tint opacity. */ 'onOverlayState': function (packet) { log(L_FUNC, 'Starlet.onOverlayState', arguments); var states = packet.data[0]; Starlet.states.ordered && Starlet.tint(states.navigation || states.shoebox || states.lightbox); }, /** @member {callbackGeneric} Starlet~onVisibleWrapper @private @desc Fired on need of recalculation of Starlet visibility state (whether Starlet is onscreen). @see {@link Starlet.onVisible} @see {@link Widget.onStarletVisible} @see {@link Starlet~onActiveWrapper} @see {@link Starlet~onFocusedWrapper} @see {@link Starlet~onMinimizedWrapper} @see {@link Starlet~onHiddenWrapper} @see {@link Starlet~onMouseWrapper} */ 'onVisibleWrapper': function () { var visible = Starlet.states.active && !Starlet.states.minimized && !Starlet.states.hidden; if (Starlet.states.visible !== visible) { //console.log('visible', visible); Starlet.states.visible = visible; Starlet.onVisible && Starlet.onVisible(visible); Starlet.speakWidget('starletVisible', visible); //Bus.tell('starlets', {'action': 'update', 'states': {'visible': visible}}); } }, /** @member {callbackFrameworkEvent} Starlet~onActiveWrapper @private @desc Fired on change of Starlet active state (input focus or switch). The Starlet is active when its WebView has primary focus in its application window, and that window is frontmost. @see {@link OrionStarletWindow.setStarletVisibleCallback} @see {@link Starlet.onActive} @see {@link Widget.onStarletActive} @see {@link Starlet~onVisibleWrapper} @see {@link Starlet~onFocusedWrapper} @see {@link Starlet~onMinimizedWrapper} @see {@link Starlet~onHiddenWrapper} @see {@link Starlet~onMouseWrapper} */ 'onActiveWrapper': function (active, instruction) { if (Starlet.states.active !== active) { //console.log('active', active); Starlet.states.active = active; Starlet.onActive && Starlet.onActive(active); Starlet.speakWidget('starletActive', active); //Bus.tell('starlets', {'action': 'update', 'states': {'active': active}}); Starlet.onVisibleWrapper(); Starlet.onPageView(active); //active && OrionStarletWindow.show(); }}, /** @member {callbackFrameworkEvent} Starlet~onFocusedWrapper @private @desc Fired on change of Starlet focused state. The Starlet is focused when its WebView is in a foreground application window. @see {@link OrionStarletWindow.setFocusCallback} @see {@link Starlet.onFocused} @see {@link Widget.onStarletFocused} @see {@link Starlet~onVisibleWrapper} @see {@link Starlet~onActiveWrapper} @see {@link Starlet~onMinimizedWrapper} @see {@link Starlet~onHiddenWrapper} @see {@link Starlet~onMouseWrapper} */ 'onFocusedWrapper': function (focused, instruction) { if (Starlet.states.focused !== focused) { //console.log('focused', focused); Starlet.states.focused = focused; Starlet.onFocused && Starlet.onFocused(focused); Starlet.speakWidget('starletFocused', focused); !instruction && Bus.tell('starlets', {'action': 'update', 'states': {'focused': focused}}); Starlet.onVisibleWrapper(); }}, /** @member {callbackFrameworkEvent} Starlet~onMinimizedWrapper @private @desc Fired on change of Starlet minimized state. The Starlet is minimized when its WebView is in a minimized application window. @see {@link OrionStarletWindow.setMinimizeCallback} @see {@link Starlet.onMinimized} @see {@link Widget.onStarletMinimized} @see {@link Starlet~onVisibleWrapper} @see {@link Starlet~onActiveWrapper} @see {@link Starlet~onFocusedWrapper} @see {@link Starlet~onHiddenWrapper} @see {@link Starlet~onMouseWrapper} */ 'onMinimizedWrapper': function (minimized, instruction) { if (Starlet.states.minimized !== minimized) { //console.log('minimized', minimized); Starlet.states.minimized = minimized; Starlet.onMinimized && Starlet.onMinimized(minimized); Starlet.speakWidget('starletMinimized', minimized); !instruction && Bus.tell('starlets', {'action': 'update', 'states': {'minimized': minimized}}); Starlet.onVisibleWrapper(); }}, /** @member {callbackFrameworkEvent} Starlet~onHiddenWrapper @private @desc Fired on change of Starlet hidden state. The Starlet is hidden when its WebView is in a hidden application window. @see {@link OrionStarletWindow.setHiddenCallback} @see {@link Starlet.onHidden} @see {@link Widget.onStarletHidden} @see {@link Starlet~onVisibleWrapper} @see {@link Starlet~onActiveWrapper} @see {@link Starlet~onFocusedWrapper} @see {@link Starlet~onMinimizedWrapper} @see {@link Starlet~onMouseWrapper} */ 'onHiddenWrapper': function (hidden, instruction) { if (Starlet.states.hidden !== hidden && new Date().getTime() - Starlet.loaded > T_SECOND) { //console.log('hidden', hidden); Starlet.states.hidden = hidden; Starlet.onHidden && Starlet.onHidden(hidden); Starlet.speakWidget('starletHidden', hidden); !instruction && Bus.tell('starlets', {'action': 'update', 'states': {'hidden': hidden}}); Starlet.onVisibleWrapper(); }}, /** @member {callbackFrameworkEvent} Starlet~onMouseWrapper @private @desc Fired on change of Starlet mouse state. The Starlet "has" mouse when the mouse pointer is within its WebView. @see {@link OrionStarletWindow.setMouseCallback} @see {@link Starlet.onMouse} @see {@link Widget.onStarletMouse} @see {@link Starlet~onVisibleWrapper} @see {@link Starlet~onActiveWrapper} @see {@link Starlet~onFocusedWrapper} @see {@link Starlet~onMinimizedWrapper} @see {@link Starlet~onHiddenWrapper} */ 'onMouseWrapper': function (mouse, instruction) { if (Starlet.states.mouse !== mouse) { //console.log('mouse', mouse); Starlet.states.mouse = mouse; Starlet.onMouse && Starlet.onMouse(mouse); Starlet.speakWidget('starletMouse', mouse); !instruction && Bus.tell('starlets', {'action': 'update', 'states': {'mouse': mouse}}); Starlet.onVisibleWrapper(); }}, /** @member {callbackEvent} Starlet~onMouseMove @private @desc Fired on mouse movement within the Starlet's WebView. @see {@link http://api.jquery.com/mousemove/} @see {@link Starlet.onMouse} @see {@link Widget.onStarletMouse} */ 'onMouseMove': function (event) { !Starlet.states.mouse && Starlet.states.visible && Starlet.onMouseWrapper(true); if (!Starlet.states.overlay && !Starlet.states.minitearoff) { var width = $(document).width(), distanceSb = event.pageX, distanceNav = width - event.pageX, nearSb1 = distanceSb <= Starlet.near1, nearSb2 = distanceSb <= Starlet.near2, nearNav1 = distanceNav <= Starlet.near1, nearNav2 = distanceNav <= Starlet.near2; if (nearSb1 !== Starlet.nearSb1 || nearSb2 !== Starlet.nearSb2) { Starlet.nearSb1 = nearSb1; Starlet.nearSb2 = nearSb2; Bus.tell('proximity', {'action': 'set', 'sb1': nearSb1, 'sb2': nearSb2}); } if (nearNav1 !== Starlet.nearNav1 || nearNav2 !== Starlet.nearNav2) { Starlet.nearNav1 = nearNav1; Starlet.nearNav2 = nearNav2; Bus.tell('proximity', {'action': 'set', 'nav1': nearNav1, 'nav2': nearNav2}); } } }, /** @member {callbackData} Starlet~onProximity @private @desc Fired on movement of mouse through overlay handle proximity thresholds. */ 'onProximity': function (packet) { var data = packet.data[0]; // Starlet.$body // [data.sb1 ? 'addClass' : 'removeClass']('proximitySb') // [data.nav1 ? 'addClass' : 'removeClass']('proximityNav'); }, /** @member {callbackEvent} Starlet~onTintClick @private @desc Fired on click of tint layer. @see {@link http://api.jquery.com/click/} */ 'onTintClick': function (event) { event.preventDefault(); Bus.tell('overlays', {'action': 'set', 'states': { 'navigation': false, 'shoebox' : false, 'lightbox' : false }}); }, /** @member {callbackEvent} Starlet~onLinkBubble @private @desc Fired on click of anchor tags, prevents location change and determines whether and how to open externally. @see {@link http://api.jquery.com/click/} */ 'onLinkBubble': function (event) { event.preventDefault(); var href = null; if (href = this.href) { var isFiller = /^(javascript|#)/i.test(href), isMail = /^mailto:/i.test(href); !isFiller && isExternal(href) && ( ISORION ? (isMail ? OrionSystem.executeMailTo(href) : OrionSystem.openExternalURL(href)) : window.open(href) ); } }, /** @member {callbackEvent} Starlet~onDragBubble @private @desc Fired on start of drag events, cancels if not marked with class ui-draggable. @see {@link https://developer.mozilla.org/en-US/docs/Web/Events/dragstart} */ 'onDragBubble': function (event) { !$(event.target).closest('.ui-draggable').length // not inside draggable? && event.preventDefault(); }, /** @member {callbackEvent} Starlet~onTrack @private @desc Fired on click of trackable elements, records with Google Analytics. @see {@link http://api.jquery.com/click/} @see {@link http://www.google.com/analytics/} */ 'onTrack': function (event, alternateText) { var gaLoaded = typeof ga !== 'undefined', //type = this.nodeName === 'a' ? 'link' : 'button', //$this = $(this), $this = $(event.target).closest(TRACKABLES.join(',')), type, eAction = 'click', $dialog = $this.closest('.modal'), text = alternateText || $.trim($this.attr('data-ga-title')) || $.trim($this.text()) || $.trim($this.attr('title')) || $.trim($this.attr('data-original-title')) || $.trim($this.attr('value')) || $this.outerHTML; // if there's a dialog, prepend the title $dialog.length && (text = '{0} - {1}'.format($.trim($dialog.find('.modal-header').text()), text)); $this.length && (type = $this[0].nodeName); // for event types that are not 'tracked, catch then here and set the type and eAction accordingly. event.type === 'keyup' && (type = 'search' ) && (eAction = type); event.type === 'keyup' && $(event.target).is('textarea') && (type = 'Edit Note') && (eAction = type); event.type === 'mousedown' && (type = 'ext-click') && (eAction = type); event.type === 'drop' && (type = 'drop' ) && (eAction = type); //var x = $(event.target).closest(TRACKABLES); if (Starlet.lastEventTime && Starlet.lastEventTime < event.timeStamp - 50) { if (type) { Starlet.lastEventTime = event.timeStamp; //console.log('onTrack: ', type, Starlet.title, text, event); gaLoaded && ga('send', 'event', Starlet.title, eAction, text); } else { //console.log('NOPE onTrack: ', type, Starlet.title, text, $this, event); } } }, /** @member {callbackFrameworkEvent} Starlet~onPageView @private @desc Fired on activation (input focus or switch) of Starlet. @see {@link Starlet.onActive} @see {@link Starlet.onActiveWrapper} */ 'onPageView': function (active) { log(L_FUNC, 'Starlet.onPageView', arguments); if (active) { // do nothing if timeout already exists if (Starlet.pageViewTimeout) { return; } Starlet.pageViewTimeout = setTimeout(function () { // clear timeout Starlet.pageViewTimeout = null; // send pageView //console.log('PageView: ', PATH, Starlet.title); ga && ga('send', 'pageview', {'page': PATH + (HASH ? HASH : ''), 'title': Starlet.title}); log(L_INFO, 'Starlet.onPageView', 'GA PageView Fired: ' + Starlet.title); }, 1000); } else if (Starlet.pageViewTimeout) { // clear timeout if exists on unfocus clearTimeout(Starlet.pageViewTimeout); Starlet.pageViewTimeout = null; } }, /** @member {callbackData} Starlet~onModal @private @desc Fired on receipt of instruction to create modal dialog. @see {@link Starlet.modal} */ 'onModal': function (packet) { log(L_FUNC, 'Starlet.onModal', arguments); // if there is already a modal dialog, don't show this if (Starlet.$modal.children().length) { console.log('Dialing Modal aborted due to modal div already in use'); return false; } var modal = packet.data[0]; if (!modal.starlet || modal.starlet === Starlet.name) { switch (modal.type) { case 'dial': modal.timeOut = 10; // For case where a parked call is dropped on a user's extension that is NOT the default extension, // need to reset the extension value in the extension object to the extension value from the // originatingExtension object modal.extension.extension = modal.originatingExtension.extension; Starlet.modal(templates.call_alert(modal), {'className': 'call_alert'}); // after 10 seconds, close the modal... var x = modal.timeOut, $counter = Starlet.$modal.find('.timeOut'); var countdown = setInterval(function () { if (x > 0) { $counter.text(--x); } else { clearInterval(countdown); Starlet.$modal .find('.btn-primary') .trigger('click') .end() .find('div.alertDialog').remove(); Starlet.modal('hide'); } }, 1000); // bindings var event = 'click.starlet.onModal', remove = function () { clearInterval(countdown); Starlet.$modal .off('.starlet.onModal') .find('div.alertDialog') .remove(); Starlet.modal('hide'); }; Starlet.$modal .on(event, '.btn.btn-primary', function () { remove(); }) .on(event, '.btn.reject', function () { Bus.relay('starscope', 'rejectCall', {'extData': modal.extension}); remove(); }) .on(event, 'label', function () { Bus.tell('options', { 'action' : 'set', 'category': '_global', 'key' : 'hideCallAlerts', 'value' : 'true' }); remove(); }) .on(event, 'i', function () { $(this).removeClass('flash').addClass('bounce'); }); break; default: break; }} }, 'onApplicationState': function (packet) // in development { //console.log('STATES', packet.data); }, /* wrapper methods - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** @method Starlet.lightbox @readonly @chainable @desc Opens a lightbox overlay with specific location and dimensions. @arg {String} name - Framework name of new WebView. @arg {String} url - Location to open in lightbox. @arg {Number} width - Width at which to open lightbox. @arg {Number} height - Height at which to open lightbox. @returns {Object} {@link Starlet} @see {@link Starlet.lightbox(2)} @see {@link OrionLightbox.open} */ /** @method Starlet.lightbox(2) @readonly @chainable @desc Closes a lightbox overlay by name. @arg {String} name - Framework name of WebView to close. @returns {Object} {@link Starlet} @see {@link Starlet.lightbox} @see {@link OrionLightbox.close} @see {@link OrionLightbox.setClosedCallback} */ 'lightbox': function () { log(L_FUNC, 'Starlet.lightbox', arguments); var args = arguments; switch (args.length) { case 1: OrionLightbox.close(args[0]); break; case 4: var closeCallback = function () { Bus.tell('overlays', {'action': 'set', 'states': {'lightbox': false}}); }, name = args[0], url = args[1], width = Math.round(args[2]), height = Math.round(args[3]); OrionLightbox.setClosedCallback('window', linkCallback(closeCallback)); Bus.tell('overlays', {'action': 'set', 'states': {'lightbox': true}}); setTimeout(function () { OrionLightbox.open(name, url, width, height); }, 0); break; default: log(L_ERROR, 'Starlet.lightbox', 'invalid arguments'); return Starlet; } return Starlet; }, /** @method Starlet.modal @readonly @desc Creates a Bootstrap modal dialog in a manner that forces a consistent z-index. @arg {String} html - Raw html to pass through to Bootstrap modal method. @arg {Object} [options={}] - Additional options. @arg {String} [options.className] - Custom CSS class name to add to modal element. @arg {Boolean|String} [options.backdrop='static'] - Whether to include backdrop, and to allow click to dismiss. @arg {Boolean} [options.keyboard=true] - Whether to allow esc to dismiss. @arg {Boolean} [options.show=true] - Whether to show immediately. @arg {Boolean|String} [options.remote=false] - Remote content URL. @returns {jQuery} {@link Starlet.$modal} @see {@link Starlet.modal(2)} @see {@link http://getbootstrap.com/2.3.2/javascript.html#modals} */ /** @method Starlet.modal(2) @readonly @desc Controls Bootstrap modal dialog. @arg {String} action - Action (show|hide|toggle) to pass through to Bootstrap modal method. @returns {jQuery} {@link Starlet.$modal} @see {@link Starlet.modal} @see {@link http://getbootstrap.com/2.3.2/javascript.html#modals} */ 'modal': function (action, options) { log(L_FUNC, 'Starlet.modal', arguments); switch (action) { case 'show' : Starlet.minishoebox().menu(); // close case 'hide' : case 'toggle': Starlet.$modal.modal(action); break; default : Starlet.$modal.attr('class', 'modal hide fade'); !options && (options = {}); !exists(options.backdrop) && (options.backdrop = 'static'); options.className && Starlet.$modal.addClass(options.className); Starlet .minishoebox().menu() // close .$modal .empty() .append(action) // html .modal(options); // show break; } return Starlet.$modal; }, /** @method Starlet.minishoebox @readonly @chainable @desc Manages a centralized, preloaded minishoebox Widget within a Bootstrap popover in a manner that forces a consistent z-index. @arg {Element|jQuery} anchor - Element with which to align popover. @arg {String} [placement='bottom'] - Direction in which to align popover. @arg {String} [session=''] - Session id for minishoebox to pass to target after selection. @arg {Array.<Number>} [participants=[]] - Array of user ids to preselect. @returns {Object} {@link Starlet} @see {@link Starlet.minishoebox(2)} @see {@link http://getbootstrap.com/2.3.2/javascript.html#popovers} */ /** @method Starlet.minishoebox(2) @readonly @chainable @desc Closes minishoebox if open. @returns {Object} {@link Starlet} @see {@link Starlet.minishoebox} @see {@link http://getbootstrap.com/2.3.2/javascript.html#popovers} */ 'minishoebox': function (anchor, placement, session, participants) { // close? if (!arguments.length) { if (Starlet.$minishoebox.data('showing')) { Starlet.$minishoebox .css({'display': 'none', 'left': 0, 'top': 0}) .append($('body > .popover div.widget')) // prevent event/data loss .data('showing', false) .popover('hide'); Starlet.$html.removeClass('minishoebox'); } return Starlet; } // validate anchor = jUnwrap(anchor); placement = placement || 'bottom'; session = session || ''; participants = participants || []; if (!anchor) { log(L_ERROR, 'Starlet.minishoebox', 'no anchor provided' ); return false; } if (!~$.inArray(placement, POPOVER_PLACEMENTS)) { log(L_ERROR, 'Starlet.minishoebox', 'invalid placement' ); return false; } // init var mode = Starlet.$minishoebox.data('mode'), pop = Starlet.$minishoebox.data('popover'), widget = Starlet.$minishoebox.data('widget'), controller = Starlet.$minishoebox.data('controller'), rect = anchor.getBoundingClientRect(), midWidth = rect.width / 2 + rect.left, midHeight = rect.height / 2 + rect.top, modal = $(anchor).parents('.modal').length; // set context Starlet.speakWidget('minishoeboxContext', { 'calling_widget' : mode, 'session_id' : session, 'current_participants': participants }); // show Starlet.menu(); Starlet.$minishoebox.css ( $.extend( { 'left' : {'left': rect.left , 'top': midHeight }, 'top' : {'left': midWidth , 'top': rect.top }, 'bottom': {'left': midWidth , 'top': rect.bottom}, 'right' : {'left': rect.right, 'top': midHeight } } [placement], {'display': 'block'}) ) .data('placement', placement) .data('showing' , true) .popover('show'); $('body > .popover')[modal ? 'addClass' : 'removeClass']('higher'); Starlet.$html.addClass('minishoebox'); return Starlet; }, /** @method Starlet.menu @readonly @chainable @desc Replaces a select element with a styled Bootstrap dropdown. @arg {Element|jQuery} select - Element to replace. @returns {Object} {@link Starlet} @see {@link Starlet.menu(2)} @see {@link Starlet.menu(3)} @see {@link Starlet.menu(4)} @see {@link http://getbootstrap.com/2.3.2/javascript.html#dropdowns} */ /** @method Starlet.menu(2) @readonly @chainable @desc Initializes a Bootstrap dropdown defined with custom structure and associates it with an element. @todo Document structure. @arg {Element|jQuery} anchor - Element to toggle menu. @arg {Object} structure - Definition of menu structure. @returns {Object} {@link Starlet} @see {@link Starlet.menu} @see {@link Starlet.menu(3)} @see {@link Starlet.menu(4)} @see {@link http://getbootstrap.com/2.3.2/javascript.html#dropdowns} */ /** @method Starlet.menu(3) @readonly @chainable @desc Closes menu if open. @returns {Object} {@link Starlet} @see {@link Starlet.menu} @see {@link Starlet.menu(2)} @see {@link Starlet.menu(4)} @see {@link http://getbootstrap.com/2.3.2/javascript.html#dropdowns} */ /** @method Starlet.menu(4) @readonly @private @chainable @desc Internal variation for toggling menus that have already been initialized. Filler arguments are only to make arguments.length 3, differentiating the behavior of this method. @arg {Element|jQuery} anchor - Element that has been initialized as a menu toggling button. @arg {Mixed} filler1 - Filler. @arg {Mixed} filler2 - Filler. @returns {Object} {@link Starlet} @see {@link Starlet.menu} @see {@link Starlet.menu(2)} @see {@link Starlet.menu(3)} @see {@link http://getbootstrap.com/2.3.2/javascript.html#dropdowns} */ 'menu': function () { // init args var self = arguments.callee, anchor = jUnwrap(arguments[0]), select = null, structure = null, initialized = false; // init static methods self.getStructure = self.getStructure || function (group, count) { // init var self = arguments.callee, $group = jWrap(group), $selected = $group.closest('select').find('option:selected'), count = count || 0, structure = { 'selected': !!$group.attr('selected'), 'disabled': !!$group.attr('disabled'), 'right' : !!$group.attr('data-right'), 'up' : !!$group.attr('data-up'), 'title' : $group.attr('title') || $group.attr('label') || $group.find('option:selected').html() || '', 'items' : [] }; count && (structure.count = count); // loop select's top level $group.find('> option, > optgroup').each(function (index, item) { var $item = $(item), submenu = item.tagName.toLowerCase() !== 'option', substructure = null; $item.attr('data-count', ++count); // add appropriate object var value = $item.attr('value'), value = typeof value === 'string' ? value : $item.text(), selected = $selected[0] === $item[0]; structure.items.push(submenu ? (substructure = self(item, count)) : // submenu (recurse) | item { 'count' : count, 'divider' : false, 'html' : true, 'value' : value, 'selected' : selected, 'disabled' : !!$item.attr('disabled'), 'title' : $item.html(), 'url' : $item.attr('data-href') || '', 'items' : [], 'callbacks': {'click': function (e) { // init var $li = $(this), value = $li.attr('data-value'), which = $li.attr('data-count'), $anchor = $(Starlet.menu.anchor).closest('div'), $select = $($anchor.data('starlet_menu_select')), $options = $select.find('option'), $option = $select.find('option[data-count="{0}"]'.format(which)); // validate if ($option.attr('disabled')) { return false; } // mark selected $options.removeAttr('selected'); $option.attr('selected', 'selected'); !$select.attr('title') && $anchor.find('span.title').html($option.html()); // close, trigger Starlet.menu(); $select.trigger('change'); }} }); substructure && (count += substructure.items.length); }); return structure; }; self.getCallbacks = self.getCallbacks || function (structure) { // init var self = arguments.callee, callbacks = [], structure = structure || Starlet.menu.structure; // validate if (!structure) { log(L_ERROR, 'Starlet.menu.getCallbacks', 'structure not found'); return callbacks; } // collect structure.items && $.each(structure.items, function (index, item) { !item.divider && callbacks.push(item.callbacks || {}); // item item.items && (callbacks = callbacks.concat(self(item))); // submenu }); return callbacks; }; self.setPosition = self.setPosition || function (e) { // init var structure, rect; // do anything? self.open && self.anchor && (rect = self.anchor .getBoundingClientRect()) && (structure = $(self.anchor).closest('div').data('starlet_menu_structure')) && Starlet.$menus.css( { 'left': rect[structure.right ? 'right' : 'left' ], 'top' : rect[structure.up ? 'top' : 'bottom'] }); }; // switch mode switch (arguments.length) { case 3 : select = anchor; // internal toggle anchor = $(select).data('starlet_menu_button'); initialized = true; break; case 1 : select = anchor; // replace element initialized = $(select).hasClass('starlet_menu'); break; case 2 : structure = arguments[1]; break; // custom structure case 0 : anchor = null; break; // close anything default: log(L_ERROR, 'Starlet.menu', 'invalid arguments'); return Starlet; // invalid } // close? if (!anchor || anchor == self.anchor) { if (self.open) { // unbind $('*') .off('.starlet.menu'); $(window).off('.starlet.menu'); // dom reset Starlet.$menus.removeClass('dropup open'); // wait for animation to finish setTimeout(function () { Starlet.$menus .hide() // hide .empty() // clear .css({'left': 0, 'top': 0}); // position }, 200); // update states Starlet.$html.removeClass('menu'); self.open = false; self.anchor = null; } return Starlet; } // replace element? if (select) { // create structure structure = self.getStructure(select); // validate if (!structure) { log(L_ERROR, 'Starlet.menu', 'select tree malformed'); return Starlet; } // replace element? if (!initialized) { var $select = $(select), $button = $(templates.menu_button(structure)), update = function () { // update title? if (!$select.attr('title')) { var title = $select.find('option:selected, option[selected]').html() || '(None Selected)'; $button.find('span.title').html(title); } // rebuild? self.open && self() && self($select, 1, 1); }, observer = new MutationObserver(update); // hide, insert $select .data('starlet_menu_button' , $button.find('> a')[0]) // cross reference .data('starlet_menu_structure', structure) .data('starlet_menu_observer' , observer) .on('update', update) .hide(); $button .data('starlet_menu_select' , select) // cross reference .data('starlet_menu_structure', structure) .data('starlet_menu_observer' , observer) .on('click.starlet', function (e) // bind internal toggle { !$select.attr('disabled') && Starlet.menu(select, 1, 1); }) .addClass('starlet_menu') // mark as processed .insertAfter(select); // listen for changes observer.observe($select[0], { 'childList' : true, 'characterData': true, 'subtree' : true }); // stop return Starlet; } } // validate if (!structure) { log(L_ERROR, 'Starlet.menu', 'no structure provided'); return Starlet; } // init for build var rect = anchor.getBoundingClientRect(), callbacks = self.getCallbacks(structure), $select = $(select), $menu = $(templates.menu(structure)), gaTitle = ($select && $select.length) ? $select.attr('data-ga-title') : ''; gaTitle && $menu.attr('data-ga-title', gaTitle); // update states self.open = true; self.anchor = anchor; Starlet.$html.addClass('menu'); // dom setup Starlet.$menus .empty() // clear .addClass(structure.up ? 'dropup' : '') // position .css( { 'left': rect[structure.right ? 'right' : 'left' ], 'top' : rect[structure.up ? 'top' : 'bottom'] }) .append($menu) // insert .show() // show .find('li:not(.divider)') // bind .each(function (index, item) { var callbacksItem = callbacks[index], count = 0; // add namespaces for (var name in callbacksItem) { if (callbacksItem.hasOwnProperty(name)) { var callback = callbacksItem[name], eventspace = '{0}.starlet.menu'.format(name); // wrap delete callbacksItem[name]; callbacksItem[eventspace] = function (e) { callback.apply(this, arguments); killEvent(e); }; count++; }} // any? bind count && $(this).on(callbacksItem); }); // "fork" for rendering fork(function () { // init bootstrap $menu.dropdown('toggle'); // listen for click-out $('*:not(body > #menus *)') .not(anchor) .not($(anchor).find('*')) .on('click.starlet.menu', function (e) { self.open && Starlet.menu(); }); // listen for scroll|resize var throttled = $.throttle(10, Starlet.menu.setPosition); $(window) .on( 'resize.starlet.menu', throttled); $(document).on('mousewheel.starlet.menu', throttled); }); return Starlet; }, /* methods - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** @method Starlet.speakWidget @readonly @chainable @desc Emit data under a specific packet name to any Widget listening for it. Listening Widgets must prepend 'Starlet_' to the packet name to differentiate between broadcasts originating from Starlet versus another Widget. Alternatively, trigger listeners as a Widget would using {@link Starlet.broadcastWidget}. @arg {String} packetName - Topic to target specific listeners. @arg {Object|Array} data - The item to broadcast. @returns {Object} {@link Starlet} @see {@link Starlet.broadcastWidget} @see {@link Starlet.listenWidget} @see {@link Starlet.listenBroadcast} @see {@link Starlet.ignoreWidget} @see {@link Starlet.ignoreBroadcast} */ 'speakWidget': function (packetName, data) { log(L_DEBUG, 'Starlet.speakWidget', arguments); var widgets = Widget.instances(), packet = {'widget': 'Starlet', 'name': packetName, 'data': typeof data !== 'undefined' ? data : {}}; $.each(widgets, function () { this.$element.triggerHandler('Starlet_' + packetName, [packet]); }); return Starlet; }, /** @method Starlet.broadcastWidget @readonly @chainable @desc Emit data under a specific packet name to any Widget listening for it. Listening Widgets will be triggered as if by another Widget so they needn't prepend 'Starlet_' to the packet name. Alternatively, trigger listeners in a way that signifies the origin is Starlet by using {@link Starlet.speakWidget}. @arg {String} packetName - Topic to target specific listeners. @arg {Object|Array} data - The item to broadcast. @returns {Object} {@link Starlet} @see {@link Starlet.speakWidget} @see {@link Starlet.listenWidget} @see {@link Starlet.listenBroadcast} @see {@link Starlet.ignoreWidget} @see {@link Starlet.ignoreBroadcast} */ 'broadcastWidget': function (packetName, data) { log(L_DEBUG, 'Starlet.broadcastWidget', arguments); var widgets = Widget.instances(), packet = {'name': packetName, 'data': typeof data !== 'undefined' ? data : {}}; $.each(widgets, function () { this.$element.triggerHandler(packetName, [packet]); }); return Starlet; }, /** @method Starlet.listenWidget @readonly @chainable @desc Listen for data from a specific Widget type and of a specific packet name. @arg {String} widgetType - Type of Widget to listen for data from. @arg {String} packetName - Topic to listen for. @arg {Function} callback - Function to respond to incoming data. @returns {Object} {@link Starlet} @see {@link Starlet.speakWidget} @see {@link Starlet.broadcastWidget} @see {@link Starlet.listenBroadcast} @see {@link Starlet.ignoreWidget} @see {@link Starlet.ignoreBroadcast} */ 'listenWidget': function (widgetType, packetName, callback) { log(L_DEBUG, 'Starlet.listenWidget', arguments); Starlet.$content.on(widgetType + '_' + packetName, function (e, packet) { callback(packet); }); return Starlet; }, /** @method Starlet.ignoreWidget @readonly @chainable @desc Stop listening for data from a specific Widget type and of a specific packet name. @arg {String} widgetType - Type of Widget to stop listening for data from. @arg {String} packetName - Topic to stop listening for. @returns {Object} {@link Starlet} @see {@link Starlet.speakWidget} @see {@link Starlet.broadcastWidget} @see {@link Starlet.listenWidget} @see {@link Starlet.listenBroadcast} @see {@link Starlet.ignoreBroadcast} */ 'ignoreWidget': function (widgetType, packetName) { log(L_DEBUG, 'Starlet.ignoreWidget', arguments); Starlet.$content.off(widgetType + '_' + packetName); return Starlet; }, /** @method Starlet.listenBroadcast @readonly @chainable @desc Listen for data of a specific packet name. @arg {String} packetName - Topic to listen for. @arg {Function} callback - Function to respond to incoming data. @returns {Object} {@link Starlet} @see {@link Starlet.speakWidget} @see {@link Starlet.broadcastWidget} @see {@link Starlet.listenWidget} @see {@link Starlet.ignoreWidget} @see {@link Starlet.ignoreBroadcast} */ 'listenBroadcast': function (packetName, callback) { log(L_DEBUG, 'Starlet.listenBroadcast', arguments); Starlet.$content.on('_{0}'.format(packetName), function (e, packet) { callback(packet); }); return Starlet; }, /** @method Starlet.ignoreBroadcast @readonly @chainable @desc Stop listening for data of a specific packet name. @todo Eliminate widgetType as an argument. How broadcast differs from speak is that no type filter is needed. @arg {String} widgetType - Type of Widget to stop listening for data from (see todo). @arg {String} packetName - Topic to stop listening for. @returns {Object} {@link Starlet} @see {@link Starlet.speakWidget} @see {@link Starlet.broadcastWidget} @see {@link Starlet.listenWidget} @see {@link Starlet.listenBroadcast} @see {@link Starlet.ignoreWidget} */ 'ignoreBroadcast': function (widgetType, packetName) { log(L_DEBUG, 'Starlet.ignoreBroadcast', arguments); Starlet.$content.off('_{0}'.format(packetName)); return Starlet; }, /** @method Starlet.define @readonly @chainable @desc Extends Starlet with custom properties, methods, and callbacks. Call this in your Starlet directory's index.js, it signifies to the synchronizer that fundamental custom Starlet resources have been loaded and parsed. @todo Document definition. @arg {Object} definition - Structure with which to extend Starlet, must include override for onRun to initiate any behavior. @returns {Object} {@link Starlet} @see {@link Starlet.title} @see {@link Starlet.header} @see {@link Starlet.footerData} @see {@link Starlet.onRun} @see {@link Starlet.onDisconnect} @see {@link Starlet.onReconnect} @see {@link Starlet.onUnload} @see {@link Starlet.onVisible} @see {@link Starlet.onActive} @see {@link Starlet.onFocused} @see {@link Starlet.onMinimized} @see {@link Starlet.onHidden} @see {@link Starlet.onMouse} @see {@link Starlet.load} @see {@link Starlet~synchronizer} */ 'define': function (definition) { log(L_FUNC, 'Starlet.define'); if (!definition) { log(L_ERROR, 'Starlet.define', 'nothing provided'); return false; } $.extend(Starlet, definition); // process counterparts detectTemplates(); detectStyles(); // css flags DEVELOPER && Starlet.$html.addClass('dev'); !Starlet.header && Starlet.$html.addClass('no-header'); Starlet.states.overlay && Starlet.$html.addClass('overlay'); Starlet.states.lightbox && Starlet.$html.addClass('lightbox'); // set up content Starlet.$title.text(Starlet.title); Starlet.$body.append(templates.starlet(Starlet)); Starlet.$header = Starlet.$body.find('> header'); Starlet.$content = Starlet.$body.find('> section#content'); Starlet.$footer = Starlet.$body.find('> footer'); Starlet.$minishoebox = Starlet.$body.find('> #minishoebox'); Starlet.$menus = Starlet.$body.find('> #menus'); 'footer' in templates ? Starlet.$footer.append(templates.footer(Starlet.footerData)) : Starlet.$html.addClass('no-footer'); // signal Starlet.synchronizer.notify('defined'); return Starlet; }, /** @method Starlet.load @readonly @chainable @desc Fetches custom Starlet index.html and notifies synchronizer. @todo Verify that occasional bad root was due to location hash being changed, remove relevant code here. @returns {Object} {@link Starlet} @see {@link Starlet.define} @see {@link Starlet~synchronizer} */ 'load': function () { log(L_FUNC, 'Starlet.load'); // process counterparts detectTemplates(); $('link[type="text/less"]').addClass('processed'); // less handles these initially but doesn't mark // jes hack becuase this is unset some times var d = $.Deferred(); if ( !(Starlet && Starlet.identifier && Starlet.identifier.length) || !~Starlet.root.indexOf(Starlet.identifier) ){ log(L_ERROR, 'Starlet.load', 'bad Starlet.root', Starlet.root, Starlet.identifier); Bus.ask('starlets', {'action': 'active'}).then(function (packet) { Starlet.root += packet.data[0].identifier; d.resolve(Starlet); }); } else { d.resolve(Starlet); } d.promise().then(function (data) { var requestUrl = [Starlet.root, 'index.html'].join('/'), requestTimer = null, parseTimer = null; log(L_INFO, 'Starlet.load', 'requesting...', requestUrl) && (requestTimer = new TimerClass()); $.ajax(requestUrl, {'type': 'GET'}).success(function (html, status, xhr) { log(L_INFO, 'Starlet.load', 'received in ' + requestTimer.elapsed(1), 'parsing...') && (parseTimer = new TimerClass()); // insert content $('head').append($($.trim(html))) && log(L_INFO, 'Starlet.load', 'parsed in ' + parseTimer.elapsed(1)); // signal Starlet.synchronizer.notify('loaded'); }) .error(function (xhr, status, error) { log(L_ERROR, 'Starlet.load', status, error, requestUrl); }); }); return Starlet; }, 'setting': function () { log(L_DEBUG, 'Starlet.setting', arguments); var args = Array.prototype.slice.call(arguments); args[0] = 'starlet.' + Starlet.identifier + '.' + args[0]; return Bus.setting.apply(Bus, args); }, /** @member {Object} Starlet~synchronizer @readonly @private @desc Deferred object handling foundational bindings and property population when loading as aspects of the WebView become available, finally calling {@link Starlet.onRun}. @see {@link Starlet.load} @see {@link Starlet.define} @see {@link Starlet~loadArguments} @see {@link Starlet~loadSpinner} @see {@link Starlet~loadTimer} @see {@link Starlet~flagDefined} @see {@link Starlet~flagDom} @see {@link Starlet~flagFramework} @see {@link Starlet~flagLoaded} @see {@link Starlet~flagCovered} */ 'synchronizer': (new $.Deferred()) .progress(function (flag, args) { log(L_FUNC, 'Starlet.synchronizer', arguments); // status, actions switch (flag) { case 'dom': Starlet.flagDom = true; Starlet.$html = $('html'); Starlet.$title = $('title'); Starlet.$body = $('body'); Starlet.$modal = $('#modal'); Starlet.$spinner = $('body > div.spinner'); Starlet.$tint = $('#tint'); // spinner Starlet.$spinner.fadeIn( { 'duration': 250, 'easing' : 'easeInOutCubic', 'done' : function () { Starlet.$html.removeClass('loading'); Starlet.synchronizer.notify('covered'); } }); // lock location $('html') .on('click' , 'a' , Starlet.onLinkBubble) .on('dragstart', '*:not(ui-draggable), *:not(ui-draggable) *', Starlet.onDragBubble); window.addEventListener('dragover', function (e) { e.preventDefault(); }, false); window.addEventListener('drop' , function (e) { e.preventDefault(); }, false); // analytics //Starlet.$body.off('click.ga').on('click.ga', TRACKABLES.join(', '), Starlet.onTrack); Starlet.$body[0].addEventListener('click', Starlet.onTrack, true); // other events Starlet.$html.on('mousemove', $.throttle(T_HUNDREDTH, Starlet.onMouseMove)); Starlet.$tint.on('click' , Starlet.onTintClick); Starlet.$body .on('dblclick', '> header', function (e) { killEvent(e); var starlet; DEVELOPER && (starlet = prompt('Open developer console for starlet (sql name):')) && Bus.console(starlet); }) .on('click', '> header ul.nav li#goto-shoebox', function (e) { killEvent(e); Starlet.onTrack(e, 'shoebox'); Bus.tell('overlays', {'action': 'set', 'states': {'shoebox': true}}); }) .on('click', '> header ul.nav li#goto-help', function (e) { killEvent(e); Starlet.onTrack(e, 'help'); // typeof ga !== 'undefined' && ga('send', 'event', 'button', 'click', 'Help'); // fileName - custom help file name Starlet.lightbox('help', ORIGIN + Starlet.root + '/help/{0}'.format(this.attributes['fileName'] ? this.attributes['fileName'].value : 'index.html'), window.innerWidth * .8, window.innerHeight * .8); }) .on('click', '> header ul.nav li#goto-settings', function (e) { killEvent(e); Starlet.onTrack(e, 'Settings'); //typeof ga !== 'undefined' && ga('send', 'event', 'button', 'click', 'Settings'); Bus.navRelay('settings', 'openPage', { 'page': Starlet.name }); }) .on('click', '> header ul.nav li#goto-navigation', function (e) { killEvent(e); Starlet.onTrack(e, 'Navigation'); //typeof ga !== 'undefined' && ga('send', 'event', 'button', 'click', 'Navigation'); Bus.tell('overlays', {'action': 'set', 'states': {'navigation': true}}); }) .on('click', '> header ul.nav li.dev', function (e) { killEvent(e); var $this = $(this); if ($this.hasClass('console')) { var starlet = $this.attr('data-starlet'); starlet && Bus.console(starlet); } else if ($this.hasClass('tearoff')) { Starlet.tearoff(); } }) .on('keyup', '', function (e) { e.keyCode === 27 && Starlet.$tint && Starlet.$tint.click(); }); // fetch Starlet.load(); break; case 'framework': // init var url = location.href.parseUrl(); Starlet.loadArguments = Array.prototype.slice.call(args); Starlet.flagFramework = true; Starlet.name = Starlet.loadArguments[0]; Starlet.states.overlay = !!~$.inArray(Starlet.name, S_NAMES_OVERLAYS); Starlet.states.lightbox = !!~$.inArray(Starlet.name, S_NAMES_LIGHTBOXES); Starlet.states.tearoff = String(url.query.tearoff ).toBoolean(); Starlet.states.minitearoff = String(url.query.minitearoff).toBoolean(); Starlet.states.main = Starlet.name !== 'notification' && !Starlet.states.tearoff && !Starlet.states.minitearoff; Starlet.states.ordered = Starlet.states.main && !Starlet.states.lightbox && !Starlet.states.overlay; // bind events OrionStarletWindow.setStarletVisibleCallback('', linkCallback(function (e) { Starlet.onActiveWrapper (e.visible); } , Starlet, true)); OrionStarletWindow.setFocusCallback ('', linkCallback(function (e) { Starlet.onFocusedWrapper (e.eventName === 'windowFocus'); } , Starlet, true)); OrionStarletWindow.setMinimizeCallback ('', linkCallback(function (e) { Starlet.onMinimizedWrapper(e.eventName === 'windowMinimize'); }, Starlet, true)); OrionStarletWindow.setHiddenCallback ('', linkCallback(function (e) { Starlet.onHiddenWrapper (e.eventName === 'windowHidden'); } , Starlet, true)); OrionStarletWindow.setMouseCallback ('', linkCallback(function (e) { Starlet.onMouseWrapper (e.eventName === 'mouseEnter'); } , Starlet, true)); Bus.ask('connectivity' , { 'subscribe': true, 'callback': Starlet.onConnectivity}); Bus.ask('starlets' , {'action': 'list', 'subscribe': true, 'callback': Starlet.onStarletData}); Bus.ask('modals' , { 'subscribe': true, 'callback': Starlet.onModal}); //Bus.ask('window' , { 'subscribe': true, 'callback': Starlet.onWindowState}); //Bus.ask('application' , { 'subscribe': true, 'callback': Starlet.onApplicationState}); if (!Starlet.states.overlay && !Starlet.states.lightbox) { Bus.ask('overlays' , {'subscribe': true, 'callback': Starlet.onOverlayState}); Bus.ask('userinfo', {'action': 'get'}).then(function (packet) { Raygun && packet.data && packet.data[0] && Raygun.setUser(packet.data[0].email); !(Raygun && packet.data && packet.data[0]) && Console.log(' user email not set yet for raygun ... starlet.js '); !packet.data[0].isGuest && Starlet.$html.removeClass('guest').addClass('user'); }); //Bus.ask('proximity', {'subscribe': true, 'callback': Starlet.onProximity}); //Bus.ask('offset' , {'subscribe': true, 'callback': Starlet.onOffset}); } break; case 'loaded' : Starlet.flagLoaded = true; break; case 'defined': Starlet.flagDefined = true; break; case 'covered': Starlet.flagCovered = true; break; default : break; } // all ready? if (Starlet.flagDom && Starlet.flagFramework && Starlet.flagLoaded) { !Widget.detected && Widget.detect($('body')[0]); if (Starlet.flagDefined) { if (!Bus.registered) // temporary to prevent double binds, redo sync promise system { Bus.register(); var mode = MINISHOEBOX_MODES.filter(function (term, index) { return ~Starlet.name.indexOf(term); })[0]; if (mode) { Starlet.$minishoebox .data('showing', false) .data('mode' , mode) .popover( { 'html' : true, 'placement': function () { return Starlet.$minishoebox.data('placement') || 'bottom'; }, 'content' : Widget.create('MiniShoebox', { 'calling_widget' : mode, 'session_id' : '', 'current_participants': [] }) }); $(document).on('click', [ 'html.minishoebox section#content', 'html.minishoebox header', 'html.minishoebox footer', 'html.minishoebox .modal', 'html.minishoebox .modal-backdrop', 'html.minishoebox #tint' ] .join(', '), function () { Starlet.minishoebox(); }); } $(window).on('beforeunload', function (e) { //e.stopPropagation(); //e.preventDefault(); log('unload'); //return false; Starlet.onUnload && Starlet.onUnload(); Widget.onUnload(); Bus.unregister(); }); window.onhashchange = function (e) { if (location.hash.length < 2) { location.href = e.oldURL; log(L_WARN, 'window.onhashchange', e.newURL, e.oldURL); }}; } if (Starlet.flagCovered && Starlet.synchronizer.state() !== 'resolved') { log(L_MAIN, 'Starlet.synchronizer', 'ready in ' + Starlet.loadTimer.elapsed(1)); Starlet.synchronizer.resolve(); } }; } }) .done(function () { var loadArguments = Starlet.loadArguments; log(L_MAIN, 'Starlet.onRun', loadArguments) && Starlet.onRun.apply(Starlet, loadArguments); Starlet.$spinner.fadeOut( { 'duration': 250, 'easing' : 'easeInOutCubic', 'done' : function () { Starlet.$spinner.remove(); setTimeout(function () { Bus.tell('starlets', {'action': 'update', 'states': {'state': S_READY}}); }, 50); } }); }), /** @method Starlet.tint @readonly @desc Shows or hides the mouse-blocking tint layer. @arg {Boolean} state - Whether to show or hide tint. @arg {Function} callback - Function to call when transition is complete. @returns {jQuery} {@link Starlet.$tint} */ 'tint': function (state, callback) { log(L_DEBUG, 'Starlet.tint', arguments); var self = arguments.callee, $tint = Starlet.$tint, fade = $tint[state ? 'fadeIn' : 'fadeOut']; clearTimeout(self.timeout || 0); $tint.stop(); self.timeout = setTimeout ( function () { fade.apply($tint, [{ 'duration': 0, 'easing' : 'easeOutQuint', 'done' : callback || function () {} }]); }, 10 ); if (state) { Starlet.minishoebox(); Starlet.menu(); } return Starlet.$tint; }, /** @method Starlet.dialog @readonly @desc Displays a modal dialog with custom content and configuration. @todo Return promise instead of deferred, since it needs to be turned into one to attach callbacks anyway. Only the creator of the deferred should have control of it, promise holders only "take a number". Also fix replacement behavior when called successively. @arg {String} title - Display title of modal dialog @arg {String} description - Body text of dialog. @arg {Boolean} isHTMLDescription - Whether provided description contains HTML. @arg {Boolean} isError - Whether dialog should be styled as an error. @arg {Boolean} showCancel - Whether to include a cancel button. @arg {String} okText - Text to fill confirmation button with. @returns {Object} Deferred. @see {@link Starlet.alertDialog} @see {@link Starlet.confirmDialog} @see {@link Starlet.freeformDialog} @see {@link Starlet.showError} @see {@link Starlet.modal} @see {@link http://api.jquery.com/category/deferred-object/} */ 'dialog': function (title, description, isHTMLDescription, isError, showCancel, okText) { var existingDeferred = $.Deferred(), deferred = $.Deferred(); // check for existing dialog if (Starlet.$modal.filter(':visible').length) { // hide existing Starlet.modal('hide'); // resolve after timeout setTimeout(existingDeferred.resolve, 500); } else { existingDeferred.resolve(); } existingDeferred.then(function () { Starlet.modal ( $(Templates.Render('_dialog', { 'Title' : title, 'Description' : description, 'IsHTMLDescription': isHTMLDescription, 'IsError' : isError, 'ShowCancel' : showCancel, 'OkText' : okText })) .find('.modal-footer .ok' ).click(deferred.resolve).end() .find('.modal-footer .cancel').click(deferred.reject ).end() ); // set focus to ok button so enter / esc work on this dialog setTimeout(function () { Starlet.$modal.find('.ok').focus(); }, 250); }); Starlet.$modal.on('webkitTransitionEnd', function (event) { !$(this).filter(':visible').length && Starlet.$modal.removeAttr('type'); }); return deferred; }, /** @method Starlet.alertDialog @readonly @desc Displays a modal alert dialog with custom content and configuration, but no HTML or cancel button. @todo Return promise instead of deferred, since it needs to be turned into one to attach callbacks anyway. Only the creator of the deferred should have control of it, promise holders only "take a number". @arg {String} title - Display title of modal dialog @arg {String} description - Body text of dialog. @arg {Boolean} isError - Whether dialog should be styled as an error. @arg {String} okText - Text to fill confirmation button with. @returns {Object} Deferred. @see {@link Starlet.dialog} @see {@link Starlet.confirmDialog} @see {@link Starlet.freeformDialog} @see {@link Starlet.showError} @see {@link Starlet.modal} @see {@link http://api.jquery.com/category/deferred-object/} */ 'alertDialog': function (title, description, isError, okText) { //console.log('existing dialog', this.$modal.filter(':visible').length); return this.dialog(title, description, false, isError, false, okText); }, /** @method Starlet.confirmDialog @readonly @desc Displays a modal confirmation dialog with custom content and configuration, but no HTML or error styling. @todo Return promise instead of deferred, since it needs to be turned into one to attach callbacks anyway. Only the creator of the deferred should have control of it, promise holders only "take a number". @arg {String} title - Display title of modal dialog @arg {String} description - Body text of dialog. @arg {String} okText - Text to fill confirmation button with. @returns {Object} Deferred. @see {@link Starlet.dialog} @see {@link Starlet.alertDialog} @see {@link Starlet.freeformDialog} @see {@link Starlet.showError} @see {@link Starlet.modal} @see {@link http://api.jquery.com/category/deferred-object/} */ 'confirmDialog': function (title, description, okText) { return this.dialog(title, description, false, false, true, okText); }, /** @method Starlet.freeformDialog @readonly @desc Displays a modal freeform dialog with completely custom content. @arg {String} content - HTML content of dialog. @arg {String} customClass - Unique classname allowing for custom styling. @returns {jQuery} {@link Starlet.$modal} @see {@link Starlet.dialog} @see {@link Starlet.alertDialog} @see {@link Starlet.confirmDialog} @see {@link Starlet.showError} @see {@link Starlet.modal} */ 'freeformDialog': function (content, customClass) { return Starlet.modal($(Templates.Render('_dialogFreeform', { Content: content, Class: customClass }))); }, /** @method Starlet.showError @readonly @desc Displays a modal error dialog and logs a separate message and object for debugging. @arg {String} userErrorMessage - Message to be displayed to the user. @arg {String} logErrorMessage - Message to log. @arg {String} logErrorObject - Object to log. @returns {Boolean} true @see {@link Starlet.dialog} @see {@link Starlet.alertDialog} @see {@link Starlet.confirmDialog} @see {@link Starlet.freeformDialog} @see {@link Starlet.modal} */ 'showError': function (userErrorMessage, logErrorMessage, logErrorObject) { // show alert if we have a user message userErrorMessage && Starlet.alertDialog('Error', userErrorMessage, true); // if we have a user message, but no log message, log the user message userErrorMessage && !logErrorMessage && (logErrorMessage = userErrorMessage); // log error if we have something to log (userErrorMessage || logErrorMessage || logErrorObject) && log(L_ERROR, logErrorMessage, logErrorObject) && console.log(logErrorMessage, logErrorObject); return true; }, /** @method Starlet.tearoff @readonly @chainable @desc Toggles whether the Starlet's WebView is in the main application window, does not cause a reload. @arg {Boolean} [mode] - Whether to take WebView out of the main application window or restore it. Toggles if omitted. @returns {Object} {@link Starlet} @see {@link Starlet.states.tearoff} @see {@link OrionTearoff.create} @see {@link OrionTearoff.remove} */ 'tearoff': function (mode) { mode = arguments.length ? !!mode : !Starlet.states.tearoff; if (Starlet.states.tearoff !== mode) { Starlet.states.tearoff = mode; Starlet.states.main = !mode; OrionTearoff[mode ? 'create' : 'remove'](); Bus.tell('starlets', {'action': 'update', 'states': {'tearoff': mode, 'main': !mode}}); } return Starlet; }, /** @method Starlet.repaint @readonly @chainable @desc Momentarily alters the DOM in a way that causes a repaint of the entire WebView. @returns {Object} {@link Starlet} */ 'repaint': function () { Starlet.$html.addClass ('repaint'); fork(function () { Starlet.$html.removeClass('repaint'); }); //$('<style></style>').appendTo(document.body).remove(); // didn't work return Starlet; } }; /* READIES */ function on_load() { Starlet.synchronizer.notify('framework', arguments); }; $(function () { Starlet.synchronizer.notify('dom'); });