<commit_before>/*
Copyright (c) 2011, Chris Umbel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

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

var PorterStemmer = require('../stemmers/porter_stemmer'),
util = require('util');

var Classifier = function(classifier, stemmer) {
    this.classifier = classifier;
    this.docs = [];
    this.features = {};
    this.stemmer = stemmer || PorterStemmer;
    this.lastAdded = 0;
};

function addDocument(text, classification) {
    if(typeof text === 'string')
	text = this.stemmer.tokenizeAndStem(text);

    this.docs.push({
	label: classification,
	text: text
    });

    for(var i = 0; i < text.length; i++) {
	this.features[text[i]] = 1;
    }
}

function textToFeatures(observation) {
    var features = [];

    if(typeof observation === 'string')
	observation = this.stemmer.tokenizeAndStem(observation);

    for(var feature in this.features) {
        if(observation.indexOf(feature) > -1)
            features.push(1);
        else
            features.push(0);
    }

    return features;
}

function train() {
    for(var i = this.lastAdded; i < this.docs.length; i++) {
	var features = this.textToFeatures(this.docs[i].text);
	this.classifier.addExample(features, this.docs[i].label);
	this.lastAdded++;
    }

    this.classifier.train();
}

function getClassifications(observation) {
    return this.classifier.getClassifications(this.textToFeatures(observation));
}

function classify(observation) {
    return this.classifier.classify(this.textToFeatures(observation));
}

function restore(classifier, stemmer) {
    classifier.stemmer = stemmer || PorterStemmer;
    return classifier;
}

function save(filename, callback) {
    var data = JSON.stringify(this);
    var fs = require('fs');
    fs.writeFile(filename, data, encoding='utf8', callback);
}

function load(filename, callback) {
    var fs = require('fs');

    fs.readFile(filename, encoding='utf8', function(err, data) {
        var classifier;

        if(!err) {
            classifier = JSON.parse(data);
        }

        if(callback)
            callback(err, classifier);
    });
}

Classifier.prototype.addDocument = addDocument;
Classifier.prototype.train = train;
Classifier.prototype.classify = classify;
Classifier.prototype.textToFeatures = textToFeatures;
Classifier.prototype.save = save;
Classifier.prototype.getClassifications = getClassifications;
Classifier.restore = restore;
Classifier.load = load;

module.exports = Classifier;
<commit_msg>ignore empty documents<commit_after>/*
Copyright (c) 2011, Chris Umbel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

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

var PorterStemmer = require('../stemmers/porter_stemmer'),
util = require('util');

var Classifier = function(classifier, stemmer) {
    this.classifier = classifier;
    this.docs = [];
    this.features = {};
    this.stemmer = stemmer || PorterStemmer;
    this.lastAdded = 0;
};

function addDocument(text, classification) {
    if(typeof text === 'string')
	text = this.stemmer.tokenizeAndStem(text);

    if(text.length === 0) {
        // ignore empty documents
        return;
    }

    this.docs.push({
	label: classification,
	text: text
    });

    for(var i = 0; i < text.length; i++) {
	this.features[text[i]] = 1;
    }
}

function textToFeatures(observation) {
    var features = [];

    if(typeof observation === 'string')
	observation = this.stemmer.tokenizeAndStem(observation);

    for(var feature in this.features) {
        if(observation.indexOf(feature) > -1)
            features.push(1);
        else
            features.push(0);
    }

    return features;
}

function train() {
    for(var i = this.lastAdded; i < this.docs.length; i++) {
	var features = this.textToFeatures(this.docs[i].text);
	this.classifier.addExample(features, this.docs[i].label);
	this.lastAdded++;
    }

    this.classifier.train();
}

function getClassifications(observation) {
    return this.classifier.getClassifications(this.textToFeatures(observation));
}

function classify(observation) {
    return this.classifier.classify(this.textToFeatures(observation));
}

function restore(classifier, stemmer) {
    classifier.stemmer = stemmer || PorterStemmer;
    return classifier;
}

function save(filename, callback) {
    var data = JSON.stringify(this);
    var fs = require('fs');
    fs.writeFile(filename, data, encoding='utf8', callback);
}

function load(filename, callback) {
    var fs = require('fs');

    fs.readFile(filename, encoding='utf8', function(err, data) {
        var classifier;

        if(!err) {
            classifier = JSON.parse(data);
        }

        if(callback)
            callback(err, classifier);
    });
}

Classifier.prototype.addDocument = addDocument;
Classifier.prototype.train = train;
Classifier.prototype.classify = classify;
Classifier.prototype.textToFeatures = textToFeatures;
Classifier.prototype.save = save;
Classifier.prototype.getClassifications = getClassifications;
Classifier.restore = restore;
Classifier.load = load;

module.exports = Classifier;
<|endoftext|>


<commit_before>/**
 * Backbone Firebase Adapter.
 */

(function() {

var _ = this._;
var Backbone = this.Backbone;

Backbone.Firebase = function(ref) {
  this._fbref = ref;
  this._children = [];
  if (typeof ref == "string") {
    this._fbref = new Firebase(ref);
  }
  _.bindAll(this);
  this._fbref.on("child_added", this._childAdded);
  this._fbref.on("child_moved", this._childMoved);
  this._fbref.on("child_changed", this._childChanged);
  this._fbref.on("child_removed", this._childRemoved);
};

_.extend(Backbone.Firebase.prototype, {

  _childAdded: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    if (prevChild) {
      var item = _.find(this._children, function(child) {
        return child.id == prevChild
      });
      this._children.splice(this._children.indexOf(item) + 1, 0, model);
    } else {
      this._children.unshift(model);
    }
  },
  _childMoved: function(childSnap, prevChild) {
    var model = childSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id;
    });
    this._childAdded(childSnap, prevChild);
  },
  _childChanged: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    var item = _.find(this._children, function(child) {
      return child.id == model.id
    });
    this._children[this._children.indexOf(item)] = model;
  },
  _childRemoved: function(oldChildSnap) {
    var model = oldChildSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id
    });
  },

  create: function(model, cb) {
    if (!model.id) {
      model.id = this._fbref.ref().push().name();
    }
    var val = model.toJSON();
    this._fbref.ref().child(model.id).set(val, _.bind(function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not create model " + model.id);
      }
    }, this));
  },

  read: function(model, cb) {
    if (!model.id) {
      _.defer(cb, "Invalid model ID provided to read");
      return;
    }
    var index = _.find(this._children, function(child) {
      return child.id == model.id
    });
    _.defer(cb, null, this._children[index]);
  },

  readAll: function(model, cb) {
    _.defer(cb, null, this._children);
  },

  update: function(model, cb) {
    var val = model.toJSON();
    this._fbref.ref().child(model.id).update(val, function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not update model " + model.id, null);
      }
    });
  },

  'delete': function(model, cb) {
    this._fbref.ref().child(model.id).remove(function(err) {
      if (!err) {
        cb(null, model);
      } else {
        cb("Could not delete model " + model.id);
      }
    });
  }
});

Backbone.Firebase.sync = function(method, model, options, error) {
  var store = model.firebase || model.collection.firebase;

  // Backwards compatibility with Backbone <= 0.3.3
  if (typeof options == 'function') {
    options = {
      success: options,
      error: error
    };
  }

  if (method == "read" && model.id == undefined) {
    method = "readAll";
  }

  store[method].apply(this, [model, function(err, val) {
    if (err) {
      model.trigger("error", model, err, options);
      if (Backbone.VERSION === "0.9.10") {
        options.error(model, err, options);
      } else {
        options.error(err);
      }
    } else {
      model.trigger("sync", model, val, options);
      if (Backbone.VERSION === "0.9.10") {
        options.success(model, val, options);
      } else {
        options.success(val);
      }
    }
  }]);
};

Backbone.oldSync = Backbone.sync;

// Override 'Backbone.sync' to default to Firebase sync.
// the original 'Backbone.sync' is still available in 'Backbone.oldSync'
Backbone.sync = function(method, model, options, error) {
  var syncMethod = Backbone.oldSync;
  if (model.firebase || (model.collection && model.collection.firebase)) {
    syncMethod = Backbone.Firebase.sync;
  }
  return syncMethod.apply(this, [method, model, options, error]);
};

