admin管理员组

文章数量:1406000

I'm attempting to replicate this demo using RxJS. The demo is a small application, where the user controls a robot. The robot can move forwards or backwards, rotate left or right, and pick up or drop an item. The user can queue mands (such as "Forward", "rotate"), and the mands in the queue are executed when the user clicks on the "Execute"-button. The user can also undo mands that have been already executed.

Traditionally this application would be quite easy to implement using a queue for the mands that have not been executed yet. Executed mands are pushed into a stack, and whenever the undo-button is pressed, the top mand is popped and undone.

I'm able to "collect" the mands and execute them by doing this:

var id = 0;
var add = Rx.Observable.fromEvent($("#add"), 'click').map(function(){
  var ret = "Command_"+id;
  id++;
  return ret
})
var invoke = Rx.Observable.fromEvent($("#invoke"), 'click')
var invokes = add.buffer(invoke)

The buffer() method transforms the stream into a stream of arrays. I can either subscribe to the invokes stream and get arrays of mands:

invokes.subscribe(function(mand_array){...})

or I can create a Rx.Subject() where I just push the mands one by one:

var invoked_mands = new Rx.Subject()
invokes.subscribe(function(mand_array){
  for(var i=0; i < mand_array.length; i++){
    invoked_mands.onNext(mand_array[i])
  }
});

invoked_mands.subscribe(function(mand){ ...});

To be honest I have no idea which approach would be better, but I then again I don't know if that's even too relevant to me right now. I've been trying to figure out how to implement the undo functionality, but I have absolutely no idea how to do it.

In my mind it would have to be something like this (sorry for the formatting):

-c1---c2-c3--------->

----------------u---u-> ("u" = clicking the undo button)

----------------c3--c2> (get mands from newest to oldest, call the undo() method)

So my question is twofold:

  1. Is my approach of collecting the mands good?
  2. How can I implement the undo feature?

EDIT: I'm paring transformative and reactive styles, and I'm implementing this demo using both. Therefore I'd like to stick to using Rx* features as much as possible.

I'm attempting to replicate this demo using RxJS. The demo is a small application, where the user controls a robot. The robot can move forwards or backwards, rotate left or right, and pick up or drop an item. The user can queue mands (such as "Forward", "rotate"), and the mands in the queue are executed when the user clicks on the "Execute"-button. The user can also undo mands that have been already executed.

Traditionally this application would be quite easy to implement using a queue for the mands that have not been executed yet. Executed mands are pushed into a stack, and whenever the undo-button is pressed, the top mand is popped and undone.

I'm able to "collect" the mands and execute them by doing this:

var id = 0;
var add = Rx.Observable.fromEvent($("#add"), 'click').map(function(){
  var ret = "Command_"+id;
  id++;
  return ret
})
var invoke = Rx.Observable.fromEvent($("#invoke"), 'click')
var invokes = add.buffer(invoke)

The buffer() method transforms the stream into a stream of arrays. I can either subscribe to the invokes stream and get arrays of mands:

invokes.subscribe(function(mand_array){...})

or I can create a Rx.Subject() where I just push the mands one by one:

var invoked_mands = new Rx.Subject()
invokes.subscribe(function(mand_array){
  for(var i=0; i < mand_array.length; i++){
    invoked_mands.onNext(mand_array[i])
  }
});

invoked_mands.subscribe(function(mand){ ...});

To be honest I have no idea which approach would be better, but I then again I don't know if that's even too relevant to me right now. I've been trying to figure out how to implement the undo functionality, but I have absolutely no idea how to do it.

In my mind it would have to be something like this (sorry for the formatting):

-c1---c2-c3--------->

----------------u---u-> ("u" = clicking the undo button)

----------------c3--c2> (get mands from newest to oldest, call the undo() method)

So my question is twofold:

  1. Is my approach of collecting the mands good?
  2. How can I implement the undo feature?

