Dispose of Actor objects properly.
[armadillo.git] / web_frontend / actor.js
1 //
2 // Armadillo File Manager
3 // Copyright (c) 2010, Robert Sesek <http://www.bluestatic.org>
4 //
5 // This program is free software: you can redistribute it and/or modify it under
6 // the terms of the GNU General Public License as published by the Free Software
7 // Foundation, either version 3 of the License, or any later version.
8 //
9
10 goog.provide('armadillo.Actor');
11
12 goog.require('armadillo.PathControl');
13 goog.require('goog.array');
14 goog.require('goog.dom');
15 goog.require('goog.events');
16 goog.require('goog.positioning.ClientPosition');
17 goog.require('goog.positioning.Corner');
18 goog.require('goog.style');
19 goog.require('goog.ui.Dialog');
20 goog.require('goog.ui.Popup');
21
22 /**
23 * The Actor is a popup that displays the various actions that can be performed
24 * on a given File.
25 * @param {armadillo.File} file The file to act on.
26 * @constructor
27 */
28 armadillo.Actor = function(file) {
29 goog.Disposable.call(this);
30
31 /**
32 * The file object on which this acts.
33 * @type {armadillo.File}
34 */
35 this.file_ = file;
36
37 /**
38 * The HTML element that draws the actor buttons
39 * @type {Element}
40 */
41 this.element_ = this.createElement_();
42
43 /**
44 * The Container that holds the display element.
45 * @type {goog.ui.Popup}
46 */
47 this.popup_ = new goog.ui.Popup(this.element_);
48 goog.events.listenOnce(this.popup_, goog.ui.PopupBase.EventType.HIDE,
49 this.onPopupClosed_, false, this);
50
51 /**
52 * The UI element used for a specific action.
53 * @type {goog.Disposable}
54 */
55 this.actionObject_ = null;
56
57 armadillo.Actor.actors_.push(this);
58 }
59 goog.inherits(armadillo.Actor, goog.Disposable);
60
61 /**
62 * An array of all the Actors that have been created.
63 */
64 armadillo.Actor.actors_ = new Array();
65
66 /**
67 * The different options that the Actor can perform.
68 */
69 armadillo.Actor.options_ = {
70 OPEN : 'open',
71 MOVE : 'move',
72 RENAME : 'rename',
73 DELETE : 'delete'
74 };
75
76 /**
77 * String values for the options.
78 */
79 armadillo.Actor.optionStrings_ = {
80 'open' : 'Open',
81 'move' : 'Move',
82 'rename' : 'Rename',
83 'delete' : 'Delete'
84 };
85
86 /**
87 * A global property that should be checked to see if an actor is present,
88 * creating a modal session.
89 */
90 armadillo.Actor.isModal = function() {
91 var isVisible = false;
92 goog.array.forEach(armadillo.Actor.actors_, function (e) {
93 isVisible |= e.popup_.isVisible();
94 });
95 return isVisible;
96 };
97
98 /**
99 * Disposer
100 * @protected
101 */
102 armadillo.Actor.prototype.disposeInternal = function() {
103 armadillo.Actor.superClass_.disposeInternal.call(this);
104
105 // Unlisten the tiles.
106 var tiles = goog.dom.getElementsByClass('tile', this.element_);
107 goog.array.forEach(tiles, function (tile) {
108 goog.events.unlistenByKey(tile.actorListener);
109 });
110
111 // Remove the actor display element.
112 goog.dom.removeNode(this.element_);
113 this.element_ = null;
114
115 // Kill the popup.
116 this.popup_.dispose();
117 this.popup_ = null;
118
119 if (this.actionObject_) {
120 this.actionObject_.dispose();
121 this.actionObject_ = null;
122 }
123
124 // Remove the actor from the list.
125 goog.array.remove(armadillo.Actor.actors_, this);
126
127 this.file_ = null;
128 };
129
130 /**
131 * Shows the popup.
132 * @param {int} x The X position to show at
133 * @param {int} y The Y position to show at
134 */
135 armadillo.Actor.prototype.show = function(x, y) {
136 if (armadillo.Actor.isModal())
137 return;
138 var firstBodyElement = goog.dom.getFirstElementChild(document.body);
139 goog.dom.insertSiblingBefore(this.element_, firstBodyElement);
140 this.popup_.setPinnedCorner(goog.positioning.Corner.TOP_LEFT);
141 this.popup_.setPosition(new goog.positioning.ClientPosition(x, y));
142 this.popup_.setHideOnEscape(true);
143 this.popup_.setVisible(true);
144 };
145
146 /**
147 * Hides the popup.
148 */
149 armadillo.Actor.prototype.hide = function() {
150 this.popup_.setVisible(false);
151 };
152
153 /**
154 * Creates the DOM Element that is inserted into the popup.
155 * @returns Element
156 */
157 armadillo.Actor.prototype.createElement_ = function() {
158 var root = goog.dom.createDom('div', 'actor');
159 for (var option in armadillo.Actor.options_) {
160 var tile = goog.dom.createDom('div', 'tile');
161 var value = armadillo.Actor.options_[option];
162 // Cannot open non-directory files.
163 if (value == armadillo.Actor.options_.OPEN && !this.file_.isDirectory()) {
164 continue;
165 }
166 var title = goog.dom.createDom('span', 'title',
167 armadillo.Actor.optionStrings_[value]);
168 goog.dom.appendChild(tile, title);
169 goog.dom.appendChild(root, tile);
170 tile.actorOption = value;
171 tile.actorListener = goog.events.listen(tile, goog.events.EventType.CLICK,
172 this.tileClickHandler_, false, this);
173 }
174 return root;
175 };
176
177 /**
178 * Click handler for individual tiles.
179 * @param {Event} e
180 */
181 armadillo.Actor.prototype.tileClickHandler_ = function(e) {
182 var option = e.target.actorOption;
183 if (option == armadillo.Actor.options_.OPEN) {
184 // TODO: assert that this.file_.isDirectory().
185 app.navigate(this.file_.getName());
186 } else if (option == armadillo.Actor.options_.MOVE ||
187 option == armadillo.Actor.options_.RENAME) {
188 this.performMove_();
189 } else if (option == armadillo.Actor.options_.DELETE) {
190 this.performDelete_();
191 }
192 this.hide();
193 };
194
195 /**
196 * Subroutine to handle bringing up the move confirmation UI.
197 * @private
198 */
199 armadillo.Actor.prototype.performMove_ = function() {
200 this.actionObject_ = this.createActionDialog_();
201 this.actionObject_.setTitle('Move File');
202
203 var editor = new armadillo.PathControl(this.file_.getFullPath(), true);
204 this.actionObject_.addChild(editor, true);
205
206 var closeCallback = function(e) {
207 if (e.key != goog.ui.Dialog.DefaultButtonKeys.CANCEL) {
208 var newPath = editor.getPath();
209 this.file_.move(newPath);
210 }
211 this.dispose();
212 };
213 // Will be removed when the event source closes.
214 goog.events.listen(this.actionObject_, goog.ui.Dialog.SELECT_EVENT,
215 closeCallback, false, this);
216
217 this.actionObject_.setVisible(true);
218 var position = goog.style.getPosition(this.actionObject_.getElement());
219 goog.style.setPosition(this.actionObject_.getElement(), position.x, '10%');
220 };
221
222 /**
223 * Subroutine to handle bringing up the delete confirmation UI.
224 * @private
225 */
226 armadillo.Actor.prototype.performDelete_ = function() {
227 this.actionObject_ = this.createActionDialog_();
228 this.actionObject_.setTitle('Confirm Delete');
229
230 var container = this.actionObject_.getContentElement();
231 var content = goog.dom.createDom('span', null,
232 'Are you sure you want to delete:',
233 goog.dom.createElement('br'),
234 goog.dom.createDom('strong', null, this.file_.getName()));
235 goog.dom.appendChild(container, content);
236
237 var closeCallback = function(e) {
238 if (e.key != goog.ui.Dialog.DefaultButtonKeys.CANCEL) {
239 this.file_.remove();
240 }
241 this.dispose();
242 };
243 // Will be removed when the event source closes.
244 goog.events.listen(this.actionObject_, goog.ui.Dialog.SELECT_EVENT,
245 closeCallback, false, this);
246
247 this.actionObject_.setVisible(true);
248 };
249
250 /**
251 * Creates a new instance of a Dialog that has some basic properties set that
252 * are common to performing actions.
253 * @private
254 */
255 armadillo.Actor.prototype.createActionDialog_ = function() {
256 var confirm = new goog.ui.Dialog();
257 confirm.setDisposeOnHide(true);
258 confirm.setEscapeToCancel(true);
259 confirm.setModal(true);
260 confirm.setDraggable(false);
261 confirm.setHasTitleCloseButton(false);
262 return confirm;
263 };
264
265 /**
266 * Event handler for when this.popup_ closes.
267 * @param {Event} e
268 */
269 armadillo.Actor.prototype.onPopupClosed_ = function(e) {
270 // If an action is not being performed, then dispose the Actor. Otherwise,
271 // this will get cleaned up after the actionObject_ closes.
272 if (!this.actionObject_) {
273 this.dispose();
274 }
275 };