diff --git a/apps/communications/contacts/fb_link.html b/apps/communications/contacts/fb_link.html index 7d57dc932e18..9b9cc0857a6f 100644 --- a/apps/communications/contacts/fb_link.html +++ b/apps/communications/contacts/fb_link.html @@ -16,7 +16,9 @@ - + + + diff --git a/apps/communications/contacts/js/fb/fb_link.js b/apps/communications/contacts/js/fb/fb_link.js index a5cb56a48d61..2251598f56d9 100644 --- a/apps/communications/contacts/js/fb/fb_link.js +++ b/apps/communications/contacts/js/fb/fb_link.js @@ -35,18 +35,18 @@ if (!fb.link) { // Conditions var MAIL_COND = ['strpos(email, ' , "'", null, "'", ') >= 0']; var CELL_COND = ['strpos(cell, ' , "'", null, "'", ') >= 0']; - var NAME_COND = ['strpos(lower(name), ' , "'", null, - "'", ') >= 0']; - // Conditions over first name and last name (second query) - var FIRST_NAME_COND = ['strpos(lower(first_name), ' , "'", null, - "'", ') >= 0']; - var LAST_NAME_COND = ['strpos(lower(last_name), ' , "'", null, - "'", ') >= 0']; + var ALL_QUERY = ['SELECT uid, name, last_name, first_name,', + ' middle_name, email from user ', + ' WHERE uid IN (SELECT uid1 FROM friend WHERE uid2=me()) ', + ' ORDER BY name' + ]; - var ALL_QUERY = ['SELECT uid, name, email from user ', - ' WHERE uid IN (SELECT uid1 FROM friend WHERE uid2=me()) ', - ' ORDER BY name']; + var SEARCH_ACCENTS_FIELDS = { + 'last_name': 'familyName', + 'first_name': 'givenName', + 'middle_name': 'givenName' + }; var friendsList; var viewButton = document.querySelector('#view-all'); @@ -65,102 +65,67 @@ if (!fb.link) { function buildQuery(contact) { var filter = []; - if (contact.name && contact.name.length > 0 && - contact.name[0].length > 0) { - // First the name condition is put there - NAME_COND[2] = contact.name[0].trim().toLowerCase(); - } - else { - // The condition will be false by definition - NAME_COND[2] = 'A'; - } - - filter.push(NAME_COND.join('')); - if (contact.tel && contact.tel.length > 0) { contact.tel.forEach(function(tel) { - filter.push(' OR '); CELL_COND[2] = tel.value.trim(); filter.push(CELL_COND.join('')); + filter.push(' OR '); }); } if (contact.email && contact.email.length > 0) { contact.email.forEach(function(email) { - filter.push(' OR '); MAIL_COND[2] = email.value.trim(); filter.push(MAIL_COND.join('')); + filter.push(' OR '); }); } - SEARCH_QUERY[3] = filter.join(''); - - return SEARCH_QUERY.join(''); - } - - // Builds the second query (name-based) for findinding a linking contact - function buildQueryNames(contact) { - var filter = []; - - if (contact.givenName && contact.givenName.length > 0 && - contact.givenName[0].length > 0) { - // First the name condition is put there - FIRST_NAME_COND[2] = contact.givenName[0].trim().toLowerCase(); - } - else { - // The condition will be false by definition - FIRST_NAME_COND[2] = 'A'; + // Remove the last OR + if (filter.length > 0) { + filter[filter.length - 1] = null; } + var filterStr = filter.join(''); - filter.push(FIRST_NAME_COND.join('')); - filter.push(' OR '); - - if (contact.familyName && contact.familyName.length > 0 && - contact.familyName[0].length > 0) { - // First the name condition is put there - LAST_NAME_COND[2] = contact.familyName[0].trim().toLowerCase(); + var out; + if (filterStr) { + SEARCH_QUERY[3] = filterStr; + out = SEARCH_QUERY.join(''); } else { - // The condition will be false by definition - LAST_NAME_COND[2] = 'A'; + out = null; } - filter.push(LAST_NAME_COND.join('')); - - SEARCH_QUERY[3] = filter.join(''); - - var out = SEARCH_QUERY.join(''); - return out; } - // entry point for obtaining a remote proposal link.getRemoteProposal = function(acc_tk, contid) { var cid = contid || contactid; var req = fb.utils.getContactData(cid); - req.onsuccess = function() { if (req.result) { cdata = req.result; numQueries = 1; currentRecommendation = null; - doGetRemoteProposal(acc_tk, cdata, buildQuery(cdata)); + var query = buildQuery(cdata); + // Check whether we have enough info for the first query + // otherwise launch the getAll query + if (query === null) { + getRemoteProposalAll(acc_tk); + return; + } + doGetRemoteProposal(acc_tk, cdata, query); } else { throw ('FB: Contact to be linked not found: ', cid); } - } + }; req.onerror = function() { throw ('FB: Error while retrieving contact data: ', cid); - } - } - - function getRemoteProposalByNames(acc_tk, contact) { - numQueries++; - doGetRemoteProposal(acc_tk, cdata, buildQueryNames(contact)); - } + }; + }; function getRemoteProposalAll(acc_tk) { numQueries++; @@ -226,7 +191,7 @@ if (!fb.link) { // Obtains a linking proposal to be shown to the user link.getProposal = function(cid, acc_tk) { link.getRemoteProposal(acc_tk, cid); - } + }; // Executed when the server response is available link.proposalReady = function(response) { @@ -247,23 +212,59 @@ if (!fb.link) { } if (response.data.length === 0 && numQueries === 1) { - getRemoteProposalByNames(access_token, cdata); - } else if (response.data.length === 0 && numQueries === 2) { getRemoteProposalAll(access_token); - } else { + } + else { var data = response.data; currentRecommendation = data; - var numFriendsProposed = response.data.length; + var sortedByName = []; + var numFriendsProposed = data.length; + var searchAccentsArrays = {}; + var index = 0; data.forEach(function(item) { if (!item.email) { item.email = ''; } + // Only do this if we need to prepare the search accents phase + if (numQueries === 2) { + // Saving the original order + sortedByName.push(item); + // Populate the arrays for doing the accents related search + Object.keys(SEARCH_ACCENTS_FIELDS).forEach(function(field) { + searchAccentsArrays[field] = searchAccentsArrays[field] || []; + if (item[field]) { + // The different words for each item + var words = item[field].split(/[ ]+/); + // The whole word itself is added + words.push(item[field]); + words.forEach(function(word) { + var obj = { + originalIndex: index + }; + obj[field] = utils.text.normalize(word).toLowerCase(); + searchAccentsArrays[field].push(obj); + }); + } + }); + index++; + } }); - if (numQueries === 3) { - mainSection.classList.add('no-proposal'); - numFriendsProposed = 0; + if (numQueries === 2) { + var accentsProposal = searchAccents(searchAccentsArrays, cdata); + if (accentsProposal.length === 0) { + data = sortedByName; + mainSection.classList.add('no-proposal'); + numFriendsProposed = 0; + } + else { + currentRecommendation = []; + accentsProposal.forEach(function(proposalIndex) { + currentRecommendation.push(sortedByName[proposalIndex]); + }); + numFriendsProposed = currentRecommendation.length; + } } else { viewButton.textContent = _('viewAll'); viewButton.onclick = UI.viewAllFriends; @@ -273,11 +274,66 @@ if (!fb.link) { numFriends: numFriendsProposed }); - utils.templates.append('#friends-list', data); + utils.templates.append('#friends-list', currentRecommendation); imgLoader.reload(); Curtain.hide(sendReadyEvent); } + }; + + + // This function needs to be called because link proposals through FB Query + // do not work properly with words with special characters (bug 796714) + function searchAccents(searchArrays, contactData) { + var out = []; + var searchFields = Object.keys(SEARCH_ACCENTS_FIELDS); + + function compareItems(target, item) { + var out; + + if (typeof target === 'string') { + out = target.localeCompare(item); + if (out !== 0) { + if (item.indexOf(target) === 0) { + out = 0; + } + } + } + + return out; + } // compareItems Function + + searchFields.forEach(function(searchField) { + // The array is ordered according to the search field + // this enables an efficient binary search + var searchArray = searchArrays[searchField].sort(function(a, b) { + return a[searchField].localeCompare(b[searchField]); + }); + + var fieldToSearch = contactData[SEARCH_ACCENTS_FIELDS[searchField]]; + if (fieldToSearch && fieldToSearch[0]) { + // Splitting in the different words + var dataToSearch = fieldToSearch[0].trim().split(/[ ]+/); + + dataToSearch.forEach(function(aData) { + var targetString = utils.text.normalize(aData).toLowerCase(); + var searchResult = utils.binarySearch(targetString, searchArray, { + arrayField: searchField, + compareFunction: compareItems + }); + + searchResult.forEach(function(aResult) { + var candidate = searchArray[aResult].originalIndex; + // Avoiding to show two times the same result element + if (out.indexOf(candidate) === -1) { + out.push(candidate); + } + }); + }); + } + }); + + return out; } function sendReadyEvent() { @@ -289,16 +345,16 @@ if (!fb.link) { function setCurtainHandlersErrorProposal() { Curtain.oncancel = function() { cancelCb(true); - } + }; Curtain.onretry = function getRemoteProposal() { Curtain.oncancel = function() { cancelCb(true); - } + }; Curtain.show('wait', state); link.getRemoteProposal(access_token, contactid); - } + }; } link.baseHandler = function(type) { @@ -318,20 +374,20 @@ if (!fb.link) { } } }); - } + }; Curtain.oncancel = Curtain.hide; } Curtain.show(type, state); - } + }; link.timeoutHandler = function() { link.baseHandler('timeout'); - } + }; link.errorHandler = function() { link.baseHandler('error'); - } + }; /** * Clears the list of contacts @@ -363,7 +419,7 @@ if (!fb.link) { }); clearList(); - + var fragment = document.createDocumentFragment(); utils.templates.append(friendsList, response.data, fragment); friendsList.appendChild(fragment); @@ -386,7 +442,7 @@ if (!fb.link) { } Curtain.show('error', 'friends'); } - } + }; function setCurtainHandlers() { Curtain.oncancel = function curtain_cancel() { @@ -412,7 +468,7 @@ if (!fb.link) { else { link.getProposal(contactId, acc_tk); } - } + }; function retryOnErrorCb() { UI.selected({ @@ -433,7 +489,7 @@ if (!fb.link) { type: 'token_error', data: '' }, fb.CONTACTS_APP_ORIGIN); - } + }; window.asyncStorage.removeItem(fb.utils.TOKEN_DATA_KEY, cb); } @@ -464,7 +520,7 @@ if (!fb.link) { Curtain.hide(function() { notifyParent(importReq.result); }); - } + }; importReq.onerror = function(e) { var error = e.target.error; @@ -478,21 +534,21 @@ if (!fb.link) { Curtain.onretry = handleTokenError; } Curtain.show('error', 'linking'); - } + }; importReq.ontimeout = function() { link.baseHandler('timeout'); - } + }; } - } + }; req.onerror = function() { window.console.error('FB: Error while importing friend data'); Curtain.oncancel = Curtain.hide; Curtain.onretry = retryOnErrorCb; Curtain.show('error', 'linking'); - } - } + }; + }; UI.end = function(event) { @@ -502,7 +558,7 @@ if (!fb.link) { }; parent.postMessage(msg, fb.CONTACTS_APP_ORIGIN); - } + }; function notifyParent(data) { var msg = { @@ -525,7 +581,7 @@ if (!fb.link) { } return false; - } + }; UI.viewRecommended = function(event) { // event.target === viewButton @@ -537,7 +593,7 @@ if (!fb.link) { imgLoader.reload(); return false; - } + }; })(document); } diff --git a/apps/communications/contacts/js/utilities/binary_search.js b/apps/communications/contacts/js/utilities/binary_search.js new file mode 100644 index 000000000000..2a303665f667 --- /dev/null +++ b/apps/communications/contacts/js/utilities/binary_search.js @@ -0,0 +1,124 @@ +'use strict'; + +var utils = this.utils || {}; + +/** + * This function performs a binary search over an already sorted array + * target is the target item to search for + * array is the sorted array + * options is an optional object which may contain + * the start and end position (from, to) + * an optional arrayField which indicates the object property that contains the + * comparable item and transform and compare functions + * + * Returns an array with the positions on which the target item was found + * + */ +utils.binarySearch = function(target, array, options) { + var arrayField = options.arrayField, + transformFunction = options.transformFunction, + compareFunction = options.compareFunction; + + // Obtains the comparable item by transforming if necessary + function getItem(array, index) { + var item = array[index]; + if (arrayField) { + item = item[arrayField]; + if (typeof transformFunction === 'function') { + item = transformFunction(item); + } + } + return item; + } + + // Compares the target with an array item + function compare(target, item) { + var out; + if (typeof compareFunction === 'function') { + out = compareFunction(target, item); + } + else { + if (typeof target === 'string') { + out = target.localeCompare(item); + } + else { + out = target.toString().localeCompare(item); + } + } + + return out; + } + + var from = options.from; + if (typeof from === 'undefined') { + from = 0; + } + var to = options.to; + if (typeof to === 'undefined') { + to = array.length - 1; + } + + if (to < from) { + // Not found + return []; + } + + var middleIndex = Math.floor((to - from) / 2); + var item = getItem(array, from + middleIndex); + + var compareResult = compare(target, item); + + if (compareResult === 0) { + // Once a result is found let's iterate in both directions to get the rest + // Just in case there are more than one result + var results = [from + middleIndex]; + + var next = from + middleIndex + 1; + var finish = false; + while (next <= (array.length - 1) && !finish) { + var item = getItem(array, next); + + if (compare(target, item) === 0) { + results.push(next); + } + else { + finish = true; + } + next++; + } + + finish = false; + next = from + middleIndex - 1; + + while (next >= 0 && !finish) { + var item = getItem(array, next); + + if (compare(target, item) === 0) { + results.push(next); + } + else { + finish = true; + } + next--; + } + return results; + } + else if (compareResult < 0) { + return utils.binarySearch(target, array, { + from: from, + to: to - middleIndex - 1, + arrayField: arrayField, + transformFunction: transformFunction, + compareFunction: compareFunction + }); + } + else { + return utils.binarySearch(target, array, { + from: from + middleIndex + 1, + to: to, + arrayField: arrayField, + transformFunction: transformFunction, + compareFunction: compareFunction + }); + } +}; diff --git a/apps/costcontrol/js/app.js b/apps/costcontrol/js/app.js index 2961a32ed561..17062c22d61c 100644 --- a/apps/costcontrol/js/app.js +++ b/apps/costcontrol/js/app.js @@ -11,24 +11,26 @@ var CostControlApp = (function() { 'use strict'; + // XXX: This is the point of entry, check common.js for more info + waitForDOMAndMessageHandler(window, onReady); var costcontrol, initialized = false; - window.addEventListener('DOMContentLoaded', function _onDOMReady() { + function onReady() { var mobileConnection = window.navigator.mozMobileConnection; // SIM is not ready if (mobileConnection.cardState !== 'ready') { debug('SIM not ready:', mobileConnection.cardState); - mobileConnection.oniccinfochange = _onDOMReady; + mobileConnection.oniccinfochange = onReady; // SIM is ready } else { mobileConnection.oniccinfochange = undefined; - _startApp(); + startApp(); } - }); + } - function _startApp() { + function startApp() { checkSIMChange(function _onSIMChecked() { CostControl.getInstance(function _onCostControlReady(instance) { if (ConfigManager.option('fte')) { @@ -104,7 +106,6 @@ var CostControlApp = (function() { initialized = true; } - var currentMode; function updateUI() { ConfigManager.requestSettings(function _onSettings(settings) { diff --git a/apps/costcontrol/js/common.js b/apps/costcontrol/js/common.js index 0026e0be9d66..1a8c70020bc8 100644 --- a/apps/costcontrol/js/common.js +++ b/apps/costcontrol/js/common.js @@ -11,7 +11,8 @@ function checkSIMChange(callback) { } ConfigManager.requestSettings(function _onSettings(settings) { if (settings.nextReset) { - setNextReset(settings.nextReset); + setNextReset(settings.nextReset, callback); + return; } if (callback) { @@ -21,21 +22,41 @@ function checkSIMChange(callback) { }); } +// Waits for DOMContentLoaded and messagehandlerready, then call the callback +function waitForDOMAndMessageHandler(window, callback) { + var remainingSteps = 2; + function checkReady(evt) { + debug(evt.type, 'event received!'); + remainingSteps--; + + // Once all events are received, execute the callback + if (!remainingSteps) { + window.removeEventListener('DOMContentLoaded', checkReady); + window.removeEventListener('messagehandlerready', checkReady); + debug('DOMContentLoaded and messagehandlerready received. Starting'); + callback(); + } + } + + window.addEventListener('DOMContentLoaded', checkReady); + window.addEventListener('messagehandlerready', checkReady); +} + function addAlarmTimeout(type, delay) { var proxy = document.getElementById('message-handler').contentWindow; return proxy.addAlarmTimeout(type, delay); } -function setNextReset(when) { +function setNextReset(when, callback) { var proxy = document.getElementById('message-handler'); - return proxy ? proxy.contentWindow.setNextReset(when) : setNextReset(when); + return proxy ? proxy.contentWindow.setNextReset(when, callback) : + setNextReset(when, callback); } // Next automatic reset date based on user preferences -function updateNextReset(trackingPeriod, value) { +function updateNextReset(trackingPeriod, value, callback) { if (trackingPeriod === 'never') { - setNextReset(null); // remove oldAlarm - debug('Automatic reset disabled'); + setNextReset(null, callback); // remove any alarm return; } @@ -63,10 +84,11 @@ function updateNextReset(trackingPeriod, value) { daysToTarget = 7 + daysToTarget; nextReset = new Date(); nextReset.setTime(nextReset.getTime() + oneDay * daysToTarget); + toMidnight(nextReset); } // remove oldAlarm and set the new one - setNextReset(nextReset); + setNextReset(nextReset, callback); } function resetData() { diff --git a/apps/costcontrol/js/fte.js b/apps/costcontrol/js/fte.js index 0194a52a877c..aa6025c236ce 100644 --- a/apps/costcontrol/js/fte.js +++ b/apps/costcontrol/js/fte.js @@ -8,6 +8,10 @@ var costcontrol; var hasSim = true; + + // Fallback from some values, just in case they are missed from configuration + var DEFAULT_LOW_LIMIT_THRESHOLD = 3; + var defaultLowLimitThreshold = DEFAULT_LOW_LIMIT_THRESHOLD; window.addEventListener('DOMContentLoaded', function _onDOMReady() { var mobileConnection = window.navigator.mozMobileConnection; var stepsLeft = 2; @@ -46,11 +50,17 @@ ConfigManager.requestAll(function _onSettings(configuration, settings) { wizard = document.getElementById('firsttime-view'); vmanager = new ViewManager(); + + // Getting some values from config + if (configuration && configuration.default_low_limit_threshold) { + defaultLowLimitThreshold = configuration.default_low_limit_threshold; + } + AutoSettings.addType('data-limit', dataLimitConfigurer); // Currency is set by config as well - if (configuration && configuration.credit - && configuration.credit.currency) { + if (configuration && configuration.credit && + configuration.credit.currency) { document.getElementById('currency').textContent = configuration.credit.currency; @@ -118,7 +128,12 @@ currentTrack = ['step-1', 'step-2', 'prepaid-step-2', 'prepaid-step-3']; AutoSettings.initialize(ConfigManager, vmanager, '#prepaid-step-2'); AutoSettings.initialize(ConfigManager, vmanager, '#prepaid-step-3'); - ConfigManager.setOption({ dataLimitValue: 40, dataLimitUnit: 'MB' }); + + ConfigManager.setOption({ + dataLimitValue: 40, + dataLimitUnit: 'MB', + lowLimit: true, + lowLimitThreshold: defaultLowLimitThreshold }); } else if (evt.target.value === 'postpaid') { currentTrack = ['step-1', 'step-2', 'postpaid-step-2', 'postpaid-step-3']; AutoSettings.initialize(ConfigManager, vmanager, '#postpaid-step-2'); @@ -199,11 +214,15 @@ step -= 1; } - function onFinish() { + function onFinish(evt) { + evt.target.disabled = true; ConfigManager.requestSettings(function _onSettings(settings) { - updateNextReset(settings.trackingPeriod, settings.resetTime); ConfigManager.setOption({ fte: false }, function _returnToApp() { - window.location = 'index.html'; + updateNextReset(settings.trackingPeriod, settings.resetTime, + function _returnToTheApplication() { + window.location = 'index.html'; + } + ); }); }); } diff --git a/apps/costcontrol/js/message_handler.js b/apps/costcontrol/js/message_handler.js index 8f9df107b81d..2af3eb62abf7 100644 --- a/apps/costcontrol/js/message_handler.js +++ b/apps/costcontrol/js/message_handler.js @@ -6,6 +6,12 @@ function inStandAloneMode() { return window.parent.location.pathname === '/message_handler.html'; } + + // Redirect global objects to parent versions to avoid conflicts + if (!inStandAloneMode()) { + ConfigManager = window.parent.ConfigManager; + } + // XXX: This case implies that message handler triggered by system // (inStandAlone check) has replaced CC application (history's length check). // @@ -15,7 +21,7 @@ // inside an iframe (no standalone mode), all the messages should be attended // so we can conclude **there is nothing to do**. if (inStandAloneMode() && window.history.length > 1) { - debug('Nothing to do, closing...') + debug('Nothing to do, closing...'); window.history.back(); } @@ -46,30 +52,46 @@ // XXX: Remove from here when this is solved // https://bugzilla.mozilla.org/show_bug.cgi?id=800431 - function setNextReset(when) { + function setNextReset(when, callback) { + + // XXX: This is not part of configuration by SIM so we bypass ConfigManager asyncStorage.getItem('nextResetAlarm', function(id) { + // There is already an alarm, remove it debug('Current nextResetAlarm', id + '.', id ? 'Removing.' : ''); - if (id) + if (id) { navigator.mozAlarms.remove(id); + } + // If no when, disable alarms passing null if (!when) { - ConfigManager.setOption({ nextReset: null }); + debug('Automatic reset disabled'); + updateResetAttributes(null, null, callback); return; } - var request = navigator.mozAlarms.add(when, 'ignoreTimezone', - {type: 'nextReset' }); + // If when is provided, request an alarm an set the new values + var alarms = navigator.mozAlarms; + var request = alarms.add(when, 'ignoreTimezone', {type: 'nextReset' }); request.onsuccess = function _onSuccess() { - ConfigManager.setOption({ nextReset: when }, function _sync() { - localStorage['sync'] = 'nextReset#' + Math.random(); - }); debug('Setting nextResetAlarm', request.result, 'to', when); - asyncStorage.setItem('nextResetAlarm', request.result); + updateResetAttributes(request.result, when, callback); }; }); - }; + } window.setNextReset = setNextReset; + // Update the nextResetAlarm and nextReset values and request for + // synchronization. + function updateResetAttributes(alarmId, date, callback) { + asyncStorage.setItem('nextResetAlarm', alarmId, function _updateOption() { + ConfigManager.setOption({ nextReset: date }, function _sync() { + localStorage['sync'] = 'nextReset#' + Math.random(); + if (callback) + callback(); + }); + }); + } + // Register in standalone or for application if (inStandAloneMode() || inApplicationMode()) { debug('Installing handlers'); @@ -253,4 +275,8 @@ ); } + + // Notify message handler is ready + var readyEvent = new CustomEvent('messagehandlerready'); + window.parent.dispatchEvent(readyEvent); }()); diff --git a/apps/costcontrol/js/views/balance.js b/apps/costcontrol/js/views/balance.js index 7bcebc1ecb13..a46f5eb3d2ea 100644 --- a/apps/costcontrol/js/views/balance.js +++ b/apps/costcontrol/js/views/balance.js @@ -231,7 +231,6 @@ var BalanceTab = (function() { setBalanceMode(status === 'error' ? 'warning' : 'updating'); if (status === 'error') setErrors(status.details); - debug(settings); updateBalance(balance, settings.lowLimit && settings.lowLimitThreshold); }); diff --git a/apps/costcontrol/js/widget.js b/apps/costcontrol/js/widget.js index 30cf0d6407dd..8cdc279c452d 100644 --- a/apps/costcontrol/js/widget.js +++ b/apps/costcontrol/js/widget.js @@ -12,30 +12,33 @@ 'use strict'; + // XXX: This is the point of entry, check common.js for more info + waitForDOMAndMessageHandler(window, onReady); + var costcontrol; var hasSim = true; - window.addEventListener('DOMContentLoaded', function _onDOMReady() { + function onReady() { var mobileConnection = window.navigator.mozMobileConnection; // No SIM if (!mobileConnection || mobileConnection.cardState === 'absent') { hasSim = false; - _startWidget(); + startWidget(); // SIM is not ready } else if (mobileConnection.cardState !== 'ready') { debug('SIM not ready:', mobileConnection.cardState); - mobileConnection.oniccinfochange = _onDOMReady; + mobileConnection.oniccinfochange = onReady; // SIM is ready } else { debug('SIM ready. ICCID:', mobileConnection.iccInfo.iccid); mobileConnection.oniccinfochange = undefined; - _startWidget(); + startWidget(); } - }); + }; - function _startWidget() { + function startWidget() { checkSIMChange(function _onSIMChecked() { CostControl.getInstance(function _onCostControlReady(instance) { costcontrol = instance; @@ -175,7 +178,7 @@ setupFte(configuration.provider, mode); return; } else { - fte.setAttribute('aria-hidden', true) + fte.setAttribute('aria-hidden', true); } // Always data usage diff --git a/apps/homescreen/everything.me/config/config.js b/apps/homescreen/everything.me/config/config.js index 465a7708dd56..92fa678be1b7 100644 --- a/apps/homescreen/everything.me/config/config.js +++ b/apps/homescreen/everything.me/config/config.js @@ -60,7 +60,6 @@ Evme.__config = { } } }, - "infoLogger": false, "maxHistoryEntries": "10", "iconsGroupSettings": [ { diff --git a/apps/homescreen/everything.me/js/Brain.js b/apps/homescreen/everything.me/js/Brain.js index 26b85469ff19..e761ace2e0f4 100644 --- a/apps/homescreen/everything.me/js/Brain.js +++ b/apps/homescreen/everything.me/js/Brain.js @@ -7,7 +7,6 @@ Evme.Brain = new function Evme_Brain() { var self = this, Brain = this, _config = {}, - logger = null, elContainer = null, QUERIES_TO_NOT_CACHE = "", DEFAULT_NUMBER_OF_APPS_TO_LOAD = 16, @@ -70,8 +69,6 @@ Evme.Brain = new function Evme_Brain() { DISPLAY_INSTALLED_APPS = _config.displayInstalledApps; - logger = _config && _config.logger || console; - ICON_SIZE = Evme.Utils.sendToOS(Evme.Utils.OSMessages.GET_ICON_SIZE); }; @@ -106,16 +103,13 @@ Evme.Brain = new function Evme_Brain() { * @_event (string) : the event that the class sent * @_data (object) : data sent with the event */ - function catchCallback(_class, _event, _data) { - logger.debug(_class + "." + _event + "(", (_data || ""), ")"); - - Evme.Utils.log(_class + '.' + _event); + function catchCallback(_class, _event, _data) { + Evme.Utils.log('Callback: ' + _class + '.' + _event); try { self[_class] && self[_class][_event] && self[_class][_event](_data || {}); } catch(ex){ Evme.Utils.log('CB Error! ' + ex.message); - logger.error(ex); } } @@ -631,6 +625,11 @@ Evme.Brain = new function Evme_Brain() { Evme.Banner.show('app-install-success', { 'name': data.data.name }); + + Evme.EventHandler.trigger("App", "addToHomeScreen", { + "id": data.data.id, + "name": data.data.name + }); }); }; @@ -682,6 +681,7 @@ Evme.Brain = new function Evme_Brain() { "favUrl": data.app.getFavLink(), "name": data.data.name, "id": data.appId, + "appType": data.data.appType || "cloud", "query": Searcher.getDisplayedQuery(), "source": Searcher.getDisplayedSource(), "icon": data.data.icon, @@ -689,6 +689,7 @@ Evme.Brain = new function Evme_Brain() { }; var elApp = data.el, + appGridPosition = data.app.getPositionOnGrid(), appBounds = elApp.getBoundingClientRect(), elAppsList = elApp.parentNode.parentNode, @@ -703,6 +704,12 @@ Evme.Brain = new function Evme_Brain() { "left": (appsListBounds.width - appBounds.width)/2 }; + // update analytics data + loadingAppAnalyticsData.rowIndex = appGridPosition.row; + loadingAppAnalyticsData.colIndex = appGridPosition.col; + loadingAppAnalyticsData.totalRows = appGridPosition.rows; + loadingAppAnalyticsData.totalCols = appGridPosition.cols; + Evme.$remove("#loading-app"); var elPseudo = Evme.$create('li', {'class': "inplace", 'id': "loading-app"}, loadingApp.getCurrentHtml()), @@ -1559,6 +1566,7 @@ Evme.Brain = new function Evme_Brain() { 'name': name, 'installed': true, 'appUrl': app.origin, + 'appType': app.isBookmark ? 'bookmark' : 'installed', 'preferences': '', 'icon': Evme.Utils.sendToOS(Evme.Utils.OSMessages.GET_APP_ICON, app), 'requiresLocation': false, diff --git a/apps/homescreen/everything.me/js/Core.js b/apps/homescreen/everything.me/js/Core.js index 319722d97c4e..c065ec590f93 100644 --- a/apps/homescreen/everything.me/js/Core.js +++ b/apps/homescreen/everything.me/js/Core.js @@ -1,5 +1,5 @@ window.Evme = new function Evme_Core() { - var NAME = "Core", self = this, logger, + var NAME = "Core", self = this, recalculateHeightRetries = 1, TIMEOUT_BEFORE_INIT_SESSION = "FROM CONFIG", OPACITY_CHANGE_DURATION = 300, @@ -10,8 +10,6 @@ window.Evme = new function Evme_Core() { this.init = function init() { data = Evme.__config; - logger = (typeof Logger !== "undefined") ? new Logger() : console; - var apiHost = Evme.Utils.getUrlParam("apiHost") || data.apiHost; apiHost && Evme.api.setHost(apiHost); @@ -19,7 +17,6 @@ window.Evme = new function Evme_Core() { Evme.Brain.init({ "numberOfAppsToLoad": data.numberOfAppsToLoad, - "logger": logger, "minimumLettersForSearch": data.minimumLettersForSearch, "timeBeforeAllowingDialogsRemoval": data.timeBeforeAllowingDialogsRemoval, "tips": data.tips, @@ -140,7 +137,6 @@ window.Evme = new function Evme_Core() { Evme.Analytics.init({ "config": data.analytics, - "logger": logger, "namespace": Evme, "DoATAPI": Evme.DoATAPI, "getCurrentAppsRowsCols": Evme.Apps.getCurrentRowsCols, diff --git a/apps/homescreen/everything.me/js/api/DoATAPI.js b/apps/homescreen/everything.me/js/api/DoATAPI.js index 2c0a6bc29255..4a052fc826b0 100644 --- a/apps/homescreen/everything.me/js/api/DoATAPI.js +++ b/apps/homescreen/everything.me/js/api/DoATAPI.js @@ -23,6 +23,11 @@ Evme.DoATAPI = new function Evme_DoATAPI() { requestsToPerformOnOnline = [], sessionInitRequest = null, + // here we will save the actual params to pass + savedParamsToPass = {}, + // which param to pass from normal requests to stats and logs + PARAM_TO_PASS_FROM_REQUEST_TO_STATS = "requestId", + requestsToCache = { "Search.apps": true, "Search.bgimage": true, @@ -37,6 +42,14 @@ Evme.DoATAPI = new function Evme_DoATAPI() { doesntNeedSession = { "Session.init": true, "Search.trending": true + }, + + /* + * config of params to pass from requests to reports + * "Search.apps": ["appClick", "returnFromApp"] + */ + paramsToPassBetweenRequests = { + "Search.apps": ["appClick", "loadMore", "addToHomeScreen"] }; this.ERROR_CODES = { @@ -395,6 +408,7 @@ Evme.DoATAPI = new function Evme_DoATAPI() { methodArr.forEach(function oggerMethodIteration(method){ self[method] = function report(options, callback){ options = addGlobals(options); + options = addSavedParams(options); return request({ "methodNamespace": "Logger", @@ -408,6 +422,7 @@ Evme.DoATAPI = new function Evme_DoATAPI() { this.report = function report(options, callback) { options = addGlobals(options); + options = addSavedParams(options); return request({ "methodNamespace": "Stats", @@ -430,6 +445,46 @@ Evme.DoATAPI = new function Evme_DoATAPI() { return options; } + // add the saved params from earlier responses to the event's data + function addSavedParams(options) { + var events = options.data; + if (events) { + try { + events = JSON.parse(events); + } catch(ex) { + events = null; + } + + if (events && typeof events === "object") { + for (var i=0,e; e=events[i++];) { + var savedValue = savedParamsToPass[e.userEvent]; + if (savedValue) { + e[PARAM_TO_PASS_FROM_REQUEST_TO_STATS] = savedValue; + } + } + + options.data = JSON.stringify(events); + } + } + return options; + } + + // takes a method's response, and saves data according to paramsToPassBetweenRequests + function saveParamFromRequest(method, response) { + var events = paramsToPassBetweenRequests[method], + paramValue = response && response[PARAM_TO_PASS_FROM_REQUEST_TO_STATS]; + + if (!paramValue || !events) { + return; + } + + // this will create a map of userEvents => requestId + // to be added to the actual event request later + for (var i=0,ev; ev=events[i++];) { + savedParamsToPass[ev] = paramValue; + } + } + this.searchLocations = function searchLocations(options, callback) { !options && (options = {}); @@ -718,6 +773,7 @@ Evme.DoATAPI = new function Evme_DoATAPI() { if (!ignoreCache) { var fromCache = getFromCache(cacheKey); if (fromCache) { + saveParamFromRequest(methodNamespace + '.' + methodName, fromCache); callback && window.setTimeout(function() { callback(fromCache); }, 10); @@ -913,6 +969,8 @@ Evme.DoATAPI = new function Evme_DoATAPI() { } function cbSuccess(methodNamespace, method, url, params, retryNumber, data, requestDuration) { + saveParamFromRequest(methodNamespace + '.' + method, data); + Evme.EventHandler.trigger(NAME, "success", { "method": methodNamespace + "/" + method, "params": params, diff --git a/apps/homescreen/everything.me/js/developer/log4js2.js b/apps/homescreen/everything.me/js/developer/log4js2.js deleted file mode 100644 index b2cf87c0fd6d..000000000000 --- a/apps/homescreen/everything.me/js/developer/log4js2.js +++ /dev/null @@ -1,372 +0,0 @@ -/** - * @fileoverview Javascript Logger (in the spirit of log4j) - * This library is designed to make the writing and debugging - * of javascript code easier, by allowing the programmer to perform - * debug or log output at any place in their code. This supports - * the concept of different levels of logging (debug < info < warn < error < fatal << none) - * as well as different log outputs. Three log outputs are included, but you can - * add your own. The included log outputs are {@link Log#writeLogger}, - * {@link Log#alertLogger}, and {@link Log#popupLogger}. For debugging on Safari, - * the log ouput {@link Log#consoleLogger} is also included. To turn off debugging - * but still leave the logger calls in your script, use the log level {@link Log#NONE}. - * - * Example usage: - *
- * <html> - * <head> - * <script src="log4js.js" type="text/javascript"></script> - * </head> - * <body> - * Log4JS test...<hr/> - * <script> - * // Setup log objects - * // - * // log object of priority debug and the popup logger - * var log = new Log(Log.DEBUG, Log.popupLogger); - * // log object of priority warn and the alert logger - * var log2 = new Log(Log.WARN, Log.alertLogger); - * // log object of priority debug and the console logger (Safari) - * var log3 = new Log(Log.DEBUG, Log.consoleLogger); - * - * log.debug('foo1'); // will popup a new window and log 'foo' - * log.warn('bar1'); // will add a new 'bar' message to the popup - * log2.debug('foo2'); // will do nothing (Log object's priority threshold is WARN) - * log2.warn('bar2'); // will display a javascript alert with the string 'bar' - * log3.debug('foo3'); // will log message to Safari console or existing popup - * log3.warn('bar3'); // same - * - * log.info(Log.dumpObject(new Array('apple','pear','orange','banana'))); - * </script> - * </body> - * </html> - *- * - * @author Marcus R Breese mailto:mbreese@users.sourceforge.net - * @license Apache License 2.0 - * @version 0.31 - *
- ************************************************************** - * - * Copyright 2005 Fourspaces Consulting, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - * - ************************************************************** - * - * Changelog: - * 0.31 Bug fix (resizeable should be resizable - Darryl Lyons) - * 0.3 Migrated to SF.net SVN repository - test cleanups - * 0.2 - Added consoleLogger for Safari - * - Changed popupLogger so that it only notifies once (or twice) - * that a popup blocker is active. - * - Added Log.NONE level for silencing all logging - *- */ - - - -/** - * Create a new logger - * @constructor - * @class The main Log class. Create a new instance of this class to send all logging events. - * @param level The cut-off logger level. You can adjust this level in the constructor and leave all other logging events in place. Defaults to {@link Log#WARN}. - * @param logger The logger to use. The logger is a function that accepts the logging events and informs the user or developer. Defaults to {@link Log#writeLogger}. - */ -function Log(level,logger,prefix) { - var _currentLevel = Log.WARN; - var _logger = Log.writeLogger; // default to write Logger - var _prefix = false; - this._isMobileBrowser = Log.isMobileBrowser(); - /** - * Sets the current logger prefix - * @param {String} prefix This prefix will be prepended to all messages. - */ - this.setPrefix = function setPrefix(pre) { - if (pre!='undefined') { _prefix = pre; } - else { _prefix = false; } - } - /** - * Sets the current logger function - * @param logger The function that will be called when a log event needs to be displayed - */ - this.setLogger = function setLogger(logger) { - if (logger!='undefined') { _logger = logger; } - if (logger === Log.writeLogger){ - var loggerWrapper = document.createElement("div"); - loggerWrapper.id = "logger"; - loggerWrapper.style.backgroundColor = "white"; - loggerWrapper.style.position = "fixed"; - loggerWrapper.style.top = "100px"; - loggerWrapper.style.left = "0"; - loggerWrapper.style.zIndex = "20"; - loggerWrapper.style.overflow = "scroll"; - loggerWrapper.style.height = "400px"; - - document.body.appendChild(loggerWrapper); - - var clear = document.createElement("a"); - clear.innerHTML = "clear"; - clear.href = "javascript://"; - clear.addEventListener("click", function onClick(){ - loggerList.innerHTML = ""; - }, false); - loggerWrapper.appendChild(clear); - - loggerList = document.createElement("ul"); - loggerWrapper.appendChild(loggerList); - } - } - - /** - * Sets the current threshold log level for this Log instance. Only events that have a priority of this level or greater are logged. - * @param level The new threshold priority level for logging events. This can be one of the static members {@link Log#DEBUG}, {@link Log#INFO}, {@link Log#WARN}, {@link Log#ERROR}, {@link Log#FATAL}, {@link Log#NONE}, or it can be one of the strings ["debug", "info", "warn", "error", "fatal", "none"]. - */ - this.setLevel = function setLevel(level) { - if (level!='undefined' && typeof level =='number') { - _currentLevel = level; - } else if (level!='undefined') { - if (level=='debug') { _currentLevel = Log.DEBUG; } - else if (level=='info') { _currentLevel = Log.INFO; } - else if (level=='error') { _currentLevel = Log.ERROR; } - else if (level=='fatal') { _currentLevel = Log.FATAL; } - else if (level=='warn') { _currentLevel = Log.WARN; } - else { _currentLevel = Log.NONE; } - } - } - - /** - * Gets the current prefix - * @return current prefix - */ - - this.getPrefix = function getPrefix() { return _prefix; } - - /** - * Gets the current event logger function - * @return current logger - */ - - this.getLogger = function getLogger() { return _logger; } - - /** - * Gets the current threshold priority level - * @return current level - */ - - this.getLevel = function getLevel() { return _currentLevel; } - - if (level!='undefined') { this.setLevel(level); } - if (logger!='undefined') { this.setLogger(logger); } - if (prefix!='undefined') { this.setPrefix(prefix); } -} -/** - * Log an event with priority of "debug" - * @param s the log message - */ -Log.prototype.debug = function debug() { if (this.getLevel()<=Log.DEBUG) { this._log("DEBUG",this, arguments); } } -/** - * Log an event with priority of "info" - * @param s the log message - */ -Log.prototype.info = function info() { if (this.getLevel()<=Log.INFO ) { this._log("INFO",this, arguments); } } -/** - * Log an event with priority of "warn" - * @param s the log message - */ -Log.prototype.warn = function warn() { if (this.getLevel()<=Log.WARN ) { this._log("WARN",this, arguments); } } -/** - * Log an event with priority of "error" - * @param s the log message - */ -Log.prototype.error = function error() { if (this.getLevel()<=Log.ERROR) { this._log("ERROR",this, arguments); } } -/** - * Log an event with priority of "fatal" - * @param s the log message - */ -Log.prototype.fatal = function fatal() { if (this.getLevel()<=Log.FATAL) { this._log("FATAL",this, arguments); } } - -/** - * _log is the function that actually calling the configured logger function. - * It is possible that this function could be extended to allow for more - * than one logger. - * - * This method is used by {@link Log#debug}, {@link Log#info}, {@link Log#warn}, {@link Log#error}, and {@link Log#fatal} - * @private - * @param {String} msg The message to display - * @param level The priority level of this log event - * @param {Log} obj The originating {@link Log} object. - */ -Log.prototype._log = function _log(level,obj,msgObj) { - // INFO: 2010-12-26 14:11:42,690 in User::resetDailyVotes() (User.php:850): Reset Daily Votes for 0 Use - var date = new Date(); - var d = ""; - d += ((date.getHours() < 10)? "0" + date.getHours() : date.getHours()); - d += ":" + ((date.getMinutes() < 10)? "0" + date.getMinutes() : date.getMinutes()); - d += ":" + ((date.getSeconds() < 10)? "0" + date.getSeconds() : date.getSeconds()); - d += "." + ((date.getMilliseconds() < 100)? ((date.getMilliseconds() < 10)? "00" + date.getMilliseconds() : "0" + date.getMilliseconds()) : date.getMilliseconds()); - - var msgArr = [], dateStr = '('+d+')'; - if (this._isMobileBrowser){ - var msgStr = dateStr+" "; - for (var i=0, len=msgObj.length; i