documentationfor yFiles for HTML 2.6

IMementoSupport

This interface provides access to the memento design pattern to provide undoability for arbitrary models.

Inheritance Hierarchy
IMementoSupport

Remarks

By implementing this interface as well as ILookup, clients can add undoability for changes to their model classes. The yFiles undo mechanism uses the return value of getState to retrieve a state of an item at the beginning of the compound editing process. When the process ends, another state will be retrieved of the same item and compared to the original state. If they differ, an IUndoUnit is created that uses the applyState method to apply either state to the item in case of undo or redo. This represents an abstraction to the undo mechanism where it is only needed to define "states" of items and hides the more complicated mechanism of creating and inserting IUndoUnits.

The following is an example implementation of an item that is being managed using IMementoSupport:

A sample business object that implements ILookup to return IMementoSupport
class Employee extends BaseClass(ILookup) {
  _name
  position
  age

  /**
   * @param {!string} name
   * @param {!string} position
   * @param {number} age
   */
  constructor(name, position, age) {
    super()
    this._name = name
    this.position = position
    this.age = age
  }

  /**
   * @type {!string}
   */
  get name() {
    return this._name
  }

  /**
   * @template {*} T
   * @param {!Class.<T>} type
   * @returns {?T}
   */
  lookup(type) {
    if (type === IMementoSupport.$class) {
      return new EmployeeMementoSupport()
    }
    return null
  }
}class Employee extends BaseClass<ILookup>(ILookup) implements ILookup {
  _name: string
  position: string
  age: number

  constructor(name: string, position: string, age: number) {
    super()
    this._name = name
    this.position = position
    this.age = age
  }

  get name(): string {
    return this._name
  }

  @typescript-eslint/no-unnecessary-type-constraint
  lookup<T extends any>(type: Class<T>): T | null {
    if (type === IMementoSupport.$class) {
      return new EmployeeMementoSupport() as T
    }
    return null
  }
}

A collection of items from this type can then be watched using the following code snippet:

Using an ICompoundEdit
const edit = graph.beginEdit(undoName, redoName, listWithMyEmployeesToWatch)

// changes to the employees are done here

if (!success) {
  // if we don't want the changes to be done after all, then we need to cancel the edit
  edit.cancel()
}

Implementing the IMementoSupport interface is quite unrestrained, the type of the state returned by getState method can by anything as long as the applyState and stateEquals methods can deal with it:

Sample implementation of IMementoSupport
class EmployeeMementoSupport extends BaseClass(IMementoSupport) {
  /**
   * @param {*} subject
   * @returns {?EmployeeState}
   */
  getState(subject) {
    if (subject instanceof Employee) {
      return new EmployeeState(subject.position, subject.age)
    }
    return null
  }

  /**
   * @param {*} subject
   * @param {*} state
   */
  applyState(subject, state) {
    if (subject instanceof Employee && state instanceof EmployeeState) {
      subject.position = state.position
      subject.age = state.age
    }
  }

  /**
   * @param {*} state1
   * @param {*} state2
   * @returns {boolean}
   */
  stateEquals(state1, state2) {
    if (state1 instanceof EmployeeState && state2 instanceof EmployeeState) {
      return state1.position === state2.position && state1.age === state2.age
    }
    return state1 === state2
  }
}
class EmployeeState {
  _position
  _age

  /**
   * @param {!string} position
   * @param {number} age
   */
  constructor(position, age) {
    this._position = position
    this._age = age
  }

  /**
   * @type {!string}
   */
  get position() {
    return this._position
  }

  /**
   * @type {number}
   */
  get age() {
    return this._age
  }
}class EmployeeMementoSupport
  extends BaseClass<IMementoSupport>(IMementoSupport)
  implements IMementoSupport
{
  getState(subject: any): EmployeeState | null {
    if (subject instanceof Employee) {
      return new EmployeeState(subject.position, subject.age)
    }
    return null
  }

  applyState(subject: any, state: any): void {
    if (subject instanceof Employee && state instanceof EmployeeState) {
      subject.position = state.position
      subject.age = state.age
    }
  }

  stateEquals(state1: any, state2: any): boolean {
    if (state1 instanceof EmployeeState && state2 instanceof EmployeeState) {
      return state1.position === state2.position && state1.age === state2.age
    }
    return state1 === state2
  }
}
class EmployeeState {
  _position: string
  _age: number

  constructor(position: string, age: number) {
    this._position = position
    this._age = age
  }

  get position(): string {
    return this._position
  }

  get age(): number {
    return this._age
  }
}

In summary, use this concept when you want to track the state of items during certain operations for undo/redo. This is efficient if it's easier to handle an item's state than the changes to the item themselves. If you want to focus on the changes or on certain events, you should use custom IUndoUnit implementations instead.

Type Details

yfiles module
view-component
yfiles-umd modules
All view modules
Legacy UMD name
yfiles.graph.IMementoSupport

See Also

Methods

Static Methods