source: gs3-extensions/seaweed-debug/trunk/src/OperationManager.js@ 25160

Last change on this file since 25160 was 25160, checked in by sjm84, 12 years ago

Initial cut at a version of seaweed for debugging purposes. Check it out live into the web/ext folder

File size: 14.3 KB
Line 
1/*
2 * file: OperationManager.js
3 *
4 * @BEGINLICENSE
5 * Copyright 2010 Brook Novak (email : [email protected])
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 * @ENDLICENSE
18 */
19bootstrap.provides("OperationManager");
20
21 /* Read Only. Stores all undoable/redoable operation logic */
22var _operationRepository = {},
23
24 /* Read Only. Stores the current list of operations. Null/undefined if there is none */
25 _curOperationList,
26
27 /* Set to true to record operations, false to ignore. Defaults to true. */
28 _recordOperations = true;
29
30/**
31 *
32 * @param {Number} opCode The unique operation code which identifies the operation.
33 *
34 * @param {Function} exec The execution function. The first argument will be the operation data.
35 * The following arguments are specific execution arguemnts to the operation.
36 *
37 * @param {Function} undo The undo function. Given one argument: the operation data.
38 *
39 * @param {Function} redo The redo function. Given one argument: the operation data.
40 *
41 * @return {Number} The operation code of the registered operation
42 */
43function _registerOperation(opCode, exec, undo, redo) {
44
45 // Should generate operation code?
46 if (!opCode) {
47 opCode = _registerOperation.genOp + 1;
48 do {
49 opCode++;
50 } while(_operationRepository[opCode]);
51 _registerOperation.genOp = opCode;
52 }
53
54 debug.assert(!_operationRepository[opCode], "Attempted to override operation with op code: " + opCode);
55 _operationRepository[opCode] = {exec:exec,undo:undo,redo:redo};
56
57 return opCode;
58}
59
60_registerOperation.genOp = 100;
61
62/**
63 * Executes an undoable operation. If _recordOperations is on then the operation will be stored
64 * in the _curOperationList list.
65 *
66 * When ever dom is to be manipulated, it is always done here. The additional arguments after the given op code
67 * are specific to the operation.
68 *
69 * @param {Number} opCode The operation code of the operation to execute.
70 */
71function _execOp(opCode) {
72
73 // Get the operation code
74 var operation = _operationRepository[opCode];
75 debug.assert(operation != null, "Unknown operation: " + opCode);
76
77 // Create argument list... begin with the operation data object
78 var args = Array.prototype.slice.call(arguments);
79 args.shift();
80 args.unshift({opCode:opCode});
81
82 // Execute the operation
83 var opRes = operation.exec.apply(operation, args);
84
85 // Store the operation data
86 if (_recordOperations) {
87 // Create a new operation list if not appending
88 if (!_curOperationList)
89 _curOperationList = [];
90 _curOperationList.push(args[0]);
91 }
92
93 // Return the operatoin-specific result
94 return opRes;
95
96}
97
98/**
99 * Note: Wipes current operation list.
100 * @return {[Object]} The current list of operations. Null if there are none.
101 */
102function _getOperations() {
103 var ops = _curOperationList;
104 _curOperationList = null;
105 return ops;
106}
107
108/**
109 * Undoes a list of operations. Assumes that the effected DOM state is the same as it was after the operations were executed.
110 *
111 * @param {[Object]} opList The list of operations to undo
112 */
113function _undoOperations(opList) {
114 for (var i = opList.length - 1; i >= 0; i--) {
115 var opData = opList[i];
116 var operation = _operationRepository[opData.opCode];
117 debug.assert(operation != null, "Unknown operation: " + opData.opCode);
118 operation.undo(opData);
119 }
120}
121
122/**
123 * Redoes a list of operations. Assumes that the effected DOM state is the same as it was after the operations were undone.
124 *
125 * @param {[Object]} opList The list of operations to redo
126 */
127function _redoOperations(opList) {
128 for (var i in opList) {
129 var opData = opList[i];
130 var operation = _operationRepository[opData.opCode];
131 debug.assert(operation != null, "Unknown operation: " + opData.opCode);
132 operation.redo(opData);
133 }
134}
135
136/**
137 * Controls whether undoable operations should be recorded.
138 * ONLY USE IF YOU KNOW WHAT YOU ARE DOING.
139 *
140 * TODO: Detailed doc.
141 *
142 * @param {Boolean} on True to turn on operation recording. False to turn off.
143 */
144de.recordOperations = function(on) {
145 _recordOperations = on;
146};
147
148/* BASE OPERATIONS */
149
150// @DEBUG ON
151_Operation = {
152 // @REPLACE _Operation.INSERT_NODE 1
153 INSERT_NODE : 1,
154
155 // @REPLACE _Operation.REMOVE_NODE 2
156 REMOVE_NODE : 2,
157
158 // @REPLACE _Operation.SPLIT_TEXT_NODE 3
159 SPLIT_TEXT_NODE : 3,
160
161 // @REPLACE _Operation.INSERT_TEXT 4
162 INSERT_TEXT : 4,
163
164 // @REPLACE _Operation.REMOVE_TEXT 5
165 REMOVE_TEXT : 5,
166
167 // @REPLACE _Operation.SET_CSS_STYLE 6
168 SET_CSS_STYLE : 6,
169
170 // @REPLACE _Operation.SET_CLASS 7
171 SET_CLASS : 7,
172
173 // @REPLACE _Operation.INSERT_ROW 8
174 INSERT_ROW : 8,
175
176 // @REPLACE _Operation.INSERT_CELL 9
177 INSERT_CELL : 9,
178
179 // @REPLACE _Operation.DELETE_ROW 10
180 DELETE_ROW : 10,
181
182 // @REPLACE _Operation.DELETE_CELL 11
183 DELETE_CELL : 11
184
185};
186// @DEBUG OFF
187
188
189_registerOperation(_Operation.INSERT_NODE,
190
191 /**
192 * Execute
193 * @param {Object} data The operation data
194 * @param {Node} newNode The new dom node to insert
195 * @param {Node} parent The parent of the dom node to insert into
196 * @param {Number} index The index in the parent to insert the dom node. Omit to append
197 */
198 function(data, newNode, parent, index){
199 data.newNode = newNode;
200 data.parent = parent;
201 if (index || index === 0) // Is it an append operation?
202 data.pos = index;
203 this.redo(data);
204 },
205
206 /* Undo */
207 function(data){
208 data.parent.removeChild(data.newNode);
209 },
210
211 /* Redo */
212 function(data){
213 if (data.pos || data.pos === 0)
214 _insertAt(data.parent, data.newNode, data.pos);
215 else data.parent.appendChild(data.newNode);
216 }
217
218);
219
220_registerOperation(_Operation.REMOVE_NODE,
221
222 /**
223 * Execute
224 * @param {Object} data The operation data
225 * @param {Node} target The Node to remove
226 */
227 function(data, target){
228 data.parent = target.parentNode;
229 data.pos = _indexInParent(target);
230 data.target = target;
231 this.redo(data);
232 },
233
234 /* Undo */
235 function(data){
236 _insertAt(data.parent, data.target, data.pos);
237 },
238
239 /* Redo */
240 function(data){
241 data.parent.removeChild(data.target);
242 }
243);
244
245
246_registerOperation(_Operation.SPLIT_TEXT_NODE,
247
248 /* Execute */
249 function(data, target, index){
250 data.target = target;
251 data.index = index;
252 data.rem = target.splitText(index);
253 return data.rem;
254 },
255
256 /* Undo */
257 function(data){
258 // Restore target nodes full text value
259 data.target.nodeValue += data.rem.nodeValue;
260
261 // Get rid of the split text node
262 data.rem.parentNode.removeChild(data.rem);
263 data.rem.nodeValue = ""; // free some memory
264 },
265
266 /* Redo */
267 function(data){
268
269 var fullText = data.target.nodeValue;
270 // Re-set the splitted nodes text
271 data.rem.nodeValue = fullText.substr(data.index);
272 data.target.nodeValue = fullText.substr(0, data.index);
273
274 // Re-insert the split node
275 _insertAfter(data.rem, data.target);
276 }
277);
278
279
280_registerOperation(_Operation.INSERT_TEXT,
281
282 /* Execute */
283 function(data, target, text, index){
284
285 data.target = target;
286 data.index = index;
287 data.len = text.length;
288
289 var pre = target.nodeValue.substr(0, index),
290 post = target.nodeValue.substr(index);
291
292 target.nodeValue = pre + text + post;
293 },
294
295 /* Undo */
296 function(data){
297
298 data.text = data.target.nodeValue.substr(data.index, data.len);
299
300 var pre = data.target.nodeValue.substr(0, data.index),
301 post = data.target.nodeValue.substr(data.index + data.len);
302
303 data.target.nodeValue = pre + post
304
305 delete data["len"]; // Free some memory
306 },
307
308 /* Redo */
309 function(data){
310
311 data.len = data.text.length;
312
313 var pre = data.target.nodeValue.substr(0, data.index),
314 post = data.target.nodeValue.substr(data.index);
315
316 data.target.nodeValue = pre + data.text + post;
317
318 delete data["text"]; // Free some memory
319 }
320);
321
322
323_registerOperation(_Operation.REMOVE_TEXT,
324
325 /* Execute */
326 function(data, target, index, length){
327
328 data.target = target;
329 data.index = index;
330 data.text = target.nodeValue.substr(index, length);
331
332 var pre = target.nodeValue.substr(0, index),
333 post = target.nodeValue.substr(index + length);
334
335 target.nodeValue = pre + post
336 },
337
338 /* Undo - same as insert text's undo */
339 _operationRepository[_Operation.INSERT_TEXT].redo,
340
341 /* Redp - same as insert text's undo */
342 _operationRepository[_Operation.INSERT_TEXT].undo
343
344);
345
346_registerOperation(_Operation.SET_CSS_STYLE,
347
348 /**
349 * Execute
350 * @param {Object} data The operation data
351 * @param {Node} target The target element to set CSS
352 * @param {String} css The CSS Field to set in javascript notation
353 * @param {String} value The value of the CSS to set
354 */
355 function(data, target, css, value){
356 data.target = target;
357 data.css = css;
358 data.newValue = value;
359 data.oldValue = _engine == _Platform.TRIDENT ? data.target.style.getAttribute(data.css) : data.target.style[data.css];
360 this.redo(data);
361 },
362
363 /* Undo */
364 function(data){
365 if (_engine == _Platform.TRIDENT)
366 data.target.style.setAttribute(data.css, data.oldValue);
367 else
368 data.target.style[data.css] = data.oldValue;
369 },
370
371 /* Redo */
372 function(data){
373 if (_engine == _Platform.TRIDENT)
374 data.target.style.setAttribute(data.css, data.newValue);
375 else
376 data.target.style[data.css] = data.newValue;
377 }
378);
379
380
381_registerOperation(_Operation.SET_CLASS,
382
383 /**
384 * Execute
385 * @param {Object} data The operation data
386 * @param {Node} target The target element to set CSS
387 * @param {String} name The class name to set (replaces full class name)
388 */
389 function(data, target, name){
390 data.target = target;
391 data.newName = name;
392 data.oldName = _getClassName(target);
393 this.redo(data);
394 },
395
396 /* Undo */
397 function(data){
398 _setClassName(data.target, data.oldName);
399 },
400
401 /* Redo */
402 function(data){
403 _setClassName(data.target, data.newName);
404 }
405);
406
407
408_registerOperation(_Operation.INSERT_CELL,
409
410 /**
411 * Execute
412 * @param {Object} data The operation data
413 * @param {Node} row The target row element to insert the cell into
414 * @param {Number} index The index in the row to insert into. Clamped if out of bounds
415 * @return {Node} The new cell that was created
416 */
417 function(data, row, index){
418 data.row = row;
419 data.index = index > row.cells.length ? row.cells.length : index; // Safely clamp range
420 if (data.index < 0) data.index = 0;
421 return this.redo(data);
422
423 },
424
425 /* Undo */
426 function(data){
427 data.row.deleteCell(data.index);
428 },
429
430 /* Redo */
431 function(data){
432 return data.row.insertCell(data.index);
433 }
434);
435
436
437
438_registerOperation(_Operation.INSERT_ROW,
439
440 /**
441 * Execute
442 * @param {Object} data The operation data
443 * @param {Node} table The target table element to insert the row into
444 * @param {Number} index The index in the row to insert into. Clamped if out of bounds
445 * @return {Node} The new row that was created
446 */
447 function(data, table, index){
448 data.table = table;
449 data.index = index > table.rows.length ? table.rows.length : index; // Safely clamp range
450 if (data.index < 0) data.index = 0;
451 return this.redo(data);
452
453 },
454
455 /* Undo */
456 function(data){
457 data.table.deleteRow(data.index);
458 },
459
460 /* Redo */
461 function(data){
462 return data.table.insertRow(data.index);
463 }
464);
465
466
467_registerOperation(_Operation.DELETE_ROW,
468
469 /**
470 * Execute
471 * @param {Object} data The operation data
472 * @param {Node} table The target table element to insert the row into
473 * @param {Number} index The index in the row to delete. Clamped if out of bounds
474 */
475 function(data, table, index){
476 data.table = table;
477 data.index = index >= table.rows.length ? table.rows.length-1 : index; // Safely clamp range
478 if (data.index < 0) data.index = 0;
479 this.redo(data);
480 },
481
482 /* Undo */
483 function(data){
484 var newRow = data.table.insertRow(data.index);
485
486 // Migrate contents from old removed row into new row
487 while(data.row.firstChild) {
488 var migrant = data.row.firstChild;
489 data.row.removeChild(migrant);
490 newRow.appendChild(migrant);
491 }
492
493 // Save memory - get rid of old removed row reference
494 delete data["row"];
495 },
496
497 /* Redo */
498 function(data){
499 data.row = data.table.rows[data.index]; // Save row - need to keep contents
500 data.table.deleteRow(data.index);
501 }
502);
503
504
505_registerOperation(_Operation.DELETE_CELL,
506
507 /**
508 * Execute
509 * @param {Object} data The operation data
510 * @param {Node} row The target row element to delete the cell from
511 * @param {Number} index The cell index in the row to delete. Clamped if out of bounds
512 */
513 function(data, row, index){
514 data.row = row;
515 data.index = index >= row.cells.length ? row.cells.length-1 : index; // Safely clamp range
516 if (data.index < 0) data.index = 0;
517 this.redo(data);
518 },
519
520 /* Undo */
521 function(data){
522 var newCell = data.row.insertCell(data.index);
523
524 // Migrate contents from old removed row into new row
525 while(data.cell.firstChild) {
526 var migrant = data.cell.firstChild;
527 data.cell.removeChild(migrant);
528 newCell.appendChild(migrant);
529 }
530
531 // Save memory - get rid of old removed row reference
532 delete data["cell"];
533 },
534
535 /* Redo */
536 function(data){
537 data.cell = data.row.cells[data.index]; // Save cell - need to keep contents
538 data.row.deleteCell(data.index);
539 }
540);
541
542
Note: See TracBrowser for help on using the repository browser.