Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery AJAX "undo helper"

The common pattern in AJAX-based UIs is that when user performs an action, it is reflected in the UI instantly, but is marked as unfinished until the arrival AJAX response confirming that everything is OK. This is how e.g. adding an event in Google Calendar works. And when there is an error, that temporary UI change is reverted.

Now, doing such reversion manually is not too creative, so I suspect that there is some "undo helper" in jQuery, which allows to preserve the state of UI elements, and then restore it - kind of attribute stack or something like that. This don't even has to have anything to do with AJAX.

Is there anything like that?

like image 321
Tomasz Zieliński Avatar asked Nov 18 '10 14:11

Tomasz Zieliński


1 Answers

In these sort of situations, I have always found that state machines are the way to go. Design your page/JavaScript such that, at any point in time you can specify a set of inputs and you will always get the same output across the entire page. The nice thing about this approach is you don't know how you got to where you are in your program, you just have to know what data resulted in the current state.

For your case, you could create a basic object describing your state:

var state = { user: 'Tomasz', ajaxSent: false, otherState: 123 };

function updateFromState(state){
    // Set all dependent UI visibilities, etc. based on state object
}

Then, before you make any changes, push your current state onto an array so you can revert back if needed:

var stateList = [];
stateList.push({ user: 'Tomasz', ajaxSent: false, otherState: 123 });

// Do something that changes your state
var newState =  {user: 'Tomasz', ajaxSent: true, otherState: 123 };
updateFromState(newState);

// Now, if you need to revert
var previousState = stateList.pop();
updateFromState(previousState);

The downside to this approach is that you're not actually "undoing" any side-effects you might have invoked. If, for example, you made an AJAX post that modified data on the server, you would have to understand how to "undo" that AJAX post. This goes beyond just changing the local state of the page. But, if you don't need to undo server-side effects, but just be able to accurately reflect the correct state of the page, this may work for you.

Here is what I would probably use to get started if I were doing something like this:

var StateHelper = function(){
    var stateList = [];
    var callbacks = [];

    this.onChange = function(callback){
        callbacks.push(callback);
    };

    this.set = function(opts){
        stateList.push(opts);
        apply(opts);
    };

    this.undo = function(){
        if(stateList.length <= 1){
            return; // nothing to undo
        }

        // To undo, we go back 2, since top of stack is current
        stateList.pop();
        var last = stateList[stateList.length - 1];
        apply(last);
    };

    var apply = function(opts){
        var length = callbacks.length;
        for(var i = 0; i < length; i++){
            callbacks[i](opts);
        }
    };
};

Then, if my page looked like this:

<div id='title' style='text-decoration: underline;'>some title</div>
<div id='state1' class='clickable'>click to set state2 (title = 'second')</div>
<div id='state2' class='clickable'>click to set state3  (title = 'third')</div>
<br/>
<div id='undo' class='clickable'>undo</div>

I might use the above StateHelper like this (note I used a touch of jQuery):

// A function that should be called when state changes
var updateTitle = function(opts){
    // Update the pages title based on state
    console.log('title updating');
    $('#title').text(opts.title);
};

var myState = new StateHelper();
myState.onChange(updateTitle);  // hook the state change event

// some example states
var state2 = { title: 'second' };
var state3 = { title: 'third' };

// Just some click handlers
$(document).ready(function(){
    $('#state1').click(function(){
        myState.set(state2);
    });

    $('#state2').click(function(){
        myState.set(state3);
    });

    $('#undo').click(function(){
        myState.undo();
    });

    // Set initial state
    myState.set({title: 'some title'});
});

For live example, see: http://jsfiddle.net/belorion/Hywtc/

like image 139
Matt Avatar answered Nov 17 '22 22:11

Matt