<commit_before>// An example Backbone application contributed by
// [Jérôme Gravel-Niquet](http://jgn.me/).
// This demo shows the use of the
// [PouchDB adapter](backbone-pouchdb.js)
// to persist Backbone models within your browser
// and to be able to replicate the data to and from a server.

// Load the application once the DOM is ready, using `jQuery.ready`:
$(function(){

  // Todo Model
  // ----------

  // Our basic **Todo** model has `title`, `order`, and `done` attributes.
  var Todo = Backbone.Model.extend({

    // Default attributes for the todo item.
    defaults: function() {
      return {
        type: 'todo',
        title: "empty todo...",
        order: Todos.nextOrder(),
        done: false
      };
    },

    // Ensure that each todo created has `title`.
    initialize: function() {
      if (!this.get("title")) {
        this.set({"title": this.defaults.title});
      }
    },

    // Toggle the `done` state of this todo item.
    toggle: function() {
      this.save({done: !this.get("done")});
    },

    // Remove this Todo from *PouchDB* and delete its view.
    clear: function() {
      this.destroy();
    }
  });

  // Todo Collection
  // ---------------

  // The collection of todos is backed by *PouchDB* instead of a remote
  // server.
  var TodoList = Backbone.Collection.extend({

    // Reference to this collection's model.
    model: Todo,

    // Save all of the todo items in the `"todos-backbone"` database.
    pouch: Backbone.sync.pouch('idb://todos-backbone', {
      reduce: false,
      include_docs: true,
      view: {
        map: function(doc) {
          if (doc.type === 'todo') emit([doc.order, doc.title], null);
        }
      }
    }),

    // Filter down the list of all todo items that are finished.
    done: function() {
      return this.filter(function(todo){ return todo.get('done'); });
    },

    // Filter down the list to only todo items that are still not finished.
    remaining: function() {
      return this.without.apply(this, this.done());
    },

    // We keep the Todos in sequential order, despite being saved by unordered
    // GUID in the database. This generates the next order number for new items.
    nextOrder: function() {
      if (!this.length) return 1;
      return this.last().get('order') + 1;
    },

    // Todos are sorted by their original insertion order.
    comparator: function(todo) {
      return todo.get('order');
    }

  });

  // Create our global collection of **Todos**.
  var Todos = new TodoList;

  // Todo Item View
  // --------------

  // The DOM element for a todo item...
  var TodoView = Backbone.View.extend({

    //... is a list tag.
    tagName:  "li",

    // Cache the template function for a single item.
    template: _.template($('#item-template').html()),

    // The DOM events specific to an item.
    events: {
      "click .toggle"   : "toggleDone",
      "dblclick .view"  : "edit",
      "click a.destroy" : "clear",
      "keypress .edit"  : "updateOnEnter",
      "blur .edit"      : "close"
    },

    // The TodoView listens for changes to its model, re-rendering. Since there's
    // a one-to-one correspondence between a **Todo** and a **TodoView** in this
    // app, we set a direct reference on the model for convenience.
    initialize: function() {
      this.model.bind('change', this.render, this);
      this.model.bind('destroy', this.remove, this);
    },

    // Re-render the titles of the todo item.
    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      this.$el.toggleClass('done', this.model.get('done'));
      this.$el.toggleClass('conflicts', _.size(this.model.get('_conflicts')) > 0);
      this.input = this.$('.edit');
      return this;
    },

    // Toggle the `"done"` state of the model.
    toggleDone: function() {
      this.model.toggle();
    },

    // Switch this view into `"editing"` mode, displaying the input field.
    edit: function() {
      this.$el.addClass("editing");
      this.input.focus();
    },

    // Close the `"editing"` mode, saving changes to the todo.
    close: function() {
      var value = this.input.val();
      if (!value) this.clear();
      this.model.save({title: value});
      this.$el.removeClass("editing");
    },

    // If you hit `enter`, we're through editing the item.
    updateOnEnter: function(e) {
      if (e.keyCode == 13) this.close();
    },

    // Remove the item, destroy the model.
    clear: function() {
      this.model.clear();
    }

  });

  // The Application
  // ---------------

  // Our overall **AppView** is the top-level piece of UI.
  var AppView = Backbone.View.extend({

    // Instead of generating a new element, bind to the existing skeleton of
    // the App already present in the HTML.
    el: $("#todoapp"),

    // Our template for the line of statistics at the bottom of the app.
    statsTemplate: _.template($('#stats-template').html()),

    // Delegated events for creating new items, and clearing completed ones.
    events: {
      "keypress #new-todo":  "createOnEnter",
      "click #clear-completed": "clearCompleted",
      "click #toggle-all": "toggleAllComplete"
    },

    // At initialization we bind to the relevant events on the `Todos`
    // collection, when items are added or changed. Kick things off by
    // loading any preexisting todos that might be saved in *PouchDB*.
    initialize: function() {

      this.input = this.$("#new-todo");
      this.allCheckbox = this.$("#toggle-all")[0];

      Todos.bind('add', this.addOne, this);
      Todos.bind('reset', this.addAll, this);
      Todos.bind('all', this.render, this);

      this.stats = this.$('#stats');
      this.main = $('#main');

      Todos.fetch({
        success: this.listen
      });
    },

    listen: function listen() {
      Todos.pouch(function(err, db) {
        // get changes since info.update_seq
        var change = db.changes({
          onChange: function(change) {
            var todo = Todos.get(change.id);

            if (todo) {
              todo.set(change.doc);
            } else {
              todo = _.first(Todos.parse({ rows: [{ doc: change.doc }] }));
              todo && Todos.add(todo);
            }
          },
          error: function(e) {
            console.error('Changes feed died');
            console.log(e);
          },
          continuous: true,
          // TODO:
          // sice & info.update_seq is currently not supported by pouchdb
          //  db.info(function(err, info) {
          //  });
          // since: info.update_seq
        });
      });
    },

    // Re-rendering the App just means refreshing the statistics -- the rest
    // of the app doesn't change.
    render: function(e, a) {
      var done = Todos.done().length;
      var remaining = Todos.remaining().length;

      if (Todos.length) {
        this.main.show();
        this.stats.show();
        this.stats.html(this.statsTemplate({done: done, remaining: remaining}));
      } else {
        this.main.hide();
        this.stats.hide();
      }

      this.allCheckbox.checked = !remaining;
    },

    // Add a single todo item to the list by creating a view for it, and
    // appending its element to the `<ul>`.
    addOne: function(todo) {
      var view = new TodoView({model: todo});
      this.$("#todo-list").append(view.render().el);
    },

    // Add all items in the **Todos** collection at once.
    addAll: function() {
      Todos.each(this.addOne);
    },

    // If you hit return in the main input field, create new **Todo** model,
    // persisting it to *PouchDB*.
    createOnEnter: function(e) {
      if (e.keyCode != 13) return;
      if (!this.input.val()) return;

      Todos.create({title: this.input.val()}, { wait: true });
      this.input.val('');
    },

    // Clear all done todo items, destroying their models.
    clearCompleted: function() {
      _.each(Todos.done(), function(todo){ todo.clear(); });
      return false;
    },

    toggleAllComplete: function () {
      var done = this.allCheckbox.checked;
      Todos.each(function (todo) { todo.save({'done': done}); });
    }
  });

  // Finally, we kick things off by creating the **App**.
  var App = new AppView;

  // Replication Model
  // -----------------

  // Our **Replication** model has an `url` attribute.
  var Replication = Backbone.Model.extend({
    defaults: {
      type: 'replication'
    }
  });

  // Replication Collection
  // ----------------------

  var ReplicationList = Backbone.Collection.extend({

    // Reference to this collection's model.
    model: Replication,

    // Save replications in the `"todos-backbone"` database.
    pouch: Backbone.sync.pouch('idb://todos-backbone', {
      reduce: false,
      include_docs: true,
      // FIXME: currently not possible,
      //        that view is conflicting with the one in TodoList
      // view: {
      //   map: function(doc) {
      //     if (doc.type === 'replication') emit([doc.url], null);
      //   }
      // }
    }),

    // TODO: remove when view is fixed, see below
    parse: function(response) {
      return _.filter(Backbone.Collection.prototype.parse(response), function(doc) {
        return doc.type === 'replication';
      })
    },

    // Replications are sorted by url.
    comparator: function(replication) {
      return replication.get('url');
    }

  });

  // Create global collection of **Replications**.
  var Replications = new ReplicationList;

  // Replication Item View
  // ---------------------

  // The DOM element for a replication item...
  var ReplicationView = Backbone.View.extend({

    //... is a list tag.
    tagName:  "li",

    // Cache the template function for a single item.
    template: _.template($('#replication-item-template').html()),

    // The DOM events specific to an item.
    events: {
      "click a.destroy" : "clear"
    },

    // The ReplicationView listens for changes to its model, re-rendering. Since there's
    // a one-to-one correspondence between a **Replication** and a **ReplicationView** in this
    // app, we set a direct reference on the model for convenience.
    initialize: function() {
      this.model.bind('change', this.render, this);
      this.model.bind('destroy', this.remove, this);
    },

    // Re-render the titles of the replication item.
    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    },

    // Remove the item, destroy the model.
    clear: function() {
      this.model.destroy();
    }

  });

  // The Application
  // ---------------

  // Our overall **ReplicationAppView** is the top-level piece of UI.
  var ReplicationAppView = Backbone.View.extend({

    // Instead of generating a new element, bind to the existing skeleton of
    // the App already present in the HTML.
    el: $("#sync-app"),

    // Our template for the line of statistics at the bottom of the app.
    statsTemplate: _.template($('#sync-stats-template').html()),

    // Delegated events for creating new items, and clearing completed ones.
    events: {
      "keypress #new-replication":  "createOnEnter"
    },

    // At initialization we bind to the relevant events on the `Replications` and `Replications`
    // collections, when items are added or changed. Kick things off by
    // loading any preexisting replications and replications that might be saved in *PouchDB*.
    initialize: function() {
      this.pushResps = {};
      this.pullResps = {};

      this.input = this.$("#new-replication");

      Replications.bind('add', this.addOne, this);
      Replications.bind('reset', this.addAll, this);
      Replications.bind('all', this.render, this);

      this.stats = this.$('#sync-stats');
      this.main = $('#sync-main');

      Replications.fetch();
    },

    // Re-rendering the App just means refreshing the statistics -- the rest
    // of the app doesn't change.
    render: function() {
      if (Replications.length) {
        this.main.show();
        this.renderStats();
        this.stats.show();
      } else {
        this.main.hide();
        this.stats.hide();
      }
    },

    renderStats: function() {
      this.stats.html(this.statsTemplate(_.reduce([this.pullResps, this.pushResps], function(memo, resps) {
        memo.read += _.reduce(resps, function(sum, resp) {
          return sum + resp.docs_read;
        }, 0);
        memo.written += _.reduce(resps, function(sum, resp) {
          return sum + resp.docs_written;
        }, 0);

        return memo;
      }, {
        read: 0,
        written: 0,
        count: Replications.length
      })));
    },

    // Add a single replication item to the list by creating a view for it, and
    // appending its element to the `<ul>`.
    addOne: function(replication) {
      var view = new ReplicationView({model: replication});
      this.$("#replication-list").append(view.render().el);
      this.replicate(replication);
    },

    // Add all items in the **Replications** collection at once.
    addAll: function() {
      Replications.each(this.addOne, this);
    },

    // If you hit return in the main input field, create new **Replication** model,
    // persisting it to *PouchDB*.
    createOnEnter: function(e) {
      if (e.keyCode != 13) return;
      if (!this.input.val()) return;

      Replications.create({url: this.input.val()});
      this.input.val('');
    },

    replicate: function (model) {
      var url = model.get('url'),
          pushResps = this.pushResps,
          pullResps = this.pullResps,
          renderStats = _.bind(this.renderStats, this);

      Todos.pouch(function(err, db) {
        db.replicate.to(url, { continuous: true }, function(err, resp) {
          pushResps[url] = resp;
        });
        db.replicate.from(url, { continuous: true }, function(err, resp) {
          pullResps[url] = resp;

        });
        // get todo changes
        var change = db.changes({
          onChange: renderStats,
          continuous: true
        });
      });

      // get target changes
      new Pouch(url, function(err, db) {
        if (err !== null) return;
        var change = db.changes({
          onChange: renderStats,
          continuous: true
        });
      });
    }

  });

  // Finally, we kick things off by creating the **App**.
  var ReplicationApp = new ReplicationAppView;
});
<commit_msg>use different database for replications<commit_after>// An example Backbone application contributed by
// [Jérôme Gravel-Niquet](http://jgn.me/).
// This demo shows the use of the
// [PouchDB adapter](backbone-pouchdb.js)
// to persist Backbone models within your browser
// and to be able to replicate the data to and from a server.

