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