PDA

View Full Version : Timelines - Grid undo/redo feature



para
23 Mar 2008, 7:03 PM
I will edit this post to be the most up to date item. That way you don't have to go searching through the thread to find updates.


Hey, I've been out of mix for a month or so. I decided to finally write my preliminary attempt at a good undo/redo design.

A few definitions:
action - a single action which can be monitored
Transaction - a record which contains an id, an action, and a description (possibly for display purposes)
Marker - A special Transaction, which doesn't have an action. Instead, it can be used to group Transactions into "macros".
Timeline - A single listening object which contains the action/transaction definitions for an object
GridTimeline - An extension of Timeline, with events and Transactions for the EditorGridPanel widget.
TreeEditorTimeline - An extension of Timeline, with events and Transactions for the TreeEditor widget.
TreeTimeline - An extension of Timeline, with events and Transactions for the TreePanel widget. (Makes TreeEditorTimeline obsolete)
FormTimeline - An extension of Timeline, with events and Transactions for the FormPanel widget. (HtmlEditor doesn't work)
TimelineGrid - A Photoshop-like history grid of the timeline. (new to 1.0)

This model can be abstracted for any Widget.

Main Demo (new 4/20/08) (http://gadberry.com/travis/legacy/Timeline/index.html)
Let me know what you think.


Here's the "release" of 1.0 of Timeline (http://gadberry.com/travis/legacy/Timeline/Timeline.zip).
Included are the following files:

index.html - example of 2 grids and a tree which are all tied together into one Timeline. Has a second grid on the 2nd tab which has a separate timeline. Also has 2 TimelineGrids.

Timeline.js - The base Timeline class.
GridTimeline.js - The Timeline specific to EditorGridPanel.
TreeTimeline.js - The Timeline specific to TreePanel.
TreeEditorTimeline.js - The Timeline specific to TreeEditor. (not needed)
FormTimeline.js - The Timeline specific to FormPanel. (actually BasicForm)
TimelineGrid.js - The Grid extension which acts as a visible history.

mystix
23 Mar 2008, 7:14 PM
very neat =D>

i came up with a grid redo / undo feature for 1.x last year, but it was only limited to re-adding / removing rows, and it was ultimately abandoned. *sob*

if abstracted well, this will be very useful + powerful indeed.

jerrybrown5
23 Mar 2008, 9:06 PM
This feature is really cool. Would it be possible to tie the undo and redo actions to the back and next button? A while ago I made an integration with the yui history control. A few users then took this and tied it to a grid's store but I think what they really wanted was this kind of feature attached to the back button.

http://extjs.com/forum/showthread.php?t=15453&highlight=yui+history

Best regards,
Jerry Brown

denkoo
23 Mar 2008, 10:04 PM
I will follow your project...

Nota: first, I try it with IE7 but don't work... only work on FF2
and I waiting patiently that red coin will be delete after undo action :-)))

One suggest : If undo data are empty, ico redo is hidden or disabled, only appear or enable when undo is up and redo etc...

thanks for sharing... great job ))

jay@moduscreate.com
24 Mar 2008, 8:55 AM
Off the hook para! Great job. :D

para
24 Mar 2008, 7:09 PM
Update:

GridTimeline is nearing completion.

Red flags are removed, but not in the best way possible. Here's the problem:
I'm listening to the 'afteredit' event, which doesn't provide an indication on whether or not the edit is an 'original edit' (the first edit on a cell). I currently check whether the value in the cell is equal to the original value, which results in the red flag going away ANY time the original value is put into the box. Try changing 'Bergamot' to 'BergamotAAAA', then change it to 'Bergamot', then 'BergamotBBBBB'. Undo and redo a bit and you'll see what I'm talking about. Any ideas?

I'll start working on TreeTimeline soon.
Any other widgets need a Timeline? Let me know and I'll put them on the list.

mjlecomte
24 Mar 2008, 7:28 PM
You might be making changes at the moment, at the moment there is a terminal firebug error.

Looks like you're using ext-all so info is limited:


C has no properties
initComponent()ext-all.js (line 129)
Component(Object typeAhead=true triggerAction=all transform=light)ext-all.js (line 58)
apply()ext-base.js (line 9)
apply()ext-base.js (line 9)
apply()ext-base.js (line 9)
apply()ext-base.js (line 9)
apply()ext-base.js (line 9)
createGrid(undefined)index.html (line 90)
(no name)()index.html (line 37)
Observable()ext-all.js (line 12)
EventManager()ext-all.js (line 13)
[Break on this error] Ext.form.ComboBox=Ext.extend(Ext.form.TriggerField,{defaultAutoCreate:{tag:"inpu...

para
24 Mar 2008, 7:29 PM
check it again... the demo is also my development area... :)
I should move my development somewhere else, but I'm lazy.

mjlecomte
24 Mar 2008, 7:35 PM
Nah, I figured as much, I really hesitated to even mention it.

mjlecomte
24 Mar 2008, 7:56 PM
I see what you mean about the flag........I think. But I don't know that it's necessarily bad. If it is different than the stored version then the flag shows.

Something that might help for the observer is if you added some console.log(something) so we could see how you're building the timeline, etc. I haven't dug into your code as of yet to see how you're doing all of this yet.

Goes without saying I definitely like it though.

Some food for thought as you're developing this is that I could see someone saying, well how about if we want to click on a row and click on undo/redo and just modify changes for that record. I can't see it for a column so much. I don't intend to dictate the direction you're taking this, just playing devil's advocate planting some seeds just in case.

Then of course, the finer points of "undo all", or set break point and undo to break point or other foolishness.

Maybe instead of tracking dirty records you track changed records and use a different css style for this. Maybe that would remove some confusion?

That or maybe think of excel or something where it has undo button and with the undo it has popup or snippet of message saying what that undo step will undo or redo, basically track a message or something that can be fed back to the user so they know what each press of the undo/redo button will change (this might also help with the red flag issue?).

It looks like you're working with just one store correct? It's cool that the undo works across multiple instances of a grid. Will that work if the grid's are backed by different stores? Or will you need an undo button tied to each store? So can the undo/redo buttons handle more than one store or just one store (rhetorical question somewhat). Seems like you could track changes per set of buttons and log the store for that change and the change to that store.

para
24 Mar 2008, 8:26 PM
That post reads alot like my train of thought when implementing this.

Here's the general outline of how it works.

GridTimeline listens to a grid and it's store. It listens to grid's 'editrecord' and store's 'addrecord' and 'removerecord'. Each event causes an "action" to be created, which is simply information about the action performed. This action is then wrapped in a Transaction record, which is stored in the GridTimeline.
From then on, it's all about manipulating the GridTimeline's store of Transactions.
Now for the cool part. Any time a Transaction is added to the GridTimeline, an event is fired. If you have that GridTimeline registered with another Timeline, such as in the example, the parent Timeline gets a copy of the Transaction. Then all undo/redo is handled through the parent Timeline. That means that you can have multiple grids use the same Undo/Redo Timeline. Soon you'll be able to do the same concept with a TreeTimeline.

Record level undo/redo isn't implemented. It wouldn't be terribly different from the GridTimeline, just each record would implement a RecordTimeline (which isn't written).

Since it is associated with a Grid, changing the store will break it unless you take an additional step. If you reconfigure the grid with a different store, then you have to call GridTimeline.reconfigure(); to get the new store to register with the GridTimeline.
I haven't tested this functionality yet, but it should work just fine.

I'm working on a way for the Timeline to implement one of two undo/redo methods. One is a single Transaction undo/redo. The other counts on the DEVELOPER to put markers in, indicating that multiple Transactions go together. This way, the undo/redo will repeat until a marker is found. It would be a config option.

On my app, I have messages associated with each Transaction, which are created when the Transaction is created. Those messages can be displayed anywhere. For example, on my app I have a history panel similar to Photoshop, where the Transactions are displayed in a grid, which can be clicked to undo/redo to a specified point. It's pretty slick.

para
24 Mar 2008, 9:11 PM
I've got my development folder one level down now.

Development Demo (http://gadberry.com/travis/legacy/Timeline/dev/index.html)

I've scrapped together a TreeEditorTimeline class. The TreeEditor Timeline is going to be different from the other TreePanel Timeline.

It's not perfect, but it's a start.

chalu
24 Mar 2008, 10:46 PM
Tried using it but got some stoppers, I have a custom meta grid I'm still working on and decided to use this undo / redo feature on it, In my case I added the usage code in the initComponent method of my extension and looks thus :


initComponent: function(){
...
Ext.ux.MetaGridPanel.superclass.initComponent.call(this);

this.timeline = new Ext.ux.GridTimeline({grid:this}); // take note of this call here
this.timelineMngr = new Ext.ux.Timeline({});
this.timelineMngr.registerTimeline(this.timeline);
...
}

An attempt to edit a field in the grid gives this error in Firefox :


grid is not defined
GridTimeline.js Line 56

// this is the block in 'editRecord' of GridTimeline.js
Ext.apply(action, {
timeline: this,
action: 'editRecord',
store: grid.store // this is the line
});

Am I missing something in it's usage? Also what's the use of Transaction.js file in the example, since it's defined again in Timeline.js.

Thanks and keep the great work.

para
24 Mar 2008, 10:55 PM
You're right. I found that error a little while ago. It's corrected on the Demo and the Dev Demo.




Ext.apply(action, {
timeline: this,
action: 'editRecord',
store: this.grid.store // this is the line
});


Download the new files that are up. They're not ready for any sort of production release though. It's a work in progress. I'm scheduling it to be all working by the end of this weekend hopefully.


Of note: You don't actually need to create a wrapper Timeline class to need it. You can directly use the GridTimeline you create. Something like this:


initComponent: function(){
...
Ext.ux.MetaGridPanel.superclass.initComponent.call(this);

this.timeline = new Ext.ux.GridTimeline({grid:this}); // take note of this call here

// later you would make calls to this.timeline.undo()
...
}


The wrapper Timeline is only needed if you have more than one widget timeline. Say for instance, you have a Tree and a Grid, and you want them to have ONE timeline. Then you create a TreeTimeline(not made yet) and a GridTimeline. You then create a wrapper Timeline and register both of the other timelines with it. You would then use Timeline.undo().

Make sense?

I think the demo has a better example now... The Dev demo has 3 widgets working together (tree only partially)

Transaction.js no longer exists. The definition in Timeline suffices.

mjlecomte
25 Mar 2008, 3:54 AM
Sounds good. Yes, I was just kind of babbling thinking maybe something I mentioned might spark an idea more than anything.

I'd like to see your implementation of the photoshop like undo, etc.

Why would it be left to the developer to set the marker? Is it a public event that could be added to the undo/redo split button or something? "setMarker" or "addMarker" or something?

Another seed for thought, does this work in the event of server side change and then commit? If going down supporting that road, then what about destroying or resetting the transaction history, etc. for memory usage at some point?

Another thought, what if there are certain fields that should not be tracked? What if there are 4 fields, but one column is calculated from another column? Just for example, you have a price column a tax column and a total price column. So price is changed, tax and total are calculated. Seems like might be desirable to have a way to configure via columnModel-ish way of saying do or do not track timeline on this field. There's a defaultSortable on the columnModel I think, which is also supersedable by each column. So maybe defaultTrackable and trackable? I think you stated you're not involved with the columnModel at this point though. Ambience's filter extension does something similar to this, but in it's own config not part of the columnModel.

That's the end of my random thoughts at the moment.

para
26 Mar 2008, 5:48 PM
HAHAHA

I have the initial implementations of GridTimeline and TreeTimeline (and TreeEditorTimeline, although it's not really necessary).

Check out the demo. The 2 grids and the tree are all tied to the same Timeline.

Demo (without Markers) (http://gadberry.com/travis/legacy/Timeline/index.html)

Demo (WITH Markers) (http://gadberry.com/travis/legacy/Timeline/index2.html)

You'll have to add markers manually with the button, but if you want to group Transactions in your app, you can do it programmatically using your own custom code listening to whatever.

I don't think that TreeTimeline's drag and drop is done yet, otherwise everything should work.

Edit the tree with the context menu.

mystix
26 Mar 2008, 6:38 PM
great work thus far!

more food for thought:
how about a FormPanel timeline? :-?

mjlecomte
26 Mar 2008, 8:31 PM
Yeah, kudos. I couldn't break it. ;)

Although some of the validations limited me from editing some fields at all.:-?

para
26 Mar 2008, 8:31 PM
I've had a problem a few times...

When adding a node to another node, such as REDOing an append action, the node being added isn't shown. Debugging shows that it is correctly being added to the parent, but it is not being rendered/shown for some reason.

I do have to suspendEvents when I append/insert it back in (so that it doesn't count as another transaction). Maybe that has something to do with it.

I've also had it work sometimes when I add it twice. Something like:
action.parentNode.appendChild( action.node );
action.parentNode.appendChild( action.node );

Although that doesn't work in this case.

Any thoughts?

para
26 Mar 2008, 9:23 PM
Yeah, kudos. I couldn't break it. ;)

Although some of the validations limited me from editing some fields at all.:-?

I fixed the combo box. The number field is funky. don't know why.
Not a biggie though.

denkoo
26 Mar 2008, 11:53 PM
Very usefull exension.... thanks for sharing ))))) very very thanks

Only one question : Do you this that it's possible to have timeLine not on component datagrid or tree or datagridEditor but only on dataStore ???
My problem is :
When I have a grid Editor with combo, when I'm selected certain data on my combo, I modify dataStore himself for change some cell on dataGridEditor.
When I use UNDO, action UNDO not change what I'm change on dataStore with after select Event on my combo...


thank very much again for your WORK.... and sharing ))) :">