// Load the application once the DOM is ready, using `jQuery.ready`:
$(function(){

  // Todo Model
  // ----------

  // Our basic **Todo** model has `title`, `order`, and `done` attributes.
  var Todo = Backbone.Model.extend({

    // Default attributes for the todo item.
    defaults: function() {
      return {
        type: 'todo',
        title: "empty todo...",
        order: Todos.nextOrder(),
        done: false
      };
    },

    // Ensure that each todo created has `title`.
    initialize: function() {
      if (!this.get("title")) {
        this.set({"title": this.defaults.title});
      }
    },

    // Toggle the `done` state of this todo item.
    toggle: function() {
      this.save({done: !this.get("done")});
    },

    // Remove this Todo from *PouchDB* and delete its view.
    clear: function() {
      this.destroy();
    }
  });

  // Todo Collection
  // ---------------

  // The collection of todos is backed by *PouchDB* instead of a remote
  // server.
  var TodoList = Backbone.Collection.extend({

    // Reference to this collection's model.
    model: Todo,

    // Save all of the todo items in the `"todos-backbone"` database.
    pouch: Backbone.sync.pouch('idb://todos-backbone', {
      reduce: false,
      include_docs: true,
      view: {
        map: function(doc) {
          if (doc.type === 'todo') emit([doc.order, doc.title], null);
        }
      }
    }),

    // Filter down the list of all todo items that are finished.
    done: function() {
      return this.filter(function(todo){ return todo.get('done'); });
    },

    // Filter down the list to only todo items that are still not finished.
    remaining: function() {
      return this.without.apply(this, this.done());
    },

    // We keep the Todos in sequential order, despite being saved by unordered
    // GUID in the database. This generates the next order number for new items.
    nextOrder: function() {
      if (!this.length) return 1;
      return this.last().get('order') + 1;
    },

    // Todos are sorted by their original insertion order.
    comparator: function(todo) {
      return todo.get('order');
    }

  });

  // Create our global collection of **Todos**.
  var Todos = new TodoList;

  // Todo Item View
  // --------------

  // The DOM element for a todo item...
  var TodoView = Backbone.View.extend({

    //... is a list tag.
    tagName:  "li",

    // Cache the template function for a single item.
    template: _.template($('#item-template').html()),

    // The DOM events specific to an item.
    events: {
      "click .toggle"   : "toggleDone",
      "dblclick .view"  : "edit",
      "click a.destroy" : "clear",
      "keypress .edit"  : "updateOnEnter",
      "blur .edit"      : "close"
    },

    // The TodoView listens for changes to its model, re-rendering. Since there's
    // a one-to-one correspondence between a **Todo** and a **TodoView** in this
    // app, we set a direct reference on the model for convenience.
    initialize: function() {
      this.model.bind('change', this.render, this);
      this.model.bind('destroy', this.remove, this);
    },

    // Re-render the titles of the todo item.
    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      this.$el.toggleClass('done', this.model.get('done'));
      this.$el.toggleClass('conflicts', _.size(this.model.get('_conflicts')) > 0);
      this.input = this.$('.edit');
      return this;
    },

    // Toggle the `"done"` state of the model.
    toggleDone: function() {
      this.model.toggle();
    },

    // Switch this view into `"editing"` mode, displaying the input field.
    edit: function() {
      this.$el.addClass("editing");
      this.input.focus();
    },

    // Close the `"editing"` mode, saving changes to the todo.
    close: function() {
      var value = this.input.val();
      if (!value) this.clear();
      this.model.save({title: value});
      this.$el.removeClass("editing");
    },

    // If you hit `enter`, we're through editing the item.
    updateOnEnter: function(e) {
      if (e.keyCode == 13) this.close();
    },

    // Remove the item, destroy the model.
    clear: function() {
      this.model.clear();
    }

  });

  // The Application
  // ---------------

  // Our overall **AppView** is the top-level piece of UI.
  var AppView = Backbone.View.extend({

    // Instead of generating a new element, bind to the existing skeleton of
    // the App already present in the HTML.
    el: $("#todoapp"),

    // Our template for the line of statistics at the bottom of the app.
    statsTemplate: _.template($('#stats-template').html()),

    // Delegated events for creating new items, and clearing completed ones.
    events: {
      "keypress #new-todo":  "createOnEnter",
      "click #clear-completed": "clearCompleted",
      "click #toggle-all": "toggleAllComplete"
    },

    // At initialization we bind to the relevant events on the `Todos`
    // collection, when items are added or changed. Kick things off by
    // loading any preexisting todos that might be saved in *PouchDB*.
    initialize: function() {

      this.input = this.$("#new-todo");
      this.allCheckbox = this.$("#toggle-all")[0];

      Todos.bind('add', this.addOne, this);
      Todos.bind('reset', this.addAll, this);
      Todos.bind('all', this.render, this);

      this.stats = this.$('#stats');
      this.main = $('#main');

      Todos.fetch({
        success: this.listen
      });
    },

    listen: function listen() {
      Todos.pouch(function(err, db) {
        // get changes since info.update_seq
        var change = db.changes({
          onChange: function(change) {
            var todo = Todos.get(change.id);

            if (todo) {
              todo.set(change.doc);
            } else {
              todo = _.first(Todos.parse({ rows: [{ doc: change.doc }] }));
              todo && Todos.add(todo);
            }
          },
          error: function(e) {
            console.error('Changes feed died');
            console.log(e);
          },
          continuous: true,
          // TODO:
          // sice & info.update_seq is currently not supported by pouchdb
          //  db.info(function(err, info) {
          //  });
          // since: info.update_seq
        });
      });
    },

    // Re-rendering the App just means refreshing the statistics -- the rest
    // of the app doesn't change.
    render: function(e, a) {
      var done = Todos.done().length;
      var remaining = Todos.remaining().length;

      if (Todos.length) {
        this.main.show();
        this.stats.show();
        this.stats.html(this.statsTemplate({done: done, remaining: remaining}));
      } else {
        this.main.hide();
        this.stats.hide();
      }

      this.allCheckbox.checked = !remaining;
    },

    // Add a single todo item to the list by creating a view for it, and
    // appending its element to the `<ul>`.
    addOne: function(todo) {
      var view = new TodoView({model: todo});
      this.$("#todo-list").append(view.render().el);
    },

    // Add all items in the **Todos** collection at once.
    addAll: function() {
      Todos.each(this.addOne);
    },

    // If you hit return in the main input field, create new **Todo** model,
    // persisting it to *PouchDB*.
    createOnEnter: function(e) {
      if (e.keyCode != 13) return;
      if (!this.input.val()) return;

      Todos.create({title: this.input.val()}, { wait: true });
      this.input.val('');
    },

    // Clear all done todo items, destroying their models.
    clearCompleted: function() {
      _.each(Todos.done(), function(todo){ todo.clear(); });
      return false;
    },

    toggleAllComplete: function () {
      var done = this.allCheckbox.checked;
      Todos.each(function (todo) { todo.save({'done': done}); });
    }
  });

  // Finally, we kick things off by creating the **App**.
  var App = new AppView;

  // Replication Model
  // -----------------

  // Our **Replication** model has an `url` attribute.
  var Replication = Backbone.Model.extend({
    defaults: {
      type: 'replication'
    }
  });

  // Replication Collection
  // ----------------------

  var ReplicationList = Backbone.Collection.extend({

    // Reference to this collection's model.
    model: Replication,

    // Save replications in the `"replications-backbone"` database.
    pouch: Backbone.sync.pouch('idb://replications-backbone', {
      include_docs: true,
    }),

    // Replications are sorted by url.
    comparator: function(replication) {
      return replication.get('url');
    }

  });

  // Create global collection of **Replications**.
  var Replications = new ReplicationList;

  // Replication Item View
  // ---------------------

  // The DOM element for a replication item...
  var ReplicationView = Backbone.View.extend({

    //... is a list tag.
    tagName:  "li",

    // Cache the template function for a single item.
    template: _.template($('#replication-item-template').html()),

    // The DOM events specific to an item.
    events: {
      "click a.destroy" : "clear"
    },

    // The ReplicationView listens for changes to its model, re-rendering. Since there's
    // a one-to-one correspondence between a **Replication** and a **ReplicationView** in this
    // app, we set a direct reference on the model for convenience.
    initialize: function() {
      this.model.bind('change', this.render, this);
      this.model.bind('destroy', this.remove, this);
    },

    // Re-render the titles of the replication item.
    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    },

    // Remove the item, destroy the model.
    clear: function() {
      this.model.destroy();
    }

  });

  // The Application
  // ---------------

  // Our overall **ReplicationAppView** is the top-level piece of UI.
  var ReplicationAppView = Backbone.View.extend({

    // Instead of generating a new element, bind to the existing skeleton of
    // the App already present in the HTML.
    el: $("#sync-app"),

    // Our template for the line of statistics at the bottom of the app.
    statsTemplate: _.template($('#sync-stats-template').html()),

    // Delegated events for creating new items, and clearing completed ones.
    events: {
      "keypress #new-replication":  "createOnEnter"
    },

    // At initialization we bind to the relevant events on the `Replications` and `Replications`
    // collections, when items are added or changed. Kick things off by
    // loading any preexisting replications and replications that might be saved in *PouchDB*.
    initialize: function() {
      this.pushResps = {};
      this.pullResps = {};

      this.input = this.$("#new-replication");

      Replications.bind('add', this.addOne, this);
      Replications.bind('reset', this.addAll, this);
      Replications.bind('all', this.render, this);

      this.stats = this.$('#sync-stats');
      this.main = $('#sync-main');

      Replications.fetch();
    },

    // Re-rendering the App just means refreshing the statistics -- the rest
    // of the app doesn't change.
    render: function() {
      if (Replications.length) {
        this.main.show();
        this.renderStats();
        this.stats.show();
      } else {
        this.main.hide();
        this.stats.hide();
      }
    },

    renderStats: function() {
      this.stats.html(this.statsTemplate(_.reduce([this.pullResps, this.pushResps], function(memo, resps) {
        memo.read += _.reduce(resps, function(sum, resp) {
          return sum + resp.docs_read;
        }, 0);
        memo.written += _.reduce(resps, function(sum, resp) {
          return sum + resp.docs_written;
        }, 0);

        return memo;
      }, {
        read: 0,
        written: 0,
        count: Replications.length
      })));
    },

    // Add a single replication item to the list by creating a view for it, and
    // appending its element to the `<ul>`.
    addOne: function(replication) {
      var view = new ReplicationView({model: replication});
      this.$("#replication-list").append(view.render().el);
      this.replicate(replication);
    },

    // Add all items in the **Replications** collection at once.
    addAll: function() {
      Replications.each(this.addOne, this);
    },

    // If you hit return in the main input field, create new **Replication** model,
    // persisting it to *PouchDB*.
    createOnEnter: function(e) {
      if (e.keyCode != 13) return;
      if (!this.input.val()) return;

      Replications.create({url: this.input.val()});
      this.input.val('');
    },

    replicate: function (model) {
      var url = model.get('url'),
          pushResps = this.pushResps,
          pullResps = this.pullResps,
          renderStats = _.bind(this.renderStats, this);

      Todos.pouch(function(err, db) {
        db.replicate.to(url, { continuous: true }, function(err, resp) {
          pushResps[url] = resp;
        });
        db.replicate.from(url, { continuous: true }, function(err, resp) {
          pullResps[url] = resp;

        });
        // get todo changes
        var change = db.changes({
          onChange: renderStats,
          continuous: true
        });
      });

      // get target changes
      new Pouch(url, function(err, db) {
        if (err !== null) return;
        var change = db.changes({
          onChange: renderStats,
          continuous: true
        });
      });
    }

  });

  // Finally, we kick things off by creating the **App**.
  var ReplicationApp = new ReplicationAppView;
});
<|endoftext|>


