<commit_before>"use strict";

ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");

var tbSyncAccounts = {

    selectedAccount: null,

    onload: function () {
        //scan accounts, update list and select first entry (because no id is passed to updateAccountList)
        //the onSelect event of the List will load the selected account
        this.updateAccountsList();
        Services.obs.addObserver(tbSyncAccounts.updateAccountSyncStateObserver, "tbsync.changedSyncstate", false);
        Services.obs.addObserver(tbSyncAccounts.updateAccountNameObserver, "tbsync.changedAccountName", false);
        Services.obs.addObserver(tbSyncAccounts.toggleEnableStateObserver, "tbsync.toggleEnableState", false);
    },

    onunload: function () {
        Services.obs.removeObserver(tbSyncAccounts.updateAccountSyncStateObserver, "tbsync.changedSyncstate");
        Services.obs.removeObserver(tbSyncAccounts.updateAccountNameObserver, "tbsync.changedAccountName");
        Services.obs.removeObserver(tbSyncAccounts.toggleEnableStateObserver, "tbsync.toggleEnableState");
    },

    addAccount: function () {
        //EAS hardcoded, will be made dynamic as soon as different providers are usable
        document.getElementById("tbSyncAccounts.accounts").disabled=true;
        document.getElementById("tbSyncAccounts.btnAccountActions").disabled=true;
        window.openDialog("chrome://tbsync/content/provider/eas/newaccount.xul", "easnewaccount", "centerscreen,modal,resizable=no");
        document.getElementById("tbSyncAccounts.accounts").disabled=false;
        document.getElementById("tbSyncAccounts.btnAccountActions").disabled=false;
    },

    updateDropdown: function (selector) {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        let selectedAccount = null;
        let selectedAccountName = "";
        let isActionsDropdown = (selector == "accountActions");

        let isSyncing = false;
        let isConnected = false;
        let isEnabled = false;

        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)) {
            //some item is selected
            let selectedItem = accountsList.selectedItem;
            selectedAccount = selectedItem.value;
            selectedAccountName = selectedItem.getAttribute("label");
            isSyncing = tbSync.isSyncing(selectedAccount);
            isConnected = tbSync.isConnected(selectedAccount);
            isEnabled = tbSync.isEnabled(selectedAccount);
        }

        //hide if no accounts are avail (which is identical to no account selected)
        if (isActionsDropdown) document.getElementById(selector + "SyncAllAccounts").hidden = (selectedAccount === null);

        //hide if no account is selected
        if (isActionsDropdown) document.getElementById(selector + "Separator").hidden = (selectedAccount === null);
        document.getElementById(selector + "DeleteAccount").hidden = (selectedAccount === null);
        document.getElementById(selector + "DisableAccount").hidden = (selectedAccount === null) || !isEnabled;
        document.getElementById(selector + "EnableAccount").hidden = (selectedAccount === null) || isEnabled;
        document.getElementById(selector + "SyncAccount").hidden = (selectedAccount === null) || !isConnected;
        document.getElementById(selector + "RetryConnectAccount").hidden = (selectedAccount === null) || isConnected || !isEnabled;

        //Not yet implemented
        document.getElementById(selector + "ShowSyncLog").hidden = true;//(selectedAccount === null) || !isEnabled;
        document.getElementById(selector + "ShowSyncLog").disabled = true;

        if (selectedAccount !== null) {
            //disable if currently syncing (and displayed)
            document.getElementById(selector + "DeleteAccount").disabled = isSyncing;
            document.getElementById(selector + "DisableAccount").disabled = isSyncing;
            document.getElementById(selector + "EnableAccount").disabled = isSyncing;
            document.getElementById(selector + "SyncAccount").disabled = isSyncing;
            //adjust labels - only in global actions dropdown
            if (isActionsDropdown) document.getElementById(selector + "DeleteAccount").label = tbSync.getLocalizedMessage("accountacctions.delete").replace("##accountname##", selectedAccountName);
            if (isActionsDropdown) document.getElementById(selector + "SyncAccount").label = tbSync.getLocalizedMessage("accountacctions.sync").replace("##accountname##", selectedAccountName);
            if (isActionsDropdown) document.getElementById(selector + "EnableAccount").label = tbSync.getLocalizedMessage("accountacctions.enable").replace("##accountname##", selectedAccountName);
            if (isActionsDropdown) document.getElementById(selector + "DisableAccount").label = tbSync.getLocalizedMessage("accountacctions.disable").replace("##accountname##", selectedAccountName);
        }
    },

    toggleEnableState: function () {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value) && !tbSync.isSyncing(accountsList.selectedItem.value)) {
            Services.obs.notifyObservers(null, "tbsync.toggleEnableState", accountsList.selectedItem.value);
        }
    },

    synchronizeAccount: function () {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)  && !tbSync.isSyncing(accountsList.selectedItem.value)) {
            tbSync.syncAccount('sync', accountsList.selectedItem.value);
        }
    },

    deleteAccount: function () {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)  && !tbSync.isSyncing(accountsList.selectedItem.value)) {
            let nextAccount =  -1;
            if (accountsList.selectedIndex > 0) {
                //first try to select the item after this one, otherwise take the one before
                if (accountsList.selectedIndex + 1 < accountsList.getRowCount()) nextAccount = accountsList.getItemAtIndex(accountsList.selectedIndex + 1).value;
                else nextAccount = accountsList.getItemAtIndex(accountsList.selectedIndex - 1).value;
            }

            if (confirm(tbSync.getLocalizedMessage("prompt.DeleteAccount").replace("##accountName##", accountsList.selectedItem.getAttribute("label")))) {
                //disable (removes ab, triggers changelog cleanup)
                tbSync[tbSync.db.getAccountSetting(accountsList.selectedItem.value, "provider")].disableAccount(accountsList.selectedItem.value);
                //delete account and all folders from db
                tbSync.db.removeAccount(accountsList.selectedItem.value);

                this.updateAccountsList(nextAccount);
            }
        }
    },

    /* * *
    * Observer to catch enable state toggle
    */
    toggleEnableStateObserver: {
        observe: function (aSubject, aTopic, aData) {
            let account = aData;
            let isConnected = tbSync.isConnected(account);
            let isEnabled = tbSync.isEnabled(account);

            if (isEnabled) {
                //we are enabled and want to disable (do not ask, if not connected)
                if (!isConnected || window.confirm(tbSync.getLocalizedMessage("prompt.Disable"))) {
                    tbSync[tbSync.db.getAccountSetting(account, "provider")].disableAccount(account);
                    Services.obs.notifyObservers(null, "tbsync.updateAccountSettingsGui", account);
                }
            } else {
                //we are disabled and want to enabled
                tbSync[tbSync.db.getAccountSetting(account, "provider")].enableAccount(account);
                Services.obs.notifyObservers(null, "tbsync.updateAccountSettingsGui", account);
                tbSync.syncAccount("sync", account);
            }
        }
    },

    /* * *
    * Observer to catch synstate changes and to update account icons
    */
    updateAccountSyncStateObserver: {
        observe: function (aSubject, aTopic, aData) {
            if (aData != "") {
                //since we want rotating arrows on each syncstate change, we need to run this on each syncstate
                let syncstate = tbSync.getSyncData(aData,"syncstate");
                tbSyncAccounts.updateAccountStatus(aData);
            }
        }
    },

    setStatusImage: function (account, obj) {
        let statusImage = this.getStatusImage(account, obj.src);
        if (statusImage != obj.src) {
            obj.src = statusImage;
        }
    },

    getStatusImage: function (account, current = "") {
        let src = "";
        switch (tbSync.db.getAccountSetting(account, "status")) {
            case "OK":
                if (tbSync.isEnabled(account)) src = "tick16.png";
                else src = "disabled.png";
                break;

            case "disabled":
                src = "disabled.png";
                break;

            case "notsyncronized":
            case "nolightning":
            case "modified":
                src = "warning16.png";
                break;

            case "syncing":
                if (current.indexOf("sync16") == -1) {
                    //current img is something else, show sync img directly
                    src = "sync16.png";
                    tbSync.setSyncData(account, "accountManagerLastUpdated", Date.now());
                } else if ((Date.now() - tbSync.getSyncData(account, "accountManagerLastUpdated")) > 400) {
                    //current img is one of the sync images, flip at lower speed see them rotate
                    if (current.indexOf("sync16.png") == -1) src = "sync16.png"; else src = "sync16_r.png";
                    tbSync.setSyncData(account, "accountManagerLastUpdated", Date.now());
                } else {
                    return current;
                }
                break;

            default:
                src = "error16.png";
        }

        return "chrome://tbsync/skin/" + src;
    },

    updateAccountStatus: function (id) {
        let listItem = document.getElementById("tbSyncAccounts.accounts." + id);
        this.setStatusImage(id, listItem.childNodes[1].firstChild);
    },

    updateAccountNameObserver: {
        observe: function (aSubject, aTopic, aData) {
            let pos = aData.indexOf(":");
            let id = aData.substring(0, pos);
            let name = aData.substring(pos+1);
            tbSyncAccounts.updateAccountName (id, name);
        }
    },

    updateAccountName: function (id, name) {
        let listItem = document.getElementById("tbSyncAccounts.accounts." + id);
        if (listItem.firstChild.getAttribute("label") != name) listItem.firstChild.setAttribute("label", name);
    },

    updateAccountsList: function (accountToSelect = -1) {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        let accounts = tbSync.db.getAccounts();

        if (accounts.IDs.length > null) {

            //get current accounts in list and remove entries of accounts no longer there
            let listedAccounts = [];
            for (let i=accountsList.getRowCount()-1; i>=0; i--) {
                listedAccounts.push(accountsList.getItemAtIndex (i).value);
                if (accounts.IDs.indexOf(accountsList.getItemAtIndex(i).value) == -1) {
                    accountsList.removeItemAt(i);
                }
            }

            //accounts array is without order, extract keys (ids) and loop over keys
            for (let i = 0; i < accounts.IDs.length; i++) {

                if (listedAccounts.indexOf(accounts.IDs[i]) == -1) {
                    //add all missing accounts (always to the end of the list)
                    let newListItem = document.createElement("richlistitem");
                    newListItem.setAttribute("id", "tbSyncAccounts.accounts." + accounts.IDs[i]);
                    newListItem.setAttribute("value", accounts.IDs[i]);
                    newListItem.setAttribute("label", accounts.data[accounts.IDs[i]].accountname);
                    newListItem.setAttribute("ondblclick", "tbSyncAccounts.toggleEnableState();");

                    //add account name
                    let itemLabelCell = document.createElement("listcell");
                    itemLabelCell.setAttribute("class", "label");
                    itemLabelCell.setAttribute("flex", "1");
                    itemLabelCell.setAttribute("label", accounts.data[accounts.IDs[i]].accountname);
                    newListItem.appendChild(itemLabelCell);

                    //add account status
                    let itemStatusCell = document.createElement("listcell");
                    itemStatusCell.setAttribute("class", "img");
                    itemStatusCell.setAttribute("width", "30");
                    itemStatusCell.setAttribute("height", "30");
                    let itemStatus = document.createElement("image");
                    itemStatus.setAttribute("src", this.getStatusImage(accounts.IDs[i]));
                    itemStatus.setAttribute("style", "margin:2px;");
                    itemStatusCell.appendChild(itemStatus);

                    newListItem.appendChild(itemStatusCell);
                    accountsList.appendChild(newListItem);
                } else {
                    //update existing entries in list
                    this.updateAccountName(accounts.IDs[i], accounts.data[accounts.IDs[i]].accountname);
                    this.updateAccountStatus(accounts.IDs[i]);
                }
            }

            //find selected item
            for (let i=0; i<accountsList.getRowCount(); i++) {
                if (accountToSelect == accountsList.getItemAtIndex(i).value || accountToSelect == -1) {
                    accountsList.selectedIndex = i;
                    accountsList.ensureIndexIsVisible(i);
                    break;
                }
            }

        } else {
            //No defined accounts, empty accounts list and load dummy
            for (let i=accountsList.getRowCount()-1; i>=0; i--) {
                accountsList.removeItemAt(i);
            }

            const LOAD_FLAGS_NONE = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
            document.getElementById("tbSyncAccounts.contentFrame").webNavigation.loadURI("chrome://tbsync/content/manager/noaccounts.xul", LOAD_FLAGS_NONE, null, null, null);
        }
    },

    //load the pref page for the currently selected account (triggered by onSelect)
    loadSelectedAccount: function () {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)) {
            //get id of selected account from value of selectedItem
            this.selectedAccount = accountsList.selectedItem.value;
            const LOAD_FLAGS_NONE = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
            document.getElementById("tbSyncAccounts.contentFrame").webNavigation.loadURI("chrome://tbsync/content/provider/"+tbSync.db.getAccountSetting(this.selectedAccount, "provider")+"/accountSettings.xul?id=" + this.selectedAccount, LOAD_FLAGS_NONE, null, null, null);
        }
    }

};
<commit_msg>fix small bug<commit_after>"use strict";

ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");

