<commit_before>///<reference path="jquery.unserializeform.js"/>
///<reference path="Order.js"/>
//Self-Executing Anonymous Function
//Adding New Functionality to Purchasing for Edit
(function (purchasing, $, undefined) {
    "use strict";
    var orderform = "orderform";
    var orderfinancial = "orderfinancial";
    var storeOrderFormTimer;

    //Public Method
    purchasing.initLocalStorage = function () {
        if (window.Modernizr.localstorage) {
            attachFormSerializationEvents();
            attachAutosaveEvents();
        }
    };

    purchasing.initTourNotification = function () {
        if (window.Modernizr.localstorage) {
            attachTourEvents();
        }
    };

    purchasing.initSaveOrderRequest = function (url) {
        attachSaveEvents(url);
    };

    purchasing.clearAutosaveData = function () {
        localStorage.removeItem(orderform);
        localStorage.removeItem(orderfinancial);
    };

    function attachAutosaveEvents() {
        loadExistingForm();

        $("#order-form").delegate(":input", 'change', function () {
            var delay = 1000; //delay the save one second to avoid gratuitous saving
            clearTimeout(storeOrderFormTimer);
            storeOrderFormTimer = setTimeout(purchasing.storeOrderForm, delay);
        });

        $("#file-uploader").bind("fileuploaded", function (e) {
            e.preventDefault();
            purchasing.storeOrderForm();
        });

        $("#order-form").submit(function () { //TODO: maybe subscribe to an event so only valid orders clear the localstorage
            purchasing.clearAutosaveData(); //On submit, clear the saved temp data
        });

        function loadExistingForm() {
            if (window.location.toString().indexOf("?loadform=true") !== -1) {
                purchasing.loadOrderForm();
                return;
            }

            var savedForm = localStorage[orderform];

            if (savedForm !== undefined && savedForm !== null) {
                if (window.confirm(purchasing.getMessage('LoadForm'))) {
                    purchasing.loadOrderForm();
                } else {
                    localStorage.removeItem(orderform);
                }
            }
        }
    }

    function attachFormSerializationEvents() {
        purchasing.storeOrderForm = function () {
            localStorage.setItem(orderform, $("#order-form").serialize());
            localStorage.setItem(orderfinancial, ko.toJSON(purchasing.OrderModel));
        };

        purchasing.loadOrderForm = function () {
            $("#order-form").unserializeForm(localStorage[orderform],
                {
                    'callback': bindFormValues,
                    'override-values': true
                }
            );

            loadFinancialData(localStorage[orderfinancial]);
        };

        function loadFinancialData(financialData) {
            purchasing.OrderModel.disableSubaccountLoading = true;

            var data = JSON.parse(financialData); //TODO: checkout plugin for IE7 and older

            var model = purchasing.OrderModel;
            model.items.removeAll();
            model.splitType(data.splitType);
            model.orderSplitType(data.orderSplitType);
            model.shipping(data.shipping);
            model.freight(data.freight);
            model.tax(data.tax);

            purchasing.OrderModel.disableSubaccountLoading = true; //Don't search subaccounts when loading existing selections
            $.each(data.items, function (index, item) {
                var lineItem = new purchasing.LineItem(index, model);

                lineItem.quantity(item.quantity);
                lineItem.unit(item.unit);
                lineItem.desc(item.desc);
                lineItem.price(item.price);
                lineItem.catalogNumber(item.catalogNumber);

                lineItem.commodity(item.commodityCode);
                lineItem.url(item.url);
                lineItem.note(item.notes);

                if (lineItem.hasDetails()) {
                    lineItem.showDetails(true);
                }

                if (model.splitType() === "Line") {
                    var lineSplitCount = model.lineSplitCount(); //keep starting split count, and increment until we push the lineItem
                    $.each(item.splits, function (i, split) {
                        var newSplit = new purchasing.LineSplit(lineSplitCount++, lineItem);

                        addAccountIfNeeded(split.account, split.account);
                        addSubAccountIfNeeded(split.subAccount, newSplit.subAccounts);

                        newSplit.amountComputed(split.amount);
                        newSplit.account(split.account);
                        newSplit.subAccount(split.subAccount);
                        newSplit.project(split.project);

                        lineItem.splits.push(newSplit);
                    });
                }

                model.items.push(lineItem);
            });

            //Lines are in, now add Order splits if needed
            if (model.splitType() === "Order") {
                $.each(data.splits, function (i, split) {
                    //Create a new split, index starting at 0, and the model is the order/$root
                    var newSplit = new purchasing.OrderSplit(i, model);

                    addAccountIfNeeded(split.account, split.account);
                    addSubAccountIfNeeded(split.subAccount, newSplit.subAccounts);

                    newSplit.amountComputed(split.amount);
                    newSplit.account(split.account);
                    newSplit.subAccount(split.subAccount);
                    newSplit.project(split.project);

                    model.splits.push(newSplit);
                });
            }

            //Add the basic account info if there are no splits (aka just one split)
            if (model.splitType() === "None") {
                addAccountIfNeeded(data.account, data.account);
                addSubAccountIfNeeded(data.subAccount, model.subAccounts);

                model.account(data.account);
                model.subAccount(data.subAccount);
                model.project(data.project);
            }

            purchasing.OrderModel.disableSubaccountLoading = false; //Turn auto subaccount loading back on now that we are finished
        }

        //If the account is not in list of accounts, add it
        function addAccountIfNeeded(account, accountName) {
            if (account === undefined) {
                return;
            }

            var accountIfFound = ko.utils.arrayFirst(purchasing.OrderModel.accounts(), function (item) {
                return item.id === account;
            });

            if (accountIfFound === null) { //not found, add to list
                purchasing.OrderModel.addAccount(account, account, accountName);
            }
        }

        //If the subAccount is not in the associated subAccount list, add it
        function addSubAccountIfNeeded(subAccount, subAccounts) {
            if (subAccount === undefined) {
                return;
            }

            var subAccountIfFound = ko.utils.arrayFirst(subAccounts(), function (item) {
                return item === subAccount;
            });

            if (subAccountIfFound === null) { //not found, reset list and add
                purchasing.OrderModel.clearSubAccounts(subAccounts);
                purchasing.OrderModel.addSubAccount(subAccounts, subAccount, subAccount, '');
            }
        }

        function bindFormValues(key, val, el) {
            if (el.is(":checkbox")) {
                if (key === 'restricted.status') {
                    el.attr('checked', 'checked');
                    $("#order-restricted-fields").show();
                    return true;
                } else if (key === 'conditionalApprovals') {
                    //find the checkbox with the correct value, and check it
                    $(":checkbox[value=" + val + "]", "#conditional-approval-section").attr("checked", "checked");
                    return true;
                }
                return false; //handle all other checkboxes regularly
            } else if (el.attr("name") === "__RequestVerificationToken") {
                return true; //don't replace the request verification token
            } else if (key === "fileIds") {
                //manually parse fileids. if matches are found, place the ids in inputs and tell the user
                var matches = localStorage[orderform].match(/fileIds=([\w-]*)/g);

                if (matches.length > 0) {
                    var fileInputs = "";
                    $(matches).each(function () {
                        var id = this.replace("fileIds=", "");
                        fileInputs += "<input type='hidden' name='fileIds' value='" + id + "'>";
                    });

                    $("#order-preferences-list")
                        .append("

Existing Attachments:
" +
                            "
You have " + matches.length + " saved file(s) associated from earlier" +
                            fileInputs + "

");

                    console.log(fileInputs);
                }

                return true;
            }

            return false;
        }
    }

    // Before the tour, save off the form if desired and put the split back into its original state
    purchasing.preTour = function (saveForm) {
        if (saveForm) {
            purchasing.storeOrderForm();

            localStorage["tour" + orderform] = localStorage[orderform];
            localStorage["tour" + orderfinancial] = localStorage[orderfinancial];
        }
    };

    //Reset the orderform to the pre tour state if desired
    purchasing.postTour = function (restoreForm) {
        if (restoreForm) {
            localStorage[orderform] = localStorage["tour" + orderform];
            localStorage[orderfinancial] = localStorage["tour" + orderfinancial];

            var append = window.location.toString().indexOf("?loadform=true") === -1 ? "?loadform=true" : "";
            window.location = window.location + append;
        } else {
            window.location = window.location; //just reload the page, losing changes
        }
    };

    function attachTourEvents() {
        checkFirstTime();

        $(".tour-message").on('click', '#hide-tour', function (e) {
            e.preventDefault();

            localStorage[userTourToken()] = true;
            $(".tour-message").remove();
        });

        $(".tour-message").on('click', '#take-tour', function (e) {
            e.preventDefault();

            purchasing.takeTour("intro"); //take the intro tour
        });

        $("#tour-intro").click(function (e) {
            e.preventDefault();

            purchasing.takeTour("intro"); //take the intro tour
        });

        function checkFirstTime() {
            var usertoken = userTourToken();

            if (localStorage[usertoken] === undefined || localStorage[usertoken] === null) {
                localStorage[usertoken] = false;
            }

            var message;

            if (localStorage[usertoken] === 'false') {
                message = "Check out our guided tour for this page: ";
                var statusMessage = $("
" + message +
                    "Take The Tour | Hide
");
                $(".main > header").prepend(statusMessage);
            }
        }

        function userTourToken() {
            return "user-tour-" + $("#userid").html();
        }
    }

    function attachSaveEvents(url) {
        $("#order-save-dialog").dialog({
            autoOpen: false,
            height: 400,
            width: 500,
            modal: true,
            buttons: {
                "Save Order": function () { saveOrder(this); },
                "Cancel": function () { $(this).dialog("close"); }
            }
        });

        $("#save-btn").click(function (e) {
            e.preventDefault();

            $.get(purchasing._getOption("GetRequesters"), null, function (result) {
                $("option:not(:first)", "#order-save-prepared-for").remove(); //remove all but default

                $.each(result, function (key, value) {
                    $('#order-save-prepared-for')
                        .append($("<option>")
                                .attr("value", value.Id)
                                .text(value.Name));
                });
            });

            $("#order-save-dialog").dialog("open");
        });

        function saveOrder(dialog) {
            var form = $("#order-save-form");

            if (form.validate().form() === false) {
                return; //don't save the order if the form is invalid
            }

            var formData = $("#order-form").serialize();
            var accountData = ko.toJSON(purchasing.OrderModel);
            var saveId = $("#formSaveId").val();
            var saveName = $("#order-save-name").val();
            var preparedFor = $("#order-save-prepared-for").val();
            var workgroupId = $("#workgroup").val();
            var token = purchasing._getOption('AntiForgeryToken');

            $.post(url,
                { saveId: saveId, saveName: saveName, preparedFor: preparedFor, formData: formData, accountData: accountData, workgroupId: workgroupId, __RequestVerificationToken: token },
                function (result) {
                    if (result.success) {
                        purchasing.clearAutosaveData();
                        window.location = result.redirect;
                    } else {
                        alert("An error has occured, please try again.");
                    }
                }
            );

            $("#order-save-message").show();
            $("#save-btn").button("disable");
        }
    }

} (window.purchasing = window.purchasing || {}, jQuery));<commit_msg>once localstorage is bound, trigger ddl update<commit_after>///<reference path="jquery.unserializeform.js"/>
///<reference path="Order.js"/>
//Self-Executing Anonymous Function
//Adding New Functionality to Purchasing for Edit
(function (purchasing, $, undefined) {
    "use strict";
    var orderform = "orderform";
    var orderfinancial = "orderfinancial";
    var storeOrderFormTimer;

    //Public Method
    purchasing.initLocalStorage = function () {
        if (window.Modernizr.localstorage) {
            attachFormSerializationEvents();
            attachAutosaveEvents();
        }
    };

    purchasing.initTourNotification = function () {
        if (window.Modernizr.localstorage) {
            attachTourEvents();
        }
    };

    purchasing.initSaveOrderRequest = function (url) {
        attachSaveEvents(url);
    };

    purchasing.clearAutosaveData = function () {
        localStorage.removeItem(orderform);
        localStorage.removeItem(orderfinancial);
    };

    function attachAutosaveEvents() {
        loadExistingForm();

        $("#order-form").delegate(":input", 'change', function () {
            var delay = 1000; //delay the save one second to avoid gratuitous saving
            clearTimeout(storeOrderFormTimer);
            storeOrderFormTimer = setTimeout(purchasing.storeOrderForm, delay);
        });

        $("#file-uploader").bind("fileuploaded", function (e) {
            e.preventDefault();
            purchasing.storeOrderForm();
        });

        $("#order-form").submit(function () { //TODO: maybe subscribe to an event so only valid orders clear the localstorage
            purchasing.clearAutosaveData(); //On submit, clear the saved temp data
        });

        function loadExistingForm() {
            if (window.location.toString().indexOf("?loadform=true") !== -1) {
                purchasing.loadOrderForm();
                return;
            }

            var savedForm = localStorage[orderform];

            if (savedForm !== undefined && savedForm !== null) {
                if (window.confirm(purchasing.getMessage('LoadForm'))) {
                    purchasing.loadOrderForm();
                } else {
                    localStorage.removeItem(orderform);
                }
            }
        }
    }

    function attachFormSerializationEvents() {
        purchasing.storeOrderForm = function () {
            localStorage.setItem(orderform, $("#order-form").serialize());
            localStorage.setItem(orderfinancial, ko.toJSON(purchasing.OrderModel));
        };

        purchasing.loadOrderForm = function () {
            $("#order-form").unserializeForm(localStorage[orderform],
                {
                    'callback': bindFormValues,
                    'override-values': true
                }
            );

            recreateDropDownLists();

            loadFinancialData(localStorage[orderfinancial]);
        };

        function recreateDropDownLists() {
            $("#vendor").trigger("liszt:updated");
            $("#shipAddress").trigger("liszt:updated");
        }

        function loadFinancialData(financialData) {
            purchasing.OrderModel.disableSubaccountLoading = true;

            var data = JSON.parse(financialData); //TODO: checkout plugin for IE7 and older

            var model = purchasing.OrderModel;
            model.items.removeAll();
            model.splitType(data.splitType);
            model.orderSplitType(data.orderSplitType);
            model.shipping(data.shipping);
            model.freight(data.freight);
            model.tax(data.tax);

            purchasing.OrderModel.disableSubaccountLoading = true; //Don't search subaccounts when loading existing selections
            $.each(data.items, function (index, item) {
                var lineItem = new purchasing.LineItem(index, model);

                lineItem.quantity(item.quantity);
                lineItem.unit(item.unit);
                lineItem.desc(item.desc);
                lineItem.price(item.price);
                lineItem.catalogNumber(item.catalogNumber);

                lineItem.commodity(item.commodityCode);
                lineItem.url(item.url);
                lineItem.note(item.notes);

                if (lineItem.hasDetails()) {
                    lineItem.showDetails(true);
                }

                if (model.splitType() === "Line") {
                    var lineSplitCount = model.lineSplitCount(); //keep starting split count, and increment until we push the lineItem
                    $.each(item.splits, function (i, split) {
                        var newSplit = new purchasing.LineSplit(lineSplitCount++, lineItem);

                        addAccountIfNeeded(split.account, split.account);
                        addSubAccountIfNeeded(split.subAccount, newSplit.subAccounts);

                        newSplit.amountComputed(split.amount);
                        newSplit.account(split.account);
                        newSplit.subAccount(split.subAccount);
                        newSplit.project(split.project);

                        lineItem.splits.push(newSplit);
                    });
                }

                model.items.push(lineItem);
            });

            //Lines are in, now add Order splits if needed
            if (model.splitType() === "Order") {
                $.each(data.splits, function (i, split) {
                    //Create a new split, index starting at 0, and the model is the order/$root
                    var newSplit = new purchasing.OrderSplit(i, model);

                    addAccountIfNeeded(split.account, split.account);
                    addSubAccountIfNeeded(split.subAccount, newSplit.subAccounts);

                    newSplit.amountComputed(split.amount);
                    newSplit.account(split.account);
                    newSplit.subAccount(split.subAccount);
                    newSplit.project(split.project);

                    model.splits.push(newSplit);
                });
            }

            //Add the basic account info if there are no splits (aka just one split)
            if (model.splitType() === "None") {
                addAccountIfNeeded(data.account, data.account);
                addSubAccountIfNeeded(data.subAccount, model.subAccounts);

                model.account(data.account);
                model.subAccount(data.subAccount);
                model.project(data.project);
            }

            purchasing.OrderModel.disableSubaccountLoading = false; //Turn auto subaccount loading back on now that we are finished
        }

        //If the account is not in list of accounts, add it
        function addAccountIfNeeded(account, accountName) {
            if (account === undefined) {
                return;
            }

            var accountIfFound = ko.utils.arrayFirst(purchasing.OrderModel.accounts(), function (item) {
                return item.id === account;
            });

            if (accountIfFound === null) { //not found, add to list
                purchasing.OrderModel.addAccount(account, account, accountName);
            }
        }

        //If the subAccount is not in the associated subAccount list, add it
        function addSubAccountIfNeeded(subAccount, subAccounts) {
            if (subAccount === undefined) {
                return;
            }

            var subAccountIfFound = ko.utils.arrayFirst(subAccounts(), function (item) {
                return item === subAccount;
            });

            if (subAccountIfFound === null) { //not found, reset list and add
                purchasing.OrderModel.clearSubAccounts(subAccounts);
                purchasing.OrderModel.addSubAccount(subAccounts, subAccount, subAccount, '');
            }
        }

        function bindFormValues(key, val, el) {
            if (el.is(":checkbox")) {
                if (key === 'restricted.status') {
                    el.attr('checked', 'checked');
                    $("#order-restricted-fields").show();
                    return true;
                } else if (key === 'conditionalApprovals') {
                    //find the checkbox with the correct value, and check it
                    $(":checkbox[value=" + val + "]", "#conditional-approval-section").attr("checked", "checked");
                    return true;
                }
                return false; //handle all other checkboxes regularly
            } else if (el.attr("name") === "__RequestVerificationToken") {
                return true; //don't replace the request verification token
            } else if (key === "fileIds") {
                //manually parse fileids. if matches are found, place the ids in inputs and tell the user
                var matches = localStorage[orderform].match(/fileIds=([\w-]*)/g);

                if (matches.length > 0) {
                    var fileInputs = "";
                    $(matches).each(function () {
                        var id = this.replace("fileIds=", "");
                        fileInputs += "<input type='hidden' name='fileIds' value='" + id + "'>";
                    });

                    $("#order-preferences-list")
                        .append("

Existing Attachments:
" +
                            "
You have " + matches.length + " saved file(s) associated from earlier" +
                            fileInputs + "

");

                    console.log(fileInputs);
                }

                return true;
            }

            return false;
        }
    }

    // Before the tour, save off the form if desired and put the split back into its original state
    purchasing.preTour = function (saveForm) {
        if (saveForm) {
            purchasing.storeOrderForm();

            localStorage["tour" + orderform] = localStorage[orderform];
            localStorage["tour" + orderfinancial] = localStorage[orderfinancial];
        }
    };

    //Reset the orderform to the pre tour state if desired
    purchasing.postTour = function (restoreForm) {
        if (restoreForm) {
            localStorage[orderform] = localStorage["tour" + orderform];
            localStorage[orderfinancial] = localStorage["tour" + orderfinancial];

            var append = window.location.toString().indexOf("?loadform=true") === -1 ? "?loadform=true" : "";
            window.location = window.location + append;
        } else {
            window.location = window.location; //just reload the page, losing changes
        }
    };

    function attachTourEvents() {
        checkFirstTime();

        $(".tour-message").on('click', '#hide-tour', function (e) {
            e.preventDefault();

            localStorage[userTourToken()] = true;
            $(".tour-message").remove();
        });

        $(".tour-message").on('click', '#take-tour', function (e) {
            e.preventDefault();

            purchasing.takeTour("intro"); //take the intro tour
        });

        $("#tour-intro").click(function (e) {
            e.preventDefault();

            purchasing.takeTour("intro"); //take the intro tour
        });

        function checkFirstTime() {
            var usertoken = userTourToken();

            if (localStorage[usertoken] === undefined || localStorage[usertoken] === null) {
                localStorage[usertoken] = false;
            }

            var message;

            if (localStorage[usertoken] === 'false') {
                message = "Check out our guided tour for this page: ";
                var statusMessage = $("
" + message +
                    "Take The Tour | Hide
");
                $(".main > header").prepend(statusMessage);
            }
        }

        function userTourToken() {
            return "user-tour-" + $("#userid").html();
        }
    }

    function attachSaveEvents(url) {
        $("#order-save-dialog").dialog({
            autoOpen: false,
            height: 400,
            width: 500,
            modal: true,
            buttons: {
                "Save Order": function () { saveOrder(this); },
                "Cancel": function () { $(this).dialog("close"); }
            }
        });

        $("#save-btn").click(function (e) {
            e.preventDefault();

            $.get(purchasing._getOption("GetRequesters"), null, function (result) {
                $("option:not(:first)", "#order-save-prepared-for").remove(); //remove all but default

                $.each(result, function (key, value) {
                    $('#order-save-prepared-for')
                        .append($("<option>")
                                .attr("value", value.Id)
                                .text(value.Name));
                });
            });

            $("#order-save-dialog").dialog("open");
        });

        function saveOrder(dialog) {
            var form = $("#order-save-form");

            if (form.validate().form() === false) {
                return; //don't save the order if the form is invalid
            }

            var formData = $("#order-form").serialize();
            var accountData = ko.toJSON(purchasing.OrderModel);
            var saveId = $("#formSaveId").val();
            var saveName = $("#order-save-name").val();
            var preparedFor = $("#order-save-prepared-for").val();
            var workgroupId = $("#workgroup").val();
            var token = purchasing._getOption('AntiForgeryToken');

            $.post(url,
                { saveId: saveId, saveName: saveName, preparedFor: preparedFor, formData: formData, accountData: accountData, workgroupId: workgroupId, __RequestVerificationToken: token },
                function (result) {
                    if (result.success) {
                        purchasing.clearAutosaveData();
                        window.location = result.redirect;
                    } else {
                        alert("An error has occured, please try again.");
                    }
                }
            );

            $("#order-save-message").show();
            $("#save-btn").button("disable");
        }
    }

} (window.purchasing = window.purchasing || {}, jQuery));<|endoftext|>


<commit_before>
OpenSpending = "OpenSpending" in window ? OpenSpending : {};

(function ($) {

  OpenSpending.Bubbletree = function (elem, context, state) {
  var self = this;

  var resources = [OpenSpending.scriptRoot + "/lib/vendor/underscore.js",
                 OpenSpending.scriptRoot + "/lib/aggregator.js",
                 OpenSpending.scriptRoot + "/lib/utils/utils.js",
                 OpenSpending.scriptRoot + "/lib/vendor/jquery.qtip.min.js",
                 OpenSpending.scriptRoot + "/lib/vendor/jquery.history.js",
                 OpenSpending.scriptRoot + "/lib/vendor/raphael-min.js",
                 OpenSpending.scriptRoot + "/lib/vendor/vis4.js",
                 OpenSpending.scriptRoot + "/lib/vendor/Tween.js",
                 OpenSpending.scriptRoot + "/lib/vendor/bubbletree/2.0/bubbletree.js",
                 OpenSpending.scriptRoot + "/widgets/bubbletree/css/bubbletree.css"
                 ];

  this.context = context;
  this.state = state;

  this.configure = function(endConfigure) {
    self.$qb.empty();
    var qb = new OpenSpending.Widgets.QueryBuilder(
      self.$qb, self.update, endConfigure, self.context, [
            {
              variable: 'drilldowns',
              label: 'Levels:',
              type: 'select',
              'default': self.state.drilldowns,
              help: 'Each selected dimension will display as an additional level of bubbles.'
            },
            {
              variable: 'year',
              label: 'Year:',
              type: 'slider',
              'dimension': 'time',
              'attribute': 'year',
              'default': self.state.year,
              help: 'Filter by year.'
            },
            {
              variable: 'cuts',
              label: 'Filters:',
              type: 'cuts',
              'default': self.state.cuts,
              help: 'Limit the set of data to display.'
            }
          ]
    );
  };

  this.serialize = function() { return self.state; };

  this.dataLoaded = function(data) {
    self.bubbleTree = new BubbleTree({
          data: data,
          container: '#' + self.$e.prop('id'),
          bubbleType: self.state.bubbleType || self.context.bubbleType || 'icon',
          // remove all colors coming from OpenSpending API
          clearColors: self.state.clearColors || false,
          tooltip: {
              qtip: true,
              delay: 800,
              content: function(node) {
                  return [node.label, '
'+(node.description ? node.description : 'No description given')+'

£ '+node.famount+'
'];
              }
          }
    });
  };

  this.update = function(state) {
    //console.log(state);
    self.$e.empty();
    self.state = state;

    var cuts = [];
    for (var field in self.state.cuts) {
      cuts.push(field + ':' + self.state.cuts[field]);
    }
    if (self.state.drilldowns) {
      // call openspending api for data
      self.aggregator = new OpenSpending.Aggregator({
          siteUrl: self.context.siteUrl,
          dataset: self.context.dataset,
          rootNodeLabel: self.context.rootNodeLabel || 'Total',
          drilldowns: self.state.drilldowns || [],
          cuts: cuts,
          breakdown: self.state.breakdown,
          callback: self.dataLoaded
      });
    }
  };

  this.getDownlaodURL = function() {
    return self.aggregator.getCSVURL();
  };

  this.init = function() {
    self.$e = elem;
    self.$e.before('

');
    self.$qb = elem.prev();
    self.$e.addClass("bubbletree-widget");
    var $tooltip = $('
Tooltip
');
    elem.append($tooltip);
    $tooltip.hide();

    this.update(self.state);

};

  // The rest of this function is suitable for copypasta into other
  // plugins: load all scripts we need, and return a promise object
  // that will fire when the class is ready
  var dfd = $.Deferred();
  dfd.done(function(that) {that.init();});

  // The rest of this function is suitable for copypasta into other
  // plugins: load all scripts we need, and return a promise object
  // that will fire when the class is ready
  var dfd = $.Deferred();
  dfd.done(function(that) {that.init();});

  if (!window.bubbletreeWidgetLoaded) {
    yepnope({
      load: resources,
      complete: function() {
        window.bubbletreeWidgetLoaded = true;
        dfd.resolve(self);
      }
    });
  } else {
    dfd.resolve(self);
  }

  return dfd.promise();
};

})(jQuery);

  <commit_msg>Do not hardcode the currency to £ in the bubbletree. Fixes #467<commit_after>
OpenSpending = "OpenSpending" in window ? OpenSpending : {};

(function ($) {

  OpenSpending.Bubbletree = function (elem, context, state) {
  var self = this;

  var resources = [OpenSpending.scriptRoot + "/lib/vendor/underscore.js",
                 OpenSpending.scriptRoot + "/lib/aggregator.js",
                 OpenSpending.scriptRoot + "/lib/utils/utils.js",
                 OpenSpending.scriptRoot + "/lib/vendor/jquery.qtip.min.js",
                 OpenSpending.scriptRoot + "/lib/vendor/jquery.history.js",
                 OpenSpending.scriptRoot + "/lib/vendor/raphael-min.js",
                 OpenSpending.scriptRoot + "/lib/vendor/vis4.js",
                 OpenSpending.scriptRoot + "/lib/vendor/Tween.js",
                 OpenSpending.scriptRoot + "/lib/vendor/bubbletree/2.0/bubbletree.js",
                 OpenSpending.scriptRoot + "/widgets/bubbletree/css/bubbletree.css"
                 ];

  this.context = context;
  this.state = state;

  this.configure = function(endConfigure) {
    self.$qb.empty();
    var qb = new OpenSpending.Widgets.QueryBuilder(
      self.$qb, self.update, endConfigure, self.context, [
            {
              variable: 'drilldowns',
              label: 'Levels:',
              type: 'select',
              'default': self.state.drilldowns,
              help: 'Each selected dimension will display as an additional level of bubbles.'
            },
            {
              variable: 'year',
              label: 'Year:',
              type: 'slider',
              'dimension': 'time',
              'attribute': 'year',
              'default': self.state.year,
              help: 'Filter by year.'
            },
            {
              variable: 'cuts',
              label: 'Filters:',
              type: 'cuts',
              'default': self.state.cuts,
              help: 'Limit the set of data to display.'
            }
          ]
    );
  };

  this.serialize = function() { return self.state; };

  this.dataLoaded = function(data) {
    self.bubbleTree = new BubbleTree({
          data: data,
          container: '#' + self.$e.prop('id'),
          bubbleType: self.state.bubbleType || self.context.bubbleType || 'icon',
          // remove all colors coming from OpenSpending API
          clearColors: self.state.clearColors || false,
          tooltip: {
              qtip: true,
              delay: 800,
              content: function(node) {
                  var formattedAmount = OpenSpending.Utils.currencySymbol(node.currency)+' '+node.famount;
                  return [node.label, '
'+(node.description ? node.description : 'No description given')+'

'+formattedAmount+'
'];
              }
          }
    });
  };

  this.update = function(state) {
    //console.log(state);
    self.$e.empty();
    self.state = state;

    var cuts = [];
    for (var field in self.state.cuts) {
      cuts.push(field + ':' + self.state.cuts[field]);
    }
    if (self.state.drilldowns) {
      // call openspending api for data
      self.aggregator = new OpenSpending.Aggregator({
          siteUrl: self.context.siteUrl,
          dataset: self.context.dataset,
          rootNodeLabel: self.context.rootNodeLabel || 'Total',
          drilldowns: self.state.drilldowns || [],
          cuts: cuts,
          breakdown: self.state.breakdown,
          callback: self.dataLoaded
      });
    }
  };

  this.getDownlaodURL = function() {
    return self.aggregator.getCSVURL();
  };

  this.init = function() {
    self.$e = elem;
    self.$e.before('

');
    self.$qb = elem.prev();
    self.$e.addClass("bubbletree-widget");
    var $tooltip = $('
Tooltip
');
    elem.append($tooltip);
    $tooltip.hide();

    this.update(self.state);

};

  // The rest of this function is suitable for copypasta into other
  // plugins: load all scripts we need, and return a promise object
  // that will fire when the class is ready
  var dfd = $.Deferred();
  dfd.done(function(that) {that.init();});

  // The rest of this function is suitable for copypasta into other
  // plugins: load all scripts we need, and return a promise object
  // that will fire when the class is ready
  var dfd = $.Deferred();
  dfd.done(function(that) {that.init();});

  if (!window.bubbletreeWidgetLoaded) {
    yepnope({
      load: resources,
      complete: function() {
        window.bubbletreeWidgetLoaded = true;
        dfd.resolve(self);
      }
    });
  } else {
    dfd.resolve(self);
  }

  return dfd.promise();
};

})(jQuery);

  <|endoftext|>


<commit_before>var Lovi = Lovi || {};

Lovi.settings = {
	req: {}
}

Lovi.default = function(o) {
	if(o.req && o.req.options) {
		Lovi.settings.req.options = o.req.options;
	}

	if(o.req && o.req.format) {
		Lovi.settings.req.format = o.req.format;
	}
}

Lovi.Req = (function(){
	var Req = function(options, format) {
		this.options = {};
		options = options || {};

		for(var o in Lovi.settings.req.options) {
			this.options[o] = Lovi.settings.req.options[o];
		}

		for(var o in this.options) {
			this.options[o] = options[o];
		}

		this.format = format || Lovi.settings.req.format || '';
	}

	Req.prototype = {
		url: function(resource) {
			var url = this.format;

			if(resource) this.options.resource = resource;

			for(var p in this.options) {
				url = url.replace('%' + p, this.options[p]);
			}

			return url;
		}
	}

	return Req;
})();

Lovi.ViewContainer = (function() {
	var Container = function(blocks) {
		this.blocks = blocks;
	}

	Container.prototype = {
		block: function(id) {
			return this.blocks[id];
		}
	}

	return Container;
})();

Lovi.Block = (function() {
	var Block = function(el, o) {
		if(!el) throw "DOM element expected";

		this.el = el;

		for(var m in o) {
			if(m in Block.prototype) {
				this[m] = o[m];
			} else {
				throw "Can't override a non-existent method";
			}
		}
	}

	Block.prototype = {
		view: function() {
			console.log('View!');
		},

		wait: function() {
			console.log('Wait!');
		},

		update: function(data) {
			console.log('Update!');
		}
	}

	return Block;
})();

Lovi.CompleteLoader = (function() {
	var Loader = function() {
		this.map = [];
	}

	Loader.prototype = {
		bind: function(url, block) {
			if(!url || !block) throw "Two arguments expected";
			if(!(block instanceof Lovi.Block)) throw "Second argument must be a Block object";

			this.map.push({
				block: block,
				url: url
			});
		},

		load: function() {
			var resources = $.map(this.map, function(item) {
				return $.get(item.url);
			});

			$.when.apply($, resources).done(function() {
				for(var i = 0; i < arguments.length; i++) {
					Loader.map[i].block.update(arguments[i][0]);
				}
			});
		}
	}

	return Loader;
})();
<commit_msg>Fix options set<commit_after>var Lovi = Lovi || {};

Lovi.settings = {
	req: {}
}

Lovi.default = function(o) {
	if(o.req && o.req.options) {
		Lovi.settings.req.options = o.req.options;
	}

	if(o.req && o.req.format) {
		Lovi.settings.req.format = o.req.format;
	}
}

Lovi.Req = (function(){
	var Req = function(options, format) {
		this.options = {};
		options = options || {};

		for(var o in Lovi.settings.req.options) {
			this.options[o] = Lovi.settings.req.options[o];
		}

		for(var o in options) {
			this.options[o] = options[o];
		}

		this.format = format || Lovi.settings.req.format || '';
	}

	Req.prototype = {
		url: function(resource) {
			var url = this.format;

			if(resource) this.options.resource = resource;

			for(var p in this.options) {
				url = url.replace('%' + p, this.options[p]);
			}

			return url;
		}
	}

	return Req;
})();

Lovi.ViewContainer = (function() {
	var Container = function(blocks) {
		this.blocks = blocks;
	}

	Container.prototype = {
		block: function(id) {
			return this.blocks[id];
		}
	}

	return Container;
})();

Lovi.Block = (function() {
	var Block = function(el, o) {
		if(!el) throw "DOM element expected";

		this.el = el;

		for(var m in o) {
			if(m in Block.prototype) {
				this[m] = o[m];
			} else {
				throw "Can't override a non-existent method";
			}
		}
	}

	Block.prototype = {
		view: function() {
			console.log('View!');
		},

		wait: function() {
			console.log('Wait!');
		},

		update: function(data) {
			console.log('Update!');
		}
	}

	return Block;
})();

Lovi.CompleteLoader = (function() {
	var Loader = function() {
		this.map = [];
	}

	Loader.prototype = {
		bind: function(url, block) {
			if(!url || !block) throw "Two arguments expected";
			if(!(block instanceof Lovi.Block)) throw "Second argument must be a Block object";

			this.map.push({
				block: block,
				url: url
			});
		},

		load: function() {
			var resources = $.map(this.map, function(item) {
				return $.get(item.url);
			});

			$.when.apply($, resources).done(function() {
				for(var i = 0; i < arguments.length; i++) {
					Loader.map[i].block.update(arguments[i][0]);
				}
			});
		}
	}

	return Loader;
})();
<|endoftext|>


<commit_before>(function($)
{
  var methods =
  {
    update: function()
    {
      methods.loadPage.apply(this, [0]);

      return this;
    },
    nextPage: function()
    {
      var page = $(this).data("jquery.comments.options").tail;
      var comments = this;

      methods.loadPage.apply(this, [page + 1])
        .done(function(result)
        {
          if(result.length > 0)
          {
            $(comments).data("jquery.comments.options").tail++;
          }
        });

      return this;
    },
    loadPage: function(page)
    {
      var f = $(this).data("jquery.comments.options").onLoad;

      if(f)
      {
        var ul = this;
        var client = new MeatStore().createClient();
        var users = new Array();

        $.mobile.loading("show");

        return f(page, $(this).data("jquery.comments.options").pageSize)
          .done(function(comments)
          {
            // insert comments:
            $(comments).each(function(i, comment)
            {
              var li = $(ul).find('li[data-id="' + comment["id"] + '"]').first();

              if(!li.get(0))
              {
                var author = comment["user"]["username"];
                var el = $('
' +
                           '' +
                           '
' + comment["text"] + '
' +
                           '
' +new Date(comment["created_on"]["$date"]).toLocaleString() + '' +
                           ' by ' + author + '
' +
                           '
');

                var inserted = false;

                $(ul).find("li").each(function(i, c)
                {
                  if(parseInt(comment["created_on"]["$date"], 10) > parseInt($(c).data("timestamp"), 10))
                  {
                    $(c).before(el);
                    inserted = true;
                  }

                  return !inserted;
                });

                if(!inserted)
                {
                  $(ul).append(el);
                }

                if($.inArray(author, users) == -1)
                {
                  users.push(author);
                }
              }
            });

            $(ul).listview("refresh");

            // load thumbnails:
            $(users).each(function(i, username)
            {
              client.getThumbnail(username)
                .success(function(thumbnail)
                {
                  $(ul).find('img[data-source="' + username + '"]').attr("src", thumbnail);
                })
                .fail(function(r)
                {
                  $(ul).find('img[data-source="' + username + '"]').attr("src", "images/image-missing.png");
                });
            });
          })
          .always(function()
          {
            $.mobile.loading("hide");
          });
      }

      return null;
    }
  };

  function createComments(obj, options)
  {
    $(obj).data("jquery.comments", true);
    $(obj).data("jquery.comments.options", $.extend({pageSize: 10, tail: 0, onLoad: null}, options));

    $(obj).find("li").remove();
    $(obj).listview();
  }

  $.fn.comments = function(args)
  {
    if(methods[args])
    {
      var parameters = Array.prototype.slice.call(arguments, 1);

      return this.each(function()
      {
        if($(this).data("jquery.comments"))
        {
          return methods[args].apply(this, parameters);
        }
        else
        {
          return null;
        }
      });
    }
    else if(typeof args === 'object' || !args)
    {
      return this.each(function()
      {
        createComments(this, args);
        methods["update"].apply(this);
      });
    }
  }
}(jQuery));
<commit_msg>show avatars in comments list<commit_after>(function($)
{
  var methods =
  {
    update: function()
    {
      methods.loadPage.apply(this, [0]);

      return this;
    },
    nextPage: function()
    {
      var page = $(this).data("jquery.comments.options").tail;
      var comments = this;

      methods.loadPage.apply(this, [page + 1])
        .done(function(result)
        {
          if(result.length > 0)
          {
            $(comments).data("jquery.comments.options").tail++;
          }
        });

      return this;
    },
    loadPage: function(page)
    {
      var f = $(this).data("jquery.comments.options").onLoad;

      if(f)
      {
        var ul = this;
        var client = new MeatStore().createClient();
        var users = new Array();

        $.mobile.loading("show");

        return f(page, $(this).data("jquery.comments.options").pageSize)
          .done(function(comments)
          {
            // insert comments:
            $(comments).each(function(i, comment)
            {
              var li = $(ul).find('li[data-id="' + comment["id"] + '"]').first();

              if(!li.get(0))
              {
                var author = comment["user"]["username"];
                var el = $('
' +
                           '' +
                           '
' + comment["text"] + '
' +
                           '
' +new Date(comment["created_on"]["$date"]).toLocaleString() + '' +
                           ' by ' + author + '
' +
                           '
');

                var inserted = false;

                $(ul).find("li").each(function(i, c)
                {
                  if(parseInt(comment["created_on"]["$date"], 10) > parseInt($(c).data("timestamp"), 10))
                  {
                    $(c).before(el);
                    inserted = true;
                  }

                  return !inserted;
                });

                if(!inserted)
                {
                  $(ul).append(el);
                }

                if($.inArray(author, users) == -1)
                {
                  users.push(author);
                }
              }
            });

            $(ul).listview("refresh");

            // load thumbnails:
            $(users).each(function(i, username)
            {
              client.getAvatar(username)
                .success(function(avatar)
                {
                  $(ul).find('img[data-source="' + username + '"]').attr("src", avatar);
                })
                .fail(function(r)
                {
                  $(ul).find('img[data-source="' + username + '"]').attr("src", "images/image-missing.png");
                });
            });
          })
          .always(function()
          {
            $.mobile.loading("hide");
          });
      }

      return null;
    }
  };

  function createComments(obj, options)
  {
    $(obj).data("jquery.comments", true);
    $(obj).data("jquery.comments.options", $.extend({pageSize: 10, tail: 0, onLoad: null}, options));

    $(obj).find("li").remove();
    $(obj).listview();
  }

  $.fn.comments = function(args)
  {
    if(methods[args])
    {
      var parameters = Array.prototype.slice.call(arguments, 1);

      return this.each(function()
      {
        if($(this).data("jquery.comments"))
        {
          return methods[args].apply(this, parameters);
        }
        else
        {
          return null;
        }
      });
    }
    else if(typeof args === 'object' || !args)
    {
      return this.each(function()
      {
        createComments(this, args);
        methods["update"].apply(this);
      });
    }
  }
}(jQuery));
<|endoftext|>


<commit_before>"use strict";

function parseTargets( story )
{
    // Traverse story and process the targets in links/choices.
    story.chapters.forEach( function( chapter, chapterIndex ) {
        chapter.pages.forEach( function( page, pageIndex ) {

            // Process links.
            page.links.forEach( function( link, linkIndex ) {
                story = processLinkOnPage( story, chapterIndex,
                    pageIndex, linkIndex, link );
            });

            // Process choices.
            page.texts.forEach( function( text, textIndex ) {
                story = processChoiceOnPage( story, chapterIndex,
                    pageIndex, textIndex, text );
            });
        });
    });

    return story;
}

function createTarget( path, found, chapter, page )
{
    return {
        path: path,
        found: found,
        chapter: chapter,
        page: page,
    };
}

function processLinkOnPage( story, chapterIndex, pageIndex, linkIndex, link )
{
    // Grab the target address.
    link.target = processTarget( story, chapterIndex, pageIndex, link.target );

    // Update the link.
    story.chapters[ chapterIndex ].pages[ pageIndex ].links[ linkIndex ] = link;

    return story;
}

function processChoiceOnPage( story, chapterIndex, pageIndex, textIndex, text )
{
    // Grab the target address.
    text.choice.target = processTarget( story, chapterIndex, pageIndex,
        text.choice.target );

    // Update the text.
    story.chapters[ chapterIndex ].pages[ pageIndex ].texts[ textIndex ] = text;

    return story;
}

function processTarget( story, currentChapterIndex, currentPageIndex, target )
{
    /*
     *  Target Examples:
     *
     *     last
     *     next
     *     next #2
     *     [Day]
     *     [Time]
     *     [Time] #2
     *     [Day] > [Time]
     *     [Day] > [Time] : [Location]
     *     [Day] > [Time] : [Location] #2
     *
     */

    // Make path case insensitive.
    let path = caseInsensitive( target.path );
    let matchIncrement = getMatchIncrement( path );

    // Find the desired target.
    if ( !path ) {
        target = createTarget( path, false );

    } else if ( path === caseInsensitive( "next" ) ) {
        target = processRelativePath( story, currentChapterIndex,
            currentPageIndex, path, matchIncrement );

    } else if ( path === caseInsensitive( "last" ) ) {
        target = processRelativePath( story, currentChapterIndex,
            currentPageIndex, path, -matchIncrement );

    } else {

        // Locate the first instance of the absolute path.
        target = processAbsolutePath( story, currentChapterIndex, path );

        // Increment up and down, if need be.
        if ( target.found && matchIncrement > 1 ) {

            target = processRelativePath( story, target.chapter,
                target.page, path, matchIncrement );

        }
    }

    return target;
}

function processRelativePath( story, currentChapterIndex, currentPageIndex, path, incrementOrDecrementBy )
{
    // For when you want to go up or down by a certain amount of pages from a
    // given chapter and page.

    let target = createTarget( path, false );

    // If the value increments, then this is about getting what's next.
    // If the value decrements, then this is about getting what's previous.
    let isNext = ( incrementOrDecrementBy >= 0 ) ? true : false;

    // Get the next or previous pages by the amount specified.
    for ( let count = 0; count < Math.abs( incrementOrDecrementBy ); count++ ) {
        target = getNextOrLastChapterAndPage( story, path, currentChapterIndex,
            currentPageIndex, isNext );
    }

    return target;
}

function processAbsolutePath( story, currentChapterIndex, path )
{
    // For when you want to jump to a specific page.

    // Default target.
    let target = createTarget( path, false );

    // Get the tokens.
    let tokens = path.split( ">" );

    // For each token, trim.
    tokens = tokens.map( function( token ) { return token.trim() });

    // Remove the match increment from the last token.
    if ( tokens.length > 0 ) {
        let lastToken = tokens[ tokens.length - 1 ];
        let positionOfNumberSign = lastToken.lastIndexOf( "#" );
        if ( positionOfNumberSign >= 0 ) {
            tokens[ tokens.length - 1 ] =
                lastToken.substring( 0, positionOfNumberSign );
        }
    }

    // If there's one token, then it can be a chapter name or a page in the
    // current chapter. Look at the pages in the current chapter first, then
    // look at chapter names. If there's two tokens, then the first is a chapter
    // name and the second is a page name. Page names can be split in
    // time: location, or just time.

    if ( tokens.length === 1 ) {

        // Single token. Assume it's a page name.
        let chapterIndex = currentChapterIndex;
        let pageIndex = findPageInChapter( story, currentChapterIndex,
            tokens[0] );

        // Page not found. Maybe it's a chapter name.
        if ( pageIndex === -1 ) {
             chapterIndex = findChapter( story, tokens[0] );
             pageIndex = 0;
        }

        // Found as either a page or chapter name. Target found.
        if ( chapterIndex >= 0 && pageIndex >= 0 ) {
            target.found = true;
            target.chapter = chapterIndex;
            target.page = pageIndex;
        }

        // No matches.
        if ( !target.found ) {
            appendCodeWarning( path,
                "Could not find a match for the link to '" + tokens[0] + "'. " +
                "It should be a day, (eg. 'Monday'), time (eg. 'Morning'), " +
                "or time & location (eg. 'Morning: Bedroom') that is found in " +
                "your story. Make sure there are no typos." );
        }

    } else if ( tokens.length === 2 ) {

        // The first token must be a chapter name.
        let chapterIndex = findChapter( story, tokens[0] );

        // Chapter found.
        // The second token must be a page name in that chapter.
        if ( chapterIndex >= 0 ) {
            let pageIndex = findPageInChapter( story, chapterIndex, tokens[1] );

            if ( pageIndex >= 0 ) {
                target.found = true;
                target.chapter = chapterIndex;
                target.page = pageIndex;
            }
        }

        // No matches.
        if ( !target.found ) {
            appendCodeWarning( path,
                "Could not find a day identified as '" + tokens[0] + "' with " +
                "a time or time & location of '" + tokens[1] + "'. The day " +
                "should be one in your story and something like 'Monday.' " +
                "The time or time & location should be something like " +
                "'Morning' (just time) or 'Morning: Bedroom' (time & location) " +
                "that is found in your story. Make sure there are no typos." );
        }

    } else {

        // 3+ Tokens. This is unsupported right now.
        appendCodeWarning( path,
            "Contains 3 or more tokens (tokens are separated with '>')." +
            "They are " + tokens.join(", ") + ". Only 1 or 2 tokens are " +
            "supported at this time.");
    }

    return target;
}

function findChapter( story, day )
{
    for ( let chapterIndex = 0;
          chapterIndex < story.chapters.length;
          chapterIndex++ ) {

        if ( caseInsensitive( day ) ===
             caseInsensitive( story.chapters[ chapterIndex ].day ) ) {

            return chapterIndex;
        }
    }

    // No matches found.
    return -1;
}

function findPageInChapter( story, chapterIndex, timeAndLocation )
{
    let splitTimeAndLocation = splitInTwoParts( ":", timeAndLocation );
    let time = splitTimeAndLocation.left;
    let location = splitTimeAndLocation.right;
    let chapter = story.chapters[ chapterIndex ];

    for ( let pageIndex = 0;
          pageIndex < chapter.pages.length;
          pageIndex++ ) {

        let page = chapter.pages[ pageIndex ];

        // Time is mandatory. Make the comparison.
        // Location is optional. If specified, compare it.
        if ( caseInsensitive( page.time ) ===
             caseInsensitive( time ) &&
             ( location === "" ||
               ( caseInsensitive( page.location ) ===
                 caseInsensitive( location ) ) ) ) {

            return pageIndex;
        }

    }

    // No matches found.
    return -1;
}

function getMatchIncrement( path )
{
    // If the path ends with #<Number> then the match number to get is that
    // number, otherwise it's assumed to be the first match (1.)

    let positionOfNumberSign = path.lastIndexOf( "#" );

    if ( positionOfNumberSign >= 0 ) {
        let matchNumber = parseInt( path.slice( positionOfNumberSign + 1) );
        if ( Number.isInteger( matchNumber ) ) {
            return matchNumber;
        } else {
            // There's a # in a target, but no valid number that follows.
            // This should be identified as a malformed link.
            appendCodeWarning( path,
                "Contains a #, but no number follows it. Instead " +
                "it's '" + path.substring( positionOfNumberSign ) + "'." +
                "Please remove the # or have a number follow it. Numbers " +
                "are used to say okay, I want the 2nd, 3rd, 4th, etc. match." );
        }
    }

    return 1;
}

function getNextOrLastChapterAndPage( story, path, initialChapterIndex, initialPageIndex, isNext )
{
    let incrementOrDecrementBy = ( isNext ) ? +1 : -1;

    // Start at the current chapter.
    // When this chapter is exhausted, go up or down one,
    // until all chapters have been explored.
    for ( let chapterIndex = initialChapterIndex;
              chapterIndex >= 0 &&
              chapterIndex < story.chapters.length;
              chapterIndex += incrementOrDecrementBy ) {

        // The initial page is, if we're on the same chapter we started with,
        // the immediate next or previous page. Otherwise, it's the first page
        // of the next chapter (if we're incrementing) or the last page of the
        // previous chapter (if we're decrementing).
        let startPage =
            ( chapterIndex === initialChapterIndex ) ?
                initialPageIndex + incrementOrDecrementBy :
                    ( isNext ?
                        0 :
                        story.chapters[ chapterIndex ].pages.length - 1 );

        // Go up or down a page, until all have been exhausted in this chapter.
        for ( let pageIndex = startPage;
                  pageIndex >= 0 &&
                  pageIndex < story.chapters[ chapterIndex ].pages.length;
                  pageIndex += incrementOrDecrementBy ) {

            if ( doesChapterAndPageExist( story, chapterIndex, pageIndex ) ) {
                return createTarget( path, true, chapterIndex, pageIndex );
            }
        }
    }

    return createTarget( path, false );
}

function doesChapterAndPageExist( story, chapterIndex, pageIndex )
{
    return ( ( 0 <= chapterIndex < story.chapters.length ) &&
             ( 0 <= pageIndex < story.chapters[ chapterIndex ].pages.length ) );
}
<commit_msg>Debugging.<commit_after>"use strict";

function parseTargets( story )
{
    // Traverse story and process the targets in links/choices.
    story.chapters.forEach( function( chapter, chapterIndex ) {
        chapter.pages.forEach( function( page, pageIndex ) {

            // Process links.
            page.links.forEach( function( link, linkIndex ) {
                story = processLinkOnPage( story, chapterIndex,
                    pageIndex, linkIndex, link );
            });

            // Process choices.
            page.texts.forEach( function( text, textIndex ) {
                story = processChoiceOnPage( story, chapterIndex,
                    pageIndex, textIndex, text );
            });
        });
    });

    return story;
}

function createTarget( path, found, chapter, page )
{
    return {
        path: path,
        found: found,
        chapter: chapter,
        page: page,
    };
}

function processLinkOnPage( story, chapterIndex, pageIndex, linkIndex, link )
{
    // Grab the target address.
    link.target = processTarget( story, chapterIndex, pageIndex, link.target );

    // Update the link.
    story.chapters[ chapterIndex ].pages[ pageIndex ].links[ linkIndex ] = link;

    return story;
}

function processChoiceOnPage( story, chapterIndex, pageIndex, textIndex, text )
{
    // Grab the target address.
    text.choice.target = processTarget( story, chapterIndex, pageIndex,
        text.choice.target );

    // Update the text.
    story.chapters[ chapterIndex ].pages[ pageIndex ].texts[ textIndex ] = text;

    return story;
}

function processTarget( story, currentChapterIndex, currentPageIndex, target )
{
    /*
     *  Target Examples:
     *
     *     last
     *     next
     *     next #2
     *     [Day]
     *     [Time]
     *     [Time] #2
     *     [Day] > [Time]
     *     [Day] > [Time] : [Location]
     *     [Day] > [Time] : [Location] #2
     *
     */

    // Make path case insensitive.
    let path = caseInsensitive( target.path );
    let matchIncrement = getMatchIncrement( path );

    // Find the desired target.
    if ( !path ) {
        target = createTarget( path, false );

    } else if ( path === caseInsensitive( "next" ) ) {
        target = processRelativePath( story, currentChapterIndex,
            currentPageIndex, path, matchIncrement );

    } else if ( path === caseInsensitive( "last" ) ) {
        target = processRelativePath( story, currentChapterIndex,
            currentPageIndex, path, -matchIncrement );

    } else {

        // Locate the first instance of the absolute path.
        target = processAbsolutePath( story, currentChapterIndex, path );

        // Increment up and down, if need be.
        if ( target.found && matchIncrement > 1 ) {

            target = processRelativePath( story, target.chapter,
                target.page, path, matchIncrement );

        }
    }

    return target;
}

function processRelativePath( story, currentChapterIndex, currentPageIndex, path, incrementOrDecrementBy )
{
    // For when you want to go up or down by a certain amount of pages from a
    // given chapter and page.

    let target = createTarget( path, false );

    // If the value increments, then this is about getting what's next.
    // If the value decrements, then this is about getting what's previous.
    let isNext = ( incrementOrDecrementBy >= 0 ) ? true : false;

    // Get the next or previous pages by the amount specified.
    for ( let count = 0; count < Math.abs( incrementOrDecrementBy ); count++ ) {
        target = getNextOrLastChapterAndPage( story, path, currentChapterIndex,
            currentPageIndex, isNext );
    }

    return target;
}

function processAbsolutePath( story, currentChapterIndex, path )
{
    // For when you want to jump to a specific page.

    // Default target.
    let target = createTarget( path, false );

    // Get the tokens.
    let tokens = path.split( ">" );

    // For each token, trim.
    tokens = tokens.map( function( token ) { return token.trim() });

    // Remove the match increment from the last token.
    if ( tokens.length > 0 ) {
        let lastToken = tokens[ tokens.length - 1 ];
        let positionOfNumberSign = lastToken.lastIndexOf( "#" );
        if ( positionOfNumberSign >= 0 ) {
            tokens[ tokens.length - 1 ] =
                lastToken.substring( 0, positionOfNumberSign );
        }
    }

    // If there's one token, then it can be a chapter name or a page in the
    // current chapter. Look at the pages in the current chapter first, then
    // look at chapter names. If there's two tokens, then the first is a chapter
    // name and the second is a page name. Page names can be split in
    // time: location, or just time.

    if ( tokens.length === 1 ) {

        // Single token. Assume it's a page name.
        let chapterIndex = currentChapterIndex;
        let pageIndex = findPageInChapter( story, currentChapterIndex,
            tokens[0] );

        // Page not found. Maybe it's a chapter name.
        if ( pageIndex === -1 ) {
             chapterIndex = findChapter( story, tokens[0] );
             pageIndex = 0;
        }

        // Found as either a page or chapter name. Target found.
        if ( chapterIndex >= 0 && pageIndex >= 0 ) {
            target.found = true;
            target.chapter = chapterIndex;
            target.page = pageIndex;
        }

        // No matches.
        if ( !target.found ) {
            appendCodeWarning( path,
                "Could not find a match for the link to '" + tokens[0] + "'. " +
                "It should be a day, (eg. 'Monday'), time (eg. 'Morning'), " +
                "or time & location (eg. 'Morning: Bedroom') that is found in " +
                "your story. Make sure there are no typos." );
        }

    } else if ( tokens.length === 2 ) {

        // The first token must be a chapter name.
        let chapterIndex = findChapter( story, tokens[0] );

        // Chapter found.
        // The second token must be a page name in that chapter.
        if ( chapterIndex >= 0 ) {
            let pageIndex = findPageInChapter( story, chapterIndex, tokens[1] );

            if ( pageIndex >= 0 ) {
                target.found = true;
                target.chapter = chapterIndex;
                target.page = pageIndex;
            }
        }

        // No matches.
        if ( !target.found ) {
            appendCodeWarning( path,
                "Could not find a day identified as '" + tokens[0] + "' with " +
                "a time or time & location of '" + tokens[1] + "'. The day " +
                "should be one in your story and something like 'Monday.' " +
                "The time or time & location should be something like " +
                "'Morning' (just time) or 'Morning: Bedroom' (time & location) " +
                "that is found in your story. Make sure there are no typos." );
        }

    } else {

        // 3+ Tokens. This is unsupported right now.
        appendCodeWarning( path,
            "Contains 3 or more tokens (tokens are separated with '>')." +
            "They are " + tokens.join(", ") + ". Only 1 or 2 tokens are " +
            "supported at this time.");
    }

    return target;
}

function findChapter( story, day )
{
    for ( let chapterIndex = 0;
          chapterIndex < story.chapters.length;
          chapterIndex++ ) {

        if ( caseInsensitive( day ) ===
             caseInsensitive( story.chapters[ chapterIndex ].day ) ) {

            return chapterIndex;
        }
    }

    // No matches found.
    return -1;
}

function findPageInChapter( story, chapterIndex, timeAndLocation )
{
    let splitTimeAndLocation = splitInTwoParts( ":", timeAndLocation );
    let time = splitTimeAndLocation.left;
    let location = splitTimeAndLocation.right;
    let chapter = story.chapters[ chapterIndex ];

    for ( let pageIndex = 0;
          pageIndex < chapter.pages.length;
          pageIndex++ ) {

        let page = chapter.pages[ pageIndex ];

        // Time is mandatory. Make the comparison.
        // Location is optional. If specified, compare it.
        if ( caseInsensitive( page.time ) ===
             caseInsensitive( time ) &&
             ( location === "" ||
               ( caseInsensitive( page.location ) ===
                 caseInsensitive( location ) ) ) ) {

            return pageIndex;
        }

    }

    // No matches found.
    return -1;
}

function getMatchIncrement( path )
{
    // If the path ends with #<Number> then the match number to get is that
    // number, otherwise it's assumed to be the first match (1.)

    let positionOfNumberSign = path.lastIndexOf( "#" );

    if ( positionOfNumberSign >= 0 ) {
        let matchNumber = parseInt( path.slice( positionOfNumberSign + 1) );
        if ( Number.isInteger( matchNumber ) ) {
            return matchNumber;
        } else {
            // There's a # in a target, but no valid number that follows.
            // This should be identified as a malformed link.
            appendCodeWarning( path,
                "Contains a #, but no number follows it. Instead " +
                "it's '" + path.substring( positionOfNumberSign ) + "'." +
                "Please remove the # or have a number follow it. Numbers " +
                "are used to say okay, I want the 2nd, 3rd, 4th, etc. match." );
        }
    }

    return 1;
}

function getNextOrLastChapterAndPage( story, path, initialChapterIndex, initialPageIndex, isNext )
{
    let incrementOrDecrementBy = ( isNext ) ? +1 : -1;

    // Start at the current chapter.
    // When this chapter is exhausted, go up or down one,
    // until all chapters have been explored.
    for ( let chapterIndex = initialChapterIndex;
              chapterIndex >= 0 &&
              chapterIndex < story.chapters.length;
              chapterIndex += incrementOrDecrementBy ) {

        // The initial page is, if we're on the same chapter we started with,
        // the immediate next or previous page. Otherwise, it's the first page
        // of the next chapter (if we're incrementing) or the last page of the
        // previous chapter (if we're decrementing).
        let startPage =
            ( chapterIndex === initialChapterIndex ) ?
                initialPageIndex + incrementOrDecrementBy :
                    ( isNext ?
                        0 :
                        story.chapters[ chapterIndex ].pages.length - 1 );

        // Go up or down a page, until all have been exhausted in this chapter.
        for ( let pageIndex = startPage;
                  pageIndex >= 0 &&
                  pageIndex < story.chapters[ chapterIndex ].pages.length;
                  pageIndex += incrementOrDecrementBy ) {

            if ( doesChapterAndPageExist( story, chapterIndex, pageIndex ) ) {
                return createTarget( path, true, chapterIndex, pageIndex );
            }
        }
    }

    return createTarget( path, false );
}

function doesChapterAndPageExist( story, chapterIndex, pageIndex )
{
    return ( chapterIndex >= 0 &&
             chapterIndex < story.chapters.length &&
             pageIndex >= 0 &&
             pageIndex < story.chapters[ chapterIndex ].pages.length );
}
<|endoftext|>


<commit_before>Tests.prototype.ContactsTests = function() {
	module("Contacts model");
	test("should be able to define a Contacts object with name, emails, phones and id attributes.", function() {
		expect(6);
		var con = new Contact();
		ok(con != null, "new Contact() should not be null.");
		ok(con.name != null, "new Contact() should include a 'name' property.");
		ok(con.name.formatted != null, "new Contact()'s 'name' property should, at the very least, contain a 'formatted' property.");
		ok(con.emails != null, "new Contact() should include an 'emails' array.");
		ok(con.phones != null, "new Contact() should include a 'phones' array.");
	});
	module('Contacts (navigator.contacts)');
	test("should exist", function() {
  		expect(1);
  		ok(navigator.contacts != null, "navigator.contacts should not be null.");
	});
	test("should contain a find function", function() {
		expect(2);
		ok(typeof navigator.contacts.find != 'undefined' && navigator.contacts.find != null, "navigator.contacts.find should not be null.");
		ok(typeof navigator.contacts.find == 'function', "navigator.contacts.find should be a function.");
	});
	// TODO: Need to add tests for the find function. Need to be able to include test data, but how? How do we add a contact
	// 		 to the phone before running the test?
	// TODO: Need to include tests that check error-handling, doubt there is any in the framework code.
	test("contacts.find success callback should be called with an array", function() {
		expect(2);
		stop(tests.TEST_TIMEOUT);
		var win = function(result) {
			ok(typeof result == 'object', "Object returned in contacts.find success callback is of type 'object' (actually array).");
			ok(typeof result.length == 'number', "Object returned in contacts.find success callback has a length property which is numerical.");
			start();
		};
		var fail = function() { start(); };
		var filter = new Contact();
		filter.name.formatted = '';
		navigator.contacts.find(filter,win, fail);
	});
};
<commit_msg>Removing id, since we can't write contacts, only read them.  ids are required only for writing, thus don't make sense in this test.<commit_after>Tests.prototype.ContactsTests = function() {
	module("Contacts model");
	test("should be able to define a Contacts object with name, emails, and phones attributes.", function() {
		expect(5);
		var con = new Contact();
		ok(con != null, "new Contact() should not be null.");
		ok(con.name != null, "new Contact() should include a 'name' property.");
		ok(con.name.formatted != null, "new Contact()'s 'name' property should, at the very least, contain a 'formatted' property.");
		ok(con.emails != null, "new Contact() should include an 'emails' array.");
		ok(con.phones != null, "new Contact() should include a 'phones' array.");
	});
	module('Contacts (navigator.contacts)');
	test("should exist", function() {
  		expect(1);
  		ok(navigator.contacts != null, "navigator.contacts should not be null.");
	});
	test("should contain a find function", function() {
		expect(2);
		ok(typeof navigator.contacts.find != 'undefined' && navigator.contacts.find != null, "navigator.contacts.find should not be null.");
		ok(typeof navigator.contacts.find == 'function', "navigator.contacts.find should be a function.");
	});
	// TODO: Need to add tests for the find function. Need to be able to include test data, but how? How do we add a contact
	// 		 to the phone before running the test?
	// TODO: Need to include tests that check error-handling, doubt there is any in the framework code.
	test("contacts.find success callback should be called with an array", function() {
		expect(2);
		stop(tests.TEST_TIMEOUT);
		var win = function(result) {
			ok(typeof result == 'object', "Object returned in contacts.find success callback is of type 'object' (actually array).");
			ok(typeof result.length == 'number', "Object returned in contacts.find success callback has a length property which is numerical.");
			start();
		};
		var fail = function() { start(); };
		var filter = new Contact();
		filter.name.formatted = '';
		navigator.contacts.find(filter,win, fail);
	});
};
<|endoftext|>