Class: SC.UndoManager


Extends SC.Object.

This is a simple undo manager. It manages groups of actions which return something to an earlier state. It's your responsibility to make sure that these functions successfully undo the action, and register an undo action of their own (allowing redo).

Using SC.UndoManager

You should create one SC.UndoManager instance for each thing you want to allow undo on. For example, if a controller manages a single record, but you have two fields that should each have their own undo stack, you should create two separate managers.

Register undo functions via the registerUndoAction, which takes a target, action, context and optional human-readable action name. Trigger actions by calling the undo and redo methods. Actions will be called with context as their only argument.

Optionally, you can group undo actions; groups of actions are triggered together.

Simple Example: A single value

This example attaches an undo manager to a controller and registers an undo function each time the value of value changes. It also exposes methods to trigger undos and redos, triggered via buttons in the included stub view class.

// Controller:
  MyApp.myController = SC.ObjectController.create({
    // Content, with `value`.
    content: SC.Object.create({ value: 'Hello, World.' }),

    // Undo manager.
    valueUndoManager: SC.UndoManager.create(),

    // Undo action.
    _valueUndoAction(val) {
      // This call will trigger the controller's `value` observer, triggering the registration
      // of another undo; the UndoManager will automatically and correctly interpret this as
      // the registration of a redo method.
      this.set('value', val);
    },

    // Value observer; tracks `value` and registers undos.
    valueDidChange: function() {
      // Get the values.
      var value = this.get('value'),
          previousValue = this._previousValue,
          that = this;

      // Update previous value.
      this._previousValue = value;

      // GATEKEEP: If the current value is the same as the previous value, there's nothing to do.
      if (previousValue === value) return;

      // GATEKEEP: If there is no previous value, it's probably our initial spinup. We don't want
      // to register an undo-back-to-undefined method, so we should return. (Your situation may be
      // different.)
      if (SC.none(previousValue)) return;

      // Otherwise, register an undo function. (previousValue is accessed via the closure.)
      this.undoManager.registerUndoAction(this, this._valueUndoAction, previousValue);
    }.observes('value')
  });

  // Stub view:
  MyApp.UndoableValueView = SC.View.extend({
    childViews: ['labelView', 'undoButtonView', 'redoButtonView'],
    labelView: SC.LabelView.extend({
      layout: { height: 24 },
      isEdiable: YES,
      valueBinding: 'MyApp.myController.value'
    }),
    undoButtonView: SC.ButtonView.extend({
      layout: { height: 24, width: 60, bottom: 0 },
      title: 'Undo',
      isEnabledBinding: SC.Binding.oneWay('MyApp.myController.valueUndoManager.canUndo'),
      target: 'MyApp.myController.valueUndoManager',
      action: 'undo'
    }),
    redoButtonView: SC.ButtonView.extend({
      layout: { height: 24, width: 60, bottom: 0, right: 0 },
      title: 'Redo',
      isEnabledBinding: SC.Binding.oneWay('MyApp.myController.valueUndoManager.canRedo'),
      target: 'MyApp.myController.valueUndoManager',
      action: 'redo'
    })
  });

Advanced: Grouping undos

Undo events registered by registerUndoAction will undo or redo one at a time. If you wish, you can group undo events into groups (for example, if you wish to group all undos which happen within a short duration of each other). Groups are fired all at once when undo or redo is called.

To start a new undo group, call beginUndoGroup; to register undo functions to the currently- open group, call registerGroupedUndoAction; finally, to mark the end of a grouped set of undo functions, call endUndoGroup. In most cases, you will not need to call beginUndoGroup and endUndoGroup: if you call registerUndoAction, any open group will be closed, and a new group will be created and left open; calling registerGroupedUndoAction will simply add to the currently-open group, creating a new one if necessary. This means that in practice, you can call registerUndoAction to close previous groups and begin a new one, and registerGroupedUndoAction to add to an existing group.

If undo is called while an undo group is open, UndoManager will simply close the group for you before executing it. This allows you to safely leave groups open pending possible additional undo actions.

Defined in: undo_manager.js

Field Summary

Instance Methods

Field Detail

Read Only
canRedo Boolean

True if there is an redo action on the stack. Use to validate your menu item or enable your button.

Default value:
NO
Read Only
canUndo Boolean

True if there is an undo action on the stack. Use to validate your menu item or enable your button.

Default value:
NO
isRedoing Boolean
Default value:
NO
isUndoing Boolean
Default value:
NO
maxStackLength Number

The maximum number of undo groups the receiver holds. The undo stack is unlimited by default.

Default value:
0
Read Only
redoActionName String

If name arguments are passed into registerUndoAction or related methods, then this property will expose the last redo action's name. You can use this to show the user what type of action will be redone (for example "Redo typing" or "Redo delete").

Default value:
null
Read Only
redoActionTimestamp SC.DateTime
Exposes the timestamp of the most recent redo action.
Default value:
null
Read Only
undoActionName String

If name arguments are passed into registerUndoAction or related methods, then this property will expose the last undo action's name. You can use this to show the user what type of action will be undone (for example "typing" or "delete").

Default value:
null
Read Only
undoActionTimestamp SC.DateTime
Exposes the timestamp of the most recent undo action.
Default value:
null

Instance Method Detail

beginUndoGroup(name)

Begins a new undo group.

Whenever you start an action that you expect to need to bundle under a single undo action in the menu, you should begin an undo group. This way any undo actions registered by other parts of the application will be automatically bundled into this one action.

When you are finished performing the action, balance this with a call to endUndoGroup(). (You can call undo or redo with an open group; the group will simply be closed and processed as normal.)

Parameters:
name String
endUndoGroup()

Ends a group of undo functions. All functions in an undo group will be undone or redone together when undo or redo is called.

See:
beginUndoGroup()
redo()
Tries to redo the last action. Fails if a redo group is currently open.
Returns:
Boolean
YES if succeeded, NO otherwise.
registerGroupedUndoAction(target, action, context, name)

Registers an undo action to the current group. If no group is open, opens a new one.

Parameters:
target String|Object
The action's target (`this`).
action String|Function
The method on `target` to be called.
context Object
The context passed to the action when called.
name String
An optional human-readable name for the undo action. Sets or changes the current group's name.
registerUndoAction(target, action, context, name)

Registers an undo action. If called while an undo is in progress (i.e. from your undo method, or from observers which it triggers), registers a redo action instead.

Parameters:
target String|Object
The action's target (`this`).
action String|Function
The method on `target` to be called.
context Object
The context passed to the action when called.
name String
An optional human-readable name for the undo action.
reset()
Resets the undo and redo stacks.
setActionName(name)
Change the name of the current undo group.
Parameters:
name String
undo()
Tries to undo the last action. Fails if an undo group is currently open.
Returns:
Boolean
YES if succeeded, NO otherwise.
Documentation generated by JsDoc Toolkit 2.4.0 on Wed Apr 08 2015 10:02:21 GMT-0600 (CST)