1 /**
  2  * Base definition file for the JSWidgets project.
  3  * This project defines a widget structure that allows users to create very robust,
  4  * interactive web pages constructed of modular widgets with little overhead.
  5  * 
  6  * @fileoverview
  7  * @author Garth Henson (<a href="http://www.guahanweb.com">Guahan Web</a>)
  8  * @version 0.1
  9  */
 10 
 11 var JSWidgets = Class.extend(
 12 	/** @lends JSWidgets.prototype */
 13 	{
 14 	/**
 15 	 * This object allows for the easy management of existing widgets. While individual widgets
 16 	 * can easily be created and maintained manually, using the JSWidgets namespace as a
 17 	 * manager helps clean up the structure and aids with looping over all existing widgets.
 18 	 * @constructs
 19 	 * @author Garth Henson (<a href="http://www.guahanweb.com">Guahan Web</a>)
 20 	 * @class This is the management system for handling all widget objects currently created.
 21 	 * @version 0.1
 22 	 *
 23 	 * @example
 24 	 * // Start new manager
 25 	 * var widgets = new JSWidgets();
 26 	 *
 27 	 * // Add a loader widget to the manager
 28 	 * var loader = widgets.add(new LoaderWidget({
 29 	 *     title : 'Loader Widget'	
 30 	 * });
 31 	 */
 32 	construct : function() {
 33 		this.curr_id = 0;
 34 		this.widgets = new Array();
 35 		this.initialized = true;
 36 	},
 37 	
 38 	/**
 39 	 * Add the provided widget to this collection. If a callback is defined, it will be executed with the following parameters:
 40 	 * <ul>
 41 	 * 	<li>{boolean} <b>success</b> Whether or not the widget was added successfully</li>
 42 	 * 	<li>{string} <b>error</b> Upon failure, and error string will be provided</li>
 43 	 * </ul>
 44 	 * @param {Widget} w A {@link BaseWidget} or other valid child of that class
 45 	 * @param {function} callback An optional function to execute upon successful addition.
 46 	 * @returns {Widget} The widget passed in to be added
 47 	 */
 48 	add : function(w, callback) {
 49 		var that = this;
 50 		w.setManager(this);
 51 		var action = function() {
 52 			if (w.valid()) {
 53 				that.widgets[that.curr_id] = w;
 54 				w.setUid(that.curr_id);
 55 				that.curr_id++;
 56 				if (callback || false) {
 57 					callback(true);
 58 				}
 59 			} else {
 60 				var msg = "JSWidets error: Invalid widget object added!";
 61 				if (callback || false) {
 62 					callback(false, msg);
 63 				} else {
 64 					throw msg;
 65 				}
 66 			}
 67 		}
 68 
 69 		if (DEBUG || false) {
 70 			// During debug, randomize a delay on loading
 71 			var delay = Math.round(Math.random() * 1500);
 72 			setTimeout(action, delay);
 73 		} else {
 74 			action();
 75 		}
 76 		
 77 		return w;
 78 	},
 79 	
 80 	/**
 81 	 * Removes the widget with the provided <code>uid</code> and destroys it from the DOM.
 82 	 * @public
 83 	 * @param {string} uid The <code>uid</code> to be removed
 84 	 */
 85 	remove : function(uid) {
 86 		for (var i = 0; i < this.widgets.length; i++) {
 87 			var w = this.widgets[i];
 88 			if (w.uid == uid) {
 89 				this.widgets.splice(i, 1);
 90 				w.destroy();
 91 			}
 92 		}
 93 	}
 94 });
 95 
 96 /**
 97  * Load event is triggered when the widget has completed loading.
 98  * @name BaseWidget#load
 99  * @event
100  * @param {BaseWidget} w The widget object which was loaded
101  */
102 
103 var BaseWidget = Class.extend(
104 	/** @lends BaseWidget.prototype */
105 	{
106 	/**
107 	 * The name of the current widget (should be overwritten by all child classes).
108 	 * @type String
109 	 */
110 	name : 'JSWidget Base',
111 	
112 	/**
113 	 * Configuration of the current widget. This is loaded by the <code>opts</code> parameter in the
114 	 * object constructor. Valid configuration options are:
115 	 * <ul>
116 	 *	<li><b>listeners</b> - event handlers for the following custom events:
117 	 *		<ul>
118 	 *			<li><b>load</b> - executed once the widget has loaded</li>
119 	 *		</ul>
120 	 *	</li>
121 	 *	<li><b>title_selector</b> - defaults to '.title'</li>
122 	 *	<li><b>content_selector</b> - defaults to '.content'</li>
123 	 *	<li><b>title</b> - defaults to the widget name</li>
124 	 * </ul>
125 	 * 
126 	 * @type Object
127 	 */
128 	config : {
129 		closeable : false
130 	},
131 	
132 	/**
133 	 * This is the base widget from which all others extend
134 	 * @author Garth Henson (<a href="http://www.guahanweb.com">Guahan Web</a>)
135 	 * @constructs
136 	 * @class This is the object that defines the base functionality from which all other JSWidgets will extend.
137 	 * @version 0.1
138 	 * 
139 	 * @param {string} id The div (or element) ID attribute of the widget
140 	 * @param {JSWidgets} mgr The {@link JSWidgets} manager to which this widget should be assigned
141 	 * @param {object} opts Additional options for the widget
142 	 */
143 	construct : function(id, opts) {
144 		this.id = '#' + id;
145 		this.is_valid = this.validate(opts);
146 		this.config = opts || {};
147 		this.listeners = opts['listeners'] || {};
148 		this.manager = null;
149 		
150 		// Set up selectors
151 		this.title_selector = this.config.title_selector || '.title';
152 		this.content_selector = this.config.content_selector || '.content';
153 
154 		// Default constructor actions
155 		this.setTitle();
156 		this.setUp();
157 		if (this.listeners.load) {
158 			this.listeners.load(this);
159 		}
160 	},
161 	
162 	/**
163 	 * Destroy the current widget from the DOM and cleanup
164 	 * @public
165 	 */
166 	destroy : function() {
167 		var that = this;
168 		$(this.id).fadeOut('slow', function() {
169 			$(that.id).remove();
170 		});
171 	},
172 	
173 	/**
174 	 * Appends the content as a new line to the widget's content area
175 	 * @public
176 	 * @param {string} val The value to append
177 	 */
178 	appendContent : function(val) {
179 		var el = $(this.id).find(this.content_selector);
180 		var txt = el.html();
181 		if (txt == '') {
182 			el.html(val);
183 		} else {
184 			el.html(txt + '<br />' + val);
185 		}
186 	},
187 	
188 	/**
189 	 * Defines the content to replace into the current widget
190 	 * @public
191 	 * @param {String} val The raw markup to be inserted into the content region
192 	 */
193 	setContent : function(val) {
194 		$(this.id).find(this.content_selector).html(val);
195 	},
196 	
197 	/**
198 	 * Set the object's UID to the provided <code>id</code>
199 	 * @public
200 	 * @param {int} id The new UID
201 	 */
202 	setUid : function(id) {
203 		this.uid = id;
204 	},
205 	
206 	/**
207 	 * Set the content for the title region of the widget
208 	 * @public
209 	 * @param {string} val The new title to display
210 	 */
211 	setTitle : function(val) {
212 		var title = val || this.getTitle();
213 		$(this.id).find(this.title_selector).html(title);
214 	},
215 	
216 	/**
217 	 * Runs the initial required setup for this widget (including creating the <code>close</code> button if required).
218 	 * @private
219 	 */
220 	setUp : function() {
221 		var that = this;
222 		
223 		// Container needs to be relative to house all child elements
224 		$(this.id).css('position', 'relative');
225 		
226 		// If closeable, generate our close link
227 		if (this.config.closeable) {
228 			$(this.id).append('<a href="#" class="jswidget-gen-close">close</a>');
229 			$(this.id).find('a.jswidget-gen-close').css({
230 				position: 'absolute',
231 				top: '10px',
232 				right : '10px'
233 			}).click(function(e) {
234 				e.preventDefault();
235 				that.unregister();
236 			});
237 		}
238 	},
239 	
240 	setManager : function(mgr) {
241 		if (mgr instanceof JSWidgets) {
242 			this.manager = mgr;
243 		}
244 	},
245 	
246 	/**
247 	 * Returns the configured title of this widget. Defaults to the widget name if no title has been set.
248 	 * @public
249 	 * @returns {string} The widget's title
250 	 */
251 	getTitle : function() {
252 		return (this.config.title || this.name);
253 	},
254 	
255 	/**
256 	 * An accessor method that allows this widget to remove itself from the manager implicitly.
257 	 * @public
258 	 */
259 	unregister : function() {
260 		if (this.manager || false) {
261 			this.manager.remove(this.uid);
262 		}
263 	},
264 	
265 	/**
266 	 * Validate the bare minimum opts are present
267 	 * This method should be overridden by all widgets
268 	 * @public
269 	 * @returns {boolean} Whether or not this widget is valid
270 	 */
271 	validate : function(opts) { return true; },
272 	
273 	/**
274 	 *
275 	 */
276 	valid : function() {
277 		return this.is_valid || false;
278 	}
279 });
280