var tbSyncAccounts = {

    selectedAccount: null,

    onload: function () {
        //scan accounts, update list and select first entry (because no id is passed to updateAccountList)
        //the onSelect event of the List will load the selected account
        this.updateAccountsList();
        Services.obs.addObserver(tbSyncAccounts.updateAccountSyncStateObserver, "tbsync.changedSyncstate", false);
        Services.obs.addObserver(tbSyncAccounts.updateAccountNameObserver, "tbsync.changedAccountName", false);
        Services.obs.addObserver(tbSyncAccounts.toggleEnableStateObserver, "tbsync.toggleEnableState", false);
    },

    onunload: function () {
        Services.obs.removeObserver(tbSyncAccounts.updateAccountSyncStateObserver, "tbsync.changedSyncstate");
        Services.obs.removeObserver(tbSyncAccounts.updateAccountNameObserver, "tbsync.changedAccountName");
        Services.obs.removeObserver(tbSyncAccounts.toggleEnableStateObserver, "tbsync.toggleEnableState");
    },

    addAccount: function () {
        //EAS hardcoded, will be made dynamic as soon as different providers are usable
        document.getElementById("tbSyncAccounts.accounts").disabled=true;
        document.getElementById("tbSyncAccounts.btnAccountActions").disabled=true;
        window.openDialog("chrome://tbsync/content/provider/eas/newaccount.xul", "easnewaccount", "centerscreen,modal,resizable=no");
        document.getElementById("tbSyncAccounts.accounts").disabled=false;
        document.getElementById("tbSyncAccounts.btnAccountActions").disabled=false;
    },

    updateDropdown: function (selector) {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        let selectedAccount = null;
        let selectedAccountName = "";
        let isActionsDropdown = (selector == "accountActions");

        let isSyncing = false;
        let isConnected = false;
        let isEnabled = false;

        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)) {
            //some item is selected
            let selectedItem = accountsList.selectedItem;
            selectedAccount = selectedItem.value;
            selectedAccountName = selectedItem.getAttribute("label");
            isSyncing = tbSync.isSyncing(selectedAccount);
            isConnected = tbSync.isConnected(selectedAccount);
            isEnabled = tbSync.isEnabled(selectedAccount);
        }

        //hide if no accounts are avail (which is identical to no account selected)
        if (isActionsDropdown) document.getElementById(selector + "SyncAllAccounts").hidden = (selectedAccount === null);

        //hide if no account is selected
        if (isActionsDropdown) document.getElementById(selector + "Separator").hidden = (selectedAccount === null);
        document.getElementById(selector + "DeleteAccount").hidden = (selectedAccount === null);
        document.getElementById(selector + "DisableAccount").hidden = (selectedAccount === null) || !isEnabled;
        document.getElementById(selector + "EnableAccount").hidden = (selectedAccount === null) || isEnabled;
        document.getElementById(selector + "SyncAccount").hidden = (selectedAccount === null) || !isConnected;
        document.getElementById(selector + "RetryConnectAccount").hidden = (selectedAccount === null) || isConnected || !isEnabled;

        //Not yet implemented
        document.getElementById(selector + "ShowSyncLog").hidden = true;//(selectedAccount === null) || !isEnabled;
        document.getElementById(selector + "ShowSyncLog").disabled = true;

        if (selectedAccount !== null) {
            //disable if currently syncing (and displayed)
            document.getElementById(selector + "DeleteAccount").disabled = isSyncing;
            document.getElementById(selector + "DisableAccount").disabled = isSyncing;
            document.getElementById(selector + "EnableAccount").disabled = isSyncing;
            document.getElementById(selector + "SyncAccount").disabled = isSyncing;
            //adjust labels - only in global actions dropdown
            if (isActionsDropdown) document.getElementById(selector + "DeleteAccount").label = tbSync.getLocalizedMessage("accountacctions.delete").replace("##accountname##", selectedAccountName);
            if (isActionsDropdown) document.getElementById(selector + "SyncAccount").label = tbSync.getLocalizedMessage("accountacctions.sync").replace("##accountname##", selectedAccountName);
            if (isActionsDropdown) document.getElementById(selector + "EnableAccount").label = tbSync.getLocalizedMessage("accountacctions.enable").replace("##accountname##", selectedAccountName);
            if (isActionsDropdown) document.getElementById(selector + "DisableAccount").label = tbSync.getLocalizedMessage("accountacctions.disable").replace("##accountname##", selectedAccountName);
        }
    },

    toggleEnableState: function () {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value) && !tbSync.isSyncing(accountsList.selectedItem.value)) {
            Services.obs.notifyObservers(null, "tbsync.toggleEnableState", accountsList.selectedItem.value);
        }
    },

    synchronizeAccount: function () {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)  && !tbSync.isSyncing(accountsList.selectedItem.value)) {
            tbSync.syncAccount('sync', accountsList.selectedItem.value);
        }
    },

    deleteAccount: function () {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)  && !tbSync.isSyncing(accountsList.selectedItem.value)) {
            let nextAccount =  -1;
            if (accountsList.selectedIndex > 0) {
                //first try to select the item after this one, otherwise take the one before
                if (accountsList.selectedIndex + 1 < accountsList.getRowCount()) nextAccount = accountsList.getItemAtIndex(accountsList.selectedIndex + 1).value;
                else nextAccount = accountsList.getItemAtIndex(accountsList.selectedIndex - 1).value;
            }

            if (confirm(tbSync.getLocalizedMessage("prompt.DeleteAccount").replace("##accountName##", accountsList.selectedItem.getAttribute("label")))) {
                //disable (removes ab, triggers changelog cleanup)
                tbSync[tbSync.db.getAccountSetting(accountsList.selectedItem.value, "provider")].disableAccount(accountsList.selectedItem.value);
                //delete account and all folders from db
                tbSync.db.removeAccount(accountsList.selectedItem.value);

                this.updateAccountsList(nextAccount);
            }
        }
    },

    /* * *
    * Observer to catch enable state toggle
    */
    toggleEnableStateObserver: {
        observe: function (aSubject, aTopic, aData) {
            let account = aData;
            let isConnected = tbSync.isConnected(account);
            let isEnabled = tbSync.isEnabled(account);

            if (isEnabled) {
                //we are enabled and want to disable (do not ask, if not connected)
                if (!isConnected || window.confirm(tbSync.getLocalizedMessage("prompt.Disable"))) {
                    tbSync[tbSync.db.getAccountSetting(account, "provider")].disableAccount(account);
                    Services.obs.notifyObservers(null, "tbsync.updateAccountSettingsGui", account);
                }
            } else {
                //we are disabled and want to enabled
                tbSync[tbSync.db.getAccountSetting(account, "provider")].enableAccount(account);
                Services.obs.notifyObservers(null, "tbsync.updateAccountSettingsGui", account);
                tbSync.syncAccount("sync", account);
            }
        }
    },

    /* * *
    * Observer to catch synstate changes and to update account icons
    */
    updateAccountSyncStateObserver: {
        observe: function (aSubject, aTopic, aData) {
            if (aData != "") {
                //since we want rotating arrows on each syncstate change, we need to run this on each syncstate
                let syncstate = tbSync.getSyncData(aData,"syncstate");
                tbSyncAccounts.updateAccountStatus(aData);
            }
        }
    },

    setStatusImage: function (account, obj) {
        let statusImage = this.getStatusImage(account, obj.src);
        if (statusImage != obj.src) {
            obj.src = statusImage;
        }
    },

    getStatusImage: function (account, current = "") {
        let src = "";
        switch (tbSync.db.getAccountSetting(account, "status")) {
            case "OK":
                if (tbSync.isEnabled(account)) src = "tick16.png";
                else src = "disabled.png";
                break;

            case "disabled":
                src = "disabled.png";
                break;

            case "notsyncronized":
            case "nolightning":
            case "modified":
                src = "warning16.png";
                break;

            case "syncing":
                if (current.indexOf("sync16") == -1) {
                    //current img is something else, show sync img directly
                    src = "sync16.png";
                    tbSync.setSyncData(account, "accountManagerLastUpdated", Date.now());
                } else if ((Date.now() - tbSync.getSyncData(account, "accountManagerLastUpdated")) > 400) {
                    //current img is one of the sync images, flip at lower speed see them rotate
                    if (current.indexOf("sync16.png") == -1) src = "sync16.png"; else src = "sync16_r.png";
                    tbSync.setSyncData(account, "accountManagerLastUpdated", Date.now());
                } else {
                    return current;
                }
                break;

            default:
                src = "error16.png";
        }

        return "chrome://tbsync/skin/" + src;
    },

    updateAccountStatus: function (id) {
        let listItem = document.getElementById("tbSyncAccounts.accounts." + id);
        if (listItem) this.setStatusImage(id, listItem.childNodes[1].firstChild);
    },

    updateAccountNameObserver: {
        observe: function (aSubject, aTopic, aData) {
            let pos = aData.indexOf(":");
            let id = aData.substring(0, pos);
            let name = aData.substring(pos+1);
            tbSyncAccounts.updateAccountName (id, name);
        }
    },

    updateAccountName: function (id, name) {
        let listItem = document.getElementById("tbSyncAccounts.accounts." + id);
        if (listItem.firstChild.getAttribute("label") != name) listItem.firstChild.setAttribute("label", name);
    },

    updateAccountsList: function (accountToSelect = -1) {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        let accounts = tbSync.db.getAccounts();

        if (accounts.IDs.length > null) {

            //get current accounts in list and remove entries of accounts no longer there
            let listedAccounts = [];
            for (let i=accountsList.getRowCount()-1; i>=0; i--) {
                listedAccounts.push(accountsList.getItemAtIndex (i).value);
                if (accounts.IDs.indexOf(accountsList.getItemAtIndex(i).value) == -1) {
                    accountsList.removeItemAt(i);
                }
            }

            //accounts array is without order, extract keys (ids) and loop over keys
            for (let i = 0; i < accounts.IDs.length; i++) {

                if (listedAccounts.indexOf(accounts.IDs[i]) == -1) {
                    //add all missing accounts (always to the end of the list)
                    let newListItem = document.createElement("richlistitem");
                    newListItem.setAttribute("id", "tbSyncAccounts.accounts." + accounts.IDs[i]);
                    newListItem.setAttribute("value", accounts.IDs[i]);
                    newListItem.setAttribute("label", accounts.data[accounts.IDs[i]].accountname);
                    newListItem.setAttribute("ondblclick", "tbSyncAccounts.toggleEnableState();");

                    //add account name
                    let itemLabelCell = document.createElement("listcell");
                    itemLabelCell.setAttribute("class", "label");
                    itemLabelCell.setAttribute("flex", "1");
                    itemLabelCell.setAttribute("label", accounts.data[accounts.IDs[i]].accountname);
                    newListItem.appendChild(itemLabelCell);

                    //add account status
                    let itemStatusCell = document.createElement("listcell");
                    itemStatusCell.setAttribute("class", "img");
                    itemStatusCell.setAttribute("width", "30");
                    itemStatusCell.setAttribute("height", "30");
                    let itemStatus = document.createElement("image");
                    itemStatus.setAttribute("src", this.getStatusImage(accounts.IDs[i]));
                    itemStatus.setAttribute("style", "margin:2px;");
                    itemStatusCell.appendChild(itemStatus);

                    newListItem.appendChild(itemStatusCell);
                    accountsList.appendChild(newListItem);
                } else {
                    //update existing entries in list
                    this.updateAccountName(accounts.IDs[i], accounts.data[accounts.IDs[i]].accountname);
                    this.updateAccountStatus(accounts.IDs[i]);
                }
            }

            //find selected item
            for (let i=0; i<accountsList.getRowCount(); i++) {
                if (accountToSelect == accountsList.getItemAtIndex(i).value || accountToSelect == -1) {
                    accountsList.selectedIndex = i;
                    accountsList.ensureIndexIsVisible(i);
                    break;
                }
            }

        } else {
            //No defined accounts, empty accounts list and load dummy
            for (let i=accountsList.getRowCount()-1; i>=0; i--) {
                accountsList.removeItemAt(i);
            }

            const LOAD_FLAGS_NONE = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
            document.getElementById("tbSyncAccounts.contentFrame").webNavigation.loadURI("chrome://tbsync/content/manager/noaccounts.xul", LOAD_FLAGS_NONE, null, null, null);
        }
    },

    //load the pref page for the currently selected account (triggered by onSelect)
    loadSelectedAccount: function () {
        let accountsList = document.getElementById("tbSyncAccounts.accounts");
        if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)) {
            //get id of selected account from value of selectedItem
            this.selectedAccount = accountsList.selectedItem.value;
            const LOAD_FLAGS_NONE = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
            document.getElementById("tbSyncAccounts.contentFrame").webNavigation.loadURI("chrome://tbsync/content/provider/"+tbSync.db.getAccountSetting(this.selectedAccount, "provider")+"/accountSettings.xul?id=" + this.selectedAccount, LOAD_FLAGS_NONE, null, null, null);
        }
    }

};
<|endoftext|>


