1 /*jslint 
  2 browser: true,
  3 nomen: false,
  4 debug: true,
  5 forin: true,
  6 plusplus: false,
  7 undef: true,
  8 white: false,
  9 onevar: false 
 10  */
 11 var sc, jQuery;
 12 
 13 /**
 14  * @fileOverview File containing the SpazTimeline object definition
 15  * @author <a href="mailto:coj@funkatron.com">coj@funkatron.com</a>
 16  */
 17 
 18 
 19 /**
 20  * This object provides an API for managing the content of a timeline 
 21  * Currently this requires jQuery, but that could change or be overwritten
 22  * on a per-app basis
 23  * @requires jQuery
 24  * @constructor
 25  */
 26 var SpazTimeline = function(opts) {
 27 	
 28 	var thisTL = this;	
 29 	/**
 30 	 * This is a wrapper function for the refresher interval
 31 	 * we define this here and use a closure to solve a scope issue when the interval fires
 32 	 * @function
 33 	 */
 34 	this.refresh = function() {
 35 		sch.debug('Refreshing timeline');
 36 		thisTL.requestData.call(thisTL);
 37 	};
 38 	
 39 	
 40 	/**
 41 	 * Again, due to scope issues, we define this here to take advantage of the closure 
 42 	 */
 43 	this.onSuccess = function(e, data) {
 44 		sch.debug('onSuccess timeline');
 45 		thisTL.data_success.call(thisTL, e, data);
 46 		thisTL.startRefresher();	
 47 	};
 48 
 49 	/**
 50 	 * Again, due to scope issues, we define this here to take advantage of the closure 
 51 	 */
 52 	this.onFailure = function(e, data) {
 53 		sch.debug('onFailure timeline');
 54 		thisTL.data_failure.call(thisTL, e, data);
 55 		thisTL.startRefresher();	
 56 	};
 57 	
 58 	
 59 	/*
 60 		By breaking this out, we can more easily override the 
 61 		constructor process
 62 	*/
 63 	this._init(opts);
 64 };
 65 
 66 
 67 SpazTimeline.prototype._init = function(opts) {
 68 	
 69 	opts = opts || {};
 70 	
 71 	this.max_items                   = opts.max_items     || 100;	
 72 	this.refresh_time                = opts.refresh_time  || 1000*60*2; // mseconds
 73 	
 74 	this.timeline_container_selector = opts.timeline_container_selector || '#timeline';
 75 	this.timeline_item_selector      = opts.timeline_item_selector		|| 'div.timeline-entry';
 76 	// this.entry_relative_time_selector= opts.entry_relative_time_selector|| '.date';
 77 	this.event_target				 = opts.event_target || jQuery(this.timeline_container_selector).get(0);
 78 	
 79 	this.add_method			 		 = opts.add_method    || 'prepend';  // prepend or append
 80 	
 81 	this.success_event				 = opts.success_event || 'timeline-success';
 82 	this.failure_event				 = opts.failure_event || 'timeline-failure';
 83 	
 84 	this.renderer	                 = opts.renderer      || null;  // required
 85 	this.request_data				 = opts.request_data  || null;  // required
 86 	this.data_success				 = opts.data_success  || null;  // required
 87 	this.data_failure				 = opts.data_failure  || null;
 88 	this.refresher                   = opts.refresher     || null;
 89 	
 90 	if (!this.renderer) {
 91 		throw new Error ("renderer is required");
 92 	}
 93 	if (!this.request_data) {
 94 		throw new Error ("request_data is required");
 95 	}
 96 	if (!this.data_success) {
 97 		throw new Error ("data_success is required");
 98 	}
 99 
100 	this.container = jQuery(this.timeline_container_selector).get(0);
101 
102 
103 };
104 
105 /**
106  * the timeline 
107  */
108 SpazTimeline.prototype.last_id = -1;
109 
110 /**
111  * an array of data items that are represented in the timeline 
112  */
113 SpazTimeline.prototype.model = [];
114 
115 /**
116  * call this after initialization 
117  */
118 SpazTimeline.prototype.start = function() {
119 	sch.debug('Starting timeline');
120 	this.requestData();
121 };
122 
123 
124 /**
125  * This is the method that gets data from the model and calls addItems() on what is returned 
126  * 
127  * @todo needs to be written to handle async call
128  */
129 SpazTimeline.prototype.requestData = function() {
130 	sch.debug('Requesting data timeline');
131 	this.stopRefresher();
132 	
133 	this.stopListening();
134 	this.startListening();
135 	
136 	// call an appropriate model function
137 	var items = this.request_data();	
138 };
139 
140 
141 
142 SpazTimeline.prototype.startListening = function() {
143 	var thisTL = this;
144 	sc.helpers.debug("Listening for "+thisTL.success_event);
145 	sc.helpers.listen(thisTL.event_target, thisTL.success_event, thisTL.onSuccess);
146 	sc.helpers.listen(thisTL.event_target, thisTL.failure_event, thisTL.onFailure);
147 };
148 
149 
150 SpazTimeline.prototype.stopListening = function() {
151 	var thisTL = this;
152 	sc.helpers.debug("Stopping listening for "+thisTL.success_event);
153 	sc.helpers.unlisten(thisTL.event_target, thisTL.success_event);
154 	sc.helpers.unlisten(thisTL.event_target, thisTL.failure_event);
155 };
156 
157 SpazTimeline.prototype.startRefresher = function() {
158 	this.stopRefresher();
159 	
160 	sc.helpers.debug('Starting refresher');
161 	if (this.refresh_time > 1000) { // the minimum refresh is 1000ms. Otherwise we don't auto-refresh
162 		sc.helpers.debug('Refresh time is '+this.refresh_time+'ms');
163 		this.refresher = setInterval(this.refresh, this.refresh_time);
164 	} else {
165 		sc.helpers.debug('Not starting refresher; refresh time is '+this.refresh_time+'ms');
166 	}
167 };
168 
169 
170 SpazTimeline.prototype.stopRefresher = function() {
171 	sc.helpers.debug('Stopping refresher');
172 	clearInterval(this.refresher);
173 };
174 
175 
176 
177 
178 
179 
180 /**
181  * Stuff we should do when we're done using this, including
182  * removing event listeners an stopping the refresher 
183  */
184 SpazTimeline.prototype.cleanup = function() {
185 	sch.debug('Cleaning up timeline');
186 	this.stopListening();
187 	this.stopRefresher();
188 };
189 
190 /**
191  * given an array of objects, this will render them and add them to the timeline
192  * @param {array} items
193  */
194 SpazTimeline.prototype.addItems = function(items) {
195 	sch.debug('Adding items to timeline');
196 	
197 	var items_html    = [];
198 	var timeline_html = '';
199 	
200 	for (var x=0; x<items.length; x++) {
201 		items_html.push( this.renderItem(items[x], this.renderer) );
202 	}
203 	
204 	if (this.add_method === 'append') {
205 		items_html.reverse();
206 		// timeline_html = '<div>'+items_html.join('')+'</div>';
207 		timeline_html = items_html.join('');
208 		this.append(timeline_html);
209 	} else {
210 		// timeline_html = '<div>'+items_html.join('')+'</div>';
211 		timeline_html = items_html.join('');
212 		this.prepend(timeline_html);
213 	}
214 	
215 	this.removeExtraItems();
216 	
217 };
218 
219 SpazTimeline.prototype.renderItem = function(item, templatefunc) {
220 	sch.debug('Rendering item in timeline');
221 	
222 	var html = templatefunc(item);
223 	
224 	return html;
225 	
226 };
227 
228 
229 SpazTimeline.prototype.removeExtraItems = function() {
230 	
231 	sch.debug('Removing extra items in timeline');
232 	
233 	if (this.add_method === 'append') {
234 		var remove_from_top = true;
235 	} else {
236 		remove_from_top = false;
237 	}
238 	
239 	sc.helpers.removeExtraElements(this.getEntrySelector(), this.max_items, remove_from_top);
240 };
241 
242 
243 SpazTimeline.prototype.removeItems = function(selector) {};
244 
245 
246 SpazTimeline.prototype.removeItem = function(selector) {};
247 
248 /**
249  * @param {string} selector
250  * @return {boolean} 
251  */
252 SpazTimeline.prototype.itemExists = function(selector) {
253 	
254 	sch.debug('Checking it item ('+selector+') exists in timeline');
255 	
256 	var items = this.select(selector);
257 	if (items.length>0) {
258 		return true;
259 	} else {
260 		return false;
261 	}
262 	
263 };
264 
265 
266 SpazTimeline.prototype.hideItems = function(selector) {
267 	sch.debug('Hiding items in timeline');
268 	
269 	this.filterItems(selector, 'blacklist');
270 };
271 
272 
273 SpazTimeline.prototype.showItems = function(selector) {
274 	sch.debug('Showing items in timeline');
275 	
276 	this.filterItems(selector, 'whitelist');
277 };
278 
279 
280 /**
281  * @param {string} selector 
282  * @param {string} type  "whitelist" or "blacklist"
283  */
284 SpazTimeline.prototype.filterItems = function(selector, type) {};
285 
286 
287 /**
288  * sorts the elements in the timeline according to the sorting function 
289  */
290 SpazTimeline.prototype.sortItems = function(selector, sortfunc) {
291 	
292 	sch.debug('Sorting items in timeline');
293 	
294 	var items = this.select(selector);
295 	items.sort(sortfunc);
296 };
297 
298 
299 
300 /**
301  * This is a wrapper for the selector engine, so someone could swap in 
302  * their own recipe if necessary. By default we use jQuery, and return the 
303  * array of HTML elements (not the jQuery object)
304  * @type DOMelement[]
305  */
306 SpazTimeline.prototype.select = function(selector, container) {
307 	if (!container) {
308 		container = this.timeline_container_selector;
309 	}
310 	return jQuery(selector, container).get();
311 };
312 
313 /**
314  * wrapper for prepending to timeline 
315  */
316 SpazTimeline.prototype.prepend = function(htmlitem) {
317 	jQuery(this.timeline_container_selector).prepend(htmlitem);
318 };
319 SpazTimeline.prototype.append = function(htmlitem) {
320 	jQuery(this.timeline_container_selector).append(htmlitem);
321 };
322 
323 SpazTimeline.prototype.getEntrySelector = function() {
324 	return this.timeline_container_selector + ' ' + this.timeline_item_selector;
325 };
326