<commit_before>/*global m*/
var createView = function(update) {
  var increase = function(amount) {
    return function(_event) {
      update(amount);
    };
  };
  var view = function(model) {
    return [
      m("div", "Value: " + model),
      m("button", { onclick: increase( 1) }, "+1"),
      m("button", { onclick: increase(-1) }, "-1")
    ];
  };
  return view;
};

var stream = function(initial) {
  var callbacks = [];
  var createdStream = function(value) {
    for (var i = 0, t = callbacks.length; i < t; i++) {
      var callback = callbacks[i];
      callback(value);
    }
  };
  createdStream.map = function(callback) {
    var newStream = stream();

    callbacks.push(function(value) {
      newStream(callback(value));
    });

    if (initial !== undefined) {
      callback(initial);
    }

    return newStream;
  };
  return createdStream;
};

var scan = function(accumulator, initial, st) {
  var newStream = stream(initial);
  let accumulated = initial;

  st.map(function(value) {
    accumulated = accumulator(accumulated, value);
    newStream(accumulated);
  });

  return newStream;
};

var model = 0;
var update = stream();
var view = createView(update);

var models = scan(function(model, value) {
  return model + value;
}, model, update);

var element = document.getElementById("app");

models.map(function(model) {
  m.render(element, view(model));
});
<commit_msg>Update tutorial<commit_after>/*global m*/
var createView = function(update) {
  var increase = function(amount) {
    return function(_event) {
      update(amount);
    };
  };
  var view = function(model) {
    return [
      m("div", "Value: " + model),
      m("button", { onclick: increase( 1) }, "+1"),
      m("button", { onclick: increase(-1) }, "-1")
    ];
  };
  return view;
};

var stream = function(initial) {
  var callbacks = [];
  var createdStream = function(value) {
    for (var i = 0, t = callbacks.length; i < t; i++) {
      var callback = callbacks[i];
      callback(value);
    }
  };
  createdStream.map = function(callback) {
    var newStream = stream();

    callbacks.push(function(value) {
      newStream(callback(value));
    });

    if (initial !== undefined) {
      callback(initial);
    }

    return newStream;
  };
  return createdStream;
};

var scan = function(accumulator, initial, st) {
  var newStream = stream(initial);
  var accumulated = initial;

  st.map(function(value) {
    accumulated = accumulator(accumulated, value);
    newStream(accumulated);
  });

  return newStream;
};

var model = 0;
var update = stream();
var view = createView(update);

var models = scan(function(model, value) {
  return model + value;
}, model, update);

var element = document.getElementById("app");

models.map(function(model) {
  m.render(element, view(model));
});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import EJSON from 'ejson';
import { connect } from 'react-redux';
import {
  debounce,
} from '@theclinician/toolbelt';
import DDPConnector from './DDPConnector';
import {
  makeGetSubscriptionsReady,
  makeGetQueriesValues,
  makeGetQueriesReady,
} from './selectors';
import {
  createIdGenerator,
  wrapMapState,
} from './utils';
import createDeepEqualSelector from './selectors/createDeepEqualSelector';

const uniqueId = createIdGenerator('listener.');
const increase = (key, value) => prevState => ({
  ...prevState,
  [key]: (prevState[key] || 0) + value,
});

const ddp = ({
  subscriptions: makeMapStateToSubscriptions,
  queries: makeMapStateToQueries,
  messages = {},
  mutations = {},
  ...ddpOptions
}, ddpOptionsMore) => (Inner) => {
  const {
    onMutationError,
    renderLoader = defaultComponent => React.createElement(defaultComponent),
    getResourceId = params => EJSON.stringify(params),
    queriesUpdateDelay,
    subscriptionsUpdateDelay,
  } = {
    ...ddpOptions,
    ...ddpOptionsMore, // this is here to support legacy style, when options were split into two groups
  };

  const propTypes = {
    subscriptions: PropTypes.array,
    queries: PropTypes.object,
    subscriptionsReady: PropTypes.bool,
    queriesReady: PropTypes.bool,
  };

  const defaultProps = {
    subscriptions: [],
    queries: {},
    subscriptionsReady: true,
    queriesReady: true,
  };

  const contextTypes = {
    ddpConnector: PropTypes.instanceOf(DDPConnector),
  };

  class Container extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        numberOfPendingMutations: 0,
      };
      const mutate = (request, options) => {
        if (request) {
          const { name, params } = request;
          this.beginMutation();
          return this.ddpConnector.apply(name, params, options)
            .then(res => this.endMutation(res))
            .catch((err) => {
              this.endMutation();
              if (onMutationError) {
                onMutationError(err);
              } else {
                throw err;
              }
            });
        }
        return Promise.resolve();
      };

      this.handlers = {};
      Object.keys(mutations).forEach((key) => {
        this.handlers[key] = (...args) => mutations[key]({
          ...this.props,
          ...this.handlers,
          mutate,
        })(...args);
      });

      this.messageHandlers = {};
      Object.keys(messages).forEach((key) => {
        this.messageHandlers[key] = (...args) => messages[key](this.props)(...args);
      });
    }

    componentWillMount() {
      this.id = uniqueId();
      this.ddpConnector = this.context.ddpConnector;

      this.updateSubscriptions = debounce(subscriptions =>
        this.ddpConnector.subsManager.updateRequests(this.id, subscriptions),
        {
          ms: subscriptionsUpdateDelay !== undefined ? subscriptionsUpdateDelay : this.ddpConnector.resourceUpdateDelay,
        },
      );

      this.updateQueries = debounce(queries =>
        this.ddpConnector.queryManager.updateRequests(this.id, Object.keys(queries).map(key => queries[key])),
        {
          ms: queriesUpdateDelay !== undefined ? queriesUpdateDelay : this.ddpConnector.resourceUpdateDelay,
        },
      );

      this.messagesListeners = Object.keys(this.messageHandlers)
        .map(channel => this.ddpConnector.on(`messages.${channel}`, this.messageHandlers[channel]));
    }

    componentDidMount() {
      this.updateSubscriptions(this.props.subscriptions);
      this.updateQueries(this.props.queries);
    }

    componentWillReceiveProps(newProps) {
      this.updateSubscriptions(newProps.subscriptions);
      this.updateQueries(newProps.queries);
    }

    componentWillUnmount() {
      this.updateSubscriptions([]);
      this.updateQueries([]);
      if (this.messagesListeners) {
        this.messagesListeners.forEach(stop => stop());
        this.messagesListeners = null;
      }
      this.unmounted = true;
    }

    beginMutation() {
      if (!this.unmounted) {
        this.setState(increase('numberOfPendingMutations', 1));
      }
    }

    endMutation(result) {
      if (!this.unmounted) {
        this.setState(increase('numberOfPendingMutations', -1));
      }
      return result;
    }

    render() {
      const { ddpConnector } = this.context;
      const {
        numberOfPendingMutations,
      } = this.state;
      const {
        subscriptionsReady,
        queriesReady,
        queries,
        subscriptions,
        ...other
      } = this.props;
      const mutationsReady = numberOfPendingMutations <= 0;
      if (renderLoader &&
        (
          !subscriptionsReady ||
          !mutationsReady ||
          !queriesReady
        )
      ) {
        return renderLoader(ddpConnector.getLoaderComponent(), {
          ...other,
          subscriptionsReady,
          mutationsReady,
          queriesReady,
        });
      }
      return React.createElement(Inner, {
        ...other,
        ...this.handlers,
        queriesReady,
        mutationsReady,
        subscriptionsReady,
      });
    }
  }

  Container.propTypes = propTypes;
  Container.defaultProps = defaultProps;
  Container.contextTypes = contextTypes;

  if (process.env.NODE_ENV !== 'production') {
    Container.displayName = `ddp(${Inner.displayName})`;
  }

  const wrappedMapStateToSubscriptions = wrapMapState(makeMapStateToSubscriptions);
  const wrappedMapStateToQueries = wrapMapState(makeMapStateToQueries);

  const defaultValue = x => y => y || x;

  return connect(
    () => {
      const getSubscriptionsReady = makeGetSubscriptionsReady((_, x) => getResourceId(x));
      const getQueriesValues = makeGetQueriesValues((_, x) => getResourceId(x));
      const getQueriesReady = makeGetQueriesReady((_, x) => getResourceId(x));
      //----------------------------------------------------------------------
      const getSubscriptions = createDeepEqualSelector(
        wrappedMapStateToSubscriptions,
        defaultValue([]),
      );
      const getQueries = createDeepEqualSelector(
        wrappedMapStateToQueries,
        defaultValue({}),
      );
      return (state, ownProps) => {
        const subscriptions = getSubscriptions(state, ownProps);
        const queries = getQueries(state, ownProps);
        return {
          ...getQueriesValues(state, queries),
          subscriptions,
          queries,
          subscriptionsReady: getSubscriptionsReady(state, { subscriptions }),
          queriesReady: getQueriesReady(state, { queries }),
        };
      };
    },
  )(Container);
};

