Skip to content

EtienneLem/commandz

Repository files navigation

CommandZ undo and redo commands.
Add commands history support to your web app.

Gem Version Travis build

Table of contents

API

Glossary

COMMANDS: An Array of COMMAND
COMMAND: {
  up:   function() {},
  down: function() {}
}

#execute

Receive COMMAND or COMMANDS and execute COMMAND.up().
Store commands as a { command: COMMAND } object in the history array.

Single command per history item
Store one history item per COMMAND.

CommandZ.execute({
  up:   function() { console.log('up 1') },
  down: function() { console.log('down 1') }
}) // => up 1

CommandZ.execute({
  up:   function() { console.log('up 2') },
  down: function() { console.log('down 2') }
}) // => up 2

console.log(CommandZ.history.length) // => 2
console.log(CommandZ.index)           // => 1

Multiple commands per history item
Store one history item per COMMAND group (COMMANDS).
A single undo would go back through all of the COMMAND inside COMMANDS.

commands = []
for (var i=1; i <= 5; i++) {
  commands.push({
    up:   function() { 'up 1.' + i },
    down: function() { 'down 1.' + i }
  })
}

CommandZ.execute(commands) // => up 1.1, up 1.2, up 1.3, up 1.4, up 1.5

console.log(CommandZ.history.length) // => 1
console.log(CommandZ.index)           // => 0

#undo (commands)

Call COMMAND.down() and set the index to the previous history item.

CommandZ.execute({
  up:   function() { console.log('up 1') },
  down: function() { console.log('down 1') }
}) // => up 1

CommandZ.execute({
  up:   function() { console.log('up 2') },
  down: function() { console.log('down 2') }
}) // => up 2

CommandZ.undo() // => down 2

console.log(CommandZ.history.length) // => 2
console.log(CommandZ.index)           // => 0

CommandZ.undo() // => down 1

console.log(CommandZ.history.length) // => 2
console.log(CommandZ.index)           // => -1

#redo (commands)

Set the index to the next history item and call COMMAND.up().

CommandZ.execute({
  up:   function() { console.log('up 1') },
  down: function() { console.log('down 1') }
}) // => up 1

CommandZ.execute({
  up:   function() { console.log('up 2') },
  down: function() { console.log('down 2') }
}) // => up 2

CommandZ.undo(2) // => down 2, down 1
CommandZ.redo()  // => up 1

console.log(CommandZ.history.length) // => 2
console.log(CommandZ.index)           // => 0

#store

Store data as a { data: … } object in the history array.

CommandZ.store({ width: 100, height: 100 })

#undo (storage)

Set the index to the previous history item and send data via CommandZ.onStorageChange.

CommandZ.onStorageChange(function(data) {
  console.log(data)
})

CommandZ.store({ width: 100, height: 100 })
CommandZ.undo()

console.log(CommandZ.history.length) // => 1
console.log(CommandZ.index)           // => -1

CommandZ.store({ width: 100, height: 100 })
CommandZ.store({ width: 200, height: 200 })
CommandZ.undo() // => { width: 100, height: 100 }

console.log(CommandZ.history.length) // => 2
console.log(CommandZ.index)           // => 0

#redo (storage)

Set the index to the next history item and send data via CommandZ.onStorageChange.

CommandZ.onStorageChange(function(data) {
  console.log(data)
})

CommandZ.store({ width: 100, height: 100 })
CommandZ.store({ width: 200, height: 200 })
CommandZ.undo() // => { width: 100, height: 100 }
CommandZ.redo() // => { width: 200, height: 200 }

console.log(CommandZ.history.length) // => 2
console.log(CommandZ.index)           // => 1

#setThreshold (storage only)

Unlike commands, you can allow your users to spam the CMD+Z button without restoring every states at every steps.
Threshold is set in milliseconds.

CommandZ.setThreshold(500)
CommandZ.onStorageChange(function(data) {
  console.log(data)
})

CommandZ.store({ width: 100, height: 100 })
CommandZ.store({ width: 200, height: 200 })
CommandZ.store({ width: 300, height: 300 })

CommandZ.undo(100)

// Wait 500ms
// => { width: 100, height: 100 }

#status

Return the current status.