// Custom Firebase Collection.
Backbone.Firebase.Collection = Backbone.Collection.extend({
  sync: function() {
    this._log("Sync called on a Firebase collection, ignoring.");
  },

  fetch: function() {
    this._log("Fetch called on a Firebase collection, ignoring.");
  },

  constructor: function(models, options) {

    // Apply parent constructor (this will also call initialize).
    Backbone.Collection.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("child_added", _.bind(this._childAdded, this));
    this.firebase.on("child_moved", _.bind(this._childMoved, this));
    this.firebase.on("child_changed", _.bind(this._childChanged, this));
    this.firebase.on("child_removed", _.bind(this._childRemoved, this));

    // Add handlers for all models in this collection, and any future ones
    // that may be added.
    function _updateModel(model, options) {
      this.firebase.ref().child(model.id).update(model.toJSON());
    }
    function _unUpdateModel(model) {
      model.off("change", _updateModel, this);
    }

    for (var i = 0; i < this.models.length; i++) {
      this.models[i].on("change", _updateModel, this);
      this.models[i].once("remove", _unUpdateModel, this);
    }
    this.on("add", function(model) {
      model.on("change", _updateModel, this);
      model.once("remove", _unUpdateModel, this);
    }, this);
  },

  comparator: function(model) {
    return model.id;
  },

  add: function(models, options) {
    var parsed = this._parseModels(models);
    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(model, _.bind(options.success, model));
    }
  },

  remove: function(models, options) {
    var parsed = this._parseModels(models);
    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(null, _.bind(options.success, model));
    }
  },

  create: function(model, options) {
    this._log("Create called, aliasing to add. Consider using Collection.add!");
    options = options ? _.clone(options) : {};
    if (options.wait) {
      this._log("Wait option provided to create, ignoring.");
    }
    model = Backbone.Collection.prototype._prepareModel.apply(
      this, [model, options]
    );
    if (!model) {
      return false;
    }
    this.add([model], options);
    return model;
  },

  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },

  // TODO: Options will be ignored for add & remove, document this!
  _parseModels: function(models) {
    var ret = [];
    models = _.isArray(models) ? models.slice() : [models];
    for (var i = 0; i < models.length; i++) {
      var model = models[i];
      if (model.toJSON && typeof model.toJSON == "function") {
        model = model.toJSON();
      }
      if (!model.id) {
        model.id = this.firebase.ref().push().name();
      }
      ret.push(model);
    }
    return ret;
  },

  _childAdded: function(snap) {
    var model = snap.val()
    if (!model.id) model.id = snap.name()
    Backbone.Collection.prototype.add.apply(this, [model]);
  },

  _childMoved: function(snap) {
    // TODO: Investigate: can this occur without the ID changing?
    this._log("_childMoved called with " + snap.val());
  },

  _childChanged: function(snap) {
    var model = snap.val();
    if (!model.id) model.id = snap.name()
    var item = _.find(this.models, function(child) {
      return child.id == model.id
    });
    if (!item) {
      // TODO: Investigate: what is the right way to handle this case?
      throw new Error("Could not find model with ID " + model.id);
    }

    var diff = _.difference(_.keys(item.attributes), _.keys(model));
    _.each(diff, function(key) {
      item.unset(key);
    });

    item.set(model);
  },

  _childRemoved: function(snap) {
    var model = snap.val()
    if (!model.id) model.id = snap.name()
    Backbone.Collection.prototype.remove.apply(this, [model]);
  }
});

// Custom Firebase Model.
Backbone.Firebase.Model = Backbone.Model.extend({
  save: function() {
    this._log("Save called on a Firebase model, ignoring.");
  },

  destroy: function(options) {
    // TODO: Fix naive success callback. Add error callback.
    this.firebase.ref().set(null, this._log);
    this.trigger('destroy', this, this.collection, options);
    if (options.success) {
      options.success(this,null,options);
    }
  },

  constructor: function(model, options) {

    // Apply parent constructor (this will also call initialize).
    Backbone.Model.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("value", this._modelChanged.bind(this));

    this.on("change", this._updateModel, this);
  },

  _updateModel: function(model, options) {
    // Find the deleted keys and set their values to null
    // so Firebase properly deletes them.
    var modelObj = model.toJSON();
    _.each(model.changed, function(value, key) {
      if (typeof value === "undefined" || value === null)
        modelObj[key] = null;
    });
    this.firebase.ref().update(modelObj, this._log);
  },

  _modelChanged: function(snap) {
    // Unset attributes that have been deleted from the server
    // by comparing the keys that have been removed.
    var newModel = snap.val();
    if (typeof newModel === "object" && newModel !== null) {
      var diff = _.difference(_.keys(this.attributes), _.keys(newModel));
      var _this = this;
      _.each(diff, function(key) {
        _this.unset(key);
      });
    }
    this.set(newModel);
    this.trigger('sync', this, null, null);
  },

  _log: function(msg) {
    if (typeof msg === "undefined" || msg === null) return;
    if (console && console.log) {
      console.log(msg);
    }
  }

});

})();
<commit_msg>check for the existence of options and options.success<commit_after>/**
 * Backbone Firebase Adapter.
 */

(function() {

var _ = this._;
var Backbone = this.Backbone;

Backbone.Firebase = function(ref) {
  this._fbref = ref;
  this._children = [];
  if (typeof ref == "string") {
    this._fbref = new Firebase(ref);
  }
  _.bindAll(this);
  this._fbref.on("child_added", this._childAdded);
  this._fbref.on("child_moved", this._childMoved);
  this._fbref.on("child_changed", this._childChanged);
  this._fbref.on("child_removed", this._childRemoved);
};

_.extend(Backbone.Firebase.prototype, {

  _childAdded: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    if (prevChild) {
      var item = _.find(this._children, function(child) {
        return child.id == prevChild
      });
      this._children.splice(this._children.indexOf(item) + 1, 0, model);
    } else {
      this._children.unshift(model);
    }
  },
  _childMoved: function(childSnap, prevChild) {
    var model = childSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id;
    });
    this._childAdded(childSnap, prevChild);
  },
  _childChanged: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    var item = _.find(this._children, function(child) {
      return child.id == model.id
    });
    this._children[this._children.indexOf(item)] = model;
  },
  _childRemoved: function(oldChildSnap) {
    var model = oldChildSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id
    });
  },

  create: function(model, cb) {
    if (!model.id) {
      model.id = this._fbref.ref().push().name();
    }
    var val = model.toJSON();
    this._fbref.ref().child(model.id).set(val, _.bind(function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not create model " + model.id);
      }
    }, this));
  },

  read: function(model, cb) {
    if (!model.id) {
      _.defer(cb, "Invalid model ID provided to read");
      return;
    }
    var index = _.find(this._children, function(child) {
      return child.id == model.id
    });
    _.defer(cb, null, this._children[index]);
  },

  readAll: function(model, cb) {
    _.defer(cb, null, this._children);
  },

  update: function(model, cb) {
    var val = model.toJSON();
    this._fbref.ref().child(model.id).update(val, function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not update model " + model.id, null);
      }
    });
  },

  'delete': function(model, cb) {
    this._fbref.ref().child(model.id).remove(function(err) {
      if (!err) {
        cb(null, model);
      } else {
        cb("Could not delete model " + model.id);
      }
    });
  }
});

Backbone.Firebase.sync = function(method, model, options, error) {
  var store = model.firebase || model.collection.firebase;

  // Backwards compatibility with Backbone <= 0.3.3
  if (typeof options == 'function') {
    options = {
      success: options,
      error: error
    };
  }

  if (method == "read" && model.id == undefined) {
    method = "readAll";
  }

  store[method].apply(this, [model, function(err, val) {
    if (err) {
      model.trigger("error", model, err, options);
      if (Backbone.VERSION === "0.9.10") {
        options.error(model, err, options);
      } else {
        options.error(err);
      }
    } else {
      model.trigger("sync", model, val, options);
      if (Backbone.VERSION === "0.9.10") {
        options.success(model, val, options);
      } else {
        options.success(val);
      }
    }
  }]);
};