export default ddp;
<commit_msg>Add ability to inject custom selectors<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import EJSON from 'ejson';
import { connect } from 'react-redux';
import {
  debounce,
} from '@theclinician/toolbelt';
import DDPConnector from './DDPConnector';
import {
  makeGetSubscriptionsReady,
  makeGetQueriesValues,
  makeGetQueriesReady,
} from './selectors';
import {
  createIdGenerator,
  wrapMapState,
} from './utils';
import createDeepEqualSelector from './selectors/createDeepEqualSelector';
import createStructuredSelector from './selectors/createStructuredSelector';

const uniqueId = createIdGenerator('listener.');
const constant = x => () => x;
const increase = (key, value) => prevState => ({
  ...prevState,
  [key]: (prevState[key] || 0) + value,
});

const ddp = ({
  subscriptions: makeMapStateToSubscriptions,
  queries: makeMapStateToQueries,
  selectors: createSelectors,
  messages = {},
  mutations = {},
  ...ddpOptions
}, ddpOptionsMore) => (Inner) => {
  const {
    onMutationError,
    renderLoader = defaultComponent => React.createElement(defaultComponent),
    getResourceId = params => EJSON.stringify(params),
    queriesUpdateDelay,
    subscriptionsUpdateDelay,
  } = {
    ...ddpOptions,
    ...ddpOptionsMore, // this is here to support legacy style, when options were split into two groups
  };

  const propTypes = {
    subscriptions: PropTypes.array,
    queries: PropTypes.object,
    subscriptionsReady: PropTypes.bool,
    queriesReady: PropTypes.bool,
  };

  const defaultProps = {
    subscriptions: [],
    queries: {},
    subscriptionsReady: true,
    queriesReady: true,
  };

  const contextTypes = {
    ddpConnector: PropTypes.instanceOf(DDPConnector),
  };

  class Container extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        numberOfPendingMutations: 0,
      };
      const mutate = (request, options) => {
        if (request) {
          const { name, params } = request;
          this.beginMutation();
          return this.ddpConnector.apply(name, params, options)
            .then(res => this.endMutation(res))
            .catch((err) => {
              this.endMutation();
              if (onMutationError) {
                onMutationError(err);
              } else {
                throw err;
              }
            });
        }
        return Promise.resolve();
      };

      this.handlers = {};
      Object.keys(mutations).forEach((key) => {
        this.handlers[key] = (...args) => mutations[key]({
          ...this.props,
          ...this.handlers,
          mutate,
        })(...args);
      });

      this.messageHandlers = {};
      Object.keys(messages).forEach((key) => {
        this.messageHandlers[key] = (...args) => messages[key](this.props)(...args);
      });
    }

    componentWillMount() {
      this.id = uniqueId();
      this.ddpConnector = this.context.ddpConnector;

      this.updateSubscriptions = debounce(subscriptions =>
        this.ddpConnector.subsManager.updateRequests(this.id, subscriptions),
        {
          ms: subscriptionsUpdateDelay !== undefined ? subscriptionsUpdateDelay : this.ddpConnector.resourceUpdateDelay,
        },
      );

      this.updateQueries = debounce(queries =>
        this.ddpConnector.queryManager.updateRequests(this.id, Object.keys(queries).map(key => queries[key])),
        {
          ms: queriesUpdateDelay !== undefined ? queriesUpdateDelay : this.ddpConnector.resourceUpdateDelay,
        },
      );

      this.messagesListeners = Object.keys(this.messageHandlers)
        .map(channel => this.ddpConnector.on(`messages.${channel}`, this.messageHandlers[channel]));
    }

    componentDidMount() {
      this.updateSubscriptions(this.props.subscriptions);
      this.updateQueries(this.props.queries);
    }

    componentWillReceiveProps(newProps) {
      this.updateSubscriptions(newProps.subscriptions);
      this.updateQueries(newProps.queries);
    }

    componentWillUnmount() {
      this.updateSubscriptions([]);
      this.updateQueries([]);
      if (this.messagesListeners) {
        this.messagesListeners.forEach(stop => stop());
        this.messagesListeners = null;
      }
      this.unmounted = true;
    }

    beginMutation() {
      if (!this.unmounted) {
        this.setState(increase('numberOfPendingMutations', 1));
      }
    }

    endMutation(result) {
      if (!this.unmounted) {
        this.setState(increase('numberOfPendingMutations', -1));
      }
      return result;
    }

    render() {
      const { ddpConnector } = this.context;
      const {
        numberOfPendingMutations,
      } = this.state;
      const {
        subscriptionsReady,
        queriesReady,
        queries,
        subscriptions,
        ...other
      } = this.props;
      const mutationsReady = numberOfPendingMutations <= 0;
      if (renderLoader &&
        (
          !subscriptionsReady ||
          !mutationsReady ||
          !queriesReady
        )
      ) {
        return renderLoader(ddpConnector.getLoaderComponent(), {
          ...other,
          subscriptionsReady,
          mutationsReady,
          queriesReady,
        });
      }
      return React.createElement(Inner, {
        ...other,
        ...this.handlers,
        queriesReady,
        mutationsReady,
        subscriptionsReady,
      });
    }
  }

  Container.propTypes = propTypes;
  Container.defaultProps = defaultProps;
  Container.contextTypes = contextTypes;

  if (process.env.NODE_ENV !== 'production') {
    Container.displayName = `ddp(${Inner.displayName})`;
  }

  const wrappedMapStateToSubscriptions = wrapMapState(makeMapStateToSubscriptions);
  const wrappedMapStateToQueries = wrapMapState(makeMapStateToQueries);

  const defaultValue = x => y => y || x;

  return connect(
    () => {
      const getSubscriptionsReady = makeGetSubscriptionsReady((_, x) => getResourceId(x));
      const getQueriesValues = makeGetQueriesValues((_, x) => getResourceId(x));
      const getQueriesReady = makeGetQueriesReady((_, x) => getResourceId(x));
      //----------------------------------------------------------------------
      const getSubscriptions = createDeepEqualSelector(
        wrappedMapStateToSubscriptions,
        defaultValue([]),
      );
      const getQueries = createDeepEqualSelector(
        wrappedMapStateToQueries,
        defaultValue({}),
      );
      const getOtherValues = createSelectors
        ? createStructuredSelector(createSelectors())
        : constant({});
      return (state, ownProps) => {
        const subscriptions = getSubscriptions(state, ownProps);
        const queries = getQueries(state, ownProps);
        return {
          ...getOtherValues(state, ownProps),
          ...getQueriesValues(state, queries),
          subscriptions,
          queries,
          subscriptionsReady: getSubscriptionsReady(state, { subscriptions }),
          queriesReady: getQueriesReady(state, { queries }),
        };
      };
    },
  )(Container);
};

export default ddp;
<|endoftext|>


<commit_before> function isStudyQuestionMode() {
    return studyQuestionManager != undefined;
}