para
27 Mar 2008, 7:14 PM
I wanted to do it on the data store, so it would be a StoreTimeline or something. The problem is that the store doesn't have the right events to be able to implement a timeline. The EditorGridPanel's "afteredit" event is what really does it.
Besides, if it was on the store, you would get two Transactions for what you're describing. One for the initial edit, and another for the calculated field.

Here's a workaround: Use Markers, which means that you'll have to add a Marker after every edit, but after any calculations are made. Here's the flow:


After a grid editor has completed (on 'afteredit')
Make your calculated changes
for each calculated change, make the grid fire a 'afteredit' event
add a Marker to the timeline


The Markers make it very powerful, but they are a bit more difficult to use.

stever
27 Mar 2008, 9:32 PM
Very Cool! No notice of license in the files...

para
27 Mar 2008, 9:34 PM
Once I get a full version out (hopefully this weekend) I'll get some words in there about me.

Honestly, I'm not too worried about it. Unless Ext has some rules I'm supposed to follow. All the work I post on the forum is fair game for personal and/or commercial use.

Copyright 2008. No rights reserved. :)

denkoo
27 Mar 2008, 10:22 PM
The Markers make it very powerful, but they are a bit more difficult to use.

You're idea for resolved my problem is Great... thank you very much

=D>

wm003
28 Mar 2008, 7:23 AM
Cool feature and very useful.B) Thank you for sharing!