Backbone.oldSync = Backbone.sync;

// Override 'Backbone.sync' to default to Firebase sync.
// the original 'Backbone.sync' is still available in 'Backbone.oldSync'
Backbone.sync = function(method, model, options, error) {
  var syncMethod = Backbone.oldSync;
  if (model.firebase || (model.collection && model.collection.firebase)) {
    syncMethod = Backbone.Firebase.sync;
  }
  return syncMethod.apply(this, [method, model, options, error]);
};

// Custom Firebase Collection.
Backbone.Firebase.Collection = Backbone.Collection.extend({
  sync: function() {
    this._log("Sync called on a Firebase collection, ignoring.");
  },

  fetch: function() {
    this._log("Fetch called on a Firebase collection, ignoring.");
  },

  constructor: function(models, options) {

    // Apply parent constructor (this will also call initialize).
    Backbone.Collection.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("child_added", _.bind(this._childAdded, this));
    this.firebase.on("child_moved", _.bind(this._childMoved, this));
    this.firebase.on("child_changed", _.bind(this._childChanged, this));
    this.firebase.on("child_removed", _.bind(this._childRemoved, this));

    // Add handlers for all models in this collection, and any future ones
    // that may be added.
    function _updateModel(model, options) {
      this.firebase.ref().child(model.id).update(model.toJSON());
    }
    function _unUpdateModel(model) {
      model.off("change", _updateModel, this);
    }

    for (var i = 0; i < this.models.length; i++) {
      this.models[i].on("change", _updateModel, this);
      this.models[i].once("remove", _unUpdateModel, this);
    }
    this.on("add", function(model) {
      model.on("change", _updateModel, this);
      model.once("remove", _unUpdateModel, this);
    }, this);
  },

  comparator: function(model) {
    return model.id;
  },

  add: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success = _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(model, _.bind(options.success, model));
    }
  },

  remove: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success = _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(null, _.bind(options.success, model));
    }
  },

  create: function(model, options) {
    this._log("Create called, aliasing to add. Consider using Collection.add!");
    options = options ? _.clone(options) : {};
    if (options.wait) {
      this._log("Wait option provided to create, ignoring.");
    }
    model = Backbone.Collection.prototype._prepareModel.apply(
      this, [model, options]
    );
    if (!model) {
      return false;
    }
    this.add([model], options);
    return model;
  },

  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },

  // TODO: Options will be ignored for add & remove, document this!
  _parseModels: function(models) {
    var ret = [];
    models = _.isArray(models) ? models.slice() : [models];
    for (var i = 0; i < models.length; i++) {
      var model = models[i];
      if (model.toJSON && typeof model.toJSON == "function") {
        model = model.toJSON();
      }
      if (!model.id) {
        model.id = this.firebase.ref().push().name();
      }
      ret.push(model);
    }
    return ret;
  },

  _childAdded: function(snap) {
    var model = snap.val()
    if (!model.id) model.id = snap.name()
    Backbone.Collection.prototype.add.apply(this, [model]);
  },

  _childMoved: function(snap) {
    // TODO: Investigate: can this occur without the ID changing?
    this._log("_childMoved called with " + snap.val());
  },

  _childChanged: function(snap) {
    var model = snap.val();
    if (!model.id) model.id = snap.name()
    var item = _.find(this.models, function(child) {
      return child.id == model.id
    });
    if (!item) {
      // TODO: Investigate: what is the right way to handle this case?
      throw new Error("Could not find model with ID " + model.id);
    }

    var diff = _.difference(_.keys(item.attributes), _.keys(model));
    _.each(diff, function(key) {
      item.unset(key);
    });

    item.set(model);
  },

  _childRemoved: function(snap) {
    var model = snap.val()
    if (!model.id) model.id = snap.name()
    Backbone.Collection.prototype.remove.apply(this, [model]);
  }
});

// Custom Firebase Model.
Backbone.Firebase.Model = Backbone.Model.extend({
  save: function() {
    this._log("Save called on a Firebase model, ignoring.");
  },

  destroy: function(options) {
    // TODO: Fix naive success callback. Add error callback.
    this.firebase.ref().set(null, this._log);
    this.trigger('destroy', this, this.collection, options);
    if (options.success) {
      options.success(this,null,options);
    }
  },

  constructor: function(model, options) {

    // Apply parent constructor (this will also call initialize).
    Backbone.Model.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("value", this._modelChanged.bind(this));

    this.on("change", this._updateModel, this);
  },

  _updateModel: function(model, options) {
    // Find the deleted keys and set their values to null
    // so Firebase properly deletes them.
    var modelObj = model.toJSON();
    _.each(model.changed, function(value, key) {
      if (typeof value === "undefined" || value === null)
        modelObj[key] = null;
    });
    this.firebase.ref().update(modelObj, this._log);
  },

  _modelChanged: function(snap) {
    // Unset attributes that have been deleted from the server
    // by comparing the keys that have been removed.
    var newModel = snap.val();
    if (typeof newModel === "object" && newModel !== null) {
      var diff = _.difference(_.keys(this.attributes), _.keys(newModel));
      var _this = this;
      _.each(diff, function(key) {
        _this.unset(key);
      });
    }
    this.set(newModel);
    this.trigger('sync', this, null, null);
  },

  _log: function(msg) {
    if (typeof msg === "undefined" || msg === null) return;
    if (console && console.log) {
      console.log(msg);
    }
  }

});

})();
<|endoftext|>


<commit_before>/**
 * Backbone Firebase Adapter.
 */

(function() {

var _ = this._;
var Backbone = this.Backbone;

Backbone.Firebase = function(ref) {
  this._fbref = ref;
  this._children = [];
  if (typeof ref == "string") {
    this._fbref = new Firebase(ref);
  }

  this._fbref.on("child_added", this._childAdded, this);
  this._fbref.on("child_moved", this._childMoved, this);
  this._fbref.on("child_changed", this._childChanged, this);
  this._fbref.on("child_removed", this._childRemoved, this);
};

_.extend(Backbone.Firebase.prototype, {

  _childAdded: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    if (prevChild) {
      var item = _.find(this._children, function(child) {
        return child.id == prevChild
      });
      this._children.splice(this._children.indexOf(item) + 1, 0, model);
    } else {
      this._children.unshift(model);
    }
  },
  _childMoved: function(childSnap, prevChild) {
    var model = childSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id;
    });
    this._childAdded(childSnap, prevChild);
  },
  _childChanged: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    var item = _.find(this._children, function(child) {
      return child.id == model.id
    });
    this._children[this._children.indexOf(item)] = model;
  },
  _childRemoved: function(oldChildSnap) {
    var model = oldChildSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id
    });
  },

  create: function(model, cb) {
    if (!model.id) {
      model.id = this._fbref.ref().push().name();
    }
    var val = model.toJSON();
    this._fbref.ref().child(model.id).set(val, _.bind(function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not create model " + model.id);
      }
    }, this));
  },

  read: function(model, cb) {
    if (!model.id) {
      _.defer(cb, "Invalid model ID provided to read");
      return;
    }
    var index = _.find(this._children, function(child) {
      return child.id == model.id
    });
    _.defer(cb, null, this._children[index]);
  },

  readAll: function(model, cb) {
    _.defer(cb, null, this._children);
  },

  update: function(model, cb) {
    var val = model.toJSON();
    this._fbref.ref().child(model.id).update(val, function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not update model " + model.id, null);
      }
    });
  },

  "delete": function(model, cb) {
    this._fbref.ref().child(model.id).remove(function(err) {
      if (!err) {
        cb(null, model);
      } else {
        cb("Could not delete model " + model.id);
      }
    });
  }
});

Backbone.Firebase.sync = function(method, model, options, error) {
  var store = model.firebase || model.collection.firebase;

  // Backwards compatibility with Backbone <= 0.3.3
  if (typeof options == "function") {
    options = {
      success: options,
      error: error
    };
  }

  if (method == "read" && model.id == undefined) {
    method = "readAll";
  }

  store[method].apply(store, [model, function(err, val) {
    if (err) {
      model.trigger("error", model, err, options);
      if (Backbone.VERSION === "0.9.10") {
        options.error(model, err, options);
      } else {
        options.error(err);
      }
    } else {
      model.trigger("sync", model, val, options);
      if (Backbone.VERSION === "0.9.10") {
        options.success(model, val, options);
      } else {
        options.success(val);
      }
    }
  }]);
};