function clearStudyQuestionMode() {
    $('#q-and-a-div').empty();
    $("#left-block-div").remove();
    $("#right-block-div").remove();
    studyQuestionManager = undefined;
}
function getStudyQuestionManager(questions, userId, treatmentId) {
    var sqm = {};
    sqm.renderer = getStudyQuestionRenderer();
    sqm.userId = userId;
    sqm.userIdHasBeenSet = false;
    sqm.treatmentId = treatmentId;

    sqm.questionMap = {};
    sqm.questionIds = [];

    sqm.windowRangeForStep = {};
    sqm.questionWasAnswered = false;
    sqm.activeRange = undefined;

    for (var i in questions){
        var question = questions[i];
        var step = question.getStep();
        var questionIndexForStep = question.getQuestionIdForStep();;
        var questionId = getQuestionId(step, questionIndexForStep);
        var answers = question.getAnswersList();
        var qu = {};
        qu.step = step;
        qu.questionIndexForStep = questionIndexForStep;
        qu.questionId = questionId;
        qu.questionText= question.getQuestion();;
        var allTypeInfo = question.getQuestionType();
        var typeParts = allTypeInfo.split(":");
        qu.questionType = typeParts[0];
        if (qu.questionType == "waitForClick"){
            qu.regionsToAllow = typeParts[1].split("_");
            qu.clickQuestionText = typeParts[2];
        }
        qu.answers = answers;
        sqm.questionIds.push(questionId);
        sqm.questionMap[questionId] = qu;
    }
    sqm.squim = getStudyQuestionIndexManager(sqm.questionIds);
    studyQuestionIndexManager = sqm.squim;
    sqm.windowRangeForStep = getRanges(sqm.squim.getDecisionPointSteps());

    sqm.isAtEndOfRange = function(step) {
        if (this.activeRange != undefined){
            var endOfRange = this.activeRange[1];
            //if (step >= endOfRange - 1) {
            if (step >= endOfRange) {
                return true;
            }
        }
        return false;
    }

    sqm.hasShownUserId = function() {
        return this.userIdHasBeenSet;
    }

    sqm.configureForCurrentStep = function() {
        var currentStep = sessionIndexManager.getCurrentIndex();
        if (!this.hasShownUserId()) {
            this.renderer.poseUserIdQuestion();
        }
        else if (this.squim.hasQuestionForStep(currentStep)) {
            this.poseCurrentQuestion();
        }
    }

    sqm.poseFirstQuestion = function() {
        var step = this.squim.getCurrentStep();
        var args = ["" + step];
        var userCommand = new proto.scaii.common.UserCommand;
        userCommand.setCommandType(proto.scaii.common.UserCommand.UserCommandType.JUMP_TO_STEP);
        userCommand.setArgsList(args);
        stageUserCommand(userCommand);
        controlsManager.userJumped();
        this.poseCurrentQuestion();
    }

    sqm.poseNextQuestion = function() {
        var priorStep = this.squim.getCurrentStep();
        this.squim.next();
        var newStep = this.squim.getCurrentStep();
        if (priorStep != newStep && !this.squim.isCurrentQuestionSummary()) {
            // move to next step
            var args = ["" + newStep];
            var userCommand = new proto.scaii.common.UserCommand;
            userCommand.setCommandType(proto.scaii.common.UserCommand.UserCommandType.JUMP_TO_STEP);
            userCommand.setArgsList(args);
            stageUserCommand(userCommand);
            controlsManager.userJumped();
        }
        this.poseCurrentQuestion();
    }

    // sqm.jumpBackToCurrentDecisionPoint = function() {
    //     var targetStep = this.steps[this.currentStepIndex];
    //     var args = ["" + nextStep];
    //     var userCommand = new proto.scaii.common.UserCommand;
    //     userCommand.setCommandType(proto.scaii.common.UserCommand.UserCommandType.JUMP_TO_STEP);
    //     userCommand.setArgsList(args);
    //     stageUserCommand(userCommand);
    //     controlsManager.userJumped();
    // }

    sqm.poseCurrentQuestion = function() {
        var qid = this.squim.getCurrentQuestionId();
        stateMonitor.setUserAction("showQuestion:"+ qid);
        stateMonitor.setQuestionId(qid);
        var qu = this.questionMap[qid];
        this.renderer.poseQuestion(qu, this.squim.getCurrentDecisionPointNumber(), this.squim.getCurrentStep());
    }

    sqm.clearTimelineBlocks = function() {
        $("#left-block-div").remove();
        $("#right-block-div").remove();
    }

    sqm.blockClicksOutsideRange = function() {
        var step = this.squim.getCurrentStep();
        if (step == undefined){
            return;
        }
        var rangePair = this.windowRangeForStep[step];
        this.activeRange = rangePair;
        var maxIndex = sessionIndexManager.getMaxIndex();
        var widthOfTimeline = expl_ctrl_canvas.width - 2*timelineMargin;

        // get offset of explanation-control-panel relative to document
        var ecpOffset = $("#explanation-control-panel").offset();
        var x1 = ecpOffset.left;
        // calculate left window edge position
        var leftValueOnTimeline = Math.floor((rangePair[0] / maxIndex ) * 100);
        var x2 = ecpOffset.left + timelineMargin + (leftValueOnTimeline / 100) * widthOfTimeline;
        // shift x2 to the left to fully expose the current DecisionPoint;
        var currentIndex = sessionIndexManager.getCurrentIndex();
        if (currentIndex == rangePair[0]) {
            x2 = x2 - explanationPointBigDiamondHalfWidth;
        }
        else {
            x2 = x2 - explanationPointSmallDiamondHalfWidth;
        }

        // calculate right window edge position
        var rightValueOnTimeline = Math.floor((rangePair[1] / maxIndex ) * 100);
        var x3 = ecpOffset.left + timelineMargin + (rightValueOnTimeline / 100) * widthOfTimeline;
        // shift x3 to the left to fully cover the next DecisionPoint
        if (!this.squim.isAtLastDecisionPoint()) {
            x3 = x3 - explanationPointSmallDiamondHalfWidth;
        }
        var x4 = expl_ctrl_canvas.width;

        var y = ecpOffset.top;
        var width1 = x2 - x1;
        var width2 = x4 - x3;
        var height = $("#explanation-control-panel").height();
        // make blocking div from 0 -> rightXofLeftBlock
        var gradientBars = "repeating-linear-gradient(135deg,rgba(100, 100, 100, 0.1),rgba(100, 100, 100, 0.3) 20px,rgba(100, 100, 100, 0.6) 20px,rgba(100, 100, 100, 0.7) 20px)";
        var leftBlockDiv = document.createElement("DIV");
        leftBlockDiv.setAttribute("id", "left-block-div");
        leftBlockDiv.setAttribute("style", "position:absolute;left:" + x1 + "px;top:" + y + "px;z-index:" + zIndexMap["clickBlockerRectangle"] + ";background:" + gradientBars + ";width:" + width1 + "px;height:" + height + "px;");
        //$("body").append(leftBlockDiv);

        // make blocking div from leftXofRightBlock -> expl_ctrl_canvas.width
        var rightBlockDiv = document.createElement("DIV");
        rightBlockDiv.setAttribute("id", "right-block-div");
        rightBlockDiv.setAttribute("style", "position:absolute;left:" + x3 + "px;top:" + y + "px;z-index:" + zIndexMap["clickBlockerRectangle"] + ";background:" + gradientBars + ";width:" + width2 + "px;height:" + height + "px;");
        rightBlockDiv.onclick = function(e) {
            if (isStudyQuestionMode()){
                targetClickHandler(e,"clickTimelineBlocker:NA");
                regionClickHandlerGameArea(e);
                userActionMonitor.globalClick(e.clientX, e.clientY);
            }
        }

        $("body").append(rightBlockDiv);
    }
    return sqm;
}

function getRanges(steps) {
    var stepRangePairs = {};
    for (var i = 0; i < steps.length; i++) {
        if (i == steps.length - 1) {
            // looking at the final entry - pair this one with the max index
            var range_pair = [ Number(steps[i]), Number(sessionIndexManager.getMaxIndex()) ];
            stepRangePairs[steps[i]] = range_pair;
        }
        else {
            // prior to last one, we make a pair with the step prior to the next question's step
            var range_pair = [ Number(steps[i]), Number(steps[i+1]) - 1 ];
            stepRangePairs[steps[i]] = range_pair;
        }
    }
    console.log(stepRangePairs)
    return stepRangePairs;
}

function acceptUserId() {
    var userId = studyQuestionManager.userId;
    if (userId == undefined || userId == "") {
        alert('No userId specified.  Please specify a userId and then click "Next".');
    }
    else {
        $("#user-id-div").remove();
        studyQuestionManager.userIdHasBeenSet = true;
        studyQuestionManager.poseFirstQuestion();
    }

}
function acceptAnswer(e) {
    var renderer = studyQuestionManager.renderer;
    renderer.removeMissingClickInfoMessage();
    // block if no answer specified
    if (renderer.controlsWaitingForClick.length != 0) {
        if (renderer.clickInfoFromUserActionMonitor == undefined) {
            renderer.expressMissingClickInfoMessage();
            return;
        }
    }
    var answer = renderer.getCurrentAnswer();

    if (answer == undefined || answer == '') {
        alert('No answer chosen for the current question.  Please specify an answer and then click "Next Question".');
        return;
    }
    // gather answer, send to backend
    var currentStep = studyQuestionIndexManager.getCurrentStep();
    var questionId = studyQuestionIndexManager.getCurrentQuestionId();
    var currentQuestionIndexAtStep = getQuestionIndexFromQuestionId(questionId);
    var clickInfo = renderer.collectClickInfo();
    userActionMonitor.clickListener = undefined;

    if (clickInfo == undefined){
        clickInfo = "NA";
    }
    var followupAnswer;
    if (!(currentStep == 'summary')){
        followupAnswer = renderer.getCurrentFollowupAnswer();
    }
    targetClickHandler(e,"answerQuestion:"+ currentStep + "." + currentQuestionIndexAtStep + "_" + answer + "_" + followupAnswer + "_(" + clickInfo + ")");

    renderer.forgetQuestion();
    if (studyQuestionIndexManager.hasMoreQuestionsAtThisStep()) {
        studyQuestionManager.poseNextQuestion();
    }
    else {
        if (studyQuestionIndexManager.hasMoreQuestions()){
            renderer.renderCueAndArrowToPlayButton();
        }
        studyQuestionManager.questionWasAnswered = true;
        if (studyQuestionIndexManager.hasMoreQuestions()){
            // wait for play button to take us to next Decision Point
            controlsManager.enablePauseResume();
        }
        else {
            renderer.poseThankYouScreen();
        }
    }
}

// function chooseNextQuestionAfterStep(step) {
//     // clear current question
//     $('#q-and-a-div').empty();

//     if (step == 'summary'){
//         renderer.poseThankYouScreen();
//     }
//     else {
//         // pose next question
//         studyQuestionManager.poseNextQuestion();
//     }
// }
<commit_msg>stop beforeblank screen when haven't answered question and click play<commit_after> function isStudyQuestionMode() {
    return studyQuestionManager != undefined;
}