jsakalos
30 Mar 2008, 3:12 AM
First of all, very bright idea and although I have no immediate use for this extension I think that it is good to know that it exists when the time comes...

Now question: I use Ext components (grid, form, tree) in client/server environment in two basic scenarios: with save button (grid, form) w/o save button (tree). For grid and form, user edits value(s) clicks the save button and changes are committed to server and saved in database. For tree (http://filetree.extjs.eu), user drags & drops a node, change is sent to server and only if successful there it is executed at client.

The question is, how would I use Timeline series in this environment? In another words, what should I do on client and server to implement it (plug it in) in an existing application?

mjlecomte
30 Mar 2008, 5:33 AM
You've stated earlier that, at least at one point, you were monitoring grid events instead of say the store events.

I'm wondering if there exists opportunity to monitor a series of subscribers to a store. For example if you have a window with tree, in west, grid in center, and form in west regions, can a timeline be implemented for the widgets in that window? I guess the specifics are not relevant, it's just a matter if you can set up subscribers (various widgets) to a given instance of a timeline.

Perhaps this is too difficult, as the tree and grid might have different stores for example. Then again, maybe it's just a matter of the timeline being able tracking transactions as store.record.field.value. Again, may be pushing this too far beyond your original intentions.

Very nice work, I can use as is, just adding thoughts in case someone has ideas/thoughts to abstract this further.

Oh, and I have similar question as Saki as your thoughts on implementing server side interaction. Will timeline bundle changes together or something? For instance if there are 10 changes in timeline. User clicks on history marker 5. What process would accumulate changes 5-10 to have them sent to the server? And then, I hate to mention throwing a wrench in that thought, but what if change #8 fails validation or update server side for whatever reason. Thinking more on that I guess the timeline is what it is (server side should not affect the timeline). But maybe timeline can keep track whether or not server side update was made or not. Someone might be able to format the history appropriately (you have red to signify where in the history you are currently at, someone might format records that have been stored server side differently maybe in italics or something).

para
30 Mar 2008, 7:46 AM
For Saki and mjlecomte:

I have not handled server requests in the Timeline, however it could be implemented. If you look at the GridTimeline, you will see how the editRecord action is laid out:


editRecord: {
add: function(){...}
undo: function(){...}
redo: function(){...}
}

You could simply override these functions to handle your server requests. On the other hand, it may be easier just to create your own class, like ServerGridTimeline or something. The model is relatively easy to expand on.

There is no way to bundle transactions together into atomic units. Undoing to a point half-way through the timeline simply undoes transactions one at a time until that point.
Although that may be overridden too.


For mjlecomte:
That's the main feature of the Timeline series. It doesn't matter what widget you're registered to, or what store it uses. You can register a TreeTimeline, a GridTimeline, and a FormTimeline on the same page, regardless of which stores they use. You can them tie the timelines together with a master Timeline.
Now, if you have something set up where TreeNodes represent a Store's records or something, then it may not work to listen to a Tree and a Grid with the same store. Essentially, this implementation should work for the default cases. Any custom code at all will have to override some of the functions here.
I'm not sure if that was your question...

As far as displaying the Transactions, my Photoshop-like history panel is simply a GridPanel. I have a bunch of styling applied to it, to show the current record, the future and the past. That's the main reason for inserting events into the 0th index instead of appending them.
I can't give you an example of that right now since the current implementation is on a different platform (codebase) and it was for a client. I'll write one soon enough and put it on the example.

jsakalos
30 Mar 2008, 8:18 AM
Thanks for explanation, it's clear now what I need to do if I wanna implement it.

chalu
31 Mar 2008, 2:14 PM
Thanks for this feature, but it now adds a level of complexity that I have to give a thought. In my case I use it for undo / redo of column editing and record actions (create / delete). a user can thus have an initial record set of 5 records, he edits some fields and deletes some rows and creates new ones at random (at any order as he does his work). Now it's time to save, the grid store's state is no longer as it was initially (deleted records as well as saved records, with some of these having fields with fresh data), how can one save these records to the server in a way that we keep referential integrity at the database i.e a field that may have been edited and about to be saved may be a foreign key (which must point to a value in another table), also a record that seemingly have been deleted in the grid's store may have a field referenced by a field in another table in the database (foreign key) which may have been set up with the 'cascade on delete' constraint (meaning that traditionally, when the record in the grid is deleted the record in the table referring to it also gets deleted).
The bottom line is that some records may have dependencies in the database, if a user can do all the stuff (undo and redo at will) at the client and then save when satisfied, how can one get the records to be saved at a particular order, preserving data integrity at the database.
Any suggestions .....

para
31 Mar 2008, 9:26 PM
This is a perfect example of a custom use of the Timeline.

So the user makes his edits, removes, and adds to the grid. The order in which he does these actions is important. We must be able to assume that if we do the same actions to the database in the same order, it will be stable.
With this, when you save the grid, you must do it in changes made one at a time, right? It's really not terribly bad. When you save the grid, go through the Timeline's transactionList, which contains all of the Transactions, in order, for the timeline.
You can construct your sql updates using those actions. You could either send them back in JSON and construct the sql in ASP/PHP/JAVA/Whatever serverside, or you could construct the sql in JS and just send back a list of sql queries to perform in order.
Once you save the grid, you may have to remove the Transactions. This means that you may have to eliminate the possibility to undo Transactions made before the commit.

Make sense?

chalu
29 Apr 2008, 5:26 PM
Once you save the grid, you may have to remove the Transactions

Exactly how do I do this, tried calling the removeTransactionsAfterCurrent() method on the my grid's timeline but I was still able to undo and redo.

para
29 Apr 2008, 5:32 PM
To clear all transactions, use Timeline.emptyTransactions()

Maybe that needs to change to "removeAllTransactions()" ?

removeTransactionsAfterCurrent() is used for something else. Here's a scenario: If the user does 10 actions, undoes 3, then there are 7 "before" current, and 3 "after" current. If the user then makes an action, the 3 after current need to be erased, and the new one appended.

chalu
29 Apr 2008, 5:40 PM
Very well then, thanks a lot.
On a save attempt, do I have a hook to say :
1. all added records
2. all deleted records
3. all records containing dirty fields (they were edited)

in the current transaction, in their order of execution.
So we can save appropriately, or do I consult the grid's store ?

para
29 Apr 2008, 5:49 PM
You can directly access the transactions in the Timeline's store.

timeline.transactionList is a standard data store with records of type Transaction
A Transaction is a simple wrapper that has a Definition and an Action.
The action is just an object that defines what happened. It is different for each type of action, and the only item that you can count on is "Action.action", which will tell you which type of Action it is.

You can look in GridTimeline.js to see what items are available in each type of action.

So your loop would be something like



for(var i=0; i<myTimeline.transactionList.getCount(); i++) {
var action = myTimeline.transactionList.getAt(i).get('action');
if(action.action == 'editRecord') {
...
}
else if(action.action == 'addRecord') {
...
}
else ... {
}
}



I specifically didn't write any functions to extract a specific type of action because many times the order is necessary. For instance, if you do all your adds, then removes, then edits, you may have logic problems. If you edit a row, remove that row, and then save, you would have a problem.

Your save would first remove the row, and then would edit it.

Everything must stay in order, even if it means doing things individually instead of in bulk.

It's a dirty trap that everyone falls in.

chalu
29 Apr 2008, 8:50 PM
Thanks a bundle, will try implementing these and get back to you. thanks again

chalu
2 May 2008, 3:59 AM
First of all, my setup :


var metaTBar = null;
var metaGrid = null;
var gridActions = {}; // house for our buttons
var gridTimeline = null;

metaTBar = new Ext.ux.MetaToolbar({
// our toolbar for holding the buttons
// gets added to the metaGrid
});

metaGrid = new Ext.ux.MetaGridPanel({
// our editor grid, reconfigures it's colModel as and when required
...
tbar: metaTBar,
store: new Ext.data.GroupingStore({
listeners: {
'update': function(store, record, operation){
// so we can save when an edit is done
if(operation == Ext.data.Record.EDIT){
gridActions['save'].enable();
}
},
'metachange': function(store, meta){
var actions = null;
if(meta.editable){ // if this user can edit data

actions = [];
// the user can edit in the grid
// enable undo redo for editable fields
var saveAction = new Ext.Toolbar.Button({
id:'save',
text: 'Save',
iconCls:'save-edit',
disabled: true,
handler: function(){ metaGrid.fireEvent('save'); },
tooltip:{title:'Save', text:'Save All Changes'}
});

var undoAction = new Ext.Toolbar.Button({
id:'undo-action',
iconCls:'undo-edit',
disabled: true,
tooltip:{title:'Undo', text:'Undo Previous Action'}
});
gridTimeline.setUndoButton(undoAction);

var redoAction = new Ext.Toolbar.Button({
id:'redo-action',
iconCls:'redo-edit',
disabled: true,
tooltip:{title:'Redo', text:'Redo Previous Action'}
});
gridTimeline.setRedoButton(redoAction);

gridActions['save'] = saveAction;
gridActions['undo'] = undoAction;
gridActions['redo'] = redoAction;
actions.push('', saveAction, '', undoAction, '', redoAction, '');

}

if(actions){
metaTBar.fireEvent('metachange', actions); // build menu
}else{
metaTBar.fireEvent('reset'); // reset menu
}
}
}
}),
listeners:{
'save': function(){
// issue server request to save the changed records
// these calls are to be issued within the success response handler
this.store.commitChanges();
gridActions['save'].disable();

// clear the undo / redo timeline
gridTimeline.emptyTransactions();
},
'validateedit': function(e){ // please take note
var rec = e.record;
var store = rec.fields.items[e.column].editor.store;
if(store && store instanceof Array && store[0] instanceof Array){
for(var opt = 0; opt < store.length; opt++){
var option = store[opt];
if(option[0] == e.value){
rec.set(e.field, option[1]);

// you can comment this to allow default behaviour
return false;
}
}
}
}
}
});

gridTimeline = new Ext.ux.GridTimeline({grid: metaGrid});


Now the issues :
1. With the 'validateedit' handler of the grid commented, we see that when we edit a field in the grid, the undo button gets enabled, but clicking on it does nothing. Clicking on our save button (for now) commits the changes, clears the transaction (supposed to), but our undo button is still enabled. Do I have to manually turn the undo button off assuming that the transactions have been cleared , or does it's enabled state mean that we still have undoable actions. Since our undo button does nothing for now I have no way of evaluating the redo button.

2. With the release of Ext 2.1, making combo's to have stores from multi-dimensional arrays,
we now have combo's displaying their 'value' instead of the 'text' for a 'value' when we edit a field having a combo as it's editor, thus the need for the 'validateedit' handler workaround.
It attempts to set the 'textual' representation of the combo's selection for display, returning false so the grid does not think it was a 'true' edit. This workaround however breaks the timeline's logic and our undo button no longer gets enabled when we edit the fields, perhaps because of the return of false (which our combo relies on). How can the timline API be refactored to tolerate this change.See This Thread (http://www.extjs.com/forum/showthread.php?p=163000#post163000)

para
5 May 2008, 9:01 PM
Try using the latest version. I just modified it a bit to handle the buttons a little cleaner. (for you :) )
http://gadberry.com/travis/legacy/Timeline/Timeline 5-5-2008.zip

The buttons should (hopefully) enable and disable correctly now.

As for the combo editing (I'm assuming your using combo boxes as editors in a grid), you can extend or simply modify GridTimeline to be unique. Here's the basics (going off newest code):

On line 28 you can see that it sets up listeners. You could set up a listener for the 'validateedit' event. You wouldn't be able to replace the 'afteredit' though. You can see the structure of the 'editRecord' object on line 50. I'd recommend copying/pasting that block and modifying it to work with the 'validateedit' parameters. It should be similar...
You will have to edit the 'add' function with your custom code to determine value. There may be a bit of redundant code, but it should work.

Does that help?

chalu
7 May 2008, 1:53 PM
I think the update link http://gadberry.com/travis/legacy/Timeline/Timeline
is broken ??

para
7 May 2008, 4:08 PM
...
http://gadberry.com/travis/legacy/Timeline/Timeline 5-5-2008.zip

http://gadberry.com/travis/legacy/Timeline/Timeline 5-5-2008.zip
...

chalu
9 May 2008, 9:34 PM
The buttons are better now. When my save button is clicked and after the normal 'save' operations I clear all transactions by calling emptyTransactions() on my gridTimeline object which actually clears the transactions but leaves the redo button still enabled, allowing the user to try to redo 'actions'. This of course throws an error because at this point we have emptied our transaction list. So I edited Timeline.js and issued a call to setButtonsDisabled() just after the call to removeAll() in the emptyTransactions function. With this in place, the button states are now properly handled.

However, after a save operation and an edit is made, our undo / redo buttons are never turned on (re-enabled). That is we can only undo and redo until we save (which is technically sound). I assumed emptyTransactions() 'clears' the timeline of it's pending / current transactions and prepares a fresh slate (like erasing a drawing sheet for a re-draw) so we can begin the edit / save cycle again, of course punctuated with 'undo' and 'redo'. Right now the punctuations are not allowed once we've saved (even for just the first time).

On the validateedit issue, I'll say I haven't had any luck, created a handler in GridTimeline.js for the grid's validateedit event and in it I just did console.log(e) where e is the event object sent by the grid containing all we need for the record and column been edited. This handler was never called for the columns having combo as editors (surely because of our hack where we return false after reseting the value to display to the user). I also tried to call the addRecord.add function in my validateedit hack thus :


// e is the param passed to 'validateedit' event handlers
gridTimeline.addRecord.add.call(gridTimeline, e);

This only worked half way, we could undo but not redo.
Since we are relying on the grid's validateedit handler to return false (in our value reset hack), i guess we may not be able to build any logic for any other 'validateedit' handler again. Is there a way I can (within the validatededit handler hack) make a transcation object representing the 'edit' operation and pass it to the gridTimeline ?? I'm sure this may help.

Regards.

para
9 May 2008, 9:48 PM
The emptyTransactions() did not re-add the original marker, which could cause issues. Apply these changes:

Timeline.js


In constructor:

change
this.addMarker(this.originalMarkerText, true);
to
this.addOriginalMarker();
--------------------------------------------------

Add function
addOriginalMarker: function() {
this.addMarker(this.originalMarkerText, true); // EVERY timeline gets an Original marker, whether it uses Markers or not. (Satisfies TimelineGrid's requirement)
},
--------------------------------------------------

Add the line
this.addOriginalMarker();
to the emptyTransactions function (before firing 'removeall')







The 'validateedit' problem is pretty specific. I'm not sure if I can help that much.
To create an 'editrecord' object, you can call something like



var transaction = new Transaction({
id: Ext.id(null, "egrid-trans-"),
description: 'Changed "' + originalValue + '" to "' + newValue + '"',
isMarker: false,
action: {
timeline: yourGridTimeline,
action: 'editRecord',
store: yourGrid.store,
.... some other stuff from your event, like "record", "field", "originalValue", and "value"
}
});
yourGridTimeline.addTransaction(yourGridTimeline, transaction);


It should be relatively straight forward what you need to include in the 'action' part of the Transaction. Anything used in the undo/redo functions for that actiontype needs to be there, plus the Timeline itself, and the actiontype ('editRecord')

Does that help at all?

chalu
14 May 2008, 8:08 AM
The 'removeAction' Transaction wrapper seems to have an issue. If I delete a record, and undo the delete, at the point of saving, querying the removeRecord action still shows the record(s) that was whose 'deletion' un-done, it should have been cleared.

para
14 May 2008, 8:18 AM
If I deleted the Transaction when it is undone, then how would I redo it?


I don't have the source in front of me now, so the functions I reference may be a bit off...

On save you need to do something a bit extra. You can either:
call Timeline.removeTransactionsAfterCurrent()

Or you can change your query to make sure that the returned Transactions are <= the Timeline.currentIndex.

chalu
15 May 2008, 3:19 AM
Issuing a call to removeTransactionsAfterCurrent() does solve the problem, not only for 'removeRecord' but for the others as well.


gridTimeline.removeTransactionsAfterCurrent();
for(var n = 0; n < gridTimeline.transactionList.getCount(); n++) {
var action = gridTimeline.transactionList.getAt(n).get('action');
if(action && action.action){
if(action.action == 'editRecord') {
console.log('editRecord');
console.log(action);
}else if(action.action == 'addRecord') {
console.log('addRecord');
console.log(action);
}else if(action.action == 'removeRecord') {
console.log('removeRecord');
console.log(action);
}
}
}


What is left for me now is to come up with an 'algorithm' for the order to 'save' the state of the grid (records). I think I'll do the 'deletes' first, then the 'adds', then 'edits' on the 'adds' and then any other 'edits'.

We should not have much problems with this, since we have called removeTransactionsAfterCurrent() initially.

para
15 May 2008, 8:02 AM
I don't think you can save that way. I believe that you have to do it IN ORDER.
I think the easiest way would be to write an Ext.XTemplate for each Transaction type, and then iterate through the transactions to create the sql statements (again, in order)

What if you edit a record, and then delete that record.

If you run all the deletes first, and then all the edits, the edit will try to change a non-existing record in the database.