Backbone.oldSync = Backbone.sync;

// Override "Backbone.sync" to default to Firebase sync.
// the original "Backbone.sync" is still available in "Backbone.oldSync"
Backbone.sync = function(method, model, options, error) {
  var syncMethod = Backbone.oldSync;
  if (model.firebase || (model.collection && model.collection.firebase)) {
    syncMethod = Backbone.Firebase.sync;
  }
  return syncMethod.apply(this, [method, model, options, error]);
};

// Custom Firebase Collection.
Backbone.Firebase.Collection = Backbone.Collection.extend({
  sync: function() {
    this._log("Sync called on a Firebase collection, ignoring.");
  },

  fetch: function() {
    this._log("Fetch called on a Firebase collection, ignoring.");
  },

  constructor: function(models, options) {

    // Apply parent constructor (this will also call initialize).
    Backbone.Collection.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("child_added", _.bind(this._childAdded, this));
    this.firebase.on("child_moved", _.bind(this._childMoved, this));
    this.firebase.on("child_changed", _.bind(this._childChanged, this));
    this.firebase.on("child_removed", _.bind(this._childRemoved, this));

    this.listenTo(this, "change", this._updateModel, this);
  },

  comparator: function(model) {
    return model.id;
  },

  add: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success = _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(model, _.bind(options.success, model));
    }
  },

  remove: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success = _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.ref().child(model.id).set(null, _.bind(options.success, model));
    }
  },

  create: function(model, options) {
    this._log("Create called, aliasing to add. Consider using Collection.add!");
    options = options ? _.clone(options) : {};
    if (options.wait) {
      this._log("Wait option provided to create, ignoring.");
    }
    model = Backbone.Collection.prototype._prepareModel.apply(
      this, [model, options]
    );
    if (!model) {
      return false;
    }
    this.add([model], options);
    return model;
  },

  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },

  // TODO: Options will be ignored for add & remove, document this!
  _parseModels: function(models) {
    var ret = [];
    models = _.isArray(models) ? models.slice() : [models];
    for (var i = 0; i < models.length; i++) {
      var model = models[i];
      if (model.toJSON && typeof model.toJSON == "function") {
        model = model.toJSON();
      }
      if (!model.id) {
        model.id = this.firebase.ref().push().name();
      }
      ret.push(model);
    }
    return ret;
  },

  _childAdded: function(snap) {
    var model = snap.val()
    if (!model.id) {
      if (!_.isObject(model)) model = {};
      model.id = snap.name()
    }
    Backbone.Collection.prototype.add.apply(this, [model]);
    this.get(model.id)._remoteAttributes = model;
  },

  _childMoved: function(snap) {
    // TODO: Investigate: can this occur without the ID changing?
    this._log("_childMoved called with " + snap.val());
  },

  _childChanged: function(snap) {
    var model = snap.val();
    if (!model.id) model.id = snap.name()
    var item = _.find(this.models, function(child) {
      return child.id == model.id
    });
    if (!item) {
      // TODO: Investigate: what is the right way to handle this case?
      throw new Error("Could not find model with ID " + model.id);
    }

    this._preventSync(item, true);
    item._remoteAttributes = model;

    var diff = _.difference(_.keys(item.attributes), _.keys(model));
    _.each(diff, function(key) {
      item.unset(key);
    });

    item.set(model);

    this._preventSync(item, false);
  },

  _childRemoved: function(snap) {
    var model = snap.val()
    if (!model.id) model.id = snap.name()
    Backbone.Collection.prototype.remove.apply(this, [model]);
  },

  // Add handlers for all models in this collection, and any future ones
  // that may be added.
  _updateModel: function(model, options) {

    if (model._remoteChanging) return;

    var remoteAttributes = model._remoteAttributes || {};
    var localAttributes = model.toJSON();
    var updateAttributes = {};

    _.each(_.union(_.keys(remoteAttributes),_.keys(localAttributes)), function(key) {
      if (!_.has(localAttributes, key)) {
        updateAttributes[key] = null;
      }
      else if (localAttributes[key] != remoteAttributes[key]) {
        updateAttributes[key] = localAttributes[key];
      }
    });

    if (_.size(updateAttributes)) {
      this.firebase.ref().child(model.id).update(updateAttributes);
    }
  },

  _preventSync: function(model, state) {
    model._remoteChanging = state;
  }
});

// Custom Firebase Model.
Backbone.Firebase.Model = Backbone.Model.extend({
  save: function() {
    this._log("Save called on a Firebase model, ignoring.");
  },

  destroy: function(options) {
    // TODO: Fix naive success callback. Add error callback.
    this.firebase.ref().set(null, this._log);
    this.trigger("destroy", this, this.collection, options);
    if (options.success) {
      options.success(this,null,options);
    }
  },

  constructor: function(model, options) {
    // Store defaults so they don't get applied immediately.
    var defaults = _.result(this, "defaults");

    // Apply defaults only after first sync.
    this.once("sync", function() {
      this.set(_.defaults(this.toJSON(), defaults));
    });

    // Apply parent constructor (this will also call initialize).
    Backbone.Model.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("value", _.bind(this._modelChanged, this));

    this._listenLocalChange(true);
  },

  _listenLocalChange: function(state) {
    if (state)
      this.on("change", this._updateModel, this);
    else
      this.off("change", this._updateModel, this);
  },

  _updateModel: function(model, options) {
    // Find the deleted keys and set their values to null
    // so Firebase properly deletes them.
    var modelObj = model.changedAttributes();
    _.each(model.changed, function(value, key) {
      if (typeof value === "undefined" || value === null)
        if (key == "id")
          delete modelObj[key];
        else
          modelObj[key] = null;
    });
    if (_.size(modelObj))
      this.firebase.ref().update(modelObj, this._log);
  },

  _modelChanged: function(snap) {
    // Unset attributes that have been deleted from the server
    // by comparing the keys that have been removed.
    var newModel = snap.val();
    if (typeof newModel === "object" && newModel !== null) {
      var diff = _.difference(_.keys(this.attributes), _.keys(newModel));
      var self = this;
      _.each(diff, function(key) {
        self.unset(key);
      });
    }
    this._listenLocalChange(false);
    this.set(newModel);
    this.trigger("sync", this, null, null);
    this._listenLocalChange(true);
  },

  _log: function(msg) {
    if (typeof msg === "undefined" || msg === null) return;
    if (console && console.log) {
      console.log(msg);
    }
  }

});

})();
<commit_msg>Handle model.destroy() on a Backbone.Firebase.Collection, closes #49.<commit_after>/**
 * Backbone Firebase Adapter.
 */

(function() {

var _ = this._;
var Backbone = this.Backbone;

Backbone.Firebase = function(ref) {
  this._fbref = ref;
  this._children = [];
  if (typeof ref == "string") {
    this._fbref = new Firebase(ref);
  }

  this._fbref.on("child_added", this._childAdded, this);
  this._fbref.on("child_moved", this._childMoved, this);
  this._fbref.on("child_changed", this._childChanged, this);
  this._fbref.on("child_removed", this._childRemoved, this);
};

_.extend(Backbone.Firebase.prototype, {
  _childAdded: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    if (prevChild) {
      var item = _.find(this._children, function(child) {
        return child.id == prevChild
      });
      this._children.splice(this._children.indexOf(item) + 1, 0, model);
    } else {
      this._children.unshift(model);
    }
  },

  _childMoved: function(childSnap, prevChild) {
    var model = childSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id;
    });
    this._childAdded(childSnap, prevChild);
  },

  _childChanged: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    var item = _.find(this._children, function(child) {
      return child.id == model.id
    });
    this._children[this._children.indexOf(item)] = model;
  },

  _childRemoved: function(oldChildSnap) {
    var model = oldChildSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id
    });
  },

  create: function(model, cb) {
    if (!model.id) {
      model.id = this._fbref.ref().push().name();
    }

    var val = model.toJSON();
    this._fbref.ref().child(model.id).set(val, _.bind(function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not create model " + model.id);
      }
    }, this));
  },

  read: function(model, cb) {
    if (!model.id) {
      _.defer(cb, "Invalid model ID provided to read");
      return;
    }

    var index = _.find(this._children, function(child) {
      return child.id == model.id
    });

    _.defer(cb, null, this._children[index]);
  },

  readAll: function(model, cb) {
    _.defer(cb, null, this._children);
  },

  update: function(model, cb) {
    var val = model.toJSON();
    this._fbref.ref().child(model.id).update(val, function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not update model " + model.id, null);
      }
    });
  },

  "delete": function(model, cb) {
    this._fbref.ref().child(model.id).remove(function(err) {
      if (!err) {
        cb(null, model);
      } else {
        cb("Could not delete model " + model.id);
      }
    });
  }
});