function clearStudyQuestionMode() {
    $('#q-and-a-div').empty();
    $("#left-block-div").remove();
    $("#right-block-div").remove();
    studyQuestionManager = undefined;
}
function getStudyQuestionManager(questions, userId, treatmentId) {
    var sqm = {};
    sqm.renderer = getStudyQuestionRenderer();
    sqm.userId = userId;
    sqm.userIdHasBeenSet = false;
    sqm.treatmentId = treatmentId;

    sqm.questionMap = {};
    sqm.questionIds = [];

    sqm.windowRangeForStep = {};
    sqm.questionWasAnswered = false;
    sqm.activeRange = undefined;

    for (var i in questions){
        var question = questions[i];
        var step = question.getStep();
        var questionIndexForStep = question.getQuestionIdForStep();;
        var questionId = getQuestionId(step, questionIndexForStep);
        var answers = question.getAnswersList();
        var qu = {};
        qu.step = step;
        qu.questionIndexForStep = questionIndexForStep;
        qu.questionId = questionId;
        qu.questionText= question.getQuestion();;
        var allTypeInfo = question.getQuestionType();
        var typeParts = allTypeInfo.split(":");
        qu.questionType = typeParts[0];
        if (qu.questionType == "waitForClick"){
            qu.regionsToAllow = typeParts[1].split("_");
            qu.clickQuestionText = typeParts[2];
        }
        qu.answers = answers;
        sqm.questionIds.push(questionId);
        sqm.questionMap[questionId] = qu;
    }
    sqm.squim = getStudyQuestionIndexManager(sqm.questionIds);
    studyQuestionIndexManager = sqm.squim;
    sqm.windowRangeForStep = getRanges(sqm.squim.getDecisionPointSteps());

    sqm.isAtEndOfRange = function(step) {
        if (this.activeRange != undefined){
            var endOfRange = this.activeRange[1];
            // stop one prior to the true end to avoid showing the blank gameboard
            if (step >= endOfRange - 1) {
            //if (step >= endOfRange) {
                return true;
            }
        }
        return false;
    }

    sqm.hasShownUserId = function() {
        return this.userIdHasBeenSet;
    }

    sqm.configureForCurrentStep = function() {
        var currentStep = sessionIndexManager.getCurrentIndex();
        if (!this.hasShownUserId()) {
            this.renderer.poseUserIdQuestion();
        }
        else if (this.squim.hasQuestionForStep(currentStep)) {
            this.poseCurrentQuestion();
        }
    }

    sqm.poseFirstQuestion = function() {
        var step = this.squim.getCurrentStep();
        var args = ["" + step];
        var userCommand = new proto.scaii.common.UserCommand;
        userCommand.setCommandType(proto.scaii.common.UserCommand.UserCommandType.JUMP_TO_STEP);
        userCommand.setArgsList(args);
        stageUserCommand(userCommand);
        controlsManager.userJumped();
        this.poseCurrentQuestion();
    }

    sqm.poseNextQuestion = function() {
        var priorStep = this.squim.getCurrentStep();
        this.squim.next();
        var newStep = this.squim.getCurrentStep();
        if (priorStep != newStep && !this.squim.isCurrentQuestionSummary()) {
            // move to next step
            var args = ["" + newStep];
            var userCommand = new proto.scaii.common.UserCommand;
            userCommand.setCommandType(proto.scaii.common.UserCommand.UserCommandType.JUMP_TO_STEP);
            userCommand.setArgsList(args);
            stageUserCommand(userCommand);
            controlsManager.userJumped();
        }
        this.poseCurrentQuestion();
    }

    // sqm.jumpBackToCurrentDecisionPoint = function() {
    //     var targetStep = this.steps[this.currentStepIndex];
    //     var args = ["" + nextStep];
    //     var userCommand = new proto.scaii.common.UserCommand;
    //     userCommand.setCommandType(proto.scaii.common.UserCommand.UserCommandType.JUMP_TO_STEP);
    //     userCommand.setArgsList(args);
    //     stageUserCommand(userCommand);
    //     controlsManager.userJumped();
    // }

    sqm.poseCurrentQuestion = function() {
        var qid = this.squim.getCurrentQuestionId();
        stateMonitor.setUserAction("showQuestion:"+ qid);
        stateMonitor.setQuestionId(qid);
        var qu = this.questionMap[qid];
        this.renderer.poseQuestion(qu, this.squim.getCurrentDecisionPointNumber(), this.squim.getCurrentStep());
    }

    sqm.clearTimelineBlocks = function() {
        $("#left-block-div").remove();
        $("#right-block-div").remove();
    }

    sqm.blockClicksOutsideRange = function() {
        var step = this.squim.getCurrentStep();
        if (step == undefined){
            return;
        }
        var rangePair = this.windowRangeForStep[step];
        this.activeRange = rangePair;
        var maxIndex = sessionIndexManager.getMaxIndex();
        var widthOfTimeline = expl_ctrl_canvas.width - 2*timelineMargin;

        // get offset of explanation-control-panel relative to document
        var ecpOffset = $("#explanation-control-panel").offset();
        var x1 = ecpOffset.left;
        // calculate left window edge position
        var leftValueOnTimeline = Math.floor((rangePair[0] / maxIndex ) * 100);
        var x2 = ecpOffset.left + timelineMargin + (leftValueOnTimeline / 100) * widthOfTimeline;
        // shift x2 to the left to fully expose the current DecisionPoint;
        var currentIndex = sessionIndexManager.getCurrentIndex();
        if (currentIndex == rangePair[0]) {
            x2 = x2 - explanationPointBigDiamondHalfWidth;
        }
        else {
            x2 = x2 - explanationPointSmallDiamondHalfWidth;
        }

        // calculate right window edge position
        var rightValueOnTimeline = Math.floor((rangePair[1] / maxIndex ) * 100);
        var x3 = ecpOffset.left + timelineMargin + (rightValueOnTimeline / 100) * widthOfTimeline;
        // shift x3 to the left to fully cover the next DecisionPoint
        if (!this.squim.isAtLastDecisionPoint()) {
            x3 = x3 - explanationPointSmallDiamondHalfWidth;
        }
        var x4 = expl_ctrl_canvas.width;

        var y = ecpOffset.top;
        var width1 = x2 - x1;
        var width2 = x4 - x3;
        var height = $("#explanation-control-panel").height();
        // make blocking div from 0 -> rightXofLeftBlock
        var gradientBars = "repeating-linear-gradient(135deg,rgba(100, 100, 100, 0.1),rgba(100, 100, 100, 0.3) 20px,rgba(100, 100, 100, 0.6) 20px,rgba(100, 100, 100, 0.7) 20px)";
        var leftBlockDiv = document.createElement("DIV");
        leftBlockDiv.setAttribute("id", "left-block-div");
        leftBlockDiv.setAttribute("style", "position:absolute;left:" + x1 + "px;top:" + y + "px;z-index:" + zIndexMap["clickBlockerRectangle"] + ";background:" + gradientBars + ";width:" + width1 + "px;height:" + height + "px;");
        //$("body").append(leftBlockDiv);

        // make blocking div from leftXofRightBlock -> expl_ctrl_canvas.width
        var rightBlockDiv = document.createElement("DIV");
        rightBlockDiv.setAttribute("id", "right-block-div");
        rightBlockDiv.setAttribute("style", "position:absolute;left:" + x3 + "px;top:" + y + "px;z-index:" + zIndexMap["clickBlockerRectangle"] + ";background:" + gradientBars + ";width:" + width2 + "px;height:" + height + "px;");
        rightBlockDiv.onclick = function(e) {
            if (isStudyQuestionMode()){
                targetClickHandler(e,"clickTimelineBlocker:NA");
                regionClickHandlerGameArea(e);
                userActionMonitor.globalClick(e.clientX, e.clientY);
            }
        }

        $("body").append(rightBlockDiv);
    }
    return sqm;
}

function getRanges(steps) {
    var stepRangePairs = {};
    for (var i = 0; i < steps.length; i++) {
        if (i == steps.length - 1) {
            // looking at the final entry - pair this one with the max index
            var range_pair = [ Number(steps[i]), Number(sessionIndexManager.getMaxIndex()) ];
            stepRangePairs[steps[i]] = range_pair;
        }
        else {
            // prior to last one, we make a pair with the step prior to the next question's step
            var range_pair = [ Number(steps[i]), Number(steps[i+1]) - 1 ];
            stepRangePairs[steps[i]] = range_pair;
        }
    }
    console.log(stepRangePairs)
    return stepRangePairs;
}

function acceptUserId() {
    var userId = studyQuestionManager.userId;
    if (userId == undefined || userId == "") {
        alert('No userId specified.  Please specify a userId and then click "Next".');
    }
    else {
        $("#user-id-div").remove();
        studyQuestionManager.userIdHasBeenSet = true;
        studyQuestionManager.poseFirstQuestion();
    }

}
function acceptAnswer(e) {
    var renderer = studyQuestionManager.renderer;
    renderer.removeMissingClickInfoMessage();
    // block if no answer specified
    if (renderer.controlsWaitingForClick.length != 0) {
        if (renderer.clickInfoFromUserActionMonitor == undefined) {
            renderer.expressMissingClickInfoMessage();
            return;
        }
    }
    var answer = renderer.getCurrentAnswer();

    if (answer == undefined || answer == '') {
        alert('No answer chosen for the current question.  Please specify an answer and then click "Next Question".');
        return;
    }
    // gather answer, send to backend
    var currentStep = studyQuestionIndexManager.getCurrentStep();
    var questionId = studyQuestionIndexManager.getCurrentQuestionId();
    var currentQuestionIndexAtStep = getQuestionIndexFromQuestionId(questionId);
    var clickInfo = renderer.collectClickInfo();
    userActionMonitor.clickListener = undefined;

    if (clickInfo == undefined){
        clickInfo = "NA";
    }
    var followupAnswer;
    if (!(currentStep == 'summary')){
        followupAnswer = renderer.getCurrentFollowupAnswer();
    }
    targetClickHandler(e,"answerQuestion:"+ currentStep + "." + currentQuestionIndexAtStep + "_" + answer + "_" + followupAnswer + "_(" + clickInfo + ")");

    renderer.forgetQuestion();
    if (studyQuestionIndexManager.hasMoreQuestionsAtThisStep()) {
        studyQuestionManager.poseNextQuestion();
    }
    else {
        if (studyQuestionIndexManager.hasMoreQuestions()){
            renderer.renderCueAndArrowToPlayButton();
        }
        studyQuestionManager.questionWasAnswered = true;
        if (studyQuestionIndexManager.hasMoreQuestions()){
            // wait for play button to take us to next Decision Point
            controlsManager.enablePauseResume();
        }
        else {
            renderer.poseThankYouScreen();
        }
    }
}

// function chooseNextQuestionAfterStep(step) {
//     // clear current question
//     $('#q-and-a-div').empty();

//     if (step == 'summary'){
//         renderer.poseThankYouScreen();
//     }
//     else {
//         // pose next question
//         studyQuestionManager.poseNextQuestion();
//     }
// }
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var express = require('express');
var WPCOM = require('../../');

/**
 * wpcon app data
 */

var wpapp = require('../../test/data');

/**
 * Create a WPCOM instance
 */

var wpcom = WPCOM();

// setup middleware

var app = express();
var pub = __dirname + '/public';
app.use(express.static(pub));

app.set('views', __dirname + '/');
app.set('view engine', 'jade');

app.get('/', function(req, res){
  // set site id
  var site = wpcom.site(wpapp.site.public.url);

  // get site info
  site.get(function(err, info){
    if (err) return console.log(err);

    // get lastest posts
    site.posts({ number: 10 }, function(err, posts) {
      if (err) return console.log(err);

      res.render('layout', { site: info, posts: posts });
    });
  });
});

app.listen(3000);
console.log('wpcom app started on port 3000');
<commit_msg>example: fix postsLists method<commit_after>
/**
 * Module dependencies.
 */

var express = require('express');
var WPCOM = require('../../');

/**
 * wpcon app data
 */

var wpapp = require('../../test/data');

/**
 * Create a WPCOM instance
 */

var wpcom = WPCOM();

// setup middleware

var app = express();
var pub = __dirname + '/public';
app.use(express.static(pub));

app.set('views', __dirname + '/');
app.set('view engine', 'jade');

app.get('/', function(req, res){
  // set site id
  var site = wpcom.site(wpapp.site.public.url);

  // get site info
  site.get(function(err, info){
    if (err) return console.log(err);

    // get lastest posts
    site.postsList({ number: 10 }, function(err, posts) {
      if (err) return console.log(err);

      res.render('layout', { site: info, posts: posts });
    });
  });
});

app.listen(3000);
console.log('wpcom app started on port 3000');
<|endoftext|>


<commit_before>import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunkMiddleware from 'redux-thunk'

const exampleInitialState = {
  lastUpdate: 0,
  light: false,
  count: 0
}

export const actionTypes = {
  ADD: 'ADD',
  TICK: 'TICK'
}