CommandZ.execute({
  up:   function() { console.log('up 1') },
  down: function() { console.log('down 1') }
}) // => up 1

CommandZ.execute({
  up:   function() { console.log('up 2') },
  down: function() { console.log('down 2') }
}) // => up 2

console.log(CommandZ.status())        // => { canUndo: true, canRedo: false }
console.log(CommandZ.history.length) // => 2
console.log(CommandZ.index)           // => 1

#onStatusChange

Register a callback that will be called with the status every time there’s a change to the history.

CommandZ.onStatusChange(function(status) {
  console.log(status)
})

CommandZ.execute({
  up:   function() { 'up 1' },
  down: function() { 'down 1' }
}) // => { canUndo: true, canRedo: false }

CommandZ.execute({
  up:   function() { 'up 2' },
  down: function() { 'down 2' }
}) // => { canUndo: true, canRedo: false }

CommandZ.undo() // => { canUndo: true, canRedo: true }
CommandZ.undo() // => { canUndo: false, canRedo: true }

#onStorageChange

Register a callback that will be called with the data on undo/redo.

CommandZ.onStorageChange(function(data) {
  console.log(data)
})

CommandZ.store({ width: 100, height: 100 })
CommandZ.store({ width: 200, height: 200 })

CommandZ.undo() // => { width: 100, height: 100 }

#clear

Clear history.

CommandZ.execute({
  up:   function() { console.log('up 1') },
  down: function() { console.log('down 1') }
}) // => up 1

CommandZ.execute({
  up:   function() { console.log('up 2') },
  down: function() { console.log('down 2') }
}) // => up 2

CommandZ.clear()

console.log(CommandZ.status())        // => undefined
console.log(CommandZ.history.length) // => 0
console.log(CommandZ.index)           // => -1

#reset

Clear history, remove callbacks and set threshold to 0.

#keyboardShortcuts

Enable or disable CMD+Z & CMD+SHIFT+Z keyboard shortcuts. These shortcuts are enabled by default.
Will only undo() & redo() if the current selected element is not an input so that it doesn’t prevent your OS default behavior.

CommandZ.keyboardShortcuts(true) // default
CommandZ.keyboardShortcuts(false)

DOM Example

Commands

// This example requires jQuery or Zepto
$container = $('<div></div>')

// Lets do 5 commands
[1, 2, 3, 4, 5].forEach(function(i) {
  var $i = $('<i>' + i + '</i>')
  CommandZ.execute({
    up:   function() { $container.append($i) },
    down: function() { $i.detach() } // When removing DOM elements, I highly recommend .detach()
  })
})

console.log($container.html()) // => <i>1</i><i>2</i><i>3</i><i>4</i><i>5</i>

// Undo
CommandZ.undo()
console.log($container.html()) // => <i>1</i><i>2</i><i>3</i><i>4</i>

// Redo
CommandZ.redo()
console.log($container.html()) // => <i>1</i><i>2</i><i>3</i><i>4</i><i>5</i>

// When undoing, a new command will overwrite all upcoming commands
CommandZ.undo(3)

$i = $('<i>1337</i>')
CommandZ.execute({
  up:   function() { $container.append($i) },
  down: function() { $i.detach() }
})

console.log($container.html())        // => <i>1</i><i>2</i><i>1337</i>
console.log(CommandZ.history.length) // => 3
console.log(CommandZ.index)           // => 2

Storage

// This example requires jQuery or Zepto
$container = $('<div></div>')

img = new Image
$container.html(img)

// Register undo/redo callback
CommandZ.onStorageChange = function(data) {
  img.width = data.width
  img.height = data.height
}

img.width = 100
img.height = 100

// Lets store some states
[1, 2, 3, 4].forEach(function(i) {
  CommandZ.store({ width: i * 100, height: i * 100 })
})

CommandZ.undo(2)
console.log(img.width) // => 200

CommandZ.redo()
console.log(img.width) // => 300

Setup

Rails

  1. Add gem 'commandz' to your Gemfile.
  2. Add //= require commandz to your JavaScript manifest file.
  3. Restart your server and CMD+Z - CMD+SHIFT+Z away!

Other

Download and include commandz.min.js in your HTML pages. CommandZ is also hosted on cdnjs.com.

Tests

Run the rake spec task or bundle exec guard for continuous testing.