Backbone.Firebase.sync = function(method, model, options, error) {
  var store = model.firebase || model.collection.firebase;

  // Backwards compatibility with Backbone <= 0.3.3
  if (typeof options == "function") {
    options = {
      success: options,
      error: error
    };
  }

  if (method == "read" && model.id == undefined) {
    method = "readAll";
  }

  store[method].apply(store, [model, function(err, val) {
    if (err) {
      model.trigger("error", model, err, options);
      if (Backbone.VERSION === "0.9.10") {
        options.error(model, err, options);
      } else {
        options.error(err);
      }
    } else {
      model.trigger("sync", model, val, options);
      if (Backbone.VERSION === "0.9.10") {
        options.success(model, val, options);
      } else {
        options.success(val);
      }
    }
  }]);
};

Backbone.oldSync = Backbone.sync;

// Override "Backbone.sync" to default to Firebase sync.
// the original "Backbone.sync" is still available in "Backbone.oldSync"
Backbone.sync = function(method, model, options, error) {
  var syncMethod = Backbone.oldSync;
  if (model.firebase || (model.collection && model.collection.firebase)) {
    syncMethod = Backbone.Firebase.sync;
  }
  return syncMethod.apply(this, [method, model, options, error]);
};

// Custom Firebase Collection.
Backbone.Firebase.Collection = Backbone.Collection.extend({
  sync: function() {
    this._log("Sync called on a Firebase collection, ignoring.");
  },

  fetch: function() {
    this._log("Fetch called on a Firebase collection, ignoring.");
  },

  constructor: function(models, options) {
    // Apply parent constructor (this will also call initialize).
    Backbone.Collection.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("child_added", _.bind(this._childAdded, this));
    this.firebase.on("child_moved", _.bind(this._childMoved, this));
    this.firebase.on("child_changed", _.bind(this._childChanged, this));
    this.firebase.on("child_removed", _.bind(this._childRemoved, this));

    // Handle changes in any local models.
    this.listenTo(this, "change", this._updateModel, this);
    // Listen for destroy event to remove models.
    this.listenTo(this, "destroy", this._removeModel, this);
  },

  comparator: function(model) {
    return model.id;
  },

  add: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success =
      _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      var childRef = this.firebase.ref().child(model.id);
      childRef.set(model, _.bind(options.success, model));
    }
  },

  remove: function(models, options) {
    var parsed = this._parseModels(models);
    options = options ? _.clone(options) : {};
    options.success =
      _.isFunction(options.success) ? options.success : function() {};

    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      var childRef = this.firebase.ref().child(model.id);
      childRef.set(null, _.bind(options.success, model));
    }
  },

  create: function(model, options) {
    this._log("Create called, aliasing to add. Consider using Collection.add!");
    options = options ? _.clone(options) : {};
    if (options.wait) {
      this._log("Wait option provided to create, ignoring.");
    }
    model = Backbone.Collection.prototype._prepareModel.apply(
      this, [model, options]
    );
    if (!model) {
      return false;
    }
    this.add([model], options);
    return model;
  },

  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },

  // TODO: Options will be ignored for add & remove, document this!
  _parseModels: function(models) {
    var ret = [];
    models = _.isArray(models) ? models.slice() : [models];
    for (var i = 0; i < models.length; i++) {
      var model = models[i];
      if (model.toJSON && typeof model.toJSON == "function") {
        model = model.toJSON();
      }
      if (!model.id) {
        model.id = this.firebase.ref().push().name();
      }
      ret.push(model);
    }
    return ret;
  },

  _childAdded: function(snap) {
    var model = snap.val();
    if (!model.id) {
      if (!_.isObject(model)) model = {};
      model.id = snap.name();
    }
    Backbone.Collection.prototype.add.apply(this, [model]);
    this.get(model.id)._remoteAttributes = model;
  },

  _childMoved: function(snap) {
    // TODO: Investigate: can this occur without the ID changing?
    this._log("_childMoved called with " + snap.val());
  },

  _childChanged: function(snap) {
    var model = snap.val();
    if (!model.id) {
      model.id = snap.name();
    }

    var item = _.find(this.models, function(child) {
      return child.id == model.id
    });

    if (!item) {
      // TODO: Investigate: what is the right way to handle this case?
      throw new Error("Could not find model with ID " + model.id);
    }

    this._preventSync(item, true);
    item._remoteAttributes = model;

    var diff = _.difference(_.keys(item.attributes), _.keys(model));
    _.each(diff, function(key) {
      item.unset(key);
    });

    item.set(model);
    this._preventSync(item, false);
  },

  _childRemoved: function(snap) {
    var model = snap.val();
    if (!model.id) {
      model.id = snap.name();
    }
    Backbone.Collection.prototype.remove.apply(this, [model]);
  },

  // Add handlers for all models in this collection, and any future ones
  // that may be added.
  _updateModel: function(model, options) {
    if (model._remoteChanging) {
      return;
    }

    var remoteAttributes = model._remoteAttributes || {};
    var localAttributes = model.toJSON();
    var updateAttributes = {};

    var union = _.union(_.keys(remoteAttributes), _.keys(localAttributes));
    _.each(union, function(key) {
      if (!_.has(localAttributes, key)) {
        updateAttributes[key] = null;
      } else if (localAttributes[key] != remoteAttributes[key]) {
        updateAttributes[key] = localAttributes[key];
      }
    });

    if (_.size(updateAttributes)) {
      this.firebase.ref().child(model.id).update(updateAttributes);
    }
  },

  // Triggered when model.destroy() is called on one of the children.
  _removeModel: function(model, collection, options) {
    options.success =
      _.isFunction(options.success) ? options.success : function() {};
    var childRef = this.firebase.ref().child(model.id);
    childRef.set(null, _.bind(options.success, model));
  },

  _preventSync: function(model, state) {
    model._remoteChanging = state;
  }
});

// Custom Firebase Model.
Backbone.Firebase.Model = Backbone.Model.extend({
  save: function() {
    this._log("Save called on a Firebase model, ignoring.");
  },

  destroy: function(options) {
    // TODO: Fix naive success callback. Add error callback.
    this.firebase.ref().set(null, this._log);
    this.trigger("destroy", this, this.collection, options);
    if (options.success) {
      options.success(this, null, options);
    }
  },

  constructor: function(model, options) {
    // Store defaults so they don't get applied immediately.
    var defaults = _.result(this, "defaults");

    // Apply defaults only after first sync.
    this.once("sync", function() {
      this.set(_.defaults(this.toJSON(), defaults));
    });

    // Apply parent constructor (this will also call initialize).
    Backbone.Model.apply(this, arguments);

    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      case "function": this.firebase = this.firebase(); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("value", _.bind(this._modelChanged, this));

    this._listenLocalChange(true);
  },

  _listenLocalChange: function(state) {
    if (state) {
      this.on("change", this._updateModel, this);
    } else {
      this.off("change", this._updateModel, this);
    }
  },

  _updateModel: function(model, options) {
    // Find the deleted keys and set their values to null
    // so Firebase properly deletes them.
    var modelObj = model.changedAttributes();
    _.each(model.changed, function(value, key) {
      if (typeof value === "undefined" || value === null) {
        if (key == "id") {
          delete modelObj[key];
        } else {
          modelObj[key] = null;
        }
      }
    });
    if (_.size(modelObj)) {
      this.firebase.ref().update(modelObj, this._log);
    }
  },

  _modelChanged: function(snap) {
    // Unset attributes that have been deleted from the server
    // by comparing the keys that have been removed.
    var newModel = snap.val();
    if (typeof newModel === "object" && newModel !== null) {
      var diff = _.difference(_.keys(this.attributes), _.keys(newModel));
      var self = this;
      _.each(diff, function(key) {
        self.unset(key);
      });
    }
    this._listenLocalChange(false);
    this.set(newModel);
    this.trigger("sync", this, null, null);
    this._listenLocalChange(true);
  },

  _log: function(msg) {
    if (typeof msg === "undefined" || msg === null) return;
    if (console && console.log) {
      console.log(msg);
    }
  }

});

})();
<|endoftext|>