<commit_before>import React from 'react';
import { Link } from 'react-router';
import noop from 'lodash.noop';

export default React.createClass( {
  propTypes: {
    errors: React.PropTypes.array,
    onClearNotices: React.PropTypes.func,
    onLogOut: React.PropTypes.func,
    isAdmin: React.PropTypes.bool,
  },

  getDefaultProps() {
    return {
      errors: [],
      onClearNotices: noop,
      onLogOut: noop,
      isAdmin: false,
    }
  },

  onClickReload() {
    if ( window ) window.location.reload();
  },

  renderNotices() {
    if ( this.props.errors.length === 0 ) return;
    return (

Sorry! Something went wrong!

If the problem was temporary, you can try to keep working. Otherwise you can try to reload and see if that helps.

If you keep seeing errors, send an email to payton+voyageurhelp@foolord.com and I'll see what I can do.

        { this.renderButtons() }

Here's the raw error I saw:

Don't worry if you don't understand it; if it keeps happening just send it to me in an email.

          { this.props.errors.map( this.renderError ) }

    );
  },

  renderError( error, key ) {
    return
{ error }
;
  },

  renderButtons() {
    return (

        <button className="notices__clear btn btn-block btn-primary" onClick={ this.props.onClearNotices }>keep going</button>
        <button className="notices__reload btn btn-block btn-warning" onClick={ this.onClickReload }>try reloading</button>

    );
  },

  renderAdminButton() {
    if ( this.props.isAdmin ) return <Link to="/admin" className="btn btn-default admin-button"></Link>;
  },

  render() {
    return (

Voyageur

        <Link to="/"></Link>

          { this.renderAdminButton() }
          <button className="btn btn-default log-out-button" onClick={ this.props.onLogOut }>Log out</button>

        { this.renderNotices() }

    );
  }
} );
<commit_msg>Remove unused Header<commit_after><|endoftext|>


<commit_before>function GM_xmlhttpRequester(unsafeContentWin, chromeWindow, originUrl) {
  this.unsafeContentWin = unsafeContentWin;
  this.chromeWindow = chromeWindow;
  this.originUrl = originUrl;
}

// this function gets called by user scripts in content security scope to
// start a cross-domain xmlhttp request.
//
// details should look like:
// {method,url,onload,onerror,onreadystatechange,headers,data}
// headers should be in the form {name:value,name:value,etc}
// can't support mimetype because i think it's only used for forcing
// text/xml and we can't support that
GM_xmlhttpRequester.prototype.contentStartRequest = function(details) {
  if (!GM_apiLeakCheck("GM_xmlhttpRequest")) {
    return;
  }

  GM_log("> GM_xmlhttpRequest.contentStartRequest");

  var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                  .getService(Components.interfaces.nsIIOService);
  try {
    // Validate and parse the (possibly relative) given URL.
    var originUri = ioService.newURI(this.originUrl, null, null);
    var uri = ioService.newURI(details.url, null, originUri);
    var url = uri.spec;
  } catch (e) {
    // A malformed URL won't be parsed properly.
    throw new Error("Invalid URL: " + details.url);
  }

  // This is important - without it, GM_xmlhttpRequest can be used to get
  // access to things like files and chrome. Careful.
  switch (uri.scheme) {
    case "http":
    case "https":
    case "ftp":
        var req = new this.chromeWindow.XMLHttpRequest();
        GM_hitch(this, "chromeStartRequest", url, details, req)();
      break;
    default:
      throw new Error("Disallowed scheme in URL: " + details.url);
  }

  GM_log("< GM_xmlhttpRequest.contentStartRequest");

  return {
    abort: function() {
      req.abort();
    }
  };
};

// this function is intended to be called in chrome's security context, so
// that it can access other domains without security warning
GM_xmlhttpRequester.prototype.chromeStartRequest =
function(safeUrl, details, req) {
  GM_log("> GM_xmlhttpRequest.chromeStartRequest");

  this.setupRequestEvent(this.unsafeContentWin, req, "onload", details);
  this.setupRequestEvent(this.unsafeContentWin, req, "onerror", details);
  this.setupRequestEvent(this.unsafeContentWin, req, "onreadystatechange",
                         details);

  req.open(details.method, safeUrl);

  if (details.overrideMimeType) {
    req.overrideMimeType(details.overrideMimeType);
  }

  if (details.headers) {
    for (var prop in details.headers) {
      req.setRequestHeader(prop, details.headers[prop]);
    }
  }

  var body = details.data ? details.data : null;
  if (details.binary) {
    // xhr supports binary?
    if (!req.sendAsBinary) {
      var err = new Error("Unavailable feature: " +
              "This version of Firefox does not support sending binary data " +
              "(you should consider upgrading to version 3 or newer.)");
      GM_logError(err);
      throw err;
    }
    req.sendAsBinary(body);
  } else {
    req.send(body);
  }

  GM_log("< GM_xmlhttpRequest.chromeStartRequest");
}