EDIT: I'm paring transformative and reactive styles, and I'm implementing this demo using both. Therefore I'd like to stick to using Rx* features as much as possible.

Share Improve this question edited Mar 23, 2015 at 17:40 T.Kaukoranta asked Mar 23, 2015 at 14:56 T.KaukorantaT.Kaukoranta 42510 silver badges27 bronze badges 2
  • There’s a similar newer question with a very succint answer out there. – tomekwi Commented May 16, 2015 at 13:34
  • It turns out that the answer solves a slightly different problem though. – tomekwi Commented May 16, 2015 at 14:06
Add a ment  | 

3 Answers 3

Reset to default 4

You have to go ahead and maintain state for the undo stack. I think your approach to collecting mands is reasonable. If you keep your Subject you can sort of decouple the undo functionality from the mand execution by making another subscription to the subject:

var undoQueue = [];
invoked_mands.subscribe(function (c) { undoQueue.unshift(c); });
Rx.Observable
    .fromEvent($("#undo"), "click")
    .map(function () { return undoQueue.pop(); })
    .filter(function (mand) { return mand !== undefined; })
    .subscribe(function (mand) { /* undo mand */ });

Edit: Using only Rx without a mutable array. This seems unnecessarily convoluted, but oh well, it is functional. We use scan to maintain the undo queue, and emit a tuple with the current queue along with whether a undo mand should be executed. We merge executed mands with undo events. Execute mands add to the queue, undo events pop from the queue.

var undo = Rx.Observable
    .fromEvent($("#undo"), "click")
    .map(function () { return "undo"; });
invoked_mands
    .merge(undo)
    .scan({ undoCommand: undefined, q: [] }, function (acc, value) {
        if (value === "undo") {
            return { undoCommand: acc.q[0], q: acc.q.slice(1) };
        }

        return { undoCommand: undefined, q: [value].concat(acc.q) };
     })
     .pluck("undoCommand")
     .filter(function (c) { return c !== undefined })
     .subscribe(function (undoCommand) { ... });

I just created something similar, although a bit more plex. Maybe it helps someone.

  // Observable for all keys
  const keypresses = Rx.Observable
    .fromEvent(document, 'keydown')

  // Undo key bination was pressed
  //  mapped to function that undoes the last accumulation of pressed keys
  const undoPressed = keypresses
    .filter(event => event.metaKey && event.key === 'z')
    .map(() => (acc) => acc.slice(0, isEmpty(last(acc)) && -2 || -1).concat([[]]))

  // a 'simple' key was pressed
  const inputChars = keypresses
    .filter(event => !event.altKey && !event.metaKey && !event.ctrlKey)
    .map(get('key'))
    .filter(key => key.length === 1)

  // the user input, respecting undo
  const input = inputChars
    .map((char) => (acc) =>
      acc.slice(0, -1).concat(
        acc.slice(-1).pop().concat(char)
      )
    ) // map input keys to functions that append them to the current list
    .merge(undoPressed)
    .merge(
      inputChars
        .auditTime(1000)
        .map(() => (acc) => isEmpty(last(acc)) && acc || acc.concat([[]]))
    ) // creates functions, that start a new accumulator 1 sec after the first key of a stroke was pressed
    .scan(
      (acc, f) => f(acc),
      [[]],
    ) // applies the merged functions to a list of accumulator strings
    .map(join('')) // join string
    .distinctUntilChanged() // ignore audit event, because it doesn't affect the current string

It's great to diy first. Reinventing the wheel es with deeper understanding of concepts & makey you a brillant engineer, in real life sometimes you just gotta benefit from an already existing package to:

  • save time (efficiency)
  • ensure stability (munity approved)
  • less code (maintainability)
  • generic package (reusability)

rx-undoable

Update:

I'm more happy with Zundo now, also due to code review and usage of TypeScript and < 1kb (no offense to other lib, it works, so it's great too)

Github: 'zundo'


本文标签: javascriptSimulating a command queue and undo stack with RxJSStack Overflow