PageRenderTime 45ms CodeModel.GetById 20ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/js/chillbrain.core.js

https://github.com/mmontalbo/chillbrain
JavaScript | 678 lines | 524 code | 116 blank | 38 comment | 38 complexity | 0ba0bdc900b97b5809096929c08c0054 MD5 | raw file
  1
  2/**
  3 * 
  4 * Chillbrain core. Put the entire MVC structure in here.
  5 * 
  6 */
  7
  8var delimeter = "&";
  9var chillbrain = {
 10		constants : {
 11	delimiter : delimeter,
 12	nextRoute : ":image1" + delimeter + ":image2" 
 13},
 14
 15event : {
 16	fetch : "fetchComplete",
 17	quickFetch : "quickFetchComplete",
 18	transaction : "transactionSuccess",
 19	transactionCallback : "transactionCallback",
 20	vote : "vote",
 21	skip : "skip",
 22	share : "share"
 23}
 24};
 25
 26var loaded = false;
 27var setup = window.location.hash.length ? false : true;
 28
 29$(function() 
 30		{	
 31	var ImageModel = Backbone.Model.extend({
 32		defaults: {
 33		"id" : "",
 34		"title" : "...",
 35		"permalink" : "",
 36		"src" : "",
 37	}
 38	});
 39
 40	var globalEvents = new Object();
 41	_.extend(globalEvents, Backbone.Events);
 42
 43	var Feed = Backbone.Collection.extend({
 44		model: ImageModel,
 45		fetchSize : 10,
 46		preloadedImages : new Object(),
 47		index : 2,
 48		isFetching: false,
 49
 50		initialize : function() {
 51		this.bind("refresh", this.initialLoad);
 52		_.bindAll(this,"fetchSuccess","fetchError","getNext","addPreloaded","isExhausted","intialLoad");
 53	},
 54
 55	initialLoad : function() {
 56		_.each(this.models, this.addPreloaded, this);
 57	},
 58
 59	// point to custom feed url
 60	url: function() {
 61		return '/feed?n=' + this.fetchSize;
 62	},
 63
 64	// fetch n more images from the server and add them to the
 65	// collection
 66	fetchMoreImages : function() {
 67		this.isFetching = true;
 68		this.fetch({
 69			success : this.fetchSuccess,
 70			error : this.fetchError,
 71		});
 72	},
 73
 74	fetchSuccess : function()
 75	{
 76		//var initSize = _.values(this.preloadedImages).length;
 77		_.each(this.models, this.addPreloaded, this);
 78		//if(initSize ===  _.values(this.preloadedImages).length)
 79		//	alert("dupes: "+initSize)
 80		globalEvents.trigger(chillbrain.event.fetch);
 81		this.isFetching = false;
 82		$("#loading").css("opacity","0");
 83	},
 84
 85	fetchError : function()
 86	{
 87		this.isFetching = false;
 88	},
 89
 90	addPreloaded : function(image) {
 91		//var ind = _.indexOf(_.keys(this.preloadedImages), image.get("id"));
 92		//var ind2 =_.indexOf(_.keys(this.preloadedImages), image.get("id"));
 93		//if(ind != -1)
 94		//	alert("dupe: "+image.get("title")+" first:"+ind+" second:"+ind2);
 95		this.preloadedImages[image.get("id")] = new UI.PreloadedImage({ model : image }).render();		    
 96	},
 97
 98	getNext : function() {						
 99		preloadedImageValues = _.values(this.preloadedImages);
100
101		if(!this.isFetching &&
102				(preloadedImageValues.length - this.index <= this.fetchSize-1))
103		{
104			this.fetchMoreImages();
105		}
106
107		model = preloadedImageValues[this.index].model;
108
109		if(!this.isExhausted())
110			this.index++;
111
112		return model;
113	},
114
115	getNextId : function() {
116		return this.getNext().get("id");
117	},
118
119	getPreloaded : function(key) {
120		return this.preloadedImages[key];
121	},
122
123	isExhausted : function() {
124		return this.index > _.values(this.preloadedImages).length - 3;
125	},
126
127	backup : function() {
128		this.index = this.index - 2;
129	},
130	});	
131
132	var fetcherEvents = new Object();
133	_.extend(fetcherEvents, Backbone.Events);
134	var QuickFetcher = Backbone.Collection.extend({
135		model: ImageModel,
136		data : null,
137
138		initialize : function() {
139		_.bindAll(this, "send");
140		fetcherEvents.bind(chillbrain.event.fetch, this.send);
141	},
142
143	url : function() {
144		return '/data?data=' + this.data;
145	},
146
147	send : function() {
148		if(this.length == 1)
149			globalEvents.trigger(chillbrain.event.quickFetch, this.models[0]);
150		else
151			globalEvents.trigger(chillbrain.event.quickFetch, this.models[0], this.models[1]);
152	},
153
154	get : function(images) {
155		this.data = $.toJSON(images);
156		this.fetch({
157			success: function() {
158			fetcherEvents.trigger(chillbrain.event.fetch);
159		}
160		});
161	},
162	});
163
164	var UIController = Backbone.Controller.extend({
165		feed : null,
166		leftImage : null,
167		rightImage: null,
168		transactionPerformed : false,
169
170		initialize : function() {
171		_.bindAll(this, "transactionSuccess", "transactionCallback", "setImages", "vote", "skip", "share", "preload");
172
173		// setup global bindings for this object
174		globalEvents.bind(chillbrain.event.quickFetch, this.setImages);
175		globalEvents.bind(chillbrain.event.transaction, this.transactionSuccess);
176		globalEvents.bind(chillbrain.event.transactionCallback, this.transactionCallback);
177
178		globalEvents.bind(chillbrain.event.vote, this.vote);
179		globalEvents.bind(chillbrain.event.skip, this.skip);
180		globalEvents.bind(chillbrain.event.share, this.share);
181
182		this.feed = new Feed;
183	},
184
185	// the different controller mappings live here
186	routes : {
187		"" : "root",
188		":image1&:image2" : "next",
189		":image" : "oneNext"    		
190	},
191
192	// Setup the page. This will get the list of images (which have been rendered into the model)
193	// and bind them to the already showing images as well as pre-load the rest of the images
194	setup : function() {
195		if(setup) {
196			var left = this.feed.getNext();
197			var right = this.feed.getNext();
198
199			this.setImages(left, right);
200		} 
201	},
202
203	root : function() {
204		if(!loaded) 
205			return;
206		else
207			this.next();
208	},
209
210	oneNext : function(image) {
211		new QuickFetcher().get([image]);
212	},
213
214	next : function(image1, image2) {		    	
215		// if there was no transaction being performed then we going back or initializing the page
216		if(!loaded) {
217			new QuickFetcher().get([image1, image2]);
218			return;
219		} else if(! this.transactionPerformed) {
220			this.feed.backup();
221			if(this.feed.index == 2) {
222				image1 = this.feed.at(0).get("id");
223				image2 = this.feed.at(1).get("id");
224			}
225		}
226
227		this.leftImage = this.leftImage.replace(this.feed.getPreloaded(image1));
228		this.rightImage = this.rightImage.replace(this.feed.getPreloaded(image2));	    	
229		this.transactionPerformed = false;
230	},
231
232	vote : function(img) {
233		async("/vote?img=" + img);
234		this.transactionSuccess();
235	},
236
237	skip : function(img, img2) {
238		async("/skip?img=" + img + "&img2=" + img2);
239		this.transactionSuccess();
240	},
241
242	share : function(img) {
243		async("/share?img=" + img);
244	},	
245
246	setImages : function(left, right) {
247		left = (left) ? left : this.feed.getNext();
248		right = (right) ? right : this.feed.getNext();
249
250		this.leftImage = new UI.LeftImage({  model: left }).render();
251		this.rightImage = new UI.RightImage({  model: right }).render();
252
253		_.delay(showImages, 100);
254	},
255
256	transactionSuccess : function() {
257		this.transactionPerformed = true;
258		if(!this.feed.isExhausted())
259		{
260			nextHash = [this.feed.getNextId(), this.feed.getNextId()].join(chillbrain.constants.delimiter);
261			$("#anchor").attr("name",nextHash);
262			window.location.hash = nextHash;
263		}
264		else
265		{
266			if(!this.feed.isFetching)
267				this.feed.fetchMoreImages();
268			$("#loading").css("opacity","1");
269		}
270	},
271
272	transactionCallback : function(callback) {
273		if(callback.process_response) {			
274			//fb_share(callback.id, callback.img, this.feed.preloadedImages[callback.img].model.get("title"));
275		} else if(callback.error){
276			if(callback.error.code == 100) {
277				$("span#loginButton").addClass('pulsing');
278			}
279
280			showWarning(callback.error.msg);
281		}
282	},
283	});
284
285	// make UI namespace for View element
286	var UI = new Object();
287	UI.BindableButton = Backbone.View.extend({
288		img : null,
289		bind : function(img) {
290		this.img = img;
291	},
292
293	events : {
294		"click" : "performAction",
295		"mouseover" : "hover",
296		"mouseout" : "unhover",
297	},
298
299	performAction : function(){},
300	hover : function() {},
301	unhover : function() {}
302	});
303
304	UI.VoteButton = UI.BindableButton.extend({		
305		performAction : function() {
306		globalEvents.trigger("vote", this.img.getId());
307	},
308
309	hover : function() {
310		$(this.img.el).css("borderColor","#000000");			
311	},
312
313	unhover : function() {
314		$(this.img.el).css("borderColor","#575757");
315	}
316	});
317	
318	UI.ZoomButton = UI.BindableButton.extend({		
319		performAction : function() {
320		this.img.zoom();
321	},
322	});
323
324	UI.ShareButton = UI.BindableButton.extend({
325		performAction : function() {
326		globalEvents.trigger(chillbrain.event.share, this.img.getId());
327	}
328	});
329
330	UI.Image = Backbone.View.extend({
331		tagName : "img",
332		render : function() {
333		// render image
334		$(this.el).attr(this.model.toJSON());
335		$(this.el).removeClass().addClass(this.className);
336
337		// render title
338		$(this.title).find('span').text(this.model.get("title"));	
339
340		sizeTitles();
341
342		this.wrapper.append(this.el);
343
344
345		return this;
346	},
347
348	// utility to return the element
349	html : function() {
350		return this.el;
351	},	
352	});
353
354	// View for a preloaded image. This just sets the tag name and class
355	// name to ensure the image will be created as the proper
356	// tag type with the correct styling when it is rendered
357	UI.PreloadedImage = UI.Image.extend({
358		className : "preloaded",
359		render : function() {
360		$(this.el).attr(this.model.toJSON());
361		$("#content").append(this.el);
362		return this;
363	}
364	});
365
366	// View for a shown image. These have vote buttons associated with them.
367	// Note: Subclasses MUST contain a voteButton attribute or there will be
368	// bad things
369	var currentZoomedImage = {
370			zoomed : false,
371			image : null,
372			set : function(image) {
373		this.image = image;
374		this.zoomed = true;
375	},
376	reset : function() {
377		this.image = null;
378		this.zoomed = false;
379	},
380	isZoomed : function() {
381		return this.zoomed; 
382	},
383	canZoom : function(image) {
384		return this.image == null && this.zoomed == false; 
385	},
386	};
387	_.bindAll(currentZoomedImage);
388
389	UI.ShowingImage = UI.Image.extend({
390		zoomedIn : false,
391		zoomedTitle : $("div#zoomTitleBlock").find("span"),
392		zoomedPermalink : $("div#zoomTitleBlock").find("a"),
393
394		render : function() {	     	 
395		_.bindAll(this,'parentHover','parentUnhover', 'getId');
396
397		this.wrapper.live('mouseover',this.parentHover);
398		this.wrapper.live('mouseout',this.parentUnhover);
399
400		this.voteButton.bind(this);
401		this.zoomButton.bind(this);
402		this.shareButton.bind(this);
403
404		return new UI.Image().render.call(this);
405	},
406
407	// remove the showing image and render the pre-loaded image into the shown views
408	replace : function(preloadedImage) {
409		$(this.el).removeClass().addClass("preloaded");
410		return new this.constructor({ model : preloadedImage.model, el : preloadedImage.el }).render();
411	}, 
412
413	getId : function() {
414		return this.model.get("id");
415	},
416
417	events : {
418		"mouseover": "hover",
419		"mouseout" : "unhover",
420		"click"    : "click"
421	},
422
423	parentHover : function() {
424		this.controlBar.addClass('visible');						 
425		this.controlBar.find('ul').addClass('visible');
426	},
427
428	parentUnhover : function() {
429		this.controlBar.removeClass('visible');				
430		this.controlBar.find('ul').removeClass('visible');
431	},
432
433	hover : function() {
434		var el = $(this.el);	    	 
435		$('img.combatant').removeClass("selected notSelected");
436		el.addClass("selected");
437		var pos = el.offset();
438		el.css('borderColor','black');
439		this.voteButton.el.addClass("selected");
440	},
441
442	unhover : function() {
443		var el = $(this.el);
444		el.removeClass("selected");
445		el.css('borderColor','#575757');
446		this.voteButton.el.removeClass("selected");
447	},
448
449	zoom : function () {
450		var el = $(this.el);
451		var scaleFactor = 2;
452
453		var documentWidth = $(window).width();
454		var imgPosition = el.offset();
455		var imgWidth = el.width();
456		var imgWidthScaled = el.width() * scaleFactor;
457		var translateOffset = (documentWidth/2) - (imgPosition.left) - (imgWidth/2);
458		var zoomedOffset = documentWidth - imgWidthScaled - (imgWidth) + (imgWidthScaled / 20);
459
460		if(currentZoomedImage.isZoomed()) {
461			currentZoomedImage.reset();
462			$("div#content").css("opacity",1);
463			$("div#commandCenter").css("opacity",1);
464			$("div#titles").css("opacity",1);
465
466			$("div#zoomedImage").fadeOut(175);
467
468			$("div#zoomTitleBlock").css("top",-100);
469		} else {
470			// if there is a zoomed image then don't try to zoom in
471			if(! currentZoomedImage.canZoom(this)) return;
472			currentZoomedImage.set(this);
473
474			$("div#content").css("opacity",0.1);
475			$("div#commandCenter").css("opacity",0.1);
476			$("div#titles").css("opacity",0.1);
477
478			$("img#zoomed").attr("src", this.model.get("src"));
479			$("div#zoomedImage").fadeIn(300);
480
481			this.zoomedTitle.text(this.model.get("title"));
482			this.zoomedPermalink.text(this.model.get("permalink"));
483			this.zoomedPermalink.attr('href',this.model.get("permalink"));
484			$("div#zoomTitleBlock").each(function(i,el){//Make font as big as possible
485				$(el).textfill({ maxFontPixels: 34 })
486			}); 
487
488			$("div#zoomInPicture img").css('cursor', function() { //------ adds magnifying glass effect
489				if (jQuery.browser.mozilla) {
490					return '-moz-zoom-out';
491				} else if (jQuery.browser.webkit) {
492					return '-webkit-zoom-out';
493				} else {
494					return 'pointer'; 
495				}
496			});
497
498			$("div#zoomTitleBlock").css("top",0);
499		} 
500
501		$("div#zoomedImage").toggleClass("zoomedIn"); 		    
502	},
503
504	click : function() {		
505		globalEvents.trigger("vote", this.getId());
506	}
507	});
508
509	// View for the left image. This is bound to a tag that already exists
510	UI.LeftImage = UI.ShowingImage.extend({
511		className : "combatant leftCombatant",
512		title : $("#leftTitle"),
513		voteButton : new UI.VoteButton({ el: $("#leftVoteButton") }),
514		zoomButton : new UI.ZoomButton({ el: $("#leftZoom") }),
515		controlBar : $("div.leftControls"),
516		wrapper : $("div.leftWrapper"),
517		shareButton : new UI.ShareButton({ el: $("#leftShare") }),
518	});
519
520	// View for the right image. This is bound to a tag that already exists
521	UI.RightImage = UI.ShowingImage.extend({
522		className : "combatant rightCombatant",
523		title : $("#rightTitle"),
524		voteButton : new UI.VoteButton({ el: $("#rightVoteButton") }),
525		zoomButton : new UI.ZoomButton({ el: $("#rightZoom") }),
526		controlBar : $("div.rightControls"),
527		wrapper : $("div.rightWrapper"),
528		shareButton : new UI.ShareButton({ el: $("#rightShare") }),
529	});
530
531	// initialize the controller and start the history
532	window.UIController = new UIController;
533	Backbone.history.start();
534
535	// Utility functions
536	function async(url, get) {
537		$.ajax({
538			type: get == null || !get ? "POST" : "GET",
539					url: url,
540					cache: false,
541					success: function(msg){
542			globalEvents.trigger(chillbrain.event.transactionCallback, $.parseJSON(msg));
543		}
544		});
545	}
546
547	$("div#zoomInPicture").click(function(e){ //----- closes image unless you click on something else
548		if(event.target != this){
549			return true;
550		} else {
551			$("div#zoomInPicture").css({ //make wrapper div visible
552				display:"none"
553			});
554		}
555	});
556
557	$("img#closeButton").live("click",function(){ //----- ways to hide img
558		$("div#content").css("opacity",1);
559		$("div#commandCenter").css("opacity",1);
560		$("div#titles").css("opacity",1)
561		$("div#zoomedImage").fadeOut("fast");
562		$("div#zoomTitleBlock").css("top",-75);	
563		$("div#zoomedImage").toggleClass("zoomedIn");
564
565		currentZoomedImage.reset();
566	});	
567
568	$("div#commandCenter").find("img").click(function(){
569		$(this).addClass("jiggle");
570		window.setTimeout(stopBrainJiggle, 300);
571		if($("div#commandCenterText").find("span").attr("message") != "chillbrain") {
572			$("div#commandCenterText").removeClass("instructions");
573			showMessage("chillbrain")
574		}
575		else{
576			$("div#commandCenterText").addClass("instructions");
577			showMessage("Click the image you like or the background to skip.")
578		}
579			
580	})
581
582	// key bindings for page
583	$(document).keyup(function(event){
584		switch(event.keyCode) {
585		case 32: //spacebar
586			globalEvents.trigger("skip",$("img.leftCombatant").attr('id'),$("img.rightCombatant").attr('id'));
587			break;
588		case 37: // left arrow key
589			globalEvents.trigger("vote",$("img.leftCombatant").attr('id'));
590			break;
591		case 39: //right arrow key
592			globalEvents.trigger("vote",$("img.rightCombatant").attr('id'));
593			break;
594		}
595
596		return false;
597	});
598		});
599
600function hideImages() {
601	$("div.wrapper").css("opacity",0);
602	$("div.voteButton").css("opacity",0);
603	$("div#titles").css("opacity",0);
604}
605
606function showImages(){
607	$("div.wrapper").css("opacity",1);
608	$("div.voteButton").css("opacity",1);
609	$("div#titles").css("opacity",1);
610}
611
612function skipImages() {
613	$("div.wrapper").css("opacity",0);
614	$("div#titles").css("opacity",0);
615}
616
617function fadeTextTo(fadeTo) {
618	$("div#commandCenter").find("img").addClass("jiggle");
619	$("div#commandCenterText").find("span").css("opacity","0");
620	$("div#commandCenterText").find("span").attr("message",fadeTo);
621	window.setTimeout(stopBrainJiggle, 300);
622}
623
624function showMessage(message) {
625	fadeTextTo(message);	
626}
627
628function showWarning(warning) {
629	fadeTextTo(warning);
630}
631
632function achievmentUnlocked(achievment){
633	fadeTextTo("achievment unlocked");
634}
635
636
637function stopBrainJiggle(){
638	$("div#commandCenter").find("img").removeClass("jiggle");
639	$("div#commandCenterText").find("span").html($("div#commandCenterText").find("span").attr("message"));
640	$("div#commandCenterText").find("span").css("opacity","1");
641}
642
643function sizeTitles() {
644	$("div.imgTitle").each(function(i,el){//Make font as big as possible
645		$(el).textfill({ maxFontPixels: 38 })
646	}); 
647}
648
649function showControls(el) {
650	$("div.controlBar").find('li').addClass('visible');
651}
652
653function hideControls(el) {
654	$("div.controlBar").find('li').removeClass('visible');
655}
656
657$(window).resize(function() {
658	sizeTitles();
659});
660
661(function($) {
662	$.fn.textfill = function(options) {
663		var fontSize = options.maxFontPixels;
664		var ourText = $('span:visible:first', this);
665		var maxHeight = $(this).height();
666		var maxWidth = $(this).width();
667		var textHeight;
668		var textWidth;
669		do {
670			ourText.css('font-size', fontSize);
671			textHeight = ourText.height();
672			textWidth = ourText.width();
673			fontSize = fontSize - 1;
674		} while (textHeight > maxHeight || textWidth > maxWidth && fontSize > 3);
675		return this;
676	}
677})(jQuery);
678