// REDUCERS
export const reducer = (state = exampleInitialState, action) => {
  switch (action.type) {
    case actionTypes.TICK:
      return Object.assign({}, state, { lastUpdate: action.ts, light: !!action.light })
    case actionTypes.ADD:
      return Object.assign({}, state, {
        count: state.count + 1
      })
    default: return state
  }
}

// ACTIONS
export const serverRenderClock = (isServer) => dispatch => {
  return dispatch({ type: actionTypes.TICK, light: !isServer, ts: Date.now() })
}

export const startClock = () => dispatch => {
  return setInterval(() => dispatch({ type: 'TICK', light: true, ts: Date.now() }), 800)
}

export const addCount = () => dispatch => {
  return dispatch({ type: actionTypes.ADD })
}

export const initStore = (initialState = exampleInitialState) => {
  return createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunkMiddleware)))
}
<commit_msg>Small code fix (#2099)<commit_after>import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunkMiddleware from 'redux-thunk'

const exampleInitialState = {
  lastUpdate: 0,
  light: false,
  count: 0
}

export const actionTypes = {
  ADD: 'ADD',
  TICK: 'TICK'
}

// REDUCERS
export const reducer = (state = exampleInitialState, action) => {
  switch (action.type) {
    case actionTypes.TICK:
      return Object.assign({}, state, { lastUpdate: action.ts, light: !!action.light })
    case actionTypes.ADD:
      return Object.assign({}, state, {
        count: state.count + 1
      })
    default: return state
  }
}

// ACTIONS
export const serverRenderClock = (isServer) => dispatch => {
  return dispatch({ type: actionTypes.TICK, light: !isServer, ts: Date.now() })
}

export const startClock = () => dispatch => {
  return setInterval(() => dispatch({ type: actionTypes.TICK, light: true, ts: Date.now() }), 800)
}

export const addCount = () => dispatch => {
  return dispatch({ type: actionTypes.ADD })
}

export const initStore = (initialState = exampleInitialState) => {
  return createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunkMiddleware)))
}
<|endoftext|>


<commit_before>import mobx from 'mobx';
import spy from './spy';

export default function dev(store, config) {
  if (typeof window === 'undefined' || !window.devToolsExtension) return store;

  if (mobx.isObservable(store)) {
    spy(store, config);
  } else if (typeof store === 'function') {
    store = class extends store {
      constructor() {
        super();
        spy(this, config);
      }
    };
  } else {
    console.warn(`Passed ${typeof store} to mobx-remotedev, which is not an observable.`);
  }

  return store;
}
<commit_msg>Support arguments for the inherited class constructor<commit_after>import mobx from 'mobx';
import spy from './spy';