<commit_before>/**
 * Backbone Firebase Adapter.
 */

(function() {

var _ = this._;
var Backbone = this.Backbone;

Backbone.Firebase = function(ref) {
  this._fbref = ref;
  this._children = [];
  if (typeof ref == "string") {
    this._fbref = new Firebase(ref);
  }

  _.bindAll(this);
  this._fbref.on("child_added", this._childAdded);
  this._fbref.on("child_moved", this._childMoved);
  this._fbref.on("child_changed", this._childChanged);
  this._fbref.on("child_removed", this._childRemoved);
};

_.extend(Backbone.Firebase.prototype, {

  _childAdded: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    if (prevChild) {
      var item = _.find(this._children, function(child) {
        return child.id == prevChild
      });
      this._children.splice(this._children.indexOf(item) + 1, 0, model);
    } else {
      this._children.unshift(model);
    }
  },
  _childMoved: function(childSnap, prevChild) {
    var model = childSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id;
    });
    this._childAdded(childSnap, prevChild);
  },
  _childChanged: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    var item = _.find(this._children, function(child) {
      return child.id == model.id
    });
    this._children[this._children.indexOf(item)] = model;
  },
  _childRemoved: function(oldChildSnap) {
    var model = oldChildSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id
    });
  },

  create: function(model, cb) {
    if (!model.id) {
      model.id = this._fbref.push().name();
    }
    var val = model.toJSON();
    this._fbref.child(model.id).set(val, _.bind(function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not create model " + model.id);
      }
    }, this));
  },

  read: function(model, cb) {
    if (!model.id) {
      _.defer(cb, "Invalid model ID provided to read");
      return;
    }
    var index = _.find(this._children, function(child) {
      return child.id == model.id
    });
    _.defer(cb, null, this._children[index]);
  },

  readAll: function(model, cb) {
    _.defer(cb, null, this._children);
  },

  update: function(model, cb) {
    var val = model.toJSON();
    this._fbref.child(model.id).update(val, function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not update model " + model.id, null);
      }
    });
  },

  delete: function(model, cb) {
    this._fbref.child(model.id).remove(function(err) {
      if (!err) {
        cb(null, model);
      } else {
        cb("Could not delete model " + model.id);
      }
    });
  }
});

Backbone.Firebase.sync = function(method, model, options, error) {
  var store = model.firebase || model.collection.firebase;

  // Backwards compatibility with Backbone <= 0.3.3
  if (typeof options == 'function') {
    options = {
      success: options,
      error: error
    };
  }

  if (method == "read" && model.id == undefined) {
    method = "readAll";
  }

  store[method].apply(this, [model, function(err, val) {
    if (err) {
      options.error(model, err, options);
    } else {
      options.success(model, val, options);
    }
  }]);
};

Backbone.oldSync = Backbone.sync;

// Override 'Backbone.sync' to default to Firebase sync.
// the original 'Backbone.sync' is still available in 'Backbone.oldSync'
Backbone.sync = function(method, model, options, error) {
  var syncMethod = Backbone.oldSync;
  if (model.firebase || (model.collection && model.collection.firebase)) {
    syncMethod = Backbone.Firebase.sync;
  }
	return syncMethod.apply(this, [method, model, options, error]);
};

// Custom Firebase Collection.
Backbone.Firebase.Collection = Backbone.Collection.extend({
  sync: function() {
    this._log("Sync called on a Firebase collection, ignoring.");
  },

  fetch: function() {
    this._log("Fetch called on a Firebase collection, ignoring.");
  },

  constructor: function(models, options) {
    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Add handlers for remote events.
    this.firebase.on("child_added", this._childAdded.bind(this));
    this.firebase.on("child_moved", this._childMoved.bind(this));
    this.firebase.on("child_changed", this._childChanged.bind(this));
    this.firebase.on("child_removed", this._childRemoved.bind(this));

    // Apply parent constructor (this will also call initialize).
    Backbone.Collection.apply(this, arguments);

    // Add handlers for all models in this collection, and any future ones
    // that may be added.
    function _updateModel(model, options) {
      this.firebase.child(model.id).update(model.toJSON());
    }
    function _unUpdateModel(model) {
      model.off("change", _updateModel, this);
    }

    for (var i = 0; i < this.models.length; i++) {
      this.models[i].on("change", _updateModel, this);
      this.models[i].once("remove", _unUpdateModel, this);
    }
    this.on("add", function(model) {
      model.on("change", _updateModel, this);
      model.once("remove", _unUpdateModel, this);
    }, this);
  },

  comparator: function(model) {
    return model.id;
  },

  add: function(models, options) {
    var parsed = this._parseModels(models);
    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.child(model.id).set(model);
    }
    // TODO: Implement options.success
  },

  remove: function(models, options) {
    var parsed = this._parseModels(models);
    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.child(model.id).set(null);
    }
    // TODO: Implement options.success
  },

  create: function(model, options) {
    this._log("Create called, aliasing to add. Consider using Collection.add!");
    options = options ? _.clone(options) : {};
    if (options.wait) {
      this._log("Wait option provided to create, ignoring.");
    }
    model = Backbone.Collection.prototype._prepareModel.apply(
      this, [model, options]
    );
    if (!model) {
      return false;
    }
    this.add([model], options);
    return model;
  },

  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },

  // TODO: Options will be ignored for add & remove, document this!
  _parseModels: function(models) {
    var ret = [];
    models = _.isArray(models) ? models.slice() : [models];
    for (var i = 0; i < models.length; i++) {
      var model = models[i];
      if (!model.id) {
        model.id = this.firebase.push().name();
      }
      if (model.toJSON && typeof model.toJSON == "function") {
        model = model.toJSON();
      }
      ret.push(model);
    }
    return ret;
  },

  _childAdded: function(snap) {
    Backbone.Collection.prototype.add.apply(this, [snap.val()]);
  },

  _childMoved: function(snap) {
    // TODO: Investigate: can this occur without the ID changing?
    this._log("_childMoved called with " + snap.val());
  },

  _childChanged: function(snap) {
    var model = snap.val();
    var item = _.find(this.models, function(child) {
      return child.id == model.id
    });
    if (!item) {
      // TODO: Investigate: what is the right way to handle this case?
      throw new Error("Could not find model with ID " + model.id);
    }
    item.set(model);
  },

  _childRemoved: function(snap) {
    Backbone.Collection.prototype.remove.apply(this, [snap.val()]);
  }
});

})();

<commit_msg>Fix for collections that are initialized with existing Firebase references<commit_after>/**
 * Backbone Firebase Adapter.
 */

