Coder Social home page Coder Social logo

backbone.memento's Introduction

Backbone.Memento

Memento push and pop for Backbone.js models and collections structures.

A view may offer some editing capabilities that directly modify a structure (model or collection), directly. If you want to cancel the changes after they have already been applied to the structure, you will have to make a round trip to the back-end server or other origin of the structures's data to do so.

With the memento pattern and the Backbone.Memento plugin, you do not need to make any round trips.

Getting Started

It's easy to get up and running. You only need to have Backbone (including underscore.js - a requirement for Backbone) in your page before including the backbone.memento plugin.

Prerequisites

  • Backbone.js v0.5.3, v0.9.0, or higher

This plugin is built and tested against Backbone v0.9.0 but should run against most versions of Backbone, as it only uses functionality built into the Backbone structures. Namely, it uses the set and unset methods of models and reset and remove of collections.

Get The Memento Plugin

Download the backbone.memento.js file from this github repository and copy it into your javascripts folder. Add the needed <script> tag to bring the plugin into any page that wishes to use it. Be sure to include the modelbinding file after the backbone.js file.

Setup A Model/Collection For Mementoing

Your models must make use of the Backbone.Memento object, directly. This can easily be done in multiple ways

Extend The Memento

You can tell a model instance to extend a memento instance. This will provide all of the memento methods directly on the model.

SomeModel = Backbone.Model.extend({
  initialize: function(){
    var memento = new Backbone.Memento(this);
    _.extend(this, memento);
  }
});

Cherry-Picking Methods

You can also configure a model by instantiating the memento with your model's initializer and then providing access to the methods as needed, or by using the methods internally.

SomeModel = Backbone.Model.extend({
  initialize: function(){
    this.memento = new Backbone.Memento(this);
    this.restart = this.memento.restart;
  },

  someAppMethod: function(){
    this.memento.set();
  },

  moreAppMethod: function(){
    this.memento.store();
    // ... do stuff here

    // ... then restart it if needed
    this.memento.restore();
  }
});

This gives you more control over where the memento methods can be used.

Memento with Collections

Memento has been recently upgraded to support collections and saving state of the models under the hood. This will greatly assist with filtering collections temporarily.

Note the use of reset intead of set.

SomeCollection = Backbone.Collection.extend({
  initialize: function() {
    _.extend(this, new Backbone.Memento(this));
  }
});

var someCollection = new SomeCollection();
someCollection.reset({something: "whatever"});
someCollection.store();
someCollection.reset({something: "a change"});
someCollection.restore();

someCollection.at(0).get("something"); //=> "whatever"

Memento Methods

There are several methods provided by Backbone.Memento, to allow you more control over how the memento object works, and when.

memento.store

This method creates a copy of your structure's current state, as a memento, and stores it in a stack (first in, last out).

memento.restore

This method takes the previously stored state, and restores your structure to this state. You can call this as many times as you have called store. Calling this method more times than you have called store will result in a no-operation and your structure will not be changed.

memento.restart (formerly reset)

This method effectively rolls your structure back to the first store point, no matter how many times it has been stored in the memento.

(reset was deprecated since it has a naming conflict with Backbone.Collections.prototype.reset.)

Configuration

There is only one item of configuration for Backbone.Memento at the moment:

Ignore Model Attributes

There are some scenarios where it may cause issues to have all attributes restored from a previous state, for a model. In this case, you can ignore specific attributes for the model.

Ignore For The Model Instance

You can configure the memento to ignore the attributes when instantiating the memento:

SomeModel = Backbone.Model.extend({
  initialize: function(){
    _.extend(this, new Backbone.Memento(this, {
      ignore: ["something", "another", "whatever", "..."]
    });
  },

  // ...
});

var someModel = new SomeModel();
someModel.set({something: "whatever"});
someModel.store();
someModel.set({something: "a change"});
someModel.restore();

someModel.get("something"); //=> "a change"

Ignore For This Restore Only

Alternatively, you can override the pre-configured ignored attributes by passing an ignore array into the restore method:

SomeModel = Backbone.Model.extend({
  initialize: function(){
    this.memento = new Backbone.Memento(this);
  },

  // ...
});

var someModel = new SomeModel();
someModel.set({something: "whatever"});
someModel.store();
someModel.set({something: "a change"});
someModel.restore({ignore: ["something"]});

someModel.get("something"); //=> "a change"

Note that passing an ignore array into the restore method will override the pre-configured ignore list.

Examples

With this in place, you can push your model's state onto the memento stack by calling store, and pop the previously stored state back into the model (destroying the current state in the process) by calling restore.

myModel = new SomeModel();
myModel.set({foo: "bar"});

myModel.store();

myModel.set({foo: "a change"});

myModel.restore();

myModel.get("foo"); // => "bar"

Set And Unset Attributes

Backbone.Memento will set and unset attributes, when poping from the memento stack. For example, if you add an attribute after storing your models state, and then later restore back to the previous state, the attribute that you added will be unset. The unset attribute will have it's change event fired, as well.

myModel = new SomeModel();
myModel.set({foo: "stuff"});

myModel.store();

myModel.set({bar: "a new attribute"});

myModel.bind("change:bar", function(model, value){
  alert('bar was changed to: ' + val);
}

myModel.restore(); // => causes an alert box to say "bar was changed to undefined"

myModel.get("bar"); // => undefined, as the attribute does not exist

Release Notes

v0.4.1a

  • No code changes were made. This release is for library upgrades only, for testing purposes
    • Updated Backbone to v0.9.0
    • Updated Underscore to v1.3.1
    • Updated jQuery to v1.7.1

v0.4.1

  • Fixed global scope leak for a variable

v0.4.0

v0.3.0

  • changed the public memento API to support collections
  • updated documentation to reflect changes

v0.2.0

  • changed the public memento API and how a model is connected to the memento
  • changed the name of the 'clear' method to 'restart', to prevent hijacking the model's clear method
  • updated the documentation to include better examples and more detail

v0.1.4

  • ability to ignore model attributes - they won't be stored or restored

v0.1.3

  • Fixed a small bug with rolling back more times than had been saved

v0.1.2

  • Added ability to restart a model, moving back to the beginning of the memento stack
  • Fixed a few bugs in the removing of old attributes, related to global variables, etc
  • Code cleanup and switching to a standard object constructor function instead of return an object literal

v0.1 and v0.1.1

  • Initial releases with a few minor bug fixes

Legal Mumbo Jumbo (MIT License)

Copyright (c) 2011 Derick Bailey, Muted Solutions, LLC

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.

backbone.memento's People

Contributors

dahlbyk avatar fizker avatar jondot avatar tbranyen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

backbone.memento's Issues

Rebasing memento on current state

We're using Memento in combination with Stickit to drive our forms. In this particular case, we have a collection, which allows editing of a single model at a time. When you navigate away, the changes are obviously still there in the model, so when you come back to edit this model, you'll see the old state. This is all expected behavior, but here's where I'm running into a limitation:

  • Collection is instantiated, each model calls memento.store() so we have initial state.
  • User edits an attribute, click save. I could at this point create a new state.
  • If user navigates away or presses cancel, we can revert 1 step, but there's no real way of blocking the user from pressing cancel again, thus reverting back to beyond the saved state.

Would it be useful to have a method to set a new "starting point" so people can't restore to beyond that point, or have a configuration option to automatically do that when an entity is persisted?

support named states, and only storing / restoring certain attributes

add support for named states, or named stacks of states. from Dan Switzer via the Backbone mailing list:


For me, I think the best way for it to work would be this:

store([cid]) - always returns the cid. If no cid is provided as an
argument, one is created.

restore([cid]) - If no cid is supplied, restores previous state. If a
cid is specified, rolls all the back to the named state.

This would allow you to do:

  1. Rollback to any given point and time:
    transactionId = this.store(); // this returns transactionId
    this.restore(transactionId);

  2. Rollback to a specific point in time:
    this.store("init");
    this.restore("init");

NOTE: It might be handy to be able to have multiple transaction points
with the same name, that way you could easily create "undo" points. So,
calling this.restore("undo") would rollback to the first named "undo"
transaction. Calling this.restore("undo") would rollback to the next,
etc. That might be pretty powerful.

  1. Rollback just like you do now:
    this.store();
    this.restore();

The reason named transaction are important for me, is I have some code
currently in production that can make a series of triggered model
changes. This code currently isn't using backbone and one of the
problems I'm facing is a proper "undo". The problem is the sequence of
triggered events might make multiple "undo" points, so I need to be able
to roll back to the correct undo point.

unsetting attributes during restore/restart conflicts with backbone validation

In my application when a user changes their password we ask them for their current password and the new password. When the user first enters this modal window I call store() on the User model to save a copy of it.

I then set the 'oldPassword' and 'newPassword' attributes on the User model in a save() call. After the backend has been updated I call restart() to get the original state of the User model because I want to remove the 'oldPassword' and 'newPassword' attributes, don't want to keep that info around in memory.

The problem arises because I'm using the Backbone Validation plugin https://github.com/thedersen/backbone.validation. When memento calls unset() on the model for these two attributes it calls _validate() on the model and it fails because memento is trying to set the attributes to null, and I've specified them as required via the validation plugin. I thought about 2 potential fixes:

  1. pass the restoreConfig object in restore() and restart() down the line to the unset() call in TypeHandler.removeAttr(), this way you could do this: model.restart({silent: true}); // skip validation
  2. pass {silent: true} into the unset() call in TypeHandler.removeAttr(), this would skip validation every time attributes need to be unset, not very flexible though.

change event does not know if it originated from Memento

I am rather new to Backbone and Backbone.Memento and perhaps there is a better way to do what I want to do.

I am writing a view that binds to the model's change event. I want it to have a slightly different behavior if the change came from a set or a restore (which calls set).

var MyView = Backbone.View.extend({
  initialize: function() {
    this.model.bind('change', this.doChange, this);
  },

  doChange: function(model, options) {
    if ( this_was_a_restore )
      do_it_like_this();
    else
      do_it_like_that();
  }
});

Right now, I have hacked on a setOptions config entry in the main config that gets passed to the Serializer. I modified the restore method on the TypeHelper to pass those setOptions when it calls structure.set. The Serializer passes these options in when it calls typeHelper.restore. I can set the setOptions config at instantiation or when I restore my model.

My model looks something like this:

var MyModel = Backbone.Model.extend({
  initialize: function() {
    var memento = new Backbone.Memento(this, {setOptions: {restoring: true}});
    _.extend(this, memento);
  }
});

And my view's doChange method can now check the options passed to it:

doChange: function(model, options) {
  if ( options.restoring )
    do_it_this_way();
  else
    do_it_that_way();
}

So I guess my question is, are there ways of doing this that already exist or are there better ways of doing it? If not, is this something that you think other people would want to use or worth sticking into Memento? If so, I can and the code to the issue so you can merge it in.

I'm not sure if setOptions is a great name, considering it could be passed to Model.unset or Collection.remove or Collection.reset.

Thanks for Memento, btw, it makes for some cool things.

nested arrays/objects

Any suggestions how to deal with models that have an array as an attribute? Doing model.store(), modifying the array, model.restore() won't work, because the array was modified by reference.

What I did was pretty much replace _.clone with JSON.stringify, it does the job for me :) But I wonder if you've used memento with such models.

NPM Package

I've set up this library as an NPM package: https://github.com/stayradiated/backbone.memento

Changelog:

  • Create package.json
  • Use mocha and chai for tests (instead of Jasmine)
  • Use gulp and uglify to minify code
  • Add UMD returnExports wrapper
  • Version number bumped to 0.4.2

I would like to publish this library to NPM, but wanted to let you know before hand in case you would like to publish it using your own account.

Restoring collection creates new model instances

Hi River Lynn,

thank you for this awesome plugin, after all these years i'm still using it a lot.
I recently ran into an issue with collections that contain models that are also contained in other collections.
Using restore on a collection causes the collection to discard the original model instances and create new ones, after which changing a model in that collection won't affect the same model in another collection anymore, because they are no longer the same instance.
If it doesn't create other problems down the line, it seems beneficial if the original model instances would be kept when restoring a collection.

[enhancement] Add missing bower.json.

Hey, maintainer(s) of derickbailey/backbone.memento!

We at VersionEye are working hard to keep up the quality of the bower's registry.

We just finished our initial analysis of the quality of the Bower.io registry:

7530 - registered packages, 224 of them doesnt exists anymore;

We analysed 7306 existing packages and 1070 of them don't have bower.json on the master branch ( that's where a Bower client pulls a data ).