export default function dev(store, config) {
  if (typeof window === 'undefined' || !window.devToolsExtension) return store;

  if (mobx.isObservable(store)) {
    spy(store, config);
  } else if (typeof store === 'function') {
    store = class extends store {
      constructor(...args) {
        super(...args);
        spy(this, config);
      }
    };
  } else {
    console.warn(`Passed ${typeof store} to mobx-remotedev, which is not an observable.`);
  }

  return store;
}
<|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('options.contentSettings', function() {
  const List = cr.ui.List;
  const ListItem = cr.ui.ListItem;
  const ArrayDataModel = cr.ui.ArrayDataModel;

  /**
   * Creates a new exceptions list item.
   * @param {Array} exception A pair of the form [filter, setting].
   * @constructor
   * @extends {cr.ui.ListItem}
   */
  function ExceptionsListItem(exception) {
    var el = cr.doc.createElement('li');
    el.dataItem = exception;
    el.__proto__ = ExceptionsListItem.prototype;
    el.decorate();

    return el;
  }

  ExceptionsListItem.prototype = {
    __proto__: ListItem.prototype,

    /**
     * Called when an element is decorated as a list item.
     */
    decorate: function() {
      ListItem.prototype.decorate.call(this);

      // Labels for display mode.
      var patternLabel = cr.doc.createElement('span');
      patternLabel.textContent = this.pattern;
      this.appendChild(patternLabel);

      var settingLabel = cr.doc.createElement('span');
      settingLabel.textContent = this.settingForDisplay();
      settingLabel.className = 'exceptionSetting';
      this.appendChild(settingLabel);

      // Elements for edit mode.
      var input = cr.doc.createElement('input');
      input.type = 'text';
      this.appendChild(input);
      input.className = 'exceptionInput hidden';

      var select = cr.doc.createElement('select');
      var optionAllow = cr.doc.createElement('option');
      optionAllow.textContent = templateData.allowException;
      var optionBlock = cr.doc.createElement('option');
      optionBlock.textContent = templateData.blockException;
      select.appendChild(optionAllow);
      select.appendChild(optionBlock);
      this.appendChild(select);
      select.className = 'exceptionSetting hidden';

      this.patternLabel = patternLabel;
      this.settingLabel = settingLabel;
      this.input = input;
      this.select = select;
      this.optionAllow = optionAllow;
      this.optionBlock = optionBlock;

      this.updateEditables();

      // Handle events on the editable nodes.
      var eventsToStop =
          ['mousedown', 'mouseup', 'contextmenu', 'dblclick', 'paste'];
      eventsToStop.forEach(function(type) {
        input.addEventListener(type, function(e) {
            e.stopPropagation();
          });
        }
      );

      var listItem = this;
      // Handles enter and escape which trigger reset and commit respectively.
      function handleKeydown(e) {
        // Make sure that the tree does not handle the key.
        e.stopPropagation();

        // Calling list.focus blurs the input which will stop editing the list
        // item.
        switch (e.keyIdentifier) {
          case 'U+001B':  // Esc
            // Reset the inputs.
            listItem.updateEditables();
          case 'Enter':
            if (listItem.parentNode)
              listItem.parentNode.focus();
        }
      }

      function handleBlur(e) {
        // When the blur event happens we do not know who is getting focus so we
        // delay this a bit since we want to know if the other input got focus
        // before deciding if we should exit edit mode.
        var doc = e.target.ownerDocument;
        window.setTimeout(function() {
          var activeElement = doc.activeElement;
          if (!listItem.contains(activeElement)) {
            listItem.editing = false;
          }
        }, 50);
      }

      input.addEventListener('keydown', handleKeydown);
      input.addEventListener('blur', handleBlur);

      select.addEventListener('keydown', handleKeydown);
      select.addEventListener('blur', handleBlur);
    },

    /**
     * The pattern (e.g., a URL) for the exception.
     * @type {string}
     */
    get pattern() {
      return this.dataItem[0];
    },
    set pattern(pattern) {
      this.dataItem[0] = pattern;
    },

    /**
     * The setting (allow/block) for the exception.
     * @type {string}
     */
    get setting() {
      return this.dataItem[1];
    },
    set setting(setting) {
      this.dataItem[1] = setting;
    },

    /**
     * Gets a human-readable setting string.
     * @type {string}
     */
    settingForDisplay: function() {
      var setting = this.setting;
      if (setting == 'allow')
        return templateData.allowException;
      else if (setting == 'block')
        return templateData.blockException;
    },

    /**
     * Copy the data model values to the editable nodes.
     */
    updateEditables: function() {
      if (!(this.pattern && this.setting))
        return;

      this.input.value = this.pattern;

      if (this.setting == 'allow')
        this.optionAllow.selected = true;
      else if (this.setting == 'block')
        this.optionBlock.selected = true;
    },

    /**
     * Whether the user is currently able to edit the list item.
     * @type {boolean}
     */
    get editing() {
      return this.hasAttribute('editing');
    },
    set editing(editing) {
      var oldEditing = this.editing;
      if (oldEditing == editing)
        return;

      var listItem = this;
      var pattern = this.pattern;
      var setting = this.setting;
      var patternLabel = this.patternLabel;
      var settingLabel = this.settingLabel;
      var input = this.input;
      var select = this.select;
      var optionAllow = this.optionAllow;
      var optionBlock = this.optionBlock;

      patternLabel.classList.toggle('hidden');
      settingLabel.classList.toggle('hidden');
      input.classList.toggle('hidden');
      select.classList.toggle('hidden');

      var doc = this.ownerDocument;
      if (editing) {
        this.setAttribute('editing', '');
        cr.ui.limitInputWidth(input, this, 20);
        input.focus();
        input.select();
      } else {
        // Check that we have a valid pattern and if not we do not change then
        // editing mode.
        var newPattern = input.value;

        // TODO(estade): check for pattern validity.
        if (false) {
          // In case the item was removed before getting here we should
          // not alert.
          if (listItem.parentNode) {
            // TODO(estade): i18n
            alert('invalid pattern');
          }
          input.focus();
          input.select();
          return;
        }

        this.pattern = patternLabel.textContent = newPattern;
        if (optionAllow.selected)
          this.setting = 'allow';
        else if (optionBlock.selected)
          this.setting = 'block';
        settingLabel.textContent = this.settingForDisplay();

        this.removeAttribute('editing');

        if (pattern != this.pattern)
          chrome.send('removeImageExceptions', [pattern]);

        chrome.send('setImageException', [this.pattern, this.setting]);
      }
    }
  };

  /**
   * Creates a new exceptions list.
   * @constructor
   * @extends {cr.ui.List}
   */
  var ExceptionsList = cr.ui.define('list');

  ExceptionsList.prototype = {
    __proto__: List.prototype,

    /**
     * Called when an element is decorated as a list.
     */
    decorate: function() {
      List.prototype.decorate.call(this);

      this.dataModel = new ArrayDataModel([]);
    },

    /**
     * Creates an item to go in the list.
     * @param {Object} entry The element from the data model for this row.
     */
    createItem: function(entry) {
      return new ExceptionsListItem(entry);
    },

    /**
     * Adds an exception to the js model.
     * @param {Array} entry A pair of the form [filter, setting].
     */
    addException: function(entry) {
      this.dataModel.push(entry);
    },

    /**
     * Removes all exceptions from the js model.
     */
    clear: function() {
      this.dataModel = new ArrayDataModel([]);
    },

    /**
     * Removes all selected rows from browser's model.
     */
    removeSelectedRows: function() {
      var removePatterns = [];
      var selectedItems = this.selectedItems;
      for (var i = 0; i < selectedItems.length; ++i) {
        removePatterns.push(selectedItems[i][0]);
      }

      chrome.send('removeImageExceptions', removePatterns);
    },

    /**
     * Puts the selected row in editing mode.
     */
    editSelectedRow: function() {
      var li = this.getListItem(this.selectedItem);
      if (li)
        li.editing = true;
    }
  };

  var ExceptionsArea = cr.ui.define('div');

  ExceptionsArea.prototype = {
    __proto__: HTMLDivElement.prototype,

    decorate: function() {
      ExceptionsList.decorate($('imagesExceptionsList'));

      var addRow = cr.doc.createElement('button');
      addRow.textContent = templateData.addExceptionRow;
      this.appendChild(addRow);

      // TODO(estade): disable "Edit" when other than exactly 1 row is
      // highlighted.
      var editRow = cr.doc.createElement('button');
      editRow.textContent = templateData.editExceptionRow;
      this.appendChild(editRow);

      // TODO(estade): disable "Remove" when no row is highlighted.
      var removeRow = cr.doc.createElement('button');
      removeRow.textContent = templateData.removeExceptionRow;
      this.appendChild(removeRow);

      addRow.onclick = function(event) {
        // TODO(estade): implement this.
      };

      editRow.onclick = function(event) {
        imagesExceptionsList.editSelectedRow();
      };

      removeRow.onclick = function(event) {
        imagesExceptionsList.removeSelectedRows();
      };
    }
  };

  return {
    ExceptionsListItem: ExceptionsListItem,
    ExceptionsList: ExceptionsList,
    ExceptionsArea: ExceptionsArea
  };
});
<commit_msg>tabbed content settings:<commit_after>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('options.contentSettings', function() {
  const List = cr.ui.List;
  const ListItem = cr.ui.ListItem;
  const ArrayDataModel = cr.ui.ArrayDataModel;

  /**
   * Creates a new exceptions list item.
   * @param {Array} exception A pair of the form [filter, setting].
   * @constructor
   * @extends {cr.ui.ListItem}
   */
  function ExceptionsListItem(exception) {
    var el = cr.doc.createElement('li');
    el.dataItem = exception;
    el.__proto__ = ExceptionsListItem.prototype;
    el.decorate();

    return el;
  }

  ExceptionsListItem.prototype = {
    __proto__: ListItem.prototype,

    /**
     * Called when an element is decorated as a list item.
     */
    decorate: function() {
      ListItem.prototype.decorate.call(this);

      // Labels for display mode.
      var patternLabel = cr.doc.createElement('span');
      patternLabel.textContent = this.pattern;
      this.appendChild(patternLabel);

      var settingLabel = cr.doc.createElement('span');
      settingLabel.textContent = this.settingForDisplay();
      settingLabel.className = 'exceptionSetting';
      this.appendChild(settingLabel);

      // Elements for edit mode.
      var input = cr.doc.createElement('input');
      input.type = 'text';
      this.appendChild(input);
      input.className = 'exceptionInput hidden';

      var select = cr.doc.createElement('select');
      var optionAllow = cr.doc.createElement('option');
      optionAllow.textContent = templateData.allowException;
      var optionBlock = cr.doc.createElement('option');
      optionBlock.textContent = templateData.blockException;
      select.appendChild(optionAllow);
      select.appendChild(optionBlock);
      this.appendChild(select);
      select.className = 'exceptionSetting hidden';

      this.patternLabel = patternLabel;
      this.settingLabel = settingLabel;
      this.input = input;
      this.select = select;
      this.optionAllow = optionAllow;
      this.optionBlock = optionBlock;

      this.updateEditables();

      // Handle events on the editable nodes.
      var eventsToStop =
          ['mousedown', 'mouseup', 'contextmenu', 'dblclick', 'paste'];
      eventsToStop.forEach(function(type) {
        input.addEventListener(type, function(e) {
            e.stopPropagation();
          });
        }
      );

      var listItem = this;
      // Handles enter and escape which trigger reset and commit respectively.
      function handleKeydown(e) {
        // Make sure that the tree does not handle the key.
        e.stopPropagation();

        // Calling list.focus blurs the input which will stop editing the list
        // item.
        switch (e.keyIdentifier) {
          case 'U+001B':  // Esc
            // Reset the inputs.
            listItem.updateEditables();
          case 'Enter':
            if (listItem.parentNode)
              listItem.parentNode.focus();
        }
      }

      function handleBlur(e) {
        // When the blur event happens we do not know who is getting focus so we
        // delay this a bit since we want to know if the other input got focus
        // before deciding if we should exit edit mode.
        var doc = e.target.ownerDocument;
        window.setTimeout(function() {
          var activeElement = doc.activeElement;
          if (!listItem.contains(activeElement)) {
            listItem.editing = false;
          }
        }, 50);
      }

      input.addEventListener('keydown', handleKeydown);
      input.addEventListener('blur', handleBlur);

      select.addEventListener('keydown', handleKeydown);
      select.addEventListener('blur', handleBlur);
    },

    /**
     * The pattern (e.g., a URL) for the exception.
     * @type {string}
     */
    get pattern() {
      return this.dataItem[0];
    },
    set pattern(pattern) {
      this.dataItem[0] = pattern;
    },

    /**
     * The setting (allow/block) for the exception.
     * @type {string}
     */
    get setting() {
      return this.dataItem[1];
    },
    set setting(setting) {
      this.dataItem[1] = setting;
    },

    /**
     * Gets a human-readable setting string.
     * @type {string}
     */
    settingForDisplay: function() {
      var setting = this.setting;
      if (setting == 'allow')
        return templateData.allowException;
      else if (setting == 'block')
        return templateData.blockException;
    },

    /**
     * Copy the data model values to the editable nodes.
     */
    updateEditables: function() {
      if (!(this.pattern && this.setting))
        return;

      this.input.value = this.pattern;

      if (this.setting == 'allow')
        this.optionAllow.selected = true;
      else if (this.setting == 'block')
        this.optionBlock.selected = true;
    },

    /**
     * Whether the user is currently able to edit the list item.
     * @type {boolean}
     */
    get editing() {
      return this.hasAttribute('editing');
    },
    set editing(editing) {
      var oldEditing = this.editing;
      if (oldEditing == editing)
        return;

      var listItem = this;
      var pattern = this.pattern;
      var setting = this.setting;
      var patternLabel = this.patternLabel;
      var settingLabel = this.settingLabel;
      var input = this.input;
      var select = this.select;
      var optionAllow = this.optionAllow;
      var optionBlock = this.optionBlock;

      patternLabel.classList.toggle('hidden');
      settingLabel.classList.toggle('hidden');
      input.classList.toggle('hidden');
      select.classList.toggle('hidden');

      var doc = this.ownerDocument;
      if (editing) {
        this.setAttribute('editing', '');
        cr.ui.limitInputWidth(input, this, 20);
        input.focus();
        input.select();
      } else {
        // Check that we have a valid pattern and if not we do not change then
        // editing mode.
        var newPattern = input.value;

        // TODO(estade): check for pattern validity.
        if (false) {
          // In case the item was removed before getting here we should
          // not alert.
          if (listItem.parentNode) {
            // TODO(estade): i18n
            alert('invalid pattern');
          }
          input.focus();
          input.select();
          return;
        }

        this.pattern = patternLabel.textContent = newPattern;
        if (optionAllow.selected)
          this.setting = 'allow';
        else if (optionBlock.selected)
          this.setting = 'block';
        settingLabel.textContent = this.settingForDisplay();

        this.removeAttribute('editing');

        if (pattern != this.pattern)
          chrome.send('removeImageExceptions', [pattern]);

        chrome.send('setImageException', [this.pattern, this.setting]);
      }
    }
  };

  /**
   * Creates a new exceptions list.
   * @constructor
   * @extends {cr.ui.List}
   */
  var ExceptionsList = cr.ui.define('list');

  ExceptionsList.prototype = {
    __proto__: List.prototype,

    /**
     * Called when an element is decorated as a list.
     */
    decorate: function() {
      List.prototype.decorate.call(this);

      this.dataModel = new ArrayDataModel([]);
    },

    /**
     * Creates an item to go in the list.
     * @param {Object} entry The element from the data model for this row.
     */
    createItem: function(entry) {
      return new ExceptionsListItem(entry);
    },

    /**
     * Adds an exception to the js model.
     * @param {Array} entry A pair of the form [filter, setting].
     */
    addException: function(entry) {
      this.dataModel.push(entry);

      // When an empty row is added, put it into editing mode.
      if (!entry[0] && !entry[1]) {
        var index = this.dataModel.length - 1;
        var sm = this.selectionModel;
        sm.anchorIndex = sm.leadIndex = sm.selectedIndex = index;
        this.scrollIndexIntoView(index);
        var li = this.getListItemByIndex(index);
        li.editing = true;
      }
    },

    /**
     * Removes all exceptions from the js model.
     */
    clear: function() {
      this.dataModel = new ArrayDataModel([]);
    },

    /**
     * Removes all selected rows from browser's model.
     */
    removeSelectedRows: function() {
      var removePatterns = [];
      var selectedItems = this.selectedItems;
      for (var i = 0; i < selectedItems.length; ++i) {
        removePatterns.push(selectedItems[i][0]);
      }

      chrome.send('removeImageExceptions', removePatterns);
    },

    /**
     * Puts the selected row in editing mode.
     */
    editSelectedRow: function() {
      var li = this.getListItem(this.selectedItem);
      if (li)
        li.editing = true;
    }
  };

  var ExceptionsArea = cr.ui.define('div');

  ExceptionsArea.prototype = {
    __proto__: HTMLDivElement.prototype,

    decorate: function() {
      ExceptionsList.decorate($('imagesExceptionsList'));
      this.boundHandleOnSelectionChange_ =
          cr.bind(this.handleOnSelectionChange_, this);
      imagesExceptionsList.selectionModel.addEventListener(
          'change', this.boundHandleOnSelectionChange_);

      var addRow = cr.doc.createElement('button');
      addRow.textContent = templateData.addExceptionRow;
      this.appendChild(addRow);

      var editRow = cr.doc.createElement('button');
      editRow.textContent = templateData.editExceptionRow;
      this.appendChild(editRow);
      this.editRow = editRow;

      var removeRow = cr.doc.createElement('button');
      removeRow.textContent = templateData.removeExceptionRow;
      this.appendChild(removeRow);
      this.removeRow = removeRow;

      addRow.onclick = function(event) {
        imagesExceptionsList.addException(['', '']);
      };

      editRow.onclick = function(event) {
        imagesExceptionsList.editSelectedRow();
      };

      removeRow.onclick = function(event) {
        imagesExceptionsList.removeSelectedRows();
      };

      this.updateButtonSensitivity();
    },

    /**
     * Update the enabled/disabled state of the editing buttons based on which
     * rows are selected.
     */
    updateButtonSensitivity: function() {
      var selectionSize = imagesExceptionsList.selectedItems.length;
      this.editRow.disabled = selectionSize != 1;
      this.removeRow.disabled = selectionSize == 0;
    },

    /**
     * Callback from the selection model.
     * @param {!cr.Event} ce Event with change info.
     * @private
     */
    handleOnSelectionChange_: function(ce) {
      this.updateButtonSensitivity();
   },
  };

  return {
    ExceptionsListItem: ExceptionsListItem,
    ExceptionsList: ExceptionsList,
    ExceptionsArea: ExceptionsArea
  };
});
<|endoftext|>