(function() {

var _ = this._;
var Backbone = this.Backbone;

Backbone.Firebase = function(ref) {
  this._fbref = ref;
  this._children = [];
  if (typeof ref == "string") {
    this._fbref = new Firebase(ref);
  }

  _.bindAll(this);
  this._fbref.on("child_added", this._childAdded);
  this._fbref.on("child_moved", this._childMoved);
  this._fbref.on("child_changed", this._childChanged);
  this._fbref.on("child_removed", this._childRemoved);
};

_.extend(Backbone.Firebase.prototype, {

  _childAdded: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    if (prevChild) {
      var item = _.find(this._children, function(child) {
        return child.id == prevChild
      });
      this._children.splice(this._children.indexOf(item) + 1, 0, model);
    } else {
      this._children.unshift(model);
    }
  },
  _childMoved: function(childSnap, prevChild) {
    var model = childSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id;
    });
    this._childAdded(childSnap, prevChild);
  },
  _childChanged: function(childSnap, prevChild) {
    var model = childSnap.val();
    model.id = childSnap.name();
    var item = _.find(this._children, function(child) {
      return child.id == model.id
    });
    this._children[this._children.indexOf(item)] = model;
  },
  _childRemoved: function(oldChildSnap) {
    var model = oldChildSnap.val();
    this._children = _.reject(this._children, function(child) {
      return child.id == model.id
    });
  },

  create: function(model, cb) {
    if (!model.id) {
      model.id = this._fbref.push().name();
    }
    var val = model.toJSON();
    this._fbref.child(model.id).set(val, _.bind(function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not create model " + model.id);
      }
    }, this));
  },

  read: function(model, cb) {
    if (!model.id) {
      _.defer(cb, "Invalid model ID provided to read");
      return;
    }
    var index = _.find(this._children, function(child) {
      return child.id == model.id
    });
    _.defer(cb, null, this._children[index]);
  },

  readAll: function(model, cb) {
    _.defer(cb, null, this._children);
  },

  update: function(model, cb) {
    var val = model.toJSON();
    this._fbref.child(model.id).update(val, function(err) {
      if (!err) {
        cb(null, val);
      } else {
        cb("Could not update model " + model.id, null);
      }
    });
  },

  delete: function(model, cb) {
    this._fbref.child(model.id).remove(function(err) {
      if (!err) {
        cb(null, model);
      } else {
        cb("Could not delete model " + model.id);
      }
    });
  }
});

Backbone.Firebase.sync = function(method, model, options, error) {
  var store = model.firebase || model.collection.firebase;

  // Backwards compatibility with Backbone <= 0.3.3
  if (typeof options == 'function') {
    options = {
      success: options,
      error: error
    };
  }

  if (method == "read" && model.id == undefined) {
    method = "readAll";
  }

  store[method].apply(this, [model, function(err, val) {
    if (err) {
      options.error(model, err, options);
    } else {
      options.success(model, val, options);
    }
  }]);
};

Backbone.oldSync = Backbone.sync;

// Override 'Backbone.sync' to default to Firebase sync.
// the original 'Backbone.sync' is still available in 'Backbone.oldSync'
Backbone.sync = function(method, model, options, error) {
  var syncMethod = Backbone.oldSync;
  if (model.firebase || (model.collection && model.collection.firebase)) {
    syncMethod = Backbone.Firebase.sync;
  }
	return syncMethod.apply(this, [method, model, options, error]);
};

// Custom Firebase Collection.
Backbone.Firebase.Collection = Backbone.Collection.extend({
  sync: function() {
    this._log("Sync called on a Firebase collection, ignoring.");
  },

  fetch: function() {
    this._log("Fetch called on a Firebase collection, ignoring.");
  },

  constructor: function(models, options) {
    if (options && options.firebase) {
      this.firebase = options.firebase;
    }
    switch (typeof this.firebase) {
      case "object": break;
      case "string": this.firebase = new Firebase(this.firebase); break;
      default: throw new Error("Invalid firebase reference created");
    }

    // Apply parent constructor (this will also call initialize).
    Backbone.Collection.apply(this, arguments);

    // Add handlers for remote events.
    this.firebase.on("child_added", this._childAdded.bind(this));
    this.firebase.on("child_moved", this._childMoved.bind(this));
    this.firebase.on("child_changed", this._childChanged.bind(this));
    this.firebase.on("child_removed", this._childRemoved.bind(this));

    // Add handlers for all models in this collection, and any future ones
    // that may be added.
    function _updateModel(model, options) {
      this.firebase.child(model.id).update(model.toJSON());
    }
    function _unUpdateModel(model) {
      model.off("change", _updateModel, this);
    }

    for (var i = 0; i < this.models.length; i++) {
      this.models[i].on("change", _updateModel, this);
      this.models[i].once("remove", _unUpdateModel, this);
    }
    this.on("add", function(model) {
      model.on("change", _updateModel, this);
      model.once("remove", _unUpdateModel, this);
    }, this);
  },

  comparator: function(model) {
    return model.id;
  },

  add: function(models, options) {
    var parsed = this._parseModels(models);
    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.child(model.id).set(model);
    }
    // TODO: Implement options.success
  },

  remove: function(models, options) {
    var parsed = this._parseModels(models);
    for (var i = 0; i < parsed.length; i++) {
      var model = parsed[i];
      this.firebase.child(model.id).set(null);
    }
    // TODO: Implement options.success
  },

  create: function(model, options) {
    this._log("Create called, aliasing to add. Consider using Collection.add!");
    options = options ? _.clone(options) : {};
    if (options.wait) {
      this._log("Wait option provided to create, ignoring.");
    }
    model = Backbone.Collection.prototype._prepareModel.apply(
      this, [model, options]
    );
    if (!model) {
      return false;
    }
    this.add([model], options);
    return model;
  },

  _log: function(msg) {
    if (console && console.log) {
      console.log(msg);
    }
  },

  // TODO: Options will be ignored for add & remove, document this!
  _parseModels: function(models) {
    var ret = [];
    models = _.isArray(models) ? models.slice() : [models];
    for (var i = 0; i < models.length; i++) {
      var model = models[i];
      if (!model.id) {
        model.id = this.firebase.push().name();
      }
      if (model.toJSON && typeof model.toJSON == "function") {
        model = model.toJSON();
      }
      ret.push(model);
    }
    return ret;
  },

  _childAdded: function(snap) {
    Backbone.Collection.prototype.add.apply(this, [snap.val()]);
  },

  _childMoved: function(snap) {
    // TODO: Investigate: can this occur without the ID changing?
    this._log("_childMoved called with " + snap.val());
  },

  _childChanged: function(snap) {
    var model = snap.val();
    var item = _.find(this.models, function(child) {
      return child.id == model.id
    });
    if (!item) {
      // TODO: Investigate: what is the right way to handle this case?
      throw new Error("Could not find model with ID " + model.id);
    }
    item.set(model);
  },

  _childRemoved: function(snap) {
    Backbone.Collection.prototype.remove.apply(this, [snap.val()]);
  }
});

})();

<|endoftext|>


<commit_before>import React, { Component } from 'react'

import globalConfig from '../../config'

import { fetchLocation, fetchOrganization } from '../../core/firebaseRestAPI'

import Layout from '../../components/Layout'
import Loading from '../../components/Loading'
import Location from '../../components/Location'

export default class LocationPage extends Component {
  constructor(props) {
    super(props)
    this.state = {
      location: null,
      organization: null,
    }
  }

  componentDidMount() {
    const match = this.props.route.pattern.exec(window.location.pathname)
    const locationId = match[1]

    fetchLocation(locationId)
      .then(location => {
        this.setState({ location })
        return fetchOrganization(location.organizationId)
      }).then(organization => {
        this.setState({ organization })
      })
  }

  componentDidUpdate() {
    const { location } = this.state
    document.title = location ? location.name : globalConfig.title
  }

  render() {
    const { location, organization } = this.state

    return (
      <Layout>
        {(location && organization) ?
          <Location location={location} organization={organization} /> :
          <Loading />
        }
      </Layout>
    )
  }
}
<commit_msg>Fix org load<commit_after>import React, { Component } from 'react'

import globalConfig from '../../config'

import { fetchLocation, fetchOrganization } from '../../core/firebaseRestAPI'

import Layout from '../../components/Layout'
import Loading from '../../components/Loading'
import Location from '../../components/Location'

export default class LocationPage extends Component {
  constructor(props) {
    super(props)
    this.state = {
      location: null,
      organization: null,
    }
  }

  componentDidMount() {
    const match = this.props.route.pattern.exec(window.location.pathname)
    const locationId = match[1]

    fetchLocation(locationId)
      .then(location => {
        this.setState({ location })
      })
    fetchOrganization(location.organizationId)
      .then(organization => {
        this.setState({ organization })
      })
  }

