Playing with the kavanot editor, I wanted some way to encapsulate the process of noting that the text is "dirty"—meaning has been changed but not yet saved, and using a Promise to express the idea that the text is soon-to-be-saved, then saved.
I created a plugin that simply represents a state machine with four states, clean
, dirty
, pending
, and failed
. So you can do
var monitor = $.savemonitor();
$('textarea').on('input', function() { monitor.dirty() }); // track when the text area changes
// call this function to save (say, as a click handler for a Save button
function save(){
var text = $('textarea').val();
monitor.clean($.post('saveit.php', {data: text})); // jQuery Deferred's like $.post can be cast to Promises
}
// and keep track of the status
monitor.on('clean', function() {console.log('saved')})
monitor.on('failed', function() {console.log('not saved')})
See a simple demo.
See a demo that uses FontAwesome icons and randomly fails to simulate a network failure.
$.savemonitor
var monitor = $.savemonitor([indicator]);
creates a monitor object with the following methods:
setter(newstate {String})
- Returns a function that sets the state to
newstate
and triggers an event with the same name. Uses some basic logic for the state transitions:- Doesn't trigger events if the state isn't changing
- Only allows the four states listed above
- The only transition from 'dirty' is to 'pending'. That avoids the problem of starting to save (state is 'pending'), then editing (state is 'dirty') then getting the confirmation that the save happened (state would be 'clean' but the save is out of date).
- The only transition from 'clean' is to 'dirty', so we don't try to save an already saved version and possibly get to 'failed' even though the last save is up to date.
- Note that this returns a function to change the state; it does not actually change it. This makes it appropriate for creating a callback (like
$('textarea').on('input', monitor.setter('dirty'));
. dirty()
- Calls
setter('dirty')()
, so it changes the state and triggers the event. clean([resolver])
- Calls
setter('pending')()
, thenPromise.cast(resolver).then(this.setter('clean'), this.setter('failed'))
, so it takes advantage of thePromise.cast
method to handle error handling. Ifresolver
isundefined
or anything without athen()
method, it immediately resolves and callssetter('clean')()
. Ifresolver
is what the Promises API calls a "thenable" (like a real Promise, or a jQuery Deferred) then waits for that to resolve and callssetter('clean')()
orsetter('failed')()
as appropriate. on(event {String}, handler {function})
- Sets up an event handler for the given event (will be one of
clean
,dirty
,pending
, andfailed
). Just uses$(this).on(event, handler)
, so you can use multiple event names, event namespaces, etc. off(event {String}, handler {function})
- Removes the event handler for the given event. Uses
$(this).off(event, handler)
. trigger()
- Triggers the event for the current state.
state()
- Returns the current state.
If the indicator
parameter to $.savemonitor(indicator)
is set, then $(indicator)
is used as a target for the event changes, changing its class to the current state, with
monitor.on('clean dirty failed pending', function (event){
$(indicator).removeClass('clean dirty failed pending');
$(indicator).addClass(event.type);
});
so you can just use CSS to display the state, with no code at all.
$.fn.savemonitor
There's also a way to attach a monitor to an element directly, with var monitor = $(element).savemonitor(indicator)
. This stores the monitor object in $(element).data('savemonitor')
so it won't be created more than once. It returns the monitor object, so this plugin can't be chained.
It adds an event handler for the input event to track changes exactly as above, with $(element).on('input', monitor.setter('dirty')
so no other code is necessary.
The simplest use is just two lines of code:
<style>
button::after {
content: ' (' attr(class) ')'; /* to show the state */
}
</style>
<button>Save</button>
<textarea></textarea>
<script>
var monitor = $('textarea').savemonitor('button');
$('button').click(function() {monitor.clean( $.post('saveToServer.php', {data: $('textarea').val()})) });
</script>
Leave a Reply