1 //require <xataface/Permissions.js>
  2 
  3 /*-------------------------------------------------------------------------------
  4  * Xataface Web Application Framework
  5  * Copyright (C) 2005-2009 Web Lite Solutions Corp (shannah@sfu.ca)
  6  * 
  7  * This program is free software; you can redistribute it and/or
  8  * modify it under the terms of the GNU General Public License
  9  * as published by the Free Software Foundation; either version 2
 10  * of the License, or (at your option) any later version.
 11  * 
 12  * This program is distributed in the hope that it will be useful,
 13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU General Public License for more details.
 16  * 
 17  * You should have received a copy of the GNU General Public License
 18  * along with this program; if not, write to the Free Software
 19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 20  *-------------------------------------------------------------------------------
 21  */
 22 /**
 23  * 
 24  * This jquery plugin allows you to convert any HTML element or field into
 25  * a record browser.  Clicking the browser button will open a modal dialog
 26  * that allows the user to search and browse through a number of records
 27  * in a table.
 28  *
 29  * Example usage: 
 30  *
 31  * <a href="#" id="selector">Click me to find stuff</a>
 32  * ...
 33  *
 34  * $('#selector').RecordBrowser({
 35  *		table: 'people',
 36  *		filters: {
 37  *			group_id: 10
 38  *		},
 39  *		callback: function(values){
 40  *			// values is an object with the records that the user 
 41  *			// selected
 42  *			for ( var id in values ){
 43  *				// id is the id of the record
 44  *				// values[id] is the title of the record
 45  *			}
 46  *		}
 47  *	});
 48  *
 49  * Alternatively you could use the RecordBrowserWidget function to convert a text field
 50  * into a RecordBrowser:
 51  *
 52  * <input type="text" id="textfield"/>
 53  * ...
 54  * $('#textfield').RecordBrowserWidget({
 55  *		table: 'people',
 56  *		filters: {
 57  *			group_id: 10
 58  *		},
 59  *		callback: function(values){
 60  *			// values is an object with the records that the user 
 61  *			// selected
 62  *			for ( var id in values ){
 63  *				// id is the id of the record
 64  *				// values[id] is the title of the record
 65  *			}
 66  *		}
 67  *	});
 68  * 
 69  */
 70 (function ($){
 71 	
 72 	if ( typeof(console) == 'undefined' ) console = {'log': function(){}};
 73 	if ( typeof(console.log) == 'undefined' ) console.log = function(){};
 74 	
 75 	var xataface = XataJax.load('xataface');
 76 	
 77 	
 78 	/**
 79 	 * @name Callback
 80 	 * @memberOf xataface.RecordBrowser
 81 	 * @function
 82 	 * @description A callback function that can registered as the callback for a RecordBrowser
 83 	 * object to handle the case where a user has selected one or more records in a RecordBrowser
 84 	 * dialog.
 85 	 *
 86 	 * @param {Object} o The key-value pairs of selected  records.  The keys are the "values" of the 
 87 	 * select list, and the values are the corresponding "texts" of the select list.
 88 	 *
 89 	 * @returns {void}
 90 	 *
 91 	 * @example
 92 	 * //require <RecordBrowser/RecordBrowser.js>
 93 	 * var RecordBrowser = XataJax.load('xataface.RecordBrowser');
 94 	 * 
 95 	 * // Now let's create a dialog to select records from the people table:
 96 	 * var dlg = new RecordBrowser({
 97 	 *     table: 'people',
 98 	 *	
 99 	 *     // A callback function to be called when the clicks "Select"
100 	 *     callback: function(selected){
101 	 *         var selectedString = [];
102 	 *         $(selected).each(function(id,title){
103 	 *             selectedString.push(id+' = '+title);
104 	 *         });
105 	 *         selectedString = selectedString.join('\n');
106 	 *         alert("The following key/value pairs were selected: '+selectedString);
107 	 *     }
108 	 * });
109 	 * dlg.display(); // Display the dialog
110 	 *
111 	 */
112 	
113 	
114 	/**
115 	 * @name RecordBrowser
116 	 * @memberOf xataface
117 	 * @class
118 	 * @description A dialog to select a record from a found set in a table.  This dialog
119 	 * is used in may parts of Xataface including the lookup and select widgets.
120 	 *
121 	 * <p>Screenshot of the RecordBrowser dialog as used inside the lookup widget:</p>
122 	 * <p><img src="/assets/images/photos/Picture%2023.png"/></p>
123 	 *
124 	 * @param {Object} o Initialization parameters.
125 	 * @param {String} o.table The name of the table from which to browse records.
126 	 * @param {String} o.value The name of the column to use as the value in the option list.  If this
127 	 * 		is left null, then the primary key will be used as the value.
128 	 * @param {String} o.text The name of the column to use as the title in the option list.  If this
129 	 * 		is left null, then the record's title (i.e. the result of $record->getTitle()) will be used.
130 	 * @param {Object} o.filters Optional key-value pairs to be used to filter the results.  This is 
131 	 *		handy if you only want the dialog to show certain records from the table.
132 	 * @param {xataface.RecordBrowser.Callback} o.callback A callback function that will be called after
133 	 * 		the user clicks "select" to close the dialog.  An object with the key/value pairs of selected
134 	 *		records will be passed to this callback function.
135 	 *
136 	 * 
137 	 *  @example
138 	 * <h2>Basic Example</h2>
139 	 * //require <RecordBrowser/RecordBrowser.js>
140 	 * var RecordBrowser = XataJax.load('xataface.RecordBrowser');
141 	 * 
142 	 * // Now let's create a dialog to select records from the people table:
143 	 * var dlg = new RecordBrowser({
144 	 *     table: 'people',
145 	 *	
146 	 *     // A callback function to be called when the clicks "Select"
147 	 *     callback: function(selected){
148 	 *         var selectedString = [];
149 	 *         $(selected).each(function(id,title){
150 	 *             selectedString.push(id+' = '+title);
151 	 *         });
152 	 *         selectedString = selectedString.join('\n');
153 	 *         alert("The following key/value pairs were selected: '+selectedString);
154 	 *     }
155 	 * });
156 	 * dlg.display(); // Display the dialog
157 	 * 
158 	 * @example 
159 	 * <h2>Example with Filters</h2>
160 	 * <p>The following example only includes a list of people in Canada over the age of 18</p>
161 	 * 
162 	 * var dlg = new RecordBrowser({
163 	 *     table: 'people',
164 	 *     filters: {
165 	 *        country: '=Canada',
166 	 *        age: '>18'
167 	 *     },
168 	 *     callback: function(selected){
169 	 *         // ... do stuff with selected values
170 	 *     }
171 	 * });
172 	 * dlg.display();
173 	 *
174 	 */
175 	xataface.RecordBrowser = function(o){
176 		/**
177 		 * @name title
178 		 * @memberOf xataface.RecordBrowser#
179 		 * @description The name of the table to browse for records in.
180 		 * @var string
181 		 */
182 		this.table = null;
183 		
184 		/**
185 		 * @name value
186 		 * @memberOf xataface.RecordBrowser#
187 		 * @description 
188 		 * The name of the column to use as the value in the option list.
189 		 * Set this value to __id__ to use the record id.
190 		 * If this value is blank, then the primary key is used so long as
191 		 * the primary key only has a single column.  If it is a compound
192 		 * primary key, then the record id is used by default.
193 		 * @var string
194 		 */
195 		this.value = null;
196 		
197 		/**
198 		 * @name text
199 		 * @memberOf xataface.RecordBrowser#
200 		 * @description
201 		 * The name of the column to use as the title in the option list.
202 		 * Set this value to __title__ to use the record title ( or leave blank).
203 		 *
204 		 * @var string
205 		 */
206 		this.text = null;
207 		
208 		/**
209 		 * @name filters
210 		 * @memberOf xataface.RecordBrowser#
211 		 * @description
212 		 * Search filters to add to the query.
213 		 * @var object
214 		 */
215 		this.filters = {};
216 		
217 		/**
218 		 * @name callback
219 		 * @memberOf xataface.RecordBrowser#
220 		 * @description
221 		 * Callback function to be called with the selected values.
222 		 * 
223 		 * function(values){}
224 		 * @var function
225 		 */
226 		this.callback = null;
227 		
228 		/**
229 		 * @name el
230 		 * @memberOf xataface.RecordBrowser#
231 		 * @description The document element that is used to display the dialog.
232 		 * @var HTMLDOMElement
233 		 */
234 		this.el = document.createElement('div');
235 		
236 		/**
237 		 * @name baseURL
238 		 * @memberOf xataface.RecordBrowser#
239 		 * @description
240 		 * The base url to the RecordBrowser directory.
241 		 * @var string
242 		 */
243 		this.baseURL = DATAFACE_URL+'/js/RecordBrowser';
244 		
245 		for ( var i in o ){
246 			this[i] = o[i];
247 		}
248 		
249 		/**
250 		 * A flag to indicate whether the record select list
251 		 * needs to be updated when updateRecords() is called.
252 		 * The list would need to be updated if the filter parameters change.
253 		 * @var boolean
254 		 */
255 		this.dirty = true;
256 		//$('head').append('<link rel="stylesheet" type="text/css" href="'+DATAFACE_URL+'/css/smoothness/jquery-ui-1.7.2.custom.css"/>');
257 		
258 		
259 		
260 		
261 	}
262 	
263 	xataface.RecordBrowser.prototype = {
264 	
265 		/**
266 		 * @name display
267 		 * @memberOf xataface.RecordBrowser#
268 		 * @function
269 		 * @description Displays the record browser dialog.
270 		 * @returns {void}
271 		 */
272 		display : function(){
273 			var rb = this;
274 			$('body').append(this.el);
275 			$(this.el).load(this.baseURL+'/templates/RecordBrowser.html', function(){
276 				var dialog = this;
277 				var searchChangeHandler = function(){
278 					rb.filterRecords({
279 						'-search' : $(this).val()
280 					});
281 				};
282 				$(this).find('.xf-RecordBrowser-search-field')
283 					.keyup(searchChangeHandler)
284 					.change(searchChangeHandler);
285 					//.blur(searchChangeHandler);
286 				//$(this).find('.xf-RecordBrowser-select').css('height', '90%');
287 				$(this).find('.xf-RecordBrowser-select-field')
288 					.css('width', '100%')
289 					.attr('size', 8);
290 					
291 				$(this).find('.xf-RecordBrowser-addnew-button').RecordDialog({
292 					table: rb.table,
293 					callback: function(){
294 						rb.dirty=true;
295 						rb.updateRecords();
296 					}
297 				});
298 				
299 				$(this).dialog({
300 					'title': 'Select Record',
301 					'buttons' : {
302 						'Select' : function(){
303 							var out = {};
304 							$(dialog).find('.xf-RecordBrowser-select-field :selected').each(function(i, selected){
305 								out[$(selected).attr('value')] = $(selected).text();
306 							});
307 								
308 							if ( rb.callback ) rb.callback(out);
309 							$(this).dialog("close");
310 						
311 						},
312 						'Cancel' : function(){
313 							$(this).dialog("close");
314 						
315 						}
316 						
317 					},
318 					'position': 'center',
319 					'modal' : true,
320 					'resize': function(event, ui){
321 						$(dialog).find('.xf-RecordBrowser-select-field').css('height', ($(dialog).height()-60)+'px');
322 						
323 					}
324 				});
325 				
326 				rb.updateRecords();
327 			});
328 		},
329 		
330 		filterRecords : function(filter){
331 			
332 			for ( var i in filter ){
333 				if ( this.filters[i] != filter[i] ) this.dirty = true;
334 				this.filters[i] = filter[i];
335 			}
336 			this.updateRecords();
337 		},
338 		
339 		updateRecords : function(){
340 			
341 			if ( this.dirty ){
342 				var sel = $(this.el).find('.xf-RecordBrowser-select-field');
343 				var val = $(sel).val();
344 				//var el = $(this.el);
345 				sel.load(this.getDataURL(), function(){
346 					sel.val(val);
347 				});
348 				this.dirty = false;
349 			}
350 		},
351 		
352 		getDataURL : function(){
353 			var url = DATAFACE_SITE_HREF+'?-action=RecordBrowser_data&-table='+encodeURIComponent(this.table);
354 			if ( this.value ) url += '&-value='+encodeURIComponent(this.value);
355 			if ( this.text ) url += '&-text='+encodeURIComponent(this.text);
356 			for ( var i in this.filters ){
357 				url += '&'+encodeURIComponent(i)+'='+encodeURIComponent(this.filters[i]);
358 			}
359 			return url;
360 		}
361 	
362 	};
363 	
364 	$.fn.RecordBrowser = function(options){
365 		
366 		return this.each(function(){
367 			var obj = $(this);
368 			obj.click(function(){
369 				if ( typeof(options.click) == 'function' ){
370 					options.click();
371 				}
372 				var rb = new xataface.RecordBrowser(options);
373 				rb.display();
374 			});
375 		});
376 	};
377 	
378 	$.fn.RecordBrowserWidget = function(options){
379 		return this.each(function(){
380 			
381 			var obj = $(this);
382 			var editable = options.editable || false;
383 			
384 			if ( obj.hasClass("xf-RecordBrowserWidget") ){
385 				// This field is already a record browser with different
386 				// settings.  We need to change it.  So we remove the old
387 				// display field.
388 				var oldDisplayField = obj.next();
389 				var oldButton = oldDisplayField.next();
390 				oldDisplayField.remove();
391 				oldButton.remove();
392 				
393 				obj.removeClass('xf-RecordBrowserWidget');
394 			}
395 			
396 			
397 			var displayField = document.createElement('input');
398 			$(displayField).attr('type','text')
399 				.addClass('xf-RecordBrowserWidget-displayField')
400 				//.css('width', obj.width()+'px')
401 				//.css('height', obj.height()+'px')
402 				.css('cursor', 'pointer')
403 				//.css('border', '1px solid black')
404 				.attr('readonly', 1);
405 			
406 
407 			$(displayField).insertAfter(this);
408 			
409 			obj.css('display','none')
410 				.addClass('xf-RecordBrowserWidget');
411 			
412 			if ( !options.frozen ){
413 				obj.change(function(){
414 					var id;
415 					if ( options.value && options.value != '__id__' ){
416 						id = encodeURIComponent(options.value)+'='+encodeURIComponent(obj.val());
417 					} else {
418 						id = obj.val();
419 					}
420 					var url = DATAFACE_SITE_HREF+'?-action=RecordBrowser_lookup_single&-table='+options.table+'&-id='+encodeURIComponent(id);
421 					if ( options.text ) url += '&-text='+encodeURIComponent(options.text);
422 					$.get(url, function(text){
423 						$(displayField).val(text);
424 					});
425 					
426 					updatePermissions();
427 					
428 					
429 						
430 				});
431 				
432 				/**
433 				 * Internal function to load the permissions for the currently selected record and then update
434 				 * whether the record can be edited or not.
435 				 */
436 				function updatePermissions(){
437 					try {
438 						// Now we check the edit permission to find out if we need to show or hide the edit link
439 						// for the field.
440 						var theq = {
441 						
442 							'-table': options.table
443 							
444 						};
445 						
446 						if ( options.value && options.value != '__id__' ){
447 							theq[options.value] = obj.val();
448 						} else {
449 							theq['--id'] = obj.val();
450 						}
451 						
452 						var perms = new xataface.Permissions({
453 							query: theq
454 						});
455 						//alert('here');
456 						perms.ready(function(){
457 							//alert('there');
458 							if ( perms.checkPermission('edit') ){
459 								editable = true;
460 							} else {
461 								editable = false;
462 							}
463 							updateEditable();
464 						
465 						});
466 					} catch (e){
467 						console.log('Looks like xataface.Permissions is not loaded while handling RecordBrowser change event.');
468 						console.log(e);
469 					}
470 				
471 				}
472 				
473 				
474 				
475 				var a = document.createElement('a');
476 				$(a).addClass('xf-RecordBrowser-button')
477 					.css('cursor', 'pointer')
478 					.html('<img src="'+DATAFACE_URL+'/images/search_icon.gif" border="0" /><span class="xf-RecordBrowser-button-label"> Lookup</span>');
479 				$(a).find('.xf-RecordBrowser-button-label')
480 					.css('display', 'none');
481 					
482 					
483 					
484 				
485 						
486 				
487 				
488 				$(a).insertAfter(displayField);
489 				
490 				
491 				
492 				
493 				// If we want to allow editing, we add an edit button after the field that opens a record dialog
494 				// for editing.
495 				var editButton = $('<a>')
496 					.addClass('xf-RecordBrowser-edit-button')
497 					.html('<img src="'+DATAFACE_URL+'/images/edit.gif" border="0" /><span class="xf-RecordBrowser-button-label">Edit</span>')
498 					.css({cursor: 'hand'})
499 					;
500 					
501 				$(editButton).find('.xf-RecordBrowser-button-label')
502 					.css('display', 'none');
503 					
504 				
505 				$(editButton).click(function(){
506 				
507 					if ( !editable ){
508 						alert('This record is not currently editable.');
509 					}
510 					var id = obj.val();
511 					if ( !id ){
512 						alert('No record is currently selected.');
513 						return;
514 					}
515 					
516 					var keyColName = '__id__';
517 					if ( options.value ){
518 						keyColName = options.value;
519 					}
520 					var recordid = encodeURIComponent(options.table)+'?'+encodeURIComponent(keyColName)+'='+encodeURIComponent(id);
521 					var dlg = new xataface.RecordDialog({
522 						recordid: recordid,
523 						table: options.table
524 					});
525 					dlg.display();
526 				});
527 				
528 				$(editButton).insertAfter(a);
529 				$(editButton).hide();
530 				
531 				function updateEditable(){
532 					if ( editable ) $(editButton).show();
533 					else $(editButton).hide();
534 				}
535 			
536 				var origCallback = function(){};
537 				
538 				if ( typeof(options.callback == 'function' ) ){
539 					origCallback = options.callback;
540 				}
541 				options.callback = function(vals){
542 					for ( var i in vals ){
543 						//$(displayField).val(vals[i]);
544 						obj.val(i);
545 						obj.trigger('change');
546 					}
547 					origCallback(vals, obj);
548 				};
549 				
550 				$(a).RecordBrowser(options);
551 				$(displayField).RecordBrowser(options);
552 			} else {
553 				//alert(obj.val());
554 			}
555 			
556 			if ( obj.val() ){
557 				var id;
558 				if ( options.value && options.value != '__id__' ){
559 					id = encodeURIComponent(options.value)+'='+encodeURIComponent(obj.val());
560 				} else {
561 					id = obj.val();
562 				}
563 				var url = DATAFACE_SITE_HREF+'?-action=RecordBrowser_lookup_single&-table='+options.table+'&-id='+encodeURIComponent(id);
564 				if ( options.text ) url += '&-text='+encodeURIComponent(options.text);
565 				$.get(url, function(text){
566 					//alert(text);
567 					$(displayField).val(text);
568 				});
569 				updatePermissions();
570 			}
571 			
572 			
573 		});
574 	};
575 })(jQuery);