// arranges for the specified 'event' on xmlhttprequest 'req' to call the
// method by the same name which is a property of 'details' in the content
// window's security context.
GM_xmlhttpRequester.prototype.setupRequestEvent =
function(unsafeContentWin, req, event, details) {
  GM_log("> GM_xmlhttpRequester.setupRequestEvent");

  if (details[event]) {
    req[event] = function() {
      GM_log("> GM_xmlhttpRequester -- callback for " + event);

      var responseState = {
        // can't support responseXML because security won't
        // let the browser call properties on it
        responseText: req.responseText,
        readyState: req.readyState,
        responseHeaders: null,
        status: null,
        statusText: null,
        finalUrl: null
      };
      if (4 == req.readyState && 'onerror' != event) {
        responseState.responseHeaders = req.getAllResponseHeaders();
        responseState.status = req.status;
        responseState.statusText = req.statusText;
        responseState.finalUrl = req.channel.URI.spec;
      }

      // Pop back onto browser thread and call event handler.
      // Have to use nested function here instead of GM_hitch because
      // otherwise details[event].apply can point to window.setTimeout, which
      // can be abused to get increased priveledges.
      new XPCNativeWrapper(unsafeContentWin, "setTimeout()")
        .setTimeout(function(){details[event](responseState);}, 0);

      GM_log("< GM_xmlhttpRequester -- callback for " + event);
    }
  }

  GM_log("< GM_xmlhttpRequester.setupRequestEvent");
};
<commit_msg>using hasOwnProperty() to verify each of the header object's enumerated properties are owned by the object, they are skipped otherwise.<commit_after>function GM_xmlhttpRequester(unsafeContentWin, chromeWindow, originUrl) {
  this.unsafeContentWin = unsafeContentWin;
  this.chromeWindow = chromeWindow;
  this.originUrl = originUrl;
}

// this function gets called by user scripts in content security scope to
// start a cross-domain xmlhttp request.
//
// details should look like:
// {method,url,onload,onerror,onreadystatechange,headers,data}
// headers should be in the form {name:value,name:value,etc}
// can't support mimetype because i think it's only used for forcing
// text/xml and we can't support that
GM_xmlhttpRequester.prototype.contentStartRequest = function(details) {
  if (!GM_apiLeakCheck("GM_xmlhttpRequest")) {
    return;
  }

  GM_log("> GM_xmlhttpRequest.contentStartRequest");

  var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                  .getService(Components.interfaces.nsIIOService);
  try {
    // Validate and parse the (possibly relative) given URL.
    var originUri = ioService.newURI(this.originUrl, null, null);
    var uri = ioService.newURI(details.url, null, originUri);
    var url = uri.spec;
  } catch (e) {
    // A malformed URL won't be parsed properly.
    throw new Error("Invalid URL: " + details.url);
  }

  // This is important - without it, GM_xmlhttpRequest can be used to get
  // access to things like files and chrome. Careful.
  switch (uri.scheme) {
    case "http":
    case "https":
    case "ftp":
        var req = new this.chromeWindow.XMLHttpRequest();
        GM_hitch(this, "chromeStartRequest", url, details, req)();
      break;
    default:
      throw new Error("Disallowed scheme in URL: " + details.url);
  }

  GM_log("< GM_xmlhttpRequest.contentStartRequest");

  return {
    abort: function() {
      req.abort();
    }
  };
};

// this function is intended to be called in chrome's security context, so
// that it can access other domains without security warning
GM_xmlhttpRequester.prototype.chromeStartRequest =
function(safeUrl, details, req) {
  GM_log("> GM_xmlhttpRequest.chromeStartRequest");

  this.setupRequestEvent(this.unsafeContentWin, req, "onload", details);
  this.setupRequestEvent(this.unsafeContentWin, req, "onerror", details);
  this.setupRequestEvent(this.unsafeContentWin, req, "onreadystatechange",
                         details);

  req.open(details.method, safeUrl);

  if (details.overrideMimeType) {
    req.overrideMimeType(details.overrideMimeType);
  }

  if (details.headers) {
    for (var prop in details.headers) {
      if (details.headers.hasOwnProperty(prop)) {
        req.setRequestHeader(prop, details.headers[prop]);
      }
    }
  }

  var body = details.data ? details.data : null;
  if (details.binary) {
    // xhr supports binary?
    if (!req.sendAsBinary) {
      var err = new Error("Unavailable feature: " +
              "This version of Firefox does not support sending binary data " +
              "(you should consider upgrading to version 3 or newer.)");
      GM_logError(err);
      throw err;
    }
    req.sendAsBinary(body);
  } else {
    req.send(body);
  }

  GM_log("< GM_xmlhttpRequest.chromeStartRequest");
}

// arranges for the specified 'event' on xmlhttprequest 'req' to call the
// method by the same name which is a property of 'details' in the content
// window's security context.
GM_xmlhttpRequester.prototype.setupRequestEvent =
function(unsafeContentWin, req, event, details) {
  GM_log("> GM_xmlhttpRequester.setupRequestEvent");

  if (details[event]) {
    req[event] = function() {
      GM_log("> GM_xmlhttpRequester -- callback for " + event);

      var responseState = {
        // can't support responseXML because security won't
        // let the browser call properties on it
        responseText: req.responseText,
        readyState: req.readyState,
        responseHeaders: null,
        status: null,
        statusText: null,
        finalUrl: null
      };
      if (4 == req.readyState && 'onerror' != event) {
        responseState.responseHeaders = req.getAllResponseHeaders();
        responseState.status = req.status;
        responseState.statusText = req.statusText;
        responseState.finalUrl = req.channel.URI.spec;
      }

      // Pop back onto browser thread and call event handler.
      // Have to use nested function here instead of GM_hitch because
      // otherwise details[event].apply can point to window.setTimeout, which
      // can be abused to get increased priveledges.
      new XPCNativeWrapper(unsafeContentWin, "setTimeout()")
        .setTimeout(function(){details[event](responseState);}, 0);

      GM_log("< GM_xmlhttpRequester -- callback for " + event);
    }
  }

  GM_log("< GM_xmlhttpRequester.setupRequestEvent");
};
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Controller.extend({
  breadCrumb: function() {
    return this.get('name');
  }.property('name')
});<commit_msg>removed no longer used breadCrumb function<commit_after>import Ember from 'ember';

export default Ember.Controller.extend({
  name: function() {
    return this.get('model.name');
  }.property('model.name')
});<|endoftext|>


<commit_before>Alloy.Globals.tabGroup = $.index;

function doWindowOpen(evt) {

    if (OS_IOS) {
        return;
    }

    var activity = evt.source.getActivity();

    activity.onCreateOptionsMenu = function(e) {
        var item,
            menu;
        menu = e.menu;
        menu.clear();

        switch(Alloy.Globals.currentTab) {
        // case photoList
        case 1:
            item2 = e.menu.add({
                title : "Add",
                showAsAction : Ti.Android.SHOW_AS_ACTION_ALWAYS,
                icon : Ti.Android.R.drawable.ic_menu_camera
            });
            item2.addEventListener("click", function(e) {
                $.photoListView.addPhoto();
            });

            break;
        }
    };

    Alloy.Globals.tabGroup.addEventListener("focus", function(evt) {
        if ( typeof evt.index !== "undefined") {
            activity.invalidateOptionsMenu();
            Alloy.Globals.currentTab = evt.index;
        }
    });
}

/**
 * called when user clicks on listView. The _event will provide
 * the index of the item clicked in the listView
 *
 * @param {Object} _event
 */
function listItemClicked(_event) {

    // get data using index provided, the items are in the section
    // so we use the index against the section, not the listView
    var currentItem = $.listSection.getItemAt(_event.itemIndex);

    // we save the data as a property so that is how we access
    // the data, we do not need the data array in this case
    var sessionInfo = currentItem.properties.data;

    // now display the data
    alert("clicked on " + JSON.stringify(sessionInfo, null, 2));
}

// lets get some real data!!
Alloy.Globals.parseService = require('parseREST');

// set the API Credentials
Alloy.Globals.parseService.init();

var parseService = Alloy.Globals.parseService;

function saveSessionClicked() {
    var queryResults;

    alert("WIP");
    return;

    parseService.createObject('TutorSession', {
        sessionName : $.sessionName.value,
        sessionLocation : $.sessionLocation.value
    }).then(function(_queryResult) {
        console.log("Success Happened: " + JSON.stringify(_queryResult));
        queryResults = _queryResult;
        return parseService.getObjects('TutorSession');
    }).then(function(_sessions) {
        updateList(_sessions.response.results);
    }, function(_error) {
        console.log("Some Error Happened: " + JSON.stringify(_error));
    });
}

function updateList(_data) {
    var items = _.map(_data, function(element) {

        return {
            properties : {
                data : element // save all attributes
            },
            // bind the labels using the bindId
            name : {
                text : element.tutor.email
            },
            location : {
                text : element.place.Location + "-" + element.place.Name
            }
        };
    });
    // add the items to the section in the ListView
    $.listSection.setItems(items);

}

function whereQueryExample() {

    // find all tutoring sessions that are being done by
    // the tutors with the specified ids
    var whereQueryStr = {
        "tutor" : {
            "$inQuery" : {
                "where" : {
                    "objectId" : {
                        "$in" : ["SCV3V0GqRr", "9uKbg0Hzeb"]
                    }
                },
                "className" : "_User"
            }
        }
    };

    return parseService.getObjects('TutorSession', {
        "urlparams" : {
            "include" : "user,tutor,place",
            "where" : whereQueryStr
        }
    });
}

//
// check to see if we have a user account saved
//

/**
 * logging in a user and then testing some of the functionality
 * of the rest service
 */

function doLoginSuccess() {
    return whereQueryExample().then(function(_sessions) {
        console.log("getTutorSessions: " + JSON.stringify(_sessions.response, null, 2));

        updateList(_sessions.response.results);

        // open the main view of index.js, which is the tab
        $.index.open();

    }, function(_error) {
        Ti.API.error('ERROR: ' + JSON.stringify(_error, null, 2));
        alert("Error Loading Data\n" + _error.error);

    });
}

function loginUser() {
    return parseService.loginUser("aaronsaunders", "password").then(function(_result) {
        console.log("logged in user successfully: " + JSON.stringify(_result, null, 2));
        return doLoginSuccess();
    }, function(_error) {
        Ti.API.error('ERROR: ' + JSON.stringify(_error, null, 2));
        alert("Check Your Username/Password\n" +  _error.error.error);

        //  loginUser();
    });
}

