1 /**
  2  * @fileOverview xq.controls provides common UI elements such as dialog.
  3  */
  4 xq.controls = {};
  5 
  6 
  7 
  8 xq.controls.FormDialog = xq.Class({
  9 	/**
 10      * @constructor
 11      *
 12      * @param {String} html HTML string which contains FORM
 13      * @param {Function} [onLoadHandler] callback function to be called when the form is loaded
 14 	 */
 15 	initialize: function(xed, html, onLoadHandler, onCloseHandler) {
 16 		xq.addToFinalizeQueue(this);
 17 
 18 		this.xed = xed;
 19 		this.html = html;
 20 		this.onLoadHandler = onLoadHandler || function() {};
 21 		this.onCloseHandler = onCloseHandler || function() {};
 22 		this.form = null;
 23 	},
 24 	/**
 25 	 * Show dialog
 26 	 *
 27 	 * @param {Object} [options] collection of options
 28 	 */
 29 	show: function(options) {
 30 		options = options || {};
 31 		options.position = options.position || 'centerOfWindow';
 32 		options.mode = options.mode || 'modal';
 33 		options.cancelOnEsc = options.cancelOnEsc || true;
 34 		
 35 		var self = this;
 36 		
 37 		// create and append container
 38 		var container = document.createElement('DIV');
 39 		container.style.display = 'none';
 40 		document.body.appendChild(container);
 41 		
 42 		// initialize form
 43 		container.innerHTML = this.html;
 44 		this.form = container.getElementsByTagName('FORM')[0];
 45 		
 46 		this.form.onsubmit = function() {
 47 			self.onCloseHandler(xq.serializeForm(this));
 48 			self.close();
 49 			return false;
 50 		};
 51 		
 52 		var cancelButton = xq.getElementsByClassName(this.form, 'cancel')[0];
 53 		cancelButton.onclick = function() {
 54 			self.onCloseHandler();
 55 			self.close();
 56 		};
 57 		
 58 		// append dialog
 59 		document.body.appendChild(this.form);
 60 		container.parentNode.removeChild(container);
 61 		
 62 		// place dialog to center of window
 63 		this.setPosition(options.position);
 64 		
 65 		// give focus
 66 		var elementToFocus = xq.getElementsByClassName(this.form, 'initialFocus');
 67 		if(elementToFocus.length > 0) elementToFocus[0].focus();
 68 		
 69 		// handle cancelOnEsc option
 70 		if(options.cancelOnEsc) {
 71 			xq.observe(this.form, 'keydown', function(e) {
 72 				if(e.keyCode == 27) {
 73 					this.onCloseHandler();
 74 					this.close();
 75 				}
 76 				return false;
 77 			}.bind(this));
 78 		}
 79 		
 80 		this.onLoadHandler(this);
 81 	},
 82 	
 83 	close: function() {
 84 		this.form.parentNode.removeChild(this.form);
 85 	},
 86 	
 87 	setPosition: function(target) {
 88 		var targetElement = null;
 89 		var left = 0;
 90 		var top = 0;
 91 		
 92 		if(target == 'centerOfWindow') {
 93 			targetElement = document.documentElement;
 94 		} else if(target == 'centerOfEditor') {
 95 			targetElement = this.xed.getFrame();
 96 			var o = targetElement;
 97 			do {
 98 				left += o.offsetLeft;
 99 				top += o.offsetTop;
100 			} while(o = o.offsetParent)
101 		} else if(target == 'nearbyCaret') {
102 			throw "Not implemented yet";
103 		} else {
104 			throw "Invalid argument: " + target;
105 		}
106 		
107 		var targetWidth = targetElement.clientWidth;
108 		var targetHeight = targetElement.clientHeight;
109 		var dialogWidth = this.form.clientWidth;
110 		var dialogHeight = this.form.clientHeight;
111 		
112 		left += parseInt((targetWidth - dialogWidth) / 2);
113 		top += parseInt((targetHeight - dialogHeight) / 2);
114 		
115 		this.form.style.left = left + "px";
116 		this.form.style.top = top + "px";
117 	}
118 })
119 
120 
121 
122 xq.controls.QuickSearchDialog = xq.Class({
123 	/**
124      * @constructor
125 	 */
126 	initialize: function(xed, param) {
127 		xq.addToFinalizeQueue(this);
128 		this.xed = xed;
129 		
130 		this.rdom = xq.RichDom.createInstance();
131 		this.rdom.setRoot(document.body);
132 		
133 		this.param = param;
134 		if(!this.param.renderItem) this.param.renderItem = function(item) {
135 			return this.rdom.getInnerText(item);
136 		}.bind(this);
137 		
138 		this.container = null;
139 	},
140 	
141 	getQuery: function() {
142 		if(!this.container) return "";
143 		return this._getInputField().value;
144 	},
145 	
146 	onSubmit: function(e) {
147 		if(this.matchCount() > 0) {
148 			this.param.onSelect(this.xed, this.list[this._getSelectedIndex()]);
149 		}
150 		
151 		this.close();
152 		xq.stopEvent(e);
153 		return false;
154 	},
155 	
156 	onCancel: function(e) {
157 		if(this.param.onCancel) this.param.onCancel(this.xed);
158 		this.close();
159 	},
160 	
161 	onBlur: function(e) {
162 		// TODO: Ugly
163 		setTimeout(function() {this.onCancel(e)}.bind(this), 400);
164 	},
165 	
166 	onKey: function(e) {
167 		var esc = new xq.Shortcut("ESC");
168 		var enter = new xq.Shortcut("ENTER");
169 		var up = new xq.Shortcut("UP");
170 		var down = new xq.Shortcut("DOWN");
171 		
172 		if(esc.matches(e)) {
173 			this.onCancel(e);
174 		} else if(enter.matches(e)) {
175 			this.onSubmit(e);
176 		} else if(up.matches(e)) {
177 			this._moveSelectionUp();
178 		} else if(down.matches(e)) {
179 			this._moveSelectionDown();
180 		} else {
181 			this.updateList();
182 		}
183 	},
184 	
185 	onClick: function(e) {
186 		var target = e.srcElement || e.target;
187 		if(target.nodeName == "LI") {
188 			
189 			var index = this._getIndexOfLI(target);
190 			this.param.onSelect(this.xed, this.list[index]);
191 		}
192 	},
193 	
194 	onList: function(list) {
195 		this.list = list;
196 		this.renderList(list);
197 	},
198 	
199 	updateList: function() {
200 		window.setTimeout(function() {
201 			this.param.listProvider(this.getQuery(), this.xed, this.onList.bind(this));
202 		}.bind(this), 0);
203 	},
204 	
205 	renderList: function(list) 
206 	{
207 		var ol = this._getListContainer();
208 		ol.innerHTML = "";
209 		
210 		for(var i = 0; i < list.length; i++) {
211 			var li = this.rdom.createElement('LI');
212 			li.innerHTML = this.param.renderItem(list[i]);
213 			ol.appendChild(li);
214 		}
215 		
216 		if(ol.hasChildNodes()) {
217 			ol.firstChild.className = "selected";
218 		}
219 	},
220 	
221 	show: function() {
222 		if(!this.container) this.container = this._create();
223 		
224 		var dialog = this.rdom.insertNodeAt(this.container, this.rdom.getRoot(), "end");
225 		this.setPosition('centerOfEditor');
226 		this.updateList();
227 		this.focus();
228 	},
229 	
230 	close: function() {
231 		this.rdom.deleteNode(this.container);
232 	},
233 	
234 	focus: function() {
235 		this._getInputField().focus();
236 	},
237 	
238 	setPosition: function(target) {
239 		var targetElement = null;
240 		var left = 0;
241 		var top = 0;
242 		
243 		if(target == 'centerOfWindow') {
244 			targetElement = document.documentElement;
245 		} else if(target == 'centerOfEditor') {
246 			targetElement = this.xed.getFrame();
247 			var o = targetElement;
248 			do {
249 				left += o.offsetLeft;
250 				top += o.offsetTop;
251 			} while(o = o.offsetParent)
252 		} else if(target == 'nearbyCaret') {
253 			throw "Not implemented yet";
254 		} else {
255 			throw "Invalid argument: " + target;
256 		}
257 		
258 		var targetWidth = targetElement.clientWidth;
259 		var targetHeight = targetElement.clientHeight;
260 		var dialogWidth = this.container.clientWidth;
261 		var dialogHeight = this.container.clientHeight;
262 		
263 		left += parseInt((targetWidth - dialogWidth) / 2);
264 		top += parseInt((targetHeight - dialogHeight) / 2);
265 		
266 		this.container.style.left = left + "px";
267 		this.container.style.top = top + "px";
268 	},
269 	
270 	matchCount: function() {
271 		return this.list ? this.list.length : 0;
272 	},
273 	
274 	_create: function() {
275 		// make container
276 		var container = this.rdom.createElement("DIV");
277 		container.className = "xqQuickSearch";
278 		
279 		// make title
280 		if(this.param.title) {
281 			var title = this.rdom.createElement("H1");
282 			title.innerHTML = this.param.title;
283 			container.appendChild(title);
284 		}
285 		
286 		// make input field
287 		var inputWrapper = this.rdom.createElement("DIV");
288 		inputWrapper.className = "input";
289 		var form = this.rdom.createElement("FORM");
290 		var input = this.rdom.createElement("INPUT");
291 		input.type = "text";
292 		input.value = "";
293     	form.appendChild(input);
294 		inputWrapper.appendChild(form);
295 		container.appendChild(inputWrapper);
296 		
297 		// make list
298 		var list = this.rdom.createElement("OL");
299 
300 	    xq.observe(input, 'blur', this.onBlur.bindAsEventListener(this));
301     	xq.observe(input, 'keypress', this.onKey.bindAsEventListener(this));
302     	xq.observe(list, 'click', this.onClick.bindAsEventListener(this), true);
303     	xq.observe(form, 'submit', this.onSubmit.bindAsEventListener(this));
304     	xq.observe(form, 'reset', this.onCancel.bindAsEventListener(this));
305 
306 		container.appendChild(list);
307 		return container;
308 	},
309 	
310 	_getInputField: function() {
311 		return this.container.getElementsByTagName('INPUT')[0];
312 	},
313 	
314 	_getListContainer: function() {
315 		return this.container.getElementsByTagName('OL')[0];
316 	},
317 	
318 	_getSelectedIndex: function() {
319 		var ol = this._getListContainer();
320 		for(var i = 0; i < ol.childNodes.length; i++) {
321 			if(ol.childNodes[i].className == 'selected') return i;
322 		}
323 	},
324 	
325 	_getIndexOfLI: function(li) {
326 		var ol = this._getListContainer();
327 		for(var i = 0; i < ol.childNodes.length; i++) {
328 			if(ol.childNodes[i] == li) return i;
329 		}
330 	},
331 	
332 	_moveSelectionUp: function() {
333 		var count = this.matchCount();
334 		if(count == 0) return;
335 		var index = this._getSelectedIndex();
336 		var ol = this._getListContainer();
337 		ol.childNodes[index].className = "";
338 		
339 		index--;
340 		if(index < 0) index = count - 1;
341 
342 		ol.childNodes[index].className = "selected";
343 	},
344 	
345 	_moveSelectionDown: function() {
346 		var count = this.matchCount();
347 		if(count == 0) return;
348 		var index = this._getSelectedIndex();
349 		var ol = this._getListContainer();
350 		ol.childNodes[index].className = "";
351 		
352 		index++;
353 		if(index >= count) index = 0;
354 		
355 		ol.childNodes[index].className = "selected";
356 	}
357 });