  componentDidUpdate() {
    const { location } = this.state
    document.title = location ? location.name : globalConfig.title
  }

  render() {
    const { location, organization } = this.state

    return (
      <Layout>
        {(location && organization) ?
          <Location location={location} organization={organization} /> :
          <Loading />
        }
      </Layout>
    )
  }
}
<|endoftext|>


<commit_before>/**
 * Created by Bladerunner on 11/03/16.
 */
var mikrotik = require('mikronode-ng');

module.exports = function(RED) {
    function NodeMikrotik(config) {
        RED.nodes.createNode(this,config);
        this.action = config.action;
        this.ip = config.ip;
        this.action = config.action;

        var node = this;
        var ip = node.ip;
        var login = node.credentials.login;
        var pass = node.credentials.pass;
        var action;

        switch (parseInt(node.action)) {
            case 0:
                action = '/log/print';
                break;
            case 1:
                action = '/system/resource/print';
                break;
            case 2:
                action = '/interface/wireless/registration-table/print';
                break;
            case 3:
                action = '/system/reboot';
                break;
            case 9:
                action = '';
                break;
        }

        var connection = null;

        this.on('input', function(msg) {
            var cmd = action;
            if (cmd == '') cmd = msg.payload;
            if (cmd == '') return false;
            connection = mikrotik.getConnection(ip, login, pass, {closeOnDone : true});
            connection.getConnectPromise().then(function(conn) {
                    conn.getCommandPromise(cmd).then(function resolved(values) {
                            var parsed = mikrotik.parseItems(values);
                            var pl = [];
                            parsed.forEach(function(item) {
                                pl.push(item);
                            });
                            msg.payload = values;
                            node.send(msg);
                    }, function rejected(reason) {
                        node.error('Error executing cmd['+action+']: ' + JSON.stringify(reason));
                    });
                },
                function(err) {
                    node.error("Connection error: " + err);
                }
            );
        });

        this.on('close', function() {
            connection && connection.connected && connection.close(true);
        });
    }

    RED.nodes.registerType("mikrotik", NodeMikrotik, {
        credentials: {
            login: {type:"text"},
            pass: {type:"password"}
        }
    });
};

<commit_msg>fixed the error message after the last merge<commit_after>/**
 * Created by Bladerunner on 11/03/16.
 */
var mikrotik = require('mikronode-ng');

module.exports = function(RED) {
    function NodeMikrotik(config) {
        RED.nodes.createNode(this,config);
        this.action = config.action;
        this.ip = config.ip;
        this.action = config.action;

        var node = this;
        var ip = node.ip;
        var login = node.credentials.login;
        var pass = node.credentials.pass;
        var action;

        switch (parseInt(node.action)) {
            case 0:
                action = '/log/print';
                break;
            case 1:
                action = '/system/resource/print';
                break;
            case 2:
                action = '/interface/wireless/registration-table/print';
                break;
            case 3:
                action = '/system/reboot';
                break;
            case 9:
                action = '';
                break;
        }

        var connection = null;

        this.on('input', function(msg) {
            var cmd = action;
            if (cmd == '') cmd = msg.payload;
            if (cmd == '') return false;
            connection = mikrotik.getConnection(ip, login, pass, {closeOnDone : true});
            connection.getConnectPromise().then(function(conn) {
                    conn.getCommandPromise(cmd).then(function resolved(values) {
                            var parsed = mikrotik.parseItems(values);
                            var pl = [];
                            parsed.forEach(function(item) {
                                pl.push(item);
                            });
                            msg.payload = values;
                            node.send(msg);
                    }, function rejected(reason) {
                        node.error('Error executing cmd['+cmd+']: ' + JSON.stringify(reason));
                    });
                },
                function(err) {
                    node.error("Connection error: " + err);
                }
            );
        });

        this.on('close', function() {
            connection && connection.connected && connection.close(true);
        });
    }

    RED.nodes.registerType("mikrotik", NodeMikrotik, {
        credentials: {
            login: {type:"text"},
            pass: {type:"password"}
        }
    });
};

<|endoftext|>


<commit_before>'use strict';

/*
 * Extracts the first infobox from an article
 * @param {!Document} doc representing article
 * @return {String[]} representing infobox html
 */
function extractInfobox(doc) {
    let infobox;
    const node = doc.querySelector('.infobox');
    if (node) {
        infobox = `<table class="${node.getAttribute('class')}">${node.innerHTML}</table>`;
        // delete it
        node.parentNode.removeChild(node);
    }
    return infobox;
}

module.exports = extractInfobox;
<commit_msg>Simplify lib/transforms/extractInfobox.js<commit_after>'use strict';

/*
 * Extracts the first infobox from an article
 * @param {!Document} doc representing article
 * @return {String[]} representing infobox html
 */
function extractInfobox(doc) {
    let infobox;
    const node = doc.querySelector('.infobox');
    if (node) {
        infobox = node.parentNode.removeChild(node).outerHTML;
    }
    return infobox;
}

module.exports = extractInfobox;
<|endoftext|>


<commit_before>var Transform = require('pipestream').Transform;
var util = require('util');
var LENGTH = 3072;

function ReplacePatternTransform(pattern, value) {
	if (!(this instanceof ReplacePatternTransform)) {
		  return new ReplacePatternTransform(options);
	  }

	Transform.call(this);

	this._pattern = pattern;
	this._value = value == null ? '' : value + '';
	this._rest = '';
}

util.inherits(ReplacePatternTransform, Transform);

var proto = ReplacePatternTransform.prototype;
proto._transform = function(chunk, encoding, callback) {
	if (chunk != null) {
		chunk = this._rest + chunk;
		var index = 0;
		var value = this._value;
		var result = chunk.replace(this._pattern, function() {
			var matcher = replace(value, arguments);
			var lastIndex = arguments.length - 1;
			index = arguments[lastIndex - 1] + arguments[lastIndex].length;
			return matcher;
		});
		index = Math.max(index, chunk.length + 1 - LENGTH);
		this._rest = chunk.substring(index);
		chunk = result.substring(0, result.length - this._rest.length);
	} else if (this._rest) {
		chunk = this._rest.replace(this._pattern, function() {
			return replace(this._value, arguments);
		});
	}

	callback(null, chunk);
};

function replace(replacement, args) {
	return replacement ? replacement.replace(/(^|.)?(\$[1-9])/g,
			function(matched, $1, $2) {
		return $1 == '\\' ? $2 : ($1 || '') + (args[$2.substring(1)] || '');
	}) : '';
}

module.exports = ReplacePatternTransform;<commit_msg>enlarge replace buffer<commit_after>var Transform = require('pipestream').Transform;
var util = require('util');
var LENGTH = 5120;

function ReplacePatternTransform(pattern, value) {
	if (!(this instanceof ReplacePatternTransform)) {
		  return new ReplacePatternTransform(options);
	  }

	Transform.call(this);

	this._pattern = pattern;
	this._value = value == null ? '' : value + '';
	this._rest = '';
}

util.inherits(ReplacePatternTransform, Transform);

var proto = ReplacePatternTransform.prototype;
proto._transform = function(chunk, encoding, callback) {
	if (chunk != null) {
		chunk = this._rest + chunk;
		var index = 0;
		var value = this._value;
		var result = chunk.replace(this._pattern, function() {
			var matcher = replace(value, arguments);
			var lastIndex = arguments.length - 1;
			index = arguments[lastIndex - 1] + arguments[lastIndex].length;
			return matcher;
		});
		index = Math.max(index, chunk.length + 1 - LENGTH);
		this._rest = chunk.substring(index);
		chunk = result.substring(0, result.length - this._rest.length);
	} else if (this._rest) {
		chunk = this._rest.replace(this._pattern, function() {
			return replace(this._value, arguments);
		});
	}

	callback(null, chunk);
};

function replace(replacement, args) {
	return replacement ? replacement.replace(/(^|.)?(\$[1-9])/g,
			function(matched, $1, $2) {
		return $1 == '\\' ? $2 : ($1 || '') + (args[$2.substring(1)] || '');
	}) : '';
}

module.exports = ReplacePatternTransform;<|endoftext|>