parseService.restoreUser().then(function(_result) {
    console.log("restored user successfully: " + JSON.stringify(_result, null, 2));
    return doLoginSuccess();
}, function(_error) {
    Ti.API.error('ERROR: ' + JSON.stringify(_error, null, 2));
    console.log("no  user found" + _error.error);

    return loginUser();
});

// Logout Button Handler
$.logoutBtn.addEventListener('click', doLogout);

function doLogout() {
    parseService.logoutUser().then(function(_result) {
        console.log("logged out user successfully: " + JSON.stringify(_result, null, 2));
    }, function(_error) {
        Ti.API.error('ERROR: ' + _error.error);
        alert("Error Logging Out User\n" + _error.error.error);
    });
}

//
// PLACE HOLDER PUSH NOTIFICATION CODE
//
Ti.App.addEventListener("parse.push.recieved", function(_event) {
    //if clicks push tray, it will take to alert list page

    OS_IOS && Titanium.UI.iPhone.setAppBadge(0);
});
<commit_msg>setting titles on login/logout button to show state<commit_after>Alloy.Globals.tabGroup = $.index;

function doWindowOpen(evt) {

    if (OS_IOS) {
        return;
    }

    var activity = evt.source.getActivity();

    activity.onCreateOptionsMenu = function(e) {
        var item,
            menu;
        menu = e.menu;
        menu.clear();

        switch(Alloy.Globals.currentTab) {
        // case photoList
        case 1:
            item2 = e.menu.add({
                title : "Add",
                showAsAction : Ti.Android.SHOW_AS_ACTION_ALWAYS,
                icon : Ti.Android.R.drawable.ic_menu_camera
            });
            item2.addEventListener("click", function(e) {
                $.photoListView.addPhoto();
            });

            break;
        }
    };

    Alloy.Globals.tabGroup.addEventListener("focus", function(evt) {
        if ( typeof evt.index !== "undefined") {
            activity.invalidateOptionsMenu();
            Alloy.Globals.currentTab = evt.index;
        }
    });
}

/**
 * called when user clicks on listView. The _event will provide
 * the index of the item clicked in the listView
 *
 * @param {Object} _event
 */
function listItemClicked(_event) {

    // get data using index provided, the items are in the section
    // so we use the index against the section, not the listView
    var currentItem = $.listSection.getItemAt(_event.itemIndex);

    // we save the data as a property so that is how we access
    // the data, we do not need the data array in this case
    var sessionInfo = currentItem.properties.data;

    // now display the data
    alert("clicked on " + JSON.stringify(sessionInfo, null, 2));
}

// lets get some real data!!
Alloy.Globals.parseService = require('parseREST');

// set the API Credentials
Alloy.Globals.parseService.init();

var parseService = Alloy.Globals.parseService;

function saveSessionClicked() {
    var queryResults;

    alert("WIP");
    return;

    parseService.createObject('TutorSession', {
        sessionName : $.sessionName.value,
        sessionLocation : $.sessionLocation.value
    }).then(function(_queryResult) {
        console.log("Success Happened: " + JSON.stringify(_queryResult));
        queryResults = _queryResult;
        return parseService.getObjects('TutorSession');
    }).then(function(_sessions) {
        updateList(_sessions.response.results);
    }, function(_error) {
        console.log("Some Error Happened: " + JSON.stringify(_error));
    });
}

function updateList(_data) {
    var items = _.map(_data, function(element) {

        return {
            properties : {
                data : element // save all attributes
            },
            // bind the labels using the bindId
            name : {
                text : element.tutor.email
            },
            location : {
                text : element.place.Location + "-" + element.place.Name
            }
        };
    });
    // add the items to the section in the ListView
    $.listSection.setItems(items);

}

function whereQueryExample() {

    // find all tutoring sessions that are being done by
    // the tutors with the specified ids
    var whereQueryStr = {
        "tutor" : {
            "$inQuery" : {
                "where" : {
                    "objectId" : {
                        "$in" : ["SCV3V0GqRr", "9uKbg0Hzeb"]
                    }
                },
                "className" : "_User"
            }
        }
    };

    return parseService.getObjects('TutorSession', {
        "urlparams" : {
            "include" : "user,tutor,place",
            "where" : whereQueryStr
        }
    });
}

//
// check to see if we have a user account saved
//

/**
 * logging in a user and then testing some of the functionality
 * of the rest service
 */

function doLoginSuccess() {
    return whereQueryExample().then(function(_sessions) {
        console.log("getTutorSessions: " + JSON.stringify(_sessions.response, null, 2));

        updateList(_sessions.response.results);

        // set the title on the logout/login button
        $.logoutBtn.title = "Logout";

        // open the main view of index.js, which is the tab
        $.index.open();

    }, function(_error) {
        Ti.API.error('ERROR: ' + JSON.stringify(_error, null, 2));
        alert("Error Loading Data\n" + _error.error);

    });
}

function loginUser() {
    return parseService.loginUser("aaronsaunders", "password").then(function(_result) {
        console.log("logged in user successfully: " + JSON.stringify(_result, null, 2));
        return doLoginSuccess();
    }, function(_error) {
        Ti.API.error('ERROR: ' + JSON.stringify(_error, null, 2));
        alert("Check Your Username/Password\n" + _error.error.error);

        // set the title on the logout/login button
        $.logoutBtn.title = "Login";
    });
}

parseService.restoreUser().then(function(_result) {
    console.log("restored user successfully: " + JSON.stringify(_result, null, 2));
    return doLoginSuccess();
}, function(_error) {
    Ti.API.error('ERROR: ' + JSON.stringify(_error, null, 2));
    console.log("no  user found" + _error.error);

    // set the title on the logout/login button
    $.logoutBtn.title = "Login";

    return loginUser();
});

// Logout Button Handler
$.logoutBtn.addEventListener('click', doLogout);

function doLogout() {
    parseService.logoutUser().then(function(_result) {
        console.log("logged out user successfully: " + JSON.stringify(_result, null, 2));

        // set the title on the logout/login button
        $.logoutBtn.title = "Login";

    }, function(_error) {
        Ti.API.error('ERROR: ' + _error.error);
        alert("Error Logging Out User\n" + _error.error.error);
    });
}

//
// PLACE HOLDER PUSH NOTIFICATION CODE
//
Ti.App.addEventListener("parse.push.recieved", function(_event) {
    //if clicks push tray, it will take to alert list page

    OS_IOS && Titanium.UI.iPhone.setAppBadge(0);
});
<|endoftext|>


<commit_before>'use strict';

var path = require('path');

var _ = require('lodash');
var shortId = require('shortid');

/**
 * The options object that is made available to all instances in the service.
 * @typedef {object} Options
 * @property {string} [id]                          A unique id to identify this node
 * @property {string|number} [listen=2206]          Port on which to listen for cluster broadcasts.This can either be a number
 *                                                  which will be used to bind to tcp://0.0.0.0:<port> or the complete host
 *                                                  string.
 * @property {Discovery} [discovery]                Discovery options object, properties depend on the type (default=multicast)
 * @property {string} [discovery.type='multicast']  The type of discovery to be used. See {@link UnicastDiscovery},
 *                                                  {@link MulticastDiscovery}, {@link AwsDiscovery}
 * @property {object} [nodes]                       Configuration options regarding nodes
 * @property {object} [node.maxPeers=INFINTY]       The maximum number of other nodes this service is going to connect.
 * @property {object} [node.checkNetwork=false]     Flag that decides whether we know about all other nodes or need to ask
 *                                                  around in the cluster. This value is set dynamically and should not be
 *                                                  set by the user.
 */

/**
 *  @typedef {object} Discovery
 *  @property {string} type The type of discovery to be used, possible options are unicast, multicast and aws.
 */

/**
 * @typedef {object} UnicastDiscovery
 * @extends Discovery
 * @property {string|string[]} hosts  Used for type unicast. A list of nodes we should attempt connecting to.
 *                                    Note that the port of the host should be the handshake port of that node.
 */

/**
 * Regular expression that checks for a valid ipv4.
 * @type {RegExp}
 */
