/* Command, Composite and MenuObject interfaces. */
var Command = new Interface('Command', ['execute']);
var Composite = new Interface('Composite', ['add', 'remove', 'getChild',
'getElement']);
var MenuObject = new Interface('MenuObject', ['show']);
/* MenuBar class, a composite. */
var MenuBar = function() { // implements Composite, MenuObject
this.menus = {};
this.element = document.createElement('ul');
this.element.style.display = 'none';
};
MenuBar.prototype = {
add: function(menuObject) {
Interface.ensureImplements(menuObject, Composite, MenuObject);
this.menus[menuObject.name] = menuObject;
this.element.appendChild(this.menus[menuObject.name].getElement());
},
remove: function(name) {
delete this.menus[name];
},
getChild: function(name) {
return this.menus[name];
},
getElement: function() {
return this.element;
},
show: function() {
this.element.style.display = 'block';
for(name in this.menus) { // Pass the call down the composite.
this.menus[name].show();
}
}
};
/* Menu class, a composite. */
var Menu = function(name) { // implements Composite, MenuObject
this.name = name;
this.items = {};
this.element = document.createElement('li');
this.element.innerHTML = this.name;
this.element.style.display = 'none';
this.container = document.createElement('ul');
this.element.appendChild(this.container);
};
Menu.prototype = {
add: function(menuItemObject) {
Interface.ensureImplements(menuItemObject, Composite, MenuObject);
this.items[menuItemObject.name] = menuItemObject;
this.container.appendChild(this.items[menuItemObject.name].getElement());
},
remove: function(name) {
delete this.items[name];
},
getChild: function(name) {
return this.items[name];
},
getElement: function() {
return this.element;
},
show: function() {
this.element.style.display = 'block';
for(name in this.items) { // Pass the call down the composite.
this.items[name].show();
}
}
};
/* MenuItem class, a leaf. */
var MenuItem = function(name, command) { // implements Composite, MenuObject
Interface.ensureImplements(command, Command);
this.name = name;
this.element = document.createElement('li');
this.element.style.display = 'none';
this.anchor = document.createElement('a');
this.anchor.href = '#'; // To make it clickable.
this.element.appendChild(this.anchor);
this.anchor.innerHTML = this.name;
addEvent(this.anchor, 'click', function(e) { // Invoke the command on click.
e.preventDefault();
command.execute();
});
};
MenuItem.prototype = {
add: function() {},
remove: function() {},
getChild: function() {},
getElement: function() {
return this.element;
},
show: function() {
this.element.style.display = 'block';
}
};
/* MenuCommand class, a command object. */
var MenuCommand = function(action) { // implements Command
this.action = action;
};
MenuCommand.prototype.execute = function() {
this.action();
};
/* Implementation code. */
/* Receiver objects, instantiated from existing classes. */
var fileActions = new FileActions();
var editActions = new EditActions();
var insertActions = new InsertActions();
var helpActions = new HelpActions();
/* Create the menu bar. */
var appMenuBar = new MenuBar();
/* The File menu. */
var fileMenu = new Menu('File');
var openCommand = new MenuCommand(fileActions.open);
var closeCommand = new MenuCommand(fileActions.close);
var saveCommand = new MenuCommand(fileActions.save);
var saveAsCommand = new MenuCommand(fileActions.saveAs);
fileMenu.add(new MenuItem('Open', openCommand));
fileMenu.add(new MenuItem('Close', closeCommand));
fileMenu.add(new MenuItem('Save', saveCommand));
fileMenu.add(new MenuItem('Save As...', saveAsCommand));
appMenuBar.add(fileMenu);
/* The Edit menu. */
var editMenu = new Menu('Edit');
var cutCommand = new MenuCommand(editActions.cut);
var copyCommand = new MenuCommand(editActions.copy);
var pasteCommand = new MenuCommand(editActions.paste);
var deleteCommand = new MenuCommand(editActions.delete);
editMenu.add(new MenuItem('Cut', cutCommand));
editMenu.add(new MenuItem('Copy', copyCommand));
editMenu.add(new MenuItem('Paste', pasteCommand));
editMenu.add(new MenuItem('Delete', deleteCommand));
appMenuBar.add(editMenu);
/* The Insert menu. */
var insertMenu = new Menu('Insert');
var textBlockCommand = new MenuCommand(insertActions.textBlock);
insertMenu.add(new MenuItem('Text Block', textBlockCommand));
appMenuBar.add(insertMenu);
/* The Help menu. */
var helpMenu = new Menu('Help');
var showHelpCommand = new MenuCommand(helpActions.showHelp);
helpMenu.add(new MenuItem('Show Help', showHelpCommand));
appMenuBar.add(helpMenu);
/* Build the menu bar. */
document.getElementsByTagName('body')[0].appendChild(appMenuBar.getElement());
appMenuBar.show();
/* Adding more menu items later on. */
var imageCommand = new MenuCommand(insertActions.image);
insertMenu.add(new MenuItem('Image', imageCommand));
======================================================
带有undo功能的command模式:
/* ReversibleCommand interface. */
var ReversibleCommand = new Interface('ReversibleCommand', ['execute', 'undo']);
/* Movement commands. */
var MoveUp = function(cursor) { // implements ReversibleCommand
this.cursor = cursor;
};
MoveUp.prototype = {
execute: function() {
cursor.move(0, -10);
},
undo: function() {
cursor.move(0, 10);
}
};
var MoveDown = function(cursor) { // implements ReversibleCommand
this.cursor = cursor;
};
MoveDown.prototype = {
execute: function() {
cursor.move(0, 10);
},
undo: function() {
cursor.move(0, -10);
}
};
var MoveLeft = function(cursor) { // implements ReversibleCommand
this.cursor = cursor;
};
MoveLeft.prototype = {
execute: function() {
cursor.move(-10, 0);
},
undo: function() {
cursor.move(10, 0);
}
};
var MoveRight = function(cursor) { // implements ReversibleCommand
this.cursor = cursor;
};
MoveRight.prototype = {
execute: function() {
cursor.move(10, 0);
},
undo: function() {
cursor.move(-10, 0);
}
};
/* Cursor class. */
var Cursor = function(width, height, parent) {
this.width = width;
this.height = height;
this.position = { x: width / 2, y: height / 2 };
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
parent.appendChild(this.canvas);
this.ctx = this.canvas.getContext('2d');
this.ctx.fillStyle = '#cc0000';
this.move(0, 0);
};
Cursor.prototype.move = function(x, y) {
this.position.x += x;
this.position.y += y;
this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.fillRect(this.position.x, this.position.y, 3, 3);
};
/* UndoDecorator class. */
var UndoDecorator = function(command, undoStack) { // implements ReversibleCommand
this.command = command;
this.undoStack = undoStack;
};
UndoDecorator.prototype = {
execute: function() {
this.undoStack.push(this.command);
this.command.execute();
},
undo: function() {
this.command.undo();
}
};
/* CommandButton class. */
var CommandButton = function(label, command, parent) {
Interface.ensureImplements(command, ReversibleCommand);
this.element = document.createElement('button');
this.element.innerHTML = label;
parent.appendChild(this.element);
addEvent(this.element, 'click', function() {
command.execute();
});
};
/* UndoButton class. */
var UndoButton = function(label, parent, undoStack) {
this.element = document.createElement('button');
this.element.innerHTML = label;
parent.appendChild(this.element);
addEvent(this.element, 'click', function() {
if(undoStack.length === 0) return;
var lastCommand = undoStack.pop();
lastCommand.undo();
});
};
/* Implementation code. */
var body = document.getElementsByTagName('body')[0];
var cursor = new Cursor(400, 400, body);
var undoStack = [];
var upCommand = new UndoDecorator(new MoveUp(cursor), undoStack);
var downCommand = new UndoDecorator(new MoveDown(cursor), undoStack);
var leftCommand = new UndoDecorator(new MoveLeft(cursor), undoStack);
var rightCommand = new UndoDecorator(new MoveRight(cursor), undoStack);
var upButton = new CommandButton('Up', upCommand, body);
var downButton = new CommandButton('Down', downCommand, body);
var leftButton = new CommandButton('Left', leftCommand, body);
var rightButton = new CommandButton('Right', rightCommand, body);
var undoButton = new UndoButton('Undo', body, undoStack);