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