Sadly, your library derickbailey/backbone.memento is one of them.

Can you spare 15 minutes to help us to make Bower better?

Just add a new file bower.json and change attributes.

{
  "name": "derickbailey/backbone.memento",
  "version": "1.0.0",
  "main": "path/to/main.css",
  "description": "please add it",
  "license": "Eclipse",
  "ignore": [
    ".jshintrc",
    "**/*.txt"
  ],
  "dependencies": {
    "<dependency_name>": "<semantic_version>",
    "<dependency_name>": "<Local_folder>",
    "<dependency_name>": "<package>"
  },
  "devDependencies": {
    "<test-framework-name>": "<version>"
  }
}

Read more about bower.json on the official spefication and nodejs semver library has great examples of proper versioning.

NB! Please validate your bower.json with jsonlint before commiting your updates.

Thank you!

Timo,
twitter: @versioneye
email: [email protected]
VersionEye - no more legacy software!

How about a changed?

This is very cool, a nice addition to backbone that should probably be rolled into the core. How about a "changed" list?

Core backbone has isChanged() and changedAttributes which give if there have been any changes since the last "change" event, and those attributes that have been changed since. Having similar ones that can give if there is a differential between the last store() and current state, and those changes, would be useful.

model with sub models or collections

hi,
is there an good way to have sub models or collections of a model which uses memento have restored aswell?

the default is that only its own direct attributes are getting restored, is that correct?

AMD shim, please?

Hi Derick,

Could you please shim this lib for AMD, please?

The problem is that Backbone has recently gone AMD. This is causing single-file builds via the RequireJS optimizer to fail if the build uses the new AMD Backbone but plugins that are non-AMD compliant. You can read more about this issue here SO answer, and over at PaulUithol/Backbone-relational#215 (comment), where the same problem has been resolved by taking the hyper-standard approach of wrapping the whole lib as in https://github.com/PaulUithol/Backbone-relational/blob/master/backbone-relational.js#L48-L61.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.