var ipv4regex = /((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)/;

/**
 * The default options object, that will be combined with any passed in options.
 * @type {Options}
 */
exports.defaultOptions = {
  listen: 2206,
  discovery: {
    type: 'multicast'
  },
  nodes: {
    maxPeers: Number.POSITIVE_INFINITY,
    checkNetwork: false
  },
  debug: false
};

/**
 * The default options for when you choose to use multicast for discovery.
 * @typedef {object} MulticastDiscovery
 * @extends Discovery
 * @property {number} [port=22060]                The port on which to listen for broadcasts
 * @property {string} [address=0.0.0.0]           The adapter address on which to listen for broadcasts
 * @property {number} [sendPort=22060]            The port on which to send broadcast messages
 * @property {string} [broadcast=255.255.255.255] The address used to broadcast this node information on
 * @property {number} [interval=1000]             The interval with which messages are sent in ms.
 */
exports.defaultMulticast = {
  type: 'multicast',
  port: 22060,
  address: '0.0.0.0',
  broadcast: '255.255.255.255',
  interval: 1000
};

/**
 * The default options for when you choose AWS for discovery.
 * @typedef {object} AwsDiscovery
 * @extends Discovery
 */
exports.defaultAws = {

};

/**
 * Sets default options and verifies that all values are in the right format.
 * @param options
 * @returns {*|{}}
 */
exports.normalize = function(options) {
  if (_.isString(options)) {
    options = require(path.join(module.main.filename, options));
  }
  options = options || {};
  this.options = Object.assign({}, exports.defaultOptions, options);
  for (let prop in this.options) {
    if (this.options.hasOwnProperty(prop) && exports[prop]) {
      exports[prop]();
    }
  }
  return this.options;
};

/**
 * Helper function that check whether the host is something that zmq can understand.
 * Reference to protocol definition: {@link http://api.zeromq.org/2-1:zmq-connect}
 * @param host
 * @returns {Array|{index: number, input: string}|*}
 */
exports.isValidHost = function(host) {
  var protocol = host.substr(0, host.indexOf(':'));
  switch (protocol) {
    case 'tcp':
      return !!host.match(/^tcp:\/\/([\d\w]+([\d\w\.]*[\d\w])*|\*):[1-6]?\d{1,4}$/);
    case 'ipc':
      return !!host.match(/^ipc:\/\/[^'"!#$%&+^<=>`]{1,256}$/);
    case 'inproc':
      return !!host.match(/^inproc:\/\/.{1,256}$/);
    case 'pgm':
    case 'epgm':
      return !!host.match(/^tcp:\/\/[\d\w]+([\d\w\.]*[\d\w])*(;[\d\w]+([\d\w\.]*[\d\w])*)*:[1-6]?\d{1,4}$/);
    default:
      throw new Error('Unrecognized protocol in host' + host);
  }
};

/**
 * Sets an automatically generate id if none has been given.
 */
exports.id = function() {
  if (this.options.id) {
    this.options.id = this.options.id.trim();
  } else {
    this.options = shortId.generate();
  }
};

/**
 * Makes sure the connection string for broadcasts is something zeromq can understand.
 */
exports.listen = function() {
  if (_.isNumber(this.options.listen)) {
    this.options.listen = 'tcp://0.0.0.0:' + this.options.listen;
  }
  if (!exports.isValidHost(this.options.listen)) {
    throw new Error('Unable to listen with invalid host setting:' + this.options.listen);
  }
};

/**
 * Makes sure the discovery settings are valid.
 */
exports.discovery = function() {
  let methodMap = {
    multicast: 'multicast',
    multi: 'multicast',
    unicast: 'unicast',
    uni: 'uni',
    aws: 'aws',
    amazon: 'aws'
  };
  if (!methodMap[this.options.discovery.type]) {
    throw new Error('Unidentified discovery method: ' + this.options.discovery.type);
  }
  this.options.discovery.type = methodMap[this.options.discovery.type];
  switch(this.options.discovery.type) {
    case 'unicast':
      if(!this.options.discovery.hosts) {
        throw new Error('Unicast requires at least one host to connect to');
      }
      if(!_.isArray(this.options.discovery.hosts)) {
        this.options.discovery.hosts = [this.options.discovery.hosts];
      }
      _.map(this.options.discovery.hosts, elem => elem.trim());
      _.filter(this.options.discovery.hosts, elem => {
        if (elem.length === 0) {
          return false;
        }
        if (!exports.isValidHost(elem)) {
          throw new Error('One of the given hosts is not a valid address that zeromq can understand: ' + elem);
        }
        return true;
      });
      if (!this.options.discovery.hosts.length) {
        throw new Error('No valid hosts were found while in the configuration');
      }
      break;

    case 'multicast':
      this.options.discovery = Object.assign({}, exports.defaultMulticast, this.options.discovery);
      if (!_.isNumber(this.options.discovery.port) || this.options.discovery.port < 1 || this.options.discovery.port > 65535) {
        throw new Error('The multicast port needs to be a valid number between 1 and 65535');
      }
      if (!this.options.discovery.address.match(ipv4regex)) {
        throw new Error('The given multicast host is not a valid ipv4');
      }
      if (!this.options.discovery.broadcast.match(ipv4regex)) {
        throw new Error('The given broadcast address is not a valid ipv4');
      }
      break;

    case 'aws':
      this.options.discovery = Object.assign({}, exports.defaultAws, this.options.discovery);
      break;
  }
};

/**
 * If debug is set to true the value is initialized with the console.log function, otherwise whatever function was
 * passed in will be used to log debug messages.
 */
exports.debug = function() {
  if (!this.options.debug) {
    return this.options.debug = function() {};
  }
  if (_.isBoolean(this.options.debug)) {
    this.options.debug = console.log;
  }
  if (!_.isFunction(this.options.debug)) {
    throw new Error('Unable to use debug option as it has not been initialized with a logger or boolean');
  }
};
<commit_msg>Fixed invalid pgm regex<commit_after>'use strict';

var path = require('path');

var _ = require('lodash');
var shortId = require('shortid');

/**
 * The options object that is made available to all instances in the service.
 * @typedef {object} Options
 * @property {string} [id]                          A unique id to identify this node
 * @property {string|number} [listen=2206]          Port on which to listen for cluster broadcasts.This can either be a number
 *                                                  which will be used to bind to tcp://0.0.0.0:<port> or the complete host
 *                                                  string.
 * @property {Discovery} [discovery]                Discovery options object, properties depend on the type (default=multicast)
 * @property {string} [discovery.type='multicast']  The type of discovery to be used. See {@link UnicastDiscovery},
 *                                                  {@link MulticastDiscovery}, {@link AwsDiscovery}
 * @property {object} [nodes]                       Configuration options regarding nodes
 * @property {object} [node.maxPeers=INFINTY]       The maximum number of other nodes this service is going to connect.
 * @property {object} [node.checkNetwork=false]     Flag that decides whether we know about all other nodes or need to ask
 *                                                  around in the cluster. This value is set dynamically and should not be
 *                                                  set by the user.
 */

/**
 *  @typedef {object} Discovery
 *  @property {string} type The type of discovery to be used, possible options are unicast, multicast and aws.
 */

/**
 * @typedef {object} UnicastDiscovery
 * @extends Discovery
 * @property {string|string[]} hosts  Used for type unicast. A list of nodes we should attempt connecting to.
 *                                    Note that the port of the host should be the handshake port of that node.
 */

/**
 * Regular expression that checks for a valid ipv4.
 * @type {RegExp}
 */
var ipv4regex = /((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)/;

/**
 * The default options object, that will be combined with any passed in options.
 * @type {Options}
 */
exports.defaultOptions = {
  listen: 2206,
  discovery: {
    type: 'multicast'
  },
  nodes: {
    maxPeers: Number.POSITIVE_INFINITY,
    checkNetwork: false
  },
  debug: false
};

/**
 * The default options for when you choose to use multicast for discovery.
 * @typedef {object} MulticastDiscovery
 * @extends Discovery
 * @property {number} [port=22060]                The port on which to listen for broadcasts
 * @property {string} [address=0.0.0.0]           The adapter address on which to listen for broadcasts
 * @property {number} [sendPort=22060]            The port on which to send broadcast messages
 * @property {string} [broadcast=255.255.255.255] The address used to broadcast this node information on
 * @property {number} [interval=1000]             The interval with which messages are sent in ms.
 */
exports.defaultMulticast = {
  type: 'multicast',
  port: 22060,
  address: '0.0.0.0',
  broadcast: '255.255.255.255',
  interval: 1000
};

/**
 * The default options for when you choose AWS for discovery.
 * @typedef {object} AwsDiscovery
 * @extends Discovery
 */
exports.defaultAws = {

};

/**
 * Sets default options and verifies that all values are in the right format.
 * @param options
 * @returns {*|{}}
 */
exports.normalize = function(options) {
  if (_.isString(options)) {
    options = require(path.join(module.main.filename, options));
  }
  options = options || {};
  this.options = Object.assign({}, exports.defaultOptions, options);
  for (let prop in this.options) {
    if (this.options.hasOwnProperty(prop) && exports[prop]) {
      exports[prop]();
    }
  }
  return this.options;
};

/**
 * Helper function that check whether the host is something that zmq can understand.
 * Reference to protocol definition: {@link http://api.zeromq.org/2-1:zmq-connect}
 * @param host
 * @returns {Array|{index: number, input: string}|*}
 */
exports.isValidHost = function(host) {
  var protocol = host.substr(0, host.indexOf(':'));
  switch (protocol) {
    case 'tcp':
      return !!host.match(/^tcp:\/\/([\d\w]+([\d\w\.]*[\d\w])*|\*):[1-6]?\d{1,4}$/);
    case 'ipc':
      return !!host.match(/^ipc:\/\/[^'"!#$%&+^<=>`]{1,256}$/);
    case 'inproc':
      return !!host.match(/^inproc:\/\/.{1,256}$/);
    case 'pgm':
    case 'epgm':
      return !!host.match(/^e?pgm:\/\/[\d\w]+([\d\w\.]*[\d\w])*(;[\d\w]+([\d\w\.]*[\d\w])*)*:[1-6]?\d{1,4}$/);
    default:
      throw new Error('Unrecognized protocol in host' + host);
  }
};

/**
 * Sets an automatically generate id if none has been given.
 */
exports.id = function() {
  if (this.options.id) {
    this.options.id = this.options.id.trim();
  } else {
    this.options = shortId.generate();
  }
};

/**
 * Makes sure the connection string for broadcasts is something zeromq can understand.
 */
exports.listen = function() {
  if (_.isNumber(this.options.listen)) {
    this.options.listen = 'tcp://0.0.0.0:' + this.options.listen;
  }
  if (!exports.isValidHost(this.options.listen)) {
    throw new Error('Unable to listen with invalid host setting:' + this.options.listen);
  }
};

/**
 * Makes sure the discovery settings are valid.
 */
exports.discovery = function() {
  let methodMap = {
    multicast: 'multicast',
    multi: 'multicast',
    unicast: 'unicast',
    uni: 'uni',
    aws: 'aws',
    amazon: 'aws'
  };
  if (!methodMap[this.options.discovery.type]) {
    throw new Error('Unidentified discovery method: ' + this.options.discovery.type);
  }
  this.options.discovery.type = methodMap[this.options.discovery.type];
  switch(this.options.discovery.type) {
    case 'unicast':
      if(!this.options.discovery.hosts) {
        throw new Error('Unicast requires at least one host to connect to');
      }
      if(!_.isArray(this.options.discovery.hosts)) {
        this.options.discovery.hosts = [this.options.discovery.hosts];
      }
      _.map(this.options.discovery.hosts, elem => elem.trim());
      _.filter(this.options.discovery.hosts, elem => {
        if (elem.length === 0) {
          return false;
        }
        if (!exports.isValidHost(elem)) {
          throw new Error('One of the given hosts is not a valid address that zeromq can understand: ' + elem);
        }
        return true;
      });
      if (!this.options.discovery.hosts.length) {
        throw new Error('No valid hosts were found while in the configuration');
      }
      break;

    case 'multicast':
      this.options.discovery = Object.assign({}, exports.defaultMulticast, this.options.discovery);
      if (!_.isNumber(this.options.discovery.port) || this.options.discovery.port < 1 || this.options.discovery.port > 65535) {
        throw new Error('The multicast port needs to be a valid number between 1 and 65535');
      }
      if (!this.options.discovery.address.match(ipv4regex)) {
        throw new Error('The given multicast host is not a valid ipv4');
      }
      if (!this.options.discovery.broadcast.match(ipv4regex)) {
        throw new Error('The given broadcast address is not a valid ipv4');
      }
      break;

    case 'aws':
      this.options.discovery = Object.assign({}, exports.defaultAws, this.options.discovery);
      break;
  }
};

/**
 * If debug is set to true the value is initialized with the console.log function, otherwise whatever function was
 * passed in will be used to log debug messages.
 */
exports.debug = function() {
  if (!this.options.debug) {
    return this.options.debug = function() {};
  }
  if (_.isBoolean(this.options.debug)) {
    this.options.debug = console.log;
  }
  if (!_.isFunction(this.options.debug)) {
    throw new Error('Unable to use debug option as it has not been initialized with a logger or boolean');
  }
};
<|endoftext|>


<commit_before>/*
    7Breaths
    An application to simplify the clinical measurement of a person's respiratory rate
    Copyright (C) 2013  Neville Dastur neville [at] clinsoftsolutions.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
    PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
    FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.

*/

var APP = Alloy.Globals.APP;

if(OS_ANDROID){
    Alloy = require("alloy")
    _ = require("alloy/underscore")._
}

$.DEFAULT_ROLE_TEXT = "Please choose one ...";

var dataStore = require('datastore');

var ARGS = arguments[0] || {};
$.reachedBottom = false;
$.scrollBottomY = 0;

$.init = function() {
    var htmlfile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, "/tandc.html");
    var html = htmlfile.read().text;
    $.styledlabel.html = html;

    $.roleLabel.text = $.DEFAULT_ROLE_TEXT;

    $.reg_info.text = "Registration is entirely optional, but allows us to better"
    + " understand the data collected and also allows us send feedback to you.";

    Ti.App.addEventListener("androidback" , function() {
        // TODO don't allow close unless ok or cancel selected, i.e. prevent normal back button behaviour
        //this.close();
    });

    $.styledlabel.addEventListener("postlayout",function(e){
        $.scrollBottomY = e.source.getRect().height - $.ScrollWrapper.getSize().height;
        Ti.API.debug("Set $.scrollBottomY = " + $.scrollBottomY);
    });

    $.ScrollWrapper.addEventListener("scroll",$.scrollWrapper_ScrollEvent);

    $.accept_but.addEventListener("click",function(e){
        if ($.reachedBottom === false) {
            Ti.Analytics.featureEvent('APP:Tried_TandC_Accepted');
            alert("Please scroll down through all the Terms and Conditions before accepting them. Thank you.")
            return;
        }
        Ti.Analytics.featureEvent("APP:TandC_ACCEPTED");
        Ti.App.Properties.setBool("APP:TandC_ACCEPTED", true);
        //$.Window.close();
        $.scrollableView.setCurrentPage(1);
    });

    $.reject_but.addEventListener("click",function(e){
        Ti.Analytics.featureEvent('APP:TandC_Rejected');
        alert("Thank you for your interest in 7Breaths. As the terms are not acceptable please un-install the application.")
    });

    $.rolePickerWrapper.addEventListener("click",function(e){
        Ti.API.debug("rolePickerWrapper click")
        $.rolePickerWrapper.visible = false;
    });

    $.rolePicker.addEventListener("click", function(e){
        $.reg_firstname.blur();
        $.reg_surname.blur();
        $.reg_email.blur();
        $.rolePickerWrapper.visible = true;
    });

    $.rolePickerControl.addEventListener("change",function(e){
        Ti.API.debug("Click on picker row: " + e.row.title);
    });

    $.select_role_but.addEventListener("click",function(e){
        Ti.API.debug("Picker row selected: " + $.rolePickerControl.getSelectedRow(0).title);
        $.roleLabel.text = $.rolePickerControl.getSelectedRow(0).title;
    });

    $.register_but.addEventListener("click",function(e){
        var validate_msg = $.Validates();
        if(0 === validate_msg.length) {
            dataStore.RegisterUser(reg_email.value, reg_firstname.value, reg_surname.value,
                $.roleLabel.text);
        }
        else {
            alert(validate_msg);
        }
    });

    // If T&C already accepted then go straight to registration page
    if (Ti.App.Properties.getBool("APP:TandC_ACCEPTED", false)) {
        $.scrollableView.setCurrentPage(1);
    }

}

/**
 * Validate the fields
 * If fails then returns a message. So emmpty string indicates success
 */
$.Validates = function() {
    var msg = "";
    if ( $.reg_email.value.length < 5, $.reg_firstname.value.length < 2, $.reg_surname.value.length < 2) {
        msg += "All fields require a value please.\n";

        if ( $.reg_email.value.length < 5) {
            $.reg_email.borderColor = "#f00";
        }
        if ( $.reg_firstname.value.length < 5) {
            $.reg_firstname.borderColor = "#f00";
        }
        if ( $.reg_surname.value.length < 5) {
            $.reg_surname.borderColor = "#f00";
        }
    }

    if ($.roleLabel.text === $.DEFAULT_ROLE_TEXT) {
        msg += "Please select a role.\n";
    }

    var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;

    if($.reg_email.value.length >= 5 && reg.test($.reg_email.value) == false) {
        msg += "The email address appears to be invalid. Please correct";
        $.reg_email.borderColor = "#f00";
    }

    return msg;
}

$.scrollWrapper_ScrollEvent = function(_event) {
    //Ti.API.info("Scroll x="+_event.x+" y="+_event.y);
    if( $.isScrollBottom(_event.y) ) {
        Ti.API.info("ScrollView reached the bottom.");
        $.reachedBottom = true;
        // Don't need to listen any more
        $.ScrollWrapper.removeEventListener("scroll",$.scrollWrapper_ScrollEvent);
    }
}

$.isScrollBottom = function(_y) {
    return ( (_y >= ($.scrollBottomY-25)) ? true : false );
}

/*
 * Bootstrap
 */
$.init();<commit_msg>Add a registration skip count<commit_after>/*
    7Breaths
    An application to simplify the clinical measurement of a person's respiratory rate
    Copyright (C) 2013  Neville Dastur neville [at] clinsoftsolutions.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
    PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
    FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.

*/

var APP = Alloy.Globals.APP;

if(OS_ANDROID){
    Alloy = require("alloy")
    _ = require("alloy/underscore")._
}

$.DEFAULT_ROLE_TEXT = "Please choose one ...";

var dataStore = require('datastore');

var ARGS = arguments[0] || {};
$.reachedBottom = false;
$.scrollBottomY = 0;

$.init = function() {
    var htmlfile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, "/tandc.html");
    var html = htmlfile.read().text;
    $.styledlabel.html = html;

    $.roleLabel.text = $.DEFAULT_ROLE_TEXT;

    $.reg_info.text = "Registration is entirely optional, but allows us to better"
    + " understand the data collected and also allows us send feedback to you.";

    Ti.App.addEventListener("androidback" , function() {
        // TODO don't allow close unless ok or cancel selected, i.e. prevent normal back button behaviour
        //this.close();
    });

    $.styledlabel.addEventListener("postlayout",function(e){
        $.scrollBottomY = e.source.getRect().height - $.ScrollWrapper.getSize().height;
        Ti.API.debug("Set $.scrollBottomY = " + $.scrollBottomY);
    });

    $.ScrollWrapper.addEventListener("scroll",$.scrollWrapper_ScrollEvent);

    $.accept_but.addEventListener("click",function(e){
        if ($.reachedBottom === false) {
            Ti.Analytics.featureEvent('APP:Tried_TandC_Accepted');
            alert("Please scroll down through all the Terms and Conditions before accepting them. Thank you.")
            return;
        }
        Ti.Analytics.featureEvent("APP:TandC_ACCEPTED");
        Ti.App.Properties.setBool("APP:TandC_ACCEPTED", true);
        //$.Window.close();
        $.scrollableView.setCurrentPage(1);
    });

    $.reject_but.addEventListener("click",function(e){
        Ti.Analytics.featureEvent('APP:TandC_Rejected');
        alert("Thank you for your interest in 7Breaths. As the terms are not acceptable please un-install the application.")
    });

    $.rolePickerWrapper.addEventListener("click",function(e){
        Ti.API.debug("rolePickerWrapper click")
        $.rolePickerWrapper.visible = false;
    });

    $.rolePicker.addEventListener("click", function(e){
        $.reg_firstname.blur();
        $.reg_surname.blur();
        $.reg_email.blur();
        $.rolePickerWrapper.visible = true;
    });

    $.rolePickerControl.addEventListener("change",function(e){
        Ti.API.debug("Click on picker row: " + e.row.title);
    });

    $.select_role_but.addEventListener("click",function(e){
        Ti.API.debug("Picker row selected: " + $.rolePickerControl.getSelectedRow(0).title);
        $.roleLabel.text = $.rolePickerControl.getSelectedRow(0).title;
    });

    $.register_but.addEventListener("click",function(e){
        var validate_msg = $.Validates();
        if(0 === validate_msg.length) {
            dataStore.RegisterUser(reg_email.value, reg_firstname.value, reg_surname.value,
                $.roleLabel.text);
        }
        else {
            alert(validate_msg);
        }
    });

    $.skip_but.addEventListener("click",function(e){
        var _skipCount = Ti.App.Properties.getInt("APP:REG_SKIP_COUNT", 0) + 1;
        Ti.App.Properties.setInt("APP:REG_SKIP_COUNT", _skipCount);
        if (_skipCount < 3) {
            // We only prompt the user twice for registration if skipped
            // and with increasing intervals
            Ti.App.Properties.setInt("APP:REG_COUNTDOWN", _skipCount * 2);
        }
    });

    // If T&C already accepted then go straight to registration page
    if (Ti.App.Properties.getBool("APP:TandC_ACCEPTED", false)) {
        $.scrollableView.setCurrentPage(1);
    }

}

/**
 * Validate the fields
 * If fails then returns a message. So emmpty string indicates success
 */
$.Validates = function() {
    var msg = "";
    if ( $.reg_email.value.length < 5, $.reg_firstname.value.length < 2, $.reg_surname.value.length < 2) {
        msg += "All fields require a value please.\n";

        if ( $.reg_email.value.length < 5) {
            $.reg_email.borderColor = "#f00";
        }
        if ( $.reg_firstname.value.length < 5) {
            $.reg_firstname.borderColor = "#f00";
        }
        if ( $.reg_surname.value.length < 5) {
            $.reg_surname.borderColor = "#f00";
        }
    }

    if ($.roleLabel.text === $.DEFAULT_ROLE_TEXT) {
        msg += "Please select a role.\n";
    }

    var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;

    if($.reg_email.value.length >= 5 && reg.test($.reg_email.value) == false) {
        msg += "The email address appears to be invalid. Please correct";
        $.reg_email.borderColor = "#f00";
    }

    return msg;
}

$.scrollWrapper_ScrollEvent = function(_event) {
    //Ti.API.info("Scroll x="+_event.x+" y="+_event.y);
    if( $.isScrollBottom(_event.y) ) {
        Ti.API.info("ScrollView reached the bottom.");
        $.reachedBottom = true;
        // Don't need to listen any more
        $.ScrollWrapper.removeEventListener("scroll",$.scrollWrapper_ScrollEvent);
    }
}

$.isScrollBottom = function(_y) {
    return ( (_y >= ($.scrollBottomY-25)) ? true : false );
}

/*
 * Bootstrap
 */
$.init();<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.ArrayController.extend({
    actions: {
        createTodo: function(newTitle) {
            // Create the new Todo model
            var todo = this.store.createRecord('todo', {
                title: newTitle,
                isCompleted: false
            });

            // Clear the "New Todo" text field
            this.set('newTitle', '');

            // Save the new model
            todo.save();
        }
    }
});
<commit_msg>Added controller code for todos left<commit_after>import Ember from 'ember';

export default Ember.ArrayController.extend({
    actions: {
        createTodo: function(newTitle) {
            // Create the new Todo model
            var todo = this.store.createRecord('todo', {
                title: newTitle,
                isCompleted: false
            });

            // Clear the "New Todo" text field
            this.set('newTitle', '');

            // Save the new model
            todo.save();
        }
    },

    remaining: function() {
        return this.filterBy('isCompleted', false).get('length');
    }.property('@each.isCompleted'),

    inflection: function() {
        var remaining = this.get('remaining');
        return (remaining === 1) ? 'item' : 'items';
    }.property('remaining')
});
<|endoftext|>


<commit_before>/**
 * Contacts management script allowing ajax usage.
 *
 * @author Julien Blatecky <julien.blatecky@creatiwity.net>
 * @version 0.4.0
 */
$(document).ready(function() {
	var setNote;

	setNote = function($domObject, jsonResponse) {
		var cleaned, app, $container, _i, _len;

		if (jsonResponse['app-name']) {

			app = jsonResponse['app-name'];

			$('[data-wity-note-app="' + app + '"]').remove();

			if (jsonResponse.notes.length > 0) {
				$container = $('

');
				$domObject.before($container);

				for (_i = 0, _len = jsonResponse.notes.length; _i < _len; ++_i) {
					$container.append('
'
						+ '<button type="button" class="close" data-dismiss="alert">×</button>'
						+ jsonResponse.notes[_i].message
						+ '
');
				}
			}
		}
	};

	$('body').on('click', '[data-witycms-submit="ajax"]', function() {
		var $form, $button, url, method, data;

		$button = $(this);
		$button.button('loading');
		$form = $button.closest('form');
		url = $form.attr('action');
		method = $form.attr('method');
		data = $form.serialize();

		$.ajax({
			type: method,
			url: url,
			data: data,
			success: function(response) {
				var jResponse;

				$button.button('reset');

				try {
					jResponse = $.parseJSON(response);
				} catch(e) {
					// Debug code
					// $('body').prepend('
' + response + '
');
					return;
				}

				setNote($form, jResponse);
			}
		});

		return false;
	});

});
<commit_msg>Disabling form on send, removing form from DOM when sending successful<commit_after>/**
 * Contacts management script allowing ajax usage.
 *
 * @author Julien Blatecky <julien.blatecky@creatiwity.net>
 * @version 0.4.0
 */
$(document).ready(function() {
	var setNote;

	setNote = function($domObject, jsonResponse) {
		var cleaned, app, $container, _i, _len;

		if (jsonResponse['app-name']) {

			app = jsonResponse['app-name'];

			$('[data-wity-note-app="' + app + '"]').remove();

			if (jsonResponse.notes.length > 0) {
				$container = $('

');
				$domObject.before($container);

				for (_i = 0, _len = jsonResponse.notes.length; _i < _len; ++_i) {
					$container.append('
'
						+ '<button type="button" class="close" data-dismiss="alert">×</button>'
						+ jsonResponse.notes[_i].message
						+ '
');
				}
			}
		}
	};

	$('body').on('click', '[data-witycms-submit="ajax"]', function() {
		var $form, $button, url, method, data;

		$button = $(this);
		$button.button('loading');
		$form = $button.closest('form');
		url = $form.attr('action');
		method = $form.attr('method');
		data = $form.serialize();

		$('.wity-app.app-contact :input').attr('disabled', true);

		$.ajax({
			type: method,
			url: url,
			data: data,
			success: function(response) {
				var jResponse;

				$button.button('reset');
				$('.wity-app.app-contact :input').attr('disabled', false);

				try {
					jResponse = $.parseJSON(response);
				} catch(e) {
					// Debug code
					// $('body').prepend('
' + response + '
');
					return;
				}

				setNote($form.parent(), jResponse);

				if (jResponse && jResponse.result && jResponse.result.code === 'email_sent') {
					$form.remove();
				}
			}
		});

		return false;
	});

});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { ScrollView, StyleSheet, Dimensions } from 'react-native';
import {
  VictoryAxis,
	VictoryBar,
} from 'victory-chart-native';
import Svg from 'react-native-svg';

import Text from '../components/Text';
import data from '../data';

const defaultHeight = 300;
const defaultWidth = Dimensions.get('window').width;
const volumes = data.ticks.map( d => d.volume);
const highestVolume = Math.max(...volumes);
const lowestVolume = Math.min(...volumes);

class VolumeChart extends Component {
  render() {
    const { ticks, tradingHours } = data;
    return (
      <ScrollView style={styles.container}>
        <Text header>Volume chart</Text>
        <Text>basic volume chart using VictoryBar</Text>
        <VictoryBar
          height={200}
          data={ticks}
          x={(d) => d.time}
          y={'volume'}
        />

        <Text>add some color</Text>
        <VictoryBar
          height={200}
          data={ticks}
          x={(d) => d.time}
          y={'volume'}
          style={{
            data: {
              fill: (d) => { return d.mark ? 'red' : 'green'; }
            }
          }}
        />

        <Text>Add axis, set proper domain</Text>
        <Svg height={defaultHeight} width={defaultWidth}>
          <VictoryAxis
            standalone={false}
            domain={tradingHours.map(t => t * 1000)}
            scale="time"
          />
          <VictoryAxis
            dependentAxis
            standalone={false}
            domain={[lowestVolume, highestVolume]}
            tickFormat={v => v / 100000000}
          />
          <VictoryBar
            domain={{
              x: tradingHours.map(t => t * 1000)
            }}
            standalone={false}
            data={ticks}
            x={(d) => d.time * 1000}
            y={'volume'}
            style={{
              data: {
                fill: (d) => { return d.mark ? 'red' : 'green'; }
              }
            }}
          />
        </Svg>

      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  },
});

export default VolumeChart;
<commit_msg>add new VolumeChart demo to demostrate how to remove padding<commit_after>import React, { Component } from 'react';
import { View, ScrollView, StyleSheet, Dimensions } from 'react-native';
import {
  VictoryAxis,
	VictoryBar,
} from 'victory-chart-native';
import Svg from 'react-native-svg';

import Text from '../components/Text';
import data from '../data';

const defaultHeight = 300;
const defaultWidth = Dimensions.get('window').width;
const volumes = data.ticks.map( d => d.volume);
const highestVolume = Math.max(...volumes);
const lowestVolume = Math.min(...volumes);

class VolumeChart extends Component {
  render() {
    const { ticks, tradingHours } = data;
    return (
      <ScrollView style={styles.container}>
        <View style={{ paddingBottom: 50 }}>
          <Text heading>Volume chart</Text>
          <Text>basic volume chart using VictoryBar</Text>
          <VictoryBar
            height={200}
            data={ticks}
            x={(d) => d.time}
            y={'volume'}
          />

          <Text>add some color</Text>
          <VictoryBar
            height={200}
            data={ticks}
            x={(d) => d.time}
            y={'volume'}
            style={{
              data: {
                fill: (d) => { return d.mark ? 'red' : 'green'; }
              }
            }}
          />

          <Text>Add axis, set proper domain</Text>
          <Svg height={defaultHeight} width={defaultWidth}>
            <VictoryAxis
              standalone={false}
              domain={tradingHours.map(t => t * 1000)}
              scale="time"
            />
            <VictoryAxis
              dependentAxis
              standalone={false}
              domain={[lowestVolume, highestVolume]}
              tickFormat={v => v / 100000000}
            />
            <VictoryBar
              domain={{
                x: tradingHours.map(t => t * 1000)
              }}
              standalone={false}
              data={ticks}
              x={(d) => d.time * 1000}
              y={'volume'}
              style={{
                data: {
                  fill: (d) => { return d.mark ? 'red' : 'green'; }
                }
              }}
            />
          </Svg>

          <Text>remove the default padding to make the volume chart more compact and display fewer tickCount</Text>
          <Svg height={100} width={defaultWidth}>
            <VictoryAxis
              height={100}
              padding={{ left: 50, top: 0, bottom: 0, right: 50 }}
              standalone={false}
              domain={tradingHours.map(t => t * 1000)}
              scale="time"
            />
            <VictoryAxis
              dependentAxis
              padding={{ left: 50, top: 5, bottom: 0, right: 50 }}
              height={100}
              standalone={false}
              domain={[lowestVolume, highestVolume]}
              tickFormat={v => v / 100000000}
              tickCount={2}
            />
            <VictoryBar
              padding={{ left: 50, top: 5, bottom: 0, right: 50 }}
              height={100}
              domain={{
                x: tradingHours.map(t => t * 1000)
              }}
              standalone={false}
              data={ticks}
              x={(d) => d.time * 1000}
              y={'volume'}
              style={{
                data: {
                  fill: (d) => { return d.mark ? 'red' : 'green'; }
                }
              }}
            />
          </Svg>
        </View>
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  },
});

export default VolumeChart;
<|endoftext|>