Make client undo manager persistent as well as anyone of JavaScript objectsHere we would like to describe the possibility of creation of persistent JavaScript objects on client's side. Let's take undo manager as an example. It will demonstrate that this uniform solution expands the field of application of the same JavaScript objects. Undo managerLet's recognize the first part of our article as �pragmatic�. It is devoted to the solution of particular task - namely the creation of undo manager on client's side. Main purpose of undo manager is a recorder of undo and redoes operations. Undo manager allows users to correct their mistakes and also to try out different aspects of the application without risk of repercussions. Undo-redo mechanism provides such abilities as:
Proposed approach of undo manager provides multi-level undo support and based on undoable actions, which contains logic how to do something and how to get back to the starting point. The undo manager records commands on a stack. When the user undoes a command, the undo manager pops it from the stack, revealing the previously executed command. Once a user has undone at least one command, executing any new command clears the redo stack. If you choose, you can also force the undo manager to provide predefined level of undo support, where it remembers only predefined count of commands. One of the strong features of undo manager is customizable error processing which allows to manager be fault tolerant or fault restore in accordance with particular logic. Undoable actionAs stated above, undo manager records undoable actions. Undoable Action is such kind of action, which is not only aware of how to do something, but also how to get back to the starting point. Undoable action consists of two unfold operations: DO and UNDO. Proposed implementation of undoable Action wraps two JavaScript methods, one of them represents DO operation, another - UNDO operation and allows define corresponding parameters to invoke them. For example, let there exist some variable and an action, which increases this variable by 2. Let's name this action DO. The action, which corresponds to the UNDO, wills decrement the variable by 2. According to the most pessimistic estimation, undoable action can be represented as a set of object's state saving operations, object's change at the DO command, and object's renovation at the UNDO command. But generally, undoable action is wrapped two functions or of two functions with corresponding parameters. The following listing illustrates undoable action:
/**
* Creates undoable Action, such kind of action, which is not only * aware of how to do something, but also how to get back to the starting point. * Undoable action consists of two unfold operations: DO and UNDO. * Proposed implementation of undoable Action wraps two JavaScript methods, one of * them represents DO operation, another - UNDO operation * but really undoable Action is an interface and can be implemented * corresponding to particular logic. * * @constructor * @param {Function} The undoable Action DO method. * @param {Function} The undoable Action UNDO method. * @param {Array} The array of parameters of DO method. * @param {Array} The array of parameters of UNDO method. * @param {String} The undoable Action presentation. * @param {String} The undoable Action group. */ function UndoableAction(aDoMethod, aUndoMethod, aDoParams, aUndoParams, aName, aGroup) { this.fStatus = undefined; this.fName = COMMONS.isString(aName) ? aName : undefined; this.fGroup = aGroup; this.fDoMethod = aDoMethod; this.fUndoMethod = aUndoMethod || aDoMethod; this.fDoParams = COMMONS.isUndefined(aDoParams) || COMMONS.isArray(aDoParams) ? aDoParams : [aDoParams]; this.fUndoParams = COMMONS.isUndefined(aUndoParams) || COMMONS.isArray(aUndoParams) ? aUndoParams : [aUndoParams]; } /** * Creates undoable Action logger. */ UndoableAction.prototype.fLogger = new Logger("UndoableAction") /** * The value designates that do method of undoable Action * was called successfully. */ UndoableAction.STATUS_DO_OK = 1; /** * The value designates that do method of undoable Action * was called not successfully (same errors occurred). */ UndoableAction.STATUS_DO_ERROR = 1000; /** * The value designates that undo method of undoable Action * was called successfully. */ UndoableAction.STATUS_UNDO_OK = 2; /** * The value designates that undo method of undoable Action was called * not successfully (same errors occurred). */ UndoableAction.STATUS_UNDO_ERROR = 1001; /** * Returns undoable Action group. * Usually action group represents action type and used to group similar actions. * * @return {String} Returns undoable Action group. */ UndoableAction.prototype.getGroup = function() { return this.fGroup; }; /** * Returns undoable Action name. * Usually action name used to create undoable Action presentation. * @return {String} Returns undoable Action name. * * @param {Boolean} The true designates that "DO" presentation requested, false - "UNDO". */ UndoableAction.prototype.getName = function(anUndo) { return this.fName; }; /** * Returns undoable Action status. * The undoable Action status designates result of calling action. * If the action never was called, action status is undefined. * It value used by undo manager to select logic after action calling. * * @return {Integer} Returns undoable Action status. */ UndoableAction.prototype.getStatus = function() { return this.fStatus; }; /** * Calls undoable Action DO method. */ UndoableAction.prototype.callDo = function() { if (COMMONS.isFunction(this.fDoMethod)) { try { if (COMMONS.isArray(this.fDoParams)) { this.fDoMethod.apply(this, this.fDoParams); } else { this.fDoMethod.call(this); } this.fStatus = UndoableAction.STATUS_DO_OK; } catch(ex) { this.fStatus = UndoableAction.STATUS_DO_ERROR; this.fLogger.error("doAction, error happened", ex, this.fDoMethod); } } }; /** * Calls undoable Action UNDO method. */ UndoableAction.prototype.callUndo = function() { if (COMMONS.isFunction(this.fUndoMethod)) { try { if (COMMONS.isArray(this.fUndoParams)) { this.fUndoMethod.apply(this, this.fUndoParams); } else { this.fUndoMethod.call(this); } this.fStatus = UndoableAction.STATUS_UNDO_OK; } catch(ex) { this.fStatus = UndoableAction.STATUS_UNDO_ERROR; this.fLogger.error("undoAction, error happened", ex, this.fUndoMethod); } } }; /** * Indicates that the action is reversible. * Limitation: cross links in parameters are not supported. * * @return {Boolean} If so it returns true, otherwise it returns false. */ UndoableAction.prototype.isReversible = function() { var result = this.fDoMethod === this.fUndoMethod && COMMONS.isEquals(this.fDoParams, this.fUndoParams); return result; }; /** * Merges given action with the current action. * * @param {UndoableAction} The undoable action which should be merged with current action. * @return {Boolean} Returns true if merge operation accessible, otherwise - false. */ UndoableAction.prototype.merge = function(aUndoableAction) { var result = false; if (aUndoableAction instanceof UndoableAction) { if (COMMONS.isDefined(this.fGroup) && this.fGroup === aUndoableAction.getGroup() && COMMONS.isDefined(this.fDoParams)) { if (this.fDoMethod === aUndoableAction.fDoMethod && this.fUndoMethod === aUndoableAction.fUndoMethod) { this.fDoParams = aUndoableAction.fDoParams; result = true; } } } else { this.fLogger.error("merge, illegal agrument type:" + aUndoableAction); } return result; }; Additionally, undoable action contains:
Two ways to record undoable action to undo manager are implemented:
The following code snippet illustrates usage of Undo Manager
var undoManager = new UndoManager(64);
var action = new UndoableAction(removeMarker, addMarker, [point], [point]); /* * Adds undoable Action to undo stack. */ undoManager.addAction(action); // or undoManager.callDo(action); /* * Calls undo operation if it is available. * Gets last action from undo stack and calls action UNDO method. * If it was successfully adds action to redo stack, * otherwise - calls undo manager UNDO error processing. */ undoManager.callUndo(); /* * Calls redo operation if it is available. * Gets last action from redo stack and calls action DO method. * If it was successfully adds action to undo stack, * otherwise - calls undo manager DO error processing. */ undoManager.callRedo(); The following diagram illustrates internals of Undo Manager: ConclusionComplication and diversity of clients' applications causes the need in creation of various undo managers. Usually, client's side undo manager is:
All undo manager code is free, open source and may be used according to Apace License 2.0 for commercial and not commercial usage. Moreover, code is free for any modifications. It may be downloaded as example for JSONER library from SourceForge with live working examples of using: undo manager that stored D&D operations and undo manager that stored form states (without event handlers on every form fields). Please click on the image below to check example of live Undo Manager: Undo Manager was used in real life system within onlin journal editing component (as part of online publishing system). Here it had proved concepts of its design and demonstrated that client-side undo manage worked 10 times faster than using client-server approach. Make your JavaScript object persistentLet's recognize the second part of our article as �conceptual�, because it is dedicated to the question of creation of persistent JavaScript objects, demonstrated with an example of client side undo manager and here we will show, how to this extends undo manager usage. Fact of the matterThe considered client side undo manager has one significant drawback- information on the undo manager state is not delivered to sever and refresh page or navigation by pages erase all history of users action, let alone renovation about new session creation� For full-fledged work with client side one need have some and preferably uniform way of JavaScript objects serialization in the convenient form for network transmission and objects renovation from this mode. Undoubtedly, one can write methods of saving and restore of objects states for particular task. But undo manager has very complicated structure, which strongly depends on the client's actions. It may be a very complicated task. Besides, creating objects' hierarchy or extending of object's features required to change serialization logic properly. So we need to as it possible universal JavaScript object persistent approach. JavaScript persistent is necessary, when:
Possible solutionsSo, to make JavaScript persistent object one needs to have:
Undoubtedly, if it was enough to work only with a part of object, which represents only data, the serialization of data as JSON would be the best solution but the objects are not only a set of data, they are contains cross-references, have references or aggregate other objects, has a methods ultimately. In the article http://www.ajaxline.com/walking-on-javascript-trees dedicated to the tree traversing, we have already mentioned that on the one hand JavaScript object is close on JSON. Lets expand the standard JSON protocol, in a way it will allow to work with any kinds of JavaScript objects So, we add:
In the article, http://www.ajaxline.com/lazy-inheritance dedicated to the inheritance of objects in JavaScript, we tried to show that typified prototype-based inheritance has several advantages in comparison other variants. Protocol optimization during object serialization confirms it. Inspire if the simplicity of the idea, the task of serialization of objects tree is a difficult task, as JavaScript objects are flexible. But JavaScript object persistent is very helpful solution an in many cases it is the only possible solution. What additional abilities serialization of objects gives in our example with undo manager?Persistent undo manager may be considered as saved consequence of transfers of objects from one state to another. And how does it differ from the slide-show or demo-roll? You can load an example at see it. PSAny object does not exist in vacuum - object serialization and deserialization have sense only into corresponding context. To transfer serialized object to server don't remember decode special symbols. Final note about persisting Java Script objectObviously, that one can describe rather a wide range of tasks, when JavaScript object persistent can ease the application development sufficiently and the main is that it makes the client's application easer and faster, decreases server's and client's duty. |
|