PageSourceSearch

https://wei-mao.com/apipackage?26.11.18.1&

js wei-mao.com collected 2026-10-04 02:24:38 UTC 274,521 bytes, 10,473 lines download raw bytes

1/***************************************************************************
2 *
3 *	Cargo Namespace
4 *
5 **************************************************************************/
6var Cargo = { };
7
8// Create a default objects 
9Cargo.o 					= { };  // Default object definition (for backbone init)
10Cargo.o.model 				= { };  // Default object definition (for backbone init)
11Cargo.o.collection 			= { };  // Default object definition (for backbone init)
12Cargo.o.view 				= { };  // Default object definition (for backbone init)
13
14Cargo.Router 				= { };	// All main router methods
15Cargo.Template 				= { };	// Template controllers
16Cargo.Helper 				= { };	// Helper methods
17Cargo.API 	 				= { };	// API communication
18Cargo.Event  				= { };	// Backbone events (History, close, etc)
19
20Cargo.Collection 			= { };	// Collections base
21Cargo.Collection.Page 		= { };	// Project collection pages (for pagination in memory)
22Cargo.Collection.NavPage	= { };	// Navigation collection pages (for pagination in memory)
23Cargo.Model 				= { };	// Models base
24Cargo.View  				= { };	// Views base
25
26/**
27 *	Action namespace
28 */
29var Action = window.Action || { };
30
31 /***************************************************************************
32  *
33  *	Cargo core functionality
34  *	This contains all core functionality native to all Cargo websites
35  *	For the most part, these methods are reproductions of the existing
36  *	functionality from site.package 
37  *
38 **************************************************************************/
39
40/*
41	- Slideshow		: _cargo/_core/slideshow.js 
42	- Handlebars	: _cargo/_core/handlebars.js 
43	- Fullscreen  	: freshbox jQuery plugin in this file
44	- Lightbox 		: freshbox jQuery plugin in this file
45	- Keyboard nav 	: this file
46	- Video player	: _cargo/_core/video.js 
47	- Log 			: this file
48 */
49
50 Cargo.Core = {
51
52 	/**
53	 *	Make a safe logging method
54	 */
55	log : function(args) {
56		if (typeof console == 'undefined' || !window.console) {
57			return;
58		}
59		console.log(args);
60	},
61
62	/**
63	 *	Keyboard shortcuts
64	 *	Add and Remove
65	 *	Add Usage:
66	 *	Cargo.Core.KeyboardShortcut.Add("Shift+F", 70, function(){ });
67	 *	Remove Usage:
68	 *	Cargo.Core.KeyboardShortcut.Remove("Shift+F");
69	 */
70	KeyboardShortcut : {
71		Add : function(key, keycode, fn) {
72			var data = {
73				'type'				: 'keydown', 
74				'disable_in_input'	: true, 
75				'keycode' 			: keycode
76			};
77
78			shortcut.add(key, fn, data);
79		},
80
81		Remove : function(key) {
82			shortcut.remove(key);
83		},
84
85		/*
86		 *	Base set of keyboard shortcuts
87		 */
88		InitDefaults : function() {
89			this.Add("J", 74, function() { Action.Project.Next() });
90			this.Add("K", 75, function() { Action.Project.Prev() });
91			this.Add("R", 82, function() { Action.Project.Random() });
92			this.Add("X", 88, function() { Action.Project.Index() });
93			this.Add("I", 73, function() { Action.Project.Index() });
94
95			this.Add("Shift+F", 70, function() { 
96				if ( ! $("body").hasClass("rail_active") ) {
97					Cargo.app.Rail.Events.trigger("show_rail_following");
98				} else {
99					Cargo.app.Rail.Events.trigger("rail:hide");
100				}
101			});
102			this.Add("Shift+G", 71, function() { Cargo.Helper.GoToUrl("images"); });
103
104			this.Add("Shift+A", 65, function() { 
105				if(typeof require == "function" && Cargo.API.Config.is_editor) {
106					if(parent.location == window.location) {
107						Cargo.Event.trigger("add_history", "admin");
108					}
109				} else {
110					Cargo.Helper.GoToUrl("admin"); 
111				}
112			});
113			
114			this.Add("Shift+E", 69, function() { 
115				if(Cargo.Model.Site.GetIsEditor() && Cargo.Model.Project.GetId() && parent.location == window.location) {
116					Cargo.Helper.GoToUrl("admin/"+Cargo.Model.Project.GetId());
117				} });
118
119			this.Add("Shift+H", 72, function() { Cargo.Helper.GoToUrl("admin/html"); });
120			this.Add("Shift+C", 67, function() { Cargo.Core.loadInspector3(); });
121			this.Add("Shift+D", 68, function() { Cargo.Helper.GoToUrl("admin/designs"); });
122			this.Add("Shift+X", 88, function() { Cargo.Helper.GoToUrl(""); });
123			this.Add("Shift+I", 73, function() { loadInspector(); });
124			
125			this.Add("Esc", 27, function() { 
126				$("a.follow_popup_cancel").click(); 
127					if ( ! $("#freshbox").length > 0 ) {
128					Cargo.app.Rail.Events.trigger("rail:hide");
129				}
130			});
131
132		}
133	},
134
135	loadEditor: function(){
136		var iframe = document.createElement('iframe');
137
138		//Cargo.Rail.Init = function(){};
139		
140
141		iframe.setAttribute('id', 'content_frame');
142
143		iframe.src 				= Cargo.Helper.GetBaseUrl(true, true);
144		iframe.frameBorder 		= '0';
145		iframe.style.display 	= 'none';
146		iframe.style.width 		= 'calc(100% - 400px)';
147		iframe.style.height 	= '100%';
148		iframe.style.left 		= '400px';
149		iframe.style.left 		= '0';
150		iframe.style.top 		= '0';
151		iframe.style.position  	= 'fixed';
152
153		var nodesToRemove = [],
154			i;
155
156		for(i = 0; i < document.body.childNodes.length; i++){
157			if(document.body.childNodes[i] !== iframe){
158				nodesToRemove.push(document.body.childNodes[i]);
159			}
160		}
161
162		[].forEach.call(document.querySelectorAll('link[rel="stylesheet"], style'), function(node){
163			nodesToRemove.push(node);
164		});
165
166		require(["/_jsapps/editor/main.bootstrap.js"], function() {
167
168		});
169
170		iframe.onload = function(){
171			
172			iframe.style.display = '';
173
174			var html = document.getElementsByTagName("html")[0];
175
176			if(html !== undefined){
177				html.removeAttribute('style');
178			}
179
180			nodesToRemove.forEach(function(node){
181				if(node.parentNode !== null){
182					node.parentNode.removeChild(node);
183				}
184			});
185
186		}
187
188		document.body.appendChild(iframe);
189	},
190
191	/**
192	 *	Replace the old loading animations with retnified versions
193	 *	
194	 *	Note: This sits in a timeout because it won't fire correctly
195	 *		on doc.ready 
196	 */
197	ReplaceLoadingAnims : {
198		init : function(specificSelection) {
199			// First, check for existing spinners
200			$(".retinaSpinner").each(function() {
201				if (window.devicePixelRatio > 1 ){
202					$(this).addClass('rotating');
203				}
204			});
205
206			setTimeout(function() {
207				if(Cargo.Helper && !Cargo.Helper.isOldIE()){
208				 	var spinnerEl = $('<div></div>');
209
210					for(var i = 0; i < 8; i++){
211						spinnerEl.append(
212							'<div style="position: absolute; top: -2px;">' +
213								'<div class="spinner_circle part' + (i + 1) + '" style="-webkit-transform: rotate(' + (i * 45) +'deg) translate(6px, 0px); transform: rotate(' + (i * 45) +'deg) translate(6px, 0px);"></div>' +
214							'</div>');
215					}
216					
217					if(typeof specificSelection == "undefined" && $('img[src$="/loadingAnim.gif"]').length > 0){
218						$('img[src$="/loadingAnim.gif"]').each(function(){
219							Cargo.Core.ReplaceLoadingAnims.replace(this, spinnerEl);
220						});
221					} else if(typeof specificSelection != "undefined") {
222						specificSelection.each(function(){
223							Cargo.Core.ReplaceLoadingAnims.replace(this, spinnerEl);
224						});
225					}
226
227					Cargo.Event.trigger("loading_anims_replaced");
228				}
229			}, 100);
230		}, 
231
232		replace : function(which, spinnerEl) {
233			var thisSpinner = spinnerEl.clone();
234
235			thisSpinner.attr('class', 'retinaSpinner ' + $(which).attr('class'));
236			thisSpinner.attr('id', $(which).attr('id'));
237			thisSpinner.attr('style', $(which).attr('style'));
238			thisSpinner.attr('data-loading', $(which).attr('data-loading'));
239
240			if (window.devicePixelRatio > 1 ){
241				thisSpinner.addClass('rotating');
242			}
243
244			// replace the old GIF
245			$(which).replaceWith(thisSpinner);
246		}
247	},
248
249	/**
250	 *	This method is called by events.js on project_load_complete
251	 *	It loads the template, and loads the view into Memory
252	 *	The view for the freshbox handles the click events
253	 */
254	CheckAndInitFreshbox : function() {
255		// Remove the fullscreen from offline mode
256		if( Cargo.API.Config.offline ) {
257			return false;
258		}
259		
260		/* Check for a fullscreen button perpare it */
261		if( $("#fullscreen").length > 0 || Cargo.Model.DisplayOptions.GetLightboxView() ) {
262			/* Prevent multiple instantiation */
263			if(typeof Cargo.View.Fullscreen == "undefined") {
264				/* Add the template to the stack */
265				Cargo.Template.Add( "Freshbox" );
266
267				/* Make sure we have loaded the freshbox */
268				Cargo.Template.Load( "Freshbox", Cargo.API.GetFreshboxPath() );
269
270				/* Initialize the fullscreen view */
271				Cargo.View.Fullscreen = new Cargo.o.view.Freshbox({
272					el 		:  "body",
273					model 	:  Cargo.Model.Project
274				});
275			}
276		}
277
278	},
279
280	/**
281	 *	This finds the search data-view and loads the search view 
282	 *	if one is not already loaded
283	 */
284	FindAndLoadSearch : function(first_load) {
285		if(typeof Cargo.View.Search != "undefined" && !first_load) {
286			return false;
287		}
288
289		if($("[data-view='search']").length > 0) {
290			Cargo.View.Search = new Cargo.o.view.Search({ 
291				el : "[data-view='search']"
292			});
293		
294		} else if($("#search_form").length > 0) {
295			Cargo.View.Search = new Cargo.o.view.Search({ 
296				el : "#search_form"
297			});
298		}
299	},
300
301	/**
302	 *	This method is meant to reseed the project data, rather then refreshing the whole page
303	 *	This is called from the admin frameset upon project ordering and toggle
304	 */
305	ReseedProjectModels : function() {
306		// Reset the page and offset count
307		Cargo.API.Config.current_offset = 0;
308		Cargo.API.Config.current_page = 0;
309		Cargo.API.Config.site_recently_created = false;
310		
311		// Get the URL of the API path
312		Cargo.Collection.Navigation.url = Cargo.API.GetNavigationCargoPath( 0 );
313		Cargo.Collection.Projects.url = Cargo.API.GetProjectsPath( 0 );
314		
315		// Fetch the data
316		Cargo.Collection.Projects.fetch({reset: true, complete: function() { 
317			Cargo.Event.Common(); 
318			Cargo.Event.trigger("reseed_project_complete");
319		}});
320		Cargo.Collection.Navigation.fetch({reset: true, complete: function() { 
321			Cargo.Event.trigger("reseed_navigation_complete");
322		}});
323		
324	},
325
326	loadInlineEditor: function(){
327		
328		console.log('SIKE!');
329		
330		/*$('head').append( $('<link rel="stylesheet" type="text/css" />').attr('href', '/_jsapps/inline-editor/cargoEditor.css') );
331		$('head').append( $('<link rel="stylesheet" type="text/css" />').attr('href', '/_jsapps/inline-editor/cargoEditor_plugins_all.css') );
332
333		$.getScript( "/_jsapps/inline-editor/cargoEditor.js", function( data, textStatus, jqxhr ) {
334
335			$('head').append( $('<script/>').attr('src', '/_jsapps/inline-editor/cargoEditor_plugins_all.js') );
336
337			// attach editors to projects.
338			$('[data-ce-host="true"]').each(function() {
339				if (!$(this).is('[contenteditable]')) {
340					CargoEditor.addEditor(this);
341				}
342			});
343			
344		});*/
345 		
346
347	},
348
349	loadInspector3 : function(){
350
351		if(window.editor !== undefined){
352			return false;
353		}
354
355		if(Cargo.API.Config.network == "Persona") {
356			Cargo.Helper.GoToUrl("/admin/design/type-layout/css");
357			return;
358		}
359
360		Cargo.Core.Inspector3.load({
361
362			onInit: function(inspector){
363
364				function addClickBehavior(){
365
366					// replace all click events handled by Backbone with dblClick events.
367					_.each(Cargo.View, function(view){
368						if(typeof view.undelegateEvents == "undefined") {
369							return;
370						}
371
372						// remove all events bound to this view
373						view.undelegateEvents();
374
375						_.each(view.events, function(method, key){
376							 var match = key.match(delegateEventSplitter);
377
378							if(match !== null) {
379								// found a click event. Add a dblclick in place.
380								if(match[1] === "click") {
381									view.events['dblclick ' + match[2]] = method;
382									delete view.events[key];
383								}
384							}
385						});
386
387						// re-attached all events including the modified ones.
388						view.delegateEvents();
389					});
390
391					$("a:not([rel], #splash)").unbind("click").off('dblclick').on("dblclick", function() {
392
393						var href = this.getAttribute('href');
394
395						if(href !== null && href !== '#' && href !== '' && href.indexOf('javascript:') === -1){
396
397							var cargoURL				= (Cargo.hasOwnProperty('Helper') ? Cargo.Helper.GetUrl() : Cargo.Config.GetCookieUrl()),
398								customDomain			= inspector.options.coreWindow.location.hostname.search(/cargocollective\.com/i) === -1,
399								sameDomain				= (this.hostname.search(/cargocollective\.com/i) !== -1 ? this.pathname.split('/')[1] === cargoURL : this.hostname === inspector.options.coreWindow.location.hostname),
400								subLocation 			= this.pathname.split('/')[(customDomain ? 1 : 2)],
401								forbiddenSubLocation	= (subLocation === undefined ? false : subLocation.search(/^(following|admin|images)$/i) !== -1);
402
403							//console.log(cargoURL, customDomain, subLocation, sameDomain, forbiddenSubLocation);
404
405							if(sameDomain && !forbiddenSubLocation) {
406								inspector.options.coreDocument.location.href = this.getAttribute('href');
407							} else {
408								var newTab = inspector.options.coreWindow.open(href, '_blank');
409									newTab.focus();
410							}
411
412						}
413					});
414
415					$('img').unbind('click');
416
417					if(Cargo.Core.Slideshow.hasOwnProperty('SlideshowObjects')){
418						$.each(Cargo.Core.Slideshow.SlideshowObjects, function(key, val){
419							val.elements.$thumbnails.on('dblclick', function(){
420								val.hideThumbnails();
421								val.showContainer();
422							});
423						})
424					}
425					
426				}
427
428				var $ = inspector.options.coreWindow.$,
429					Cargo = inspector.options.coreWindow.Cargo,
430					delegateEventSplitter = /^(\S+)\s*(.*)$/;
431
432				setTimeout(function(){
433					Cargo.app.Rail.Events.on("toolset:loaded", function() { 
434						$("#toolset, #toolset *, #toolset_menu, #toolset_menu *, #design_panel, #design_panel *").unbind().off();
435						$("#toolset_follow_pair a, #toolset_follow a").unbind();
436
437						$("#toolset").fadeTo('fast', 0.3).css('pointer-events', 'none');
438
439					});
440
441					$("#toolset, #toolset *, #toolset_menu, #toolset_menu *, #design_panel, #design_panel *").unbind().off();
442					$("#toolset_follow_pair a, #toolset_follow a").unbind();
443					$("#toolset").fadeTo('fast', 0.3).css('pointer-events', 'none');
444					
445				}, 500);
446
447				$("#toolset").fadeTo('fast', 0.3).css('pointer-events', 'none');
448
449
450				Cargo.Event.on("pagination_complete", function( new_page ) { 
451					addClickBehavior();
452				});
453
454				Cargo.Event.on("project_load_complete", function( new_page ) { 
455					addClickBehavior();
456				});
457
458				$('.slideshow *').unbind().off();
459
460				addClickBehavior();
461			}
462		}); 
463	},
464
465	/**
466	 *	Loads the inspector
467	 */
468	LoadInspector : function() {
469
470		// 302'd
471		this.loadInspector3();
472
473	},
474
475	HiRes : {
476		active : false,
477
478		Queue : {
479			STATE_LOADING: 0,
480			STATE_COMPLETE: 1,
481			STATE_ERROR: -1,
482			ID: 'hi-res-queue',
483			FILE_EXTENSION: /(?:\.([^.]+))?$/,
484			FILE_NAME: /(?:\/([^\/]+))?$/,
485			element: null,
486
487			init: function() {
488				$('body').append('<div id="' + this.ID + '" style="display: none;" />');
489				this.element = $('#' + this.ID);
490			},
491
492			search: function() {
493				if ( ! Cargo.Core.HiRes.active) {
494					return;
495				}
496
497				// $('[data-mid], div.thumb_image img').map(function() {
498				$('[data-hi-res]').map(function() {
499					// @TODO Determine means to ignore slideshow thumbnails?
500					if (this.nodeName === 'IMG') {
501						Cargo.Core.HiRes.Queue.push(this);
502					}
503				});
504			},
505
506			push: function(img) {
507				var img_src = img.src
508				// var hi_res_src = img_src.replace(this.FILE_EXTENSION, '_2x.$1');
509				var hi_res_src = $(img).data('hi-res');
510				var mid = $(img).data('mid');
511				// Hack to support thumbnails
512				var queue_id = mid ? 'hi-res-queue-' + mid : 'hi-res-queue-' + (img_src.match(this.FILE_NAME)[1]).replace(/[\W\.(_2x)]/g, '');
513				var queue_img = $('#' + queue_id);
514
515				if (queue_img.length) {
516					switch (queue_img.attr('data-hi-res-status')) {
517						case this.STATE_COMPLETE:
518							return this.handleCachedImage(queue_img, img);
519						case this.STATE_LOADING:
520							return this.handleDuplicateImage(queue_img, img);
521						default:
522							return;
523					}
524				} else {
525					this.element.append('<img id="' + queue_id + '" data-hi-res-status="' + this.STATE_LOADING + '">');
526					queue_img = $('#' + queue_id);
527					queue_img.data('lo-res-images', [img]);
528					queue_img.one('load', function() {
529						Cargo.Core.HiRes.Queue.handleLoad(queue_img);
530					}).one('error', function() {
531						Cargo.Core.HiRes.Queue.handleError(queue_img);
532					}).attr('src', hi_res_src);
533				}
534			},
535
536			handleCachedImage: function(queue_img, img) {
537				img.src = queue_img.attr('src');
538			},
539
540			handleDuplicateImage: function(queue_img, img) {
541				var lo_res_images = queue_img.data('lo-res-images');
542				lo_res_images.push(img);
543				queue_img.data('lo-res-images', lo_res_images);
544			},
545
546			handleLoad: function(queue_img) {
547				var hi_res_src = queue_img.attr('src');
548				queue_img.attr('data-hi-res-status', this.STATE_COMPLETE);
549				queue_img.data('lo-res-images').map(function(img) {
550					img.src = hi_res_src;
551				});
552			},
553
554			handleError: function(queue_img) {
555				queue_img.attr('data-hi-res-status', this.STATE_ERROR);
556			}
557		},
558
559		Loader : {
560			init: function() {
561				checkPixelRatio();
562
563				function checkPixelRatio() {
564					var prevDevicePixelRatio = parseFloat($.cookie('userPixelRatio'));
565					
566					if(prevDevicePixelRatio != window.devicePixelRatio) {
567						if(window.devicePixelRatio > 1.5) {
568							enableHiRes();
569						} else {
570							disableHiRes();
571						}
572					}
573
574					$.cookie('userPixelRatio', window.devicePixelRatio, {path: '/'});
575				}
576
577				function disableHiRes() {
578					$.post("/dispatch/cargosite/cargoUseHiRes", { use : false }, function() {}, "json");
579				}
580
581				function enableHiRes() {
582					$.post("/dispatch/cargosite/cargoUseHiRes", { use : true }, function() {}, "json");
583
584					$(document).bind("projectLoadComplete", function(e, pid) {
585						Cargo.Core.HiRes.Queue.search();
586					});
587
588					$(document).bind("pageChangeComplete", function(e, newpage) {  
589						Cargo.Core.HiRes.Queue.search();
590					});
591
592					$(document).bind("paginationComplete", function(e) {  
593						Cargo.Core.HiRes.Queue.search();
594					});
595
596					Cargo.Core.HiRes.active = true;
597					Cargo.Core.HiRes.Queue.init();
598					Cargo.Core.HiRes.Queue.search();
599				}
600
601				// listen to changes while page is loaded
602				try {
603					var MediaQueryList = window.matchMedia("(-webkit-min-device-pixel-ratio: 1.5)");
604
605					if (typeof MediaQueryList.addListener === "function") {
606						MediaQueryList.addListener(function(){
607							checkPixelRatio();
608						});
609					}
610				} catch(err) {
611					// matchMedia does not exist
612				}
613			}
614		}
615		
616	},
617
618	EU_cookie_notification : function() {
619		if($('head meta[name="en_cookie_notification"]').length > 0) {
620			if(!localStorage.getItem('eu_cookie_notification')) {
621				var class_name = $('head meta[name="en_cookie_notification"]').attr('position'),
622					content = $('head meta[name="en_cookie_notification"]').attr('description'),
623					link = $('head meta[name="en_cookie_notification"]').attr('policy_link');
624				content = content.replace('[policy_link]', '<a href="'+link+'" target="_blank">').replace('[/policy_link]','</a>');
625
626				$('body').prepend('<div id="eu_cookie_notification" class="'+class_name+'">'+content+'</div>');
627
628				$("#eu_cookie_notification").click(function(e) {
629					if($(e.target).is('a')){
630						return true;
631					}
632					
633					$(this).remove();
634					localStorage.setItem('eu_cookie_notification', 'true');
635				});
636			}
637		}
638	}
639	
640
641
642 };
643
644
645 
646
647Cargo.o.analytics = Backbone.View.extend({
648
649	enabled: true,
650
651	Config: {
652		id: 'UA-1837333-7',
653		name: '__cargo_analytics',
654	},
655
656	initialize: function() {
657		
658		if(Cargo.API) {
659
660			this.enabled = Cargo.API.GetUseGoogleAnalytics();
661
662			if (Cargo.API.Config.is_domain) {
663				this.Config.custom_url = '/' + document.location.host;
664			}
665
666			if(Cargo.API.Config.network == "Persona") {
667				this.Config.id = 'UA-1837333-14';
668			}
669		} else {
670			this.Config.id = 'UA-1837333-14';
671		}
672
673		if(this.enabled) {
674
675			// Include analytics.js
676			(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
677			(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
678			m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
679			})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
680
681			// Create Google Analytics tracker, send initial pageview
682			ga('create', this.Config.id, 'auto', {'name': this.Config.name, 'cookieName': this.Config.name});
683			
684			this.pageview(false);
685
686		}
687
688	},
689
690	pageview: function(send_to_all_trackers, url) {
691
692
693		var tracker = this.Config.name,
694			pathname = document.location.pathname.replace(/\/$/, '');
695
696
697		if (typeof url !== 'undefined' && ! send_to_all_trackers) {
698			
699			if(this.enabled) {
700				
701				ga(this.Config.name + '.send', 'pageview', {
702					'page': url,
703					'dimension3': 'Cargo 1'
704				});
705
706			}
707
708			return;
709		}
710
711		if(this.enabled) {
712			
713			if (typeof this.Config.custom_url !== 'undefined') {
714				ga(this.Config.name + '.send', 'pageview', {
715					'page': this.Config.custom_url + pathname,
716					'dimension3': 'Cargo 1'
717				});
718			} else {
719				ga(this.Config.name + '.send', 'pageview', {
720					'dimension3': 'Cargo 1'
721				});
722			}
723
724		}
725
726		// Send data for any other trackers
727		if (typeof ga == 'function' && send_to_all_trackers) {
728			ga(function() {
729				ga.getAll().map(function(g) {
730					var t = g.get('name');
731					if (t != tracker) {
732						ga(t + '.send', 'pageview');
733					}
734				});
735			});
736
737			// Push data to legacy trackers
738			if (typeof _gaq !== 'undefined') {
739				_gaq.push(['_trackPageview', pathname]);
740			}
741		}
742	}
743});
744
745
746/***************************************************************************
747 *
748 *	API
749 *	It contains all methods related to the API
750 *
751 **************************************************************************/
752
753/**
754 * @namespace API
755 * @memberOf Cargo
756 * @extends {Backbone.View} 
757 */
758
759Cargo.o.api = Backbone.View.extend({
760
761	/**
762	 *  Set the config values for the API
763	 * 
764	 *  @private
765	 *  
766	 *  @memberOf Cargo.API
767	 *  @example Cargo.API.Config.api_path;
768	 */
769	Config : {
770		Version 			: "v0",
771		api_path 			: "/_api",
772		cargo_id 			: "",
773		cargo_url 			: "",
774		cargo_url_override 	: null,
775		filter 				: "",
776		search_term 		: "",
777		route_name 			: "",
778		template_basename 	: "design",
779		current_offset 		: 0, // Set in the defaults which is loaded on page load
780		current_page 		: 1, // Set in the defaults which is loaded on page load
781		start_project 		: null,
782		is_filter 			: false,
783		is_search 			: false,
784		is_editor 			: false,
785		is_solo 			: false,
786		is_direct_link 		: false,
787		is_custom_route 	: false,
788		admin_edit_project 	: false,
789		first_load_finished : false
790	},
791
792	/**
793	 *	Load the API defaults by extending the Config vars using underscore
794	 * 
795	 *  @private
796	 *  
797	 *  @memberOf Cargo.API
798	 */
799	initialize : function() {
800		_.extend(this.Config, $.parseJSON( $("script[data-set='defaults']").html() ));
801	},
802
803	/**
804	 *	returns the raw JSON data, just in case you need it
805	 * 
806	 *  @private
807	 *  
808	 *  @memberOf Cargo.API
809	 *
810	 * @param {string} [path] The path to the API endpoint
811	 * @param {function} [callback] Callback function to handle return value
812	 *
813	 * @example Cargo.API.fetch(Cargo.API.GetSitePath(), myCallback);
814	 */
815	fetch : function( path, callback ) {
816		$.getJSON( path, callback );
817	},
818
819	GetUseGoogleAnalytics: function(){
820
821		return this.Config._use_google_analytics == 1;
822
823	},
824
825	/*
826	 * ######################################################################
827	 *
828	 *	PATHS
829	 *
830	 * ######################################################################
831	 */
832	/**
833	 *  Gets the path for the Site API Model
834	 * 
835	 *  @public
836	 *  
837	 *  @memberOf Cargo.API
838	 *
839	 *  @return {string}
840	 *  @example Cargo.API.GetSitePath();
841	 */
842	GetSitePath : function() {
843		var c 		= this.Config; // shorthand
844		var path 	= c.api_path + "/" + c.Version + "/site/" + c.cargo_url;
845		return path;
846	},
847
848	/**
849	 *  Gets the path for the Design API Model
850	 * 
851	 *  @public
852	 *  
853	 *  @memberOf Cargo.API
854	 *
855	 *  @return {string}
856	 *  @example Cargo.API.GetDesignPath();
857	 */
858	GetDesignPath : function() {
859		var c 		= this.Config; // shorthand
860		var url 	= (c.cargo_url_override) ? c.cargo_url_override : c.cargo_url;
861		var path 	= c.api_path + "/" + c.Version + "/designs/" + url;
862		return path;
863	},
864
865	/**
866	 *  Gets the path for the Files API
867	 * 
868	 *  @public
869	 *  
870	 *  @memberOf Cargo.API
871	 *
872	 *  @param {string} [file] The name of the file
873	 *  @return {string}
874	 *  @example Cargo.API.GetDesignFile(file);
875	 */
876	GetDesignFile : function(file) {
877		var path = this.GetDesignPath() + "?file=" + file;
878		return path;
879	},
880
881	/**
882	 *  Gets the path for the Template files
883	 * 
884	 *  @public
885	 *  
886	 *  @memberOf Cargo.API
887	 *
888	 *  @return {string}
889	 *  @example Cargo.API.GetTemplatePath();
890	 */
891	GetTemplatePath : function() {
892		var c = this.Config; // shorthand
893		var route_name = (c.route_name) ? c.route_name + "/" : "";
894		return this.GetDesignFile(".templates/" + c.template_basename + "/" + route_name);
895	},
896
897	/**
898	 *  Gets the path for the Fullsreen handlebars
899	 * 
900	 *  @public
901	 *  
902	 *  @memberOf Cargo.API
903	 *
904	 *  @return {string}
905	 *  @example Cargo.API.GetFreshboxPath();
906	 */
907	GetFreshboxPath : function() {
908		return '/_jsapps/api/_templates/Freshbox.handlebars';
909	},
910
911	/**
912	 *	Gets the design path + a file
913	 *	optionally adds the "to_var" param to pass to the api
914	 * 
915	 *  @public
916	 *  
917	 *  @memberOf Cargo.API
918	 *
919	 *  @param {string} [file] The name of the file requested
920	 *  @param {boolean} [to_var] Optional value to return the content as a javascript var
921	 *  @return {string}
922	 *  @example Cargo.API.GetDesignAndFile(file, to_var);
923	 */
924	GetDesignAndFile : function(file, to_var) {
925		var file_path = this.GetTemplatePath() + file;
926		if( to_var ) {
927			file_path += "&to_var=true";
928		}
929
930		return file_path;
931	},
932
933	/**
934	 *  Gets the path for the Display Options API
935	 * 
936	 *  @public
937	 *  
938	 *  @memberOf Cargo.API
939	 *
940	 *  @return {string}
941	 *  @example Cargo.API.GetDisplayOptionsPath();
942	 */
943	GetDisplayOptionsPath : function() {
944		var c 		= this.Config; // shorthand
945		var path 	= c.api_path + "/" + c.Version + "/displayoptions/" + c.cargo_url;
946		return path;
947	},
948
949	/**
950	 *  Gets the path for the Images API
951	 * 
952	 *  @public
953	 *  
954	 *  @memberOf Cargo.API
955	 *
956	 *  @param {number} [offset] Offset for pagination
957	 *  @param {number} [limit] Limit for pagination
958	 *  @return {string}
959	 *  @example Cargo.API.GetImagesPath();
960	 */
961	GetImagesPath : function(offset, limit, sort) {
962		offset = (offset) ? offset : this.Config.current_offset;
963		limit  = (limit)  ? limit  : Cargo.Helper.GetPageLimit();
964		sort   = (sort)  ? sort  : 'newest';
965
966		var path = this.GetSitePath() + "/images";
967			path += "?offset=" + offset + "&limit=" + limit + "&sort=" + sort;
968
969		return path;
970	},
971
972	/**
973	 *  Gets the path for the Projects API
974	 * 
975	 *  @public
976	 *  
977	 *  @memberOf Cargo.API
978	 *
979	 *  @param {number} [offset] Offset for pagination
980	 *  @param {number} [limit] Limit for pagination
981	 *  @param {string} [type] Optional type param (page, project)
982 	 *  @param {string} [filter] Optional filter name
983	 *  @param {string} [set] Optional set name
984	 *  @param {string} [route] Optional route name
985	 *  @return {string}
986	 *  @example Cargo.API.GetProjectsPath(offset, limit, type, filter, set, route);
987	 */
988	GetProjectsPath : function(offset, limit, type, filter, set, route) {
989		/* If this is a solo link, load single project */
990		if( Cargo.Helper.IsSololink() ) {
991			return this.GetSingleProjectPath( Cargo.Model.Project.GetProjectUrl() );
992		}
993
994		offset = (offset) ? offset : this.Config.current_offset;
995		limit  = (limit)  ? limit  : Cargo.Helper.GetPageLimit();
996		type   = (type)   ? type   : 'project';
997		filter = (filter) ? filter : this.Config.filter;
998		set    = (set)    ? set    : this.Config.set;
999		route  = (route)  ? route  : Cargo.Helper.GetRouteName();
1000
1001		// Cargo.Core.log(Cargo.Helper.GetCurrentRoute()+" -- "+Cargo.Helper.GetRouteName());
1002
1003		var path = this.GetSitePath() + "/projects?type=" + type;
1004			path += "&offset=" + offset + "&limit=" + limit;
1005
1006		if( filter != "" && type != "page") {
1007			path += "&filter=" + encodeURIComponent(filter);
1008		}
1009
1010		if( set && type != "page" ) {
1011			path += "&set=" + encodeURIComponent(set);
1012		}
1013
1014		// if( route ) {
1015		// 	path += "&route=" + encodeURIComponent(route);
1016		// }
1017
1018		if( this.Config.site_recently_created ) {
1019			path += "&site_recently_created=true";
1020		}
1021
1022		return path;
1023	},
1024
1025	/**
1026	 *  Gets the path for the Admin Project List API
1027	 * 
1028	 *  @public
1029	 *  
1030	 *  @memberOf Cargo.API
1031	 *
1032	 *  @param {number} [offset] Offset for pagination
1033	 *  @param {number} [limit] Limit for pagination
1034	 *  @return {string}
1035	 *  @example Cargo.API.GetAdminListPath(offset, limit);
1036	 */
1037	GetAdminListPath : function(offset, limit) {
1038		offset = (offset) ? offset : this.Config.current_offset;
1039		limit  = (limit)  ? limit  : Cargo.Helper.GetPageLimit();
1040		
1041		var path = this.GetSitePath() + "/admincollection";
1042			// path += "?offset=" + offset + "&limit=" + limit;
1043
1044		return path;
1045	},
1046
1047	/**
1048	 *  Gets the path for the Following Rail
1049	 * 
1050	 *  @public
1051	 *  
1052	 *  @memberOf Cargo.API
1053	 *
1054	 *  @return {string}
1055	 *  @example Cargo.API.GetRailPath();
1056	 */
1057	GetRailPath : function() {
1058		return '/dispatch/rail/getRail/'+this.Config.cargo_url;
1059	},
1060
1061	/**
1062	 *  Gets the path for the Site Search API
1063	 * 
1064	 *  @public
1065	 *  
1066	 *  @memberOf Cargo.API
1067	 *
1068	 *  @param {string} [term] Search term
1069	 *  @param {number} [offset] Offset for pagination
1070	 *  @param {number} [limit] Limit for pagination
1071	 *  @param {string} [sort] The sort type ("relevant" || "recent" || "mostviewed")
1072	 *  @return {string}
1073	 *  @example Cargo.API.GetSearchPath();
1074	 */
1075	GetSearchPath : function(term, offset, limit, sort) {
1076		term 	= (term) ? term : this.Config.search_term;
1077		offset 	= (offset) ? offset : this.Config.current_offset;
1078		limit 	= (limit)  ? limit  : Cargo.Helper.GetPageLimit();
1079		sort 	= (sort)  ? sort  : 'relevant'; // 
1080
1081		var path = this.GetSitePath() + "/search";
1082			path += "?term=" + term + "&offset=" + offset + "&limit=" + limit + "&sort=" + sort;
1083
1084		return path;
1085	},
1086
1087	/**
1088	 *  Gets the path for a single Project API
1089	 * 
1090	 *  @public
1091	 *  
1092	 *  @memberOf Cargo.API
1093	 *  @param {string} [purl] The Project URL
1094	 *  @param {string} [filter] Optional filter name
1095	 *  @param {string} [set] Optional set name
1096	 *  @return {string}
1097	 *  @example Cargo.API.GetSingleProjectPath(purl, filter, set);
1098	 */
1099	GetSingleProjectPath : function(purl, filter, set) {
1100		var c 		= this.Config; // shorthand
1101		var path 	= c.api_path + "/" + c.Version + "/projects/";
1102		filter = (filter) ? filter : this.Config.filter;
1103		set    = (set)    ? set    : this.Config.set;
1104
1105		if(typeof purl != "undefined" ) path += purl;
1106		path += "?url=" + c.cargo_url;
1107		
1108		if( filter != "" ) {
1109			path += "&filter=" + encodeURIComponent(filter);
1110		}
1111
1112		if( set ) {
1113			path += "&set=" + encodeURIComponent(set);
1114		}
1115
1116		return path;
1117	},
1118
1119	/**
1120	 *	Cargo navigation uses Display Options as it’s guide. 
1121	 *	It will pull all pages and links outside of sets and filters. 
1122	 *	This is the main style of navigation data for every website. 
1123	 * 
1124	 *  @public
1125	 *  
1126	 *  @memberOf Cargo.API
1127	 *  @param {number} [offset] Offset for pagination
1128	 *  @param {number} [limit] Limit for pagination
1129	 *  @param {string} [set] Optional set name
1130	 *  @param {string} [filter] Optional filter name
1131	 *  @return {string}
1132	 *  @example Cargo.API.GetNavigationCargoPath(offset, limit, set);
1133	 */
1134	GetNavigationCargoPath : function(offset, limit, set, filter) {
1135		/* If this is a solo link, load single project */
1136		if( Cargo.Helper.IsSololink() ) {
1137			return this.GetSingleProjectPath( Cargo.Model.Project.GetProjectUrl() );
1138		}
1139
1140		offset = (offset) ? offset : this.Config.current_offset;
1141		limit  = (limit)  ? limit  : Cargo.Helper.GetPageLimit();
1142		set    = (set)    ? set    : this.Config.set;
1143		filter = (filter) ? filter : Cargo.Helper.GetActiveFilter();
1144
1145		var path = this.GetSitePath() + "/navigation";
1146			path += "?offset=" + offset + "&limit=" + limit;
1147
1148		if( set ) {
1149			path += "&set=" + encodeURIComponent(set);
1150		}
1151
1152		if( filter ) {
1153			path += "&filter=" + encodeURIComponent(filter);
1154		}
1155
1156		return path;
1157	},
1158
1159	/**
1160	 *	This method grabs only the exact navigation asked for. 
1161	 *	This is used when only the data inside of a single set, 
1162	 *	or single filter are required. 
1163	 * 
1164	 *  @public
1165	 *  
1166	 *  @memberOf Cargo.API
1167	 *  @param {number} [offset] Offset for pagination
1168	 *  @param {number} [limit] Limit for pagination
1169	 *  @param {string} [set] Optional set name
1170	 *  @return {string}
1171	 *  @example Cargo.API.GetNavigationOnlyPath(offset, limit, set);
1172	 */
1173	GetNavigationOnlyPath : function(offset, limit, set) {
1174		/* If this is a solo link, load single project */
1175		if( Cargo.Helper.IsSololink() ) {
1176			return this.GetSingleProjectPath( Cargo.Model.Project.GetProjectUrl() );
1177		}
1178
1179		offset = (offset) ? offset : this.Config.current_offset;
1180		limit  = (limit)  ? limit  : Cargo.Helper.GetPageLimit();
1181		set    = (set)    ? set    : this.Config.set;
1182
1183		var path = this.GetSitePath() + "/only_navigation";
1184			path += "?offset=" + offset + "&limit=" + limit;
1185
1186		if( set ) {
1187			path += "&set=" + encodeURIComponent(set);
1188		}
1189
1190		return path;
1191	},
1192
1193	/**
1194	 *	This method grabs all set data
1195	 * 
1196	 *  @public
1197	 *  
1198	 *  @memberOf Cargo.API
1199	 *  @param {number} [offset] Offset for pagination
1200	 *  @param {number} [limit] Limit for pagination
1201	 *  @return {string}
1202	 *  @example Cargo.API.GetSetsPath(offset, limit);
1203	 */
1204	GetSetsPath : function(offset, limit) {
1205		offset = (offset) ? offset : this.Config.current_offset;
1206		limit  = (limit)  ? limit  : Cargo.Helper.GetPageLimit();
1207
1208		var path = this.GetSitePath() + "/sets";
1209			path += "?offset=" + offset + "&limit=" + limit;
1210
1211		return path;
1212	},
1213
1214	/**
1215	 *	This method gets the location of the file put path
1216	 * 
1217	 *  @public
1218	 *  
1219	 *  @memberOf Cargo.API
1220	 *  @return {string}
1221	 *  @example Cargo.API.GetSiteFilesPutPath();
1222	 */
1223	GetSiteFilesPutPath : function() {
1224		return '/dispatch/files/putFile';
1225	},
1226	
1227	/*
1228	 * ######################################################################
1229	 *
1230	 *	MODELS AND DATA
1231	 *
1232	 * ######################################################################
1233	 */
1234
1235	/**
1236	 *	Helper method to create a data package with the model.
1237	 *	This binds the Display Options and Site model with the 
1238	 *	incoming data
1239	 *	This is used when rendering Handlebars
1240	 * 
1241	 *  @public
1242	 *  
1243	 *  @memberOf Cargo.API
1244	 *  @param {string} [model_name] The name of the model 
1245	 *  @param {JSON} [model] The raw data to be added to the model
1246	 *  @return {array}
1247	 *  @example Cargo.API.GetDataPackage(model_name, model);
1248	 */
1249	GetDataPackage : function(model_name, model) {
1250		var data_package = {
1251			"site" 				: Cargo.Model.Site.toJSON(),
1252			"display_options"	: Cargo.Model.DisplayOptions.toJSON(),
1253			"config" 			: this.Config
1254		}
1255		
1256		data_package[model_name] = model;
1257
1258		return data_package;
1259	},
1260
1261	/*
1262	 * ######################################################################
1263	 *
1264	 *	PROJECTS
1265	 *
1266	 * ######################################################################
1267	 */
1268	
1269
1270	/**
1271	 *	This will fetch a project collection via ajax by re-init a Backbone Collection
1272	 *	When we re-init, we set the value of the URL to fetch from.
1273	 *	After the fetch, the Views will update automatically
1274	 * 
1275	 *  @public
1276	 *  
1277	 *  @memberOf Cargo.API
1278	 *  @param {number} [offset] Offset for pagination
1279	 *  @param {number} [limit] Limit for pagination
1280	 *  @example Cargo.API.FetchProjectCollection(offset, limit);
1281	 */
1282	FetchProjectCollection : function(offset, limit) {
1283		offset = (offset) ? offset : this.Config.current_offset;
1284		limit  = (limit)  ? limit  : Cargo.Helper.GetPageLimit();
1285		
1286		Cargo.Collection.Projects.url = Cargo.API.GetProjectsPath( offset, limit );
1287
1288		var total_count = Cargo.Helper.GetTotalProjectsInMemory();
1289
1290		if( Cargo.Helper.IsAutopaginate() ) {
1291			/* Auto pagination just adds to the collection */
1292			Cargo.Collection.Projects.fetch({
1293				remove: false,
1294				success: function(collection) {
1295					// Check to see if there is anything left to load, stop additional loads
1296					if((Math.floor(limit) + Math.floor(total_count)) >= Cargo.Helper.GetTotalProjectsInMemory()) {
1297						Cargo.View.Autopaginate.Data.is_updating = false;
1298					}
1299					Cargo.Event.trigger("pagination_complete", Cargo.API.Config.current_page );
1300				}
1301			});
1302
1303		} else {
1304			/* Normal pagination replaces the entire collection */
1305			Cargo.Collection.Projects.fetch({reset: true});
1306		}
1307		
1308	},
1309
1310	/**
1311	 *	This will fetch a Project Collection via ajax by re-init a Backbone Collection
1312	 *	When we re-init, we set the value of the URL to fetch from.
1313	 *	After the fetch, the Views will update automatically
1314	 * 
1315	 *  @public
1316	 *  
1317	 *  @memberOf Cargo.API
1318	 *  @param {string} [purl] Project URL
1319	 *  @param {object} [colletion] Optional collection
1320	 *  @example Cargo.API.GetSingleProject(purl, collection);
1321	 */
1322	GetSingleProject : function(purl, collection) {
1323		Cargo.Event.trigger("project_load_start", purl);
1324
1325		if(typeof collection == "undefined") {
1326			collection = Cargo.Collection.Projects;
1327		}
1328		
1329		/*
1330		 *	Try and get the model from memory first
1331		 */
1332		var project_model = collection.findWhere( { project_url : purl } );
1333		var page_model = Cargo.Collection.Pages.findWhere( { project_url : purl } );
1334		var model = (page_model) ? page_model : project_model;
1335
1336		if( model && !this.Config.site_recently_created ) {
1337			/*
1338			 *	This model seems legit
1339			 */
1340			Cargo.Model.Project.set(model.toJSON());
1341
1342		} else {
1343			Cargo.Core.log("No model found, async");
1344			/*
1345			 *	If there is no model, then load one async
1346			 */
1347			Cargo.Model.Project.fetch( { data : { purl : purl }} );
1348		
1349		}
1350
1351	},
1352
1353	/**
1354	 * Gets a random project by querying the API
1355	 */
1356	GetRandomProject : function() {
1357		Cargo.Model.Project.fetch({
1358				data : { 
1359					url : Cargo.API.Config.cargo_url, 
1360					random : true 
1361				},
1362				reset: false,
1363				success: function(model) {
1364					/* Navigate to the new URL, but do not trigger */
1365					Cargo.Router.navigate( Cargo.Model.Project.GetProjectUrl(), { trigger: false });
1366				}
1367			});
1368	},
1369
1370	/**
1371	 *	Sets a Project Page Collection
1372	 *	This will follow this order
1373	 *	- Save the current collection into a page memory
1374	 *	- Attempt to load the collection from memory
1375	 *	- If fail, then ajax load the project collection
1376	 *	- Set the new page and offset
1377	 * 
1378	 *  @private
1379	 *  
1380	 *  @memberOf Cargo.API
1381	 *  @param {number} [page] Page number changing to
1382	 *  @param {number} [current_page] Current page number
1383	 *  @example Cargo.API.SetProjectsPage(page, current_page);
1384	 */
1385	SetProjectsPage : function(page, current_page) {
1386		/* Get the offset and the current page */
1387		var offset = Cargo.Helper.GetOffsetFromPage( page );
1388		
1389		/* Set the config values with the new values */
1390		this.Config.current_page   = page;
1391		this.Config.current_offset = offset;
1392
1393		/* Verify that we are not loading past our max, or pre our min */
1394		if( offset <= Cargo.Model.DisplayOptions.GetTotalProjects() && offset >= 0 ) {
1395			/* Put the current collection into memory */
1396			Cargo.Collection.Page[current_page] = Cargo.Collection.Projects.toJSON();
1397
1398			/* Check for the new page in memory */
1399			if( Cargo.Collection.Page[page] ) {
1400				/* This is in memory, so re-load the project with the stored data */
1401				Cargo.Collection.Projects.reset( Cargo.Collection.Page[page] );
1402				
1403			} else {
1404				/* Fetch a collection based on the offset */
1405				this.FetchProjectCollection( offset );
1406			}
1407
1408		}
1409		
1410	},
1411
1412	
1413
1414
1415	/*
1416	 * ######################################################################
1417	 *
1418	 *	NAVIGATION
1419	 *
1420	 * ######################################################################
1421	 */
1422
1423	/**
1424	 *	Returns the URL for the following link
1425	 *	
1426	 *  @public
1427	 *  
1428	 *  @memberOf Cargo.API
1429	 *
1430	 *  @return {string}
1431	 *  @example Cargo.API.GetFollowingUrl();
1432	 */
1433	 GetFollowingUrl : function() {
1434	 	return Cargo.Model.Site.GetDirectLink() + "/following";
1435	 },
1436
1437	/**
1438	 *	This will fetch a Project Collection via AJAX
1439	 *	by re-init a Backbone Collection
1440	 *	When we re-init, we set the value of the URL to fetch from
1441	 *	After the fetch, the views will update automatically
1442	 * 
1443	 *  @public
1444	 *  
1445	 *  @memberOf Cargo.API
1446	 *  @param {number} [offset] Offset for pagination
1447	 *  @param {number} [limit] Limit for pagination
1448	 *  @example Cargo.API.FetchNavigationCollection(offset, limit);
1449	 */
1450	FetchNavigationCollection : function(offset, limit) {
1451		offset = (offset) ? offset : this.Config.current_offset;
1452		limit  = (limit)  ? limit  : Cargo.Helper.GetPageLimit();
1453		
1454		Cargo.Collection.Navigation.url = Cargo.API.GetNavigationCargoPath( offset, limit );
1455		
1456		if( Cargo.Helper.IsAutopaginate() ) {
1457			/* Auto pagination just adds to the collection */
1458			Cargo.Collection.Navigation.fetch({remove: false});
1459
1460		} else {
1461			/* Normal pagination replaces the entire collection */
1462			Cargo.Collection.Navigation.fetch({reset: true});
1463		}
1464		
1465	},
1466
1467	/**
1468	 *	Sets the next page of navigation
1469	 *	This will follow this order
1470	 *	- Save the current collection into a page memory
1471	 *	- Attempt to load the collection from memory
1472	 *	- If fail, then ajax load the navigation collection
1473	 *	- Set the new page and offset
1474	 * 
1475	 *  @private
1476	 *  
1477	 *  @memberOf Cargo.API
1478	 *  @param {number} [page] Page number changing to
1479	 *  @param {number} [current_page] Current page number
1480	 *  @example Cargo.API.SetNavigationPage(page, current_page);
1481	 */
1482	SetNavigationPage : function(page, current_page) {
1483		/* Get the offset and the current page */
1484		var offset = Cargo.Helper.GetOffsetFromPage( page );
1485		
1486		/* Verify that we are not loading past our max, or pre our min */
1487		if( offset <= Cargo.Model.DisplayOptions.GetTotalProjects() && offset >= 0 ) {
1488			/* Put the current collection into memory */
1489			Cargo.Collection.NavPage[current_page] = Cargo.Collection.Navigation.toJSON();
1490
1491			/* Check for the new page in memory */
1492			if( Cargo.Collection.NavPage[page] ) {
1493				/* This is in memory, so re-load the project with the stored data */
1494				Cargo.Collection.Navigation.reset( Cargo.Collection.NavPage[page] );
1495			
1496			} else {
1497				/* Fetch a collection based on the offset */
1498				this.FetchNavigationCollection( offset );
1499			}
1500		}
1501
1502	},
1503
1504	/*
1505	 * ######################################################################
1506	 *
1507	 *	SITE FILES
1508	 *
1509	 * ######################################################################
1510	 */
1511
1512	 /**
1513	 *	Save a file to site files
1514	 *	This method is using ajax for now
1515	 * 
1516	 *  @private
1517	 *  
1518	 *  @memberOf Cargo.API
1519	 *  @param {string} [file] Name of the file to save
1520	 *  @param {string} [data] data of the file to save
1521	 *  @param {function} [callback] A callback function
1522	 *  @example Cargo.API.PutSiteFile(file, data, function(data){ console.log(data); });
1523	 */
1524	 PutSiteFile : function(file, data, callback) {
1525	 	$.ajax({
1526			type: 'POST',
1527			url: this.GetSiteFilesPutPath(),
1528			context: this,
1529			data: {
1530				file: file,
1531				data: data,
1532				uid: Cargo.Model.Site.GetId()
1533			},
1534			dataType: 'json',
1535			success: function(data) {
1536				if (typeof callback === "function") {
1537					callback.call(this, data);
1538				}
1539			},
1540			error: function(e) {
1541				
1542			}
1543		});
1544	 }
1545
1546});
1547
1548
1549
1550 /***************************************************************************
1551  *
1552  *	Action namespace
1553  *	This is a shorthand wrapper for view actions and is intended 
1554  *	to make it easier and more accessible to call a simple action
1555  *
1556 **************************************************************************/
1557
1558Action = {
1559	/***************************************************************************
1560	 *
1561	 *	Project actions
1562	 *	Usage:
1563	 *	Action.Project.Close();
1564	 *
1565	 **************************************************************************/
1566	 Project : {
1567	 	
1568	 	Index : function() {
1569	 		Cargo.View.Main.ShowIndex();
1570	 	},
1571
1572	 	Close : function() {
1573	 		Cargo.View.Main.ShowIndex();
1574	 	},
1575
1576	 	Next : function() {
1577	 		Cargo.View.Main.NextProject();
1578	 	},
1579
1580	 	Prev : function() {
1581	 		Cargo.View.Main.PrevProject();
1582	 	},
1583
1584	 	Random : function() {
1585	 		Cargo.View.Main.RandomProject();
1586	 	}
1587	 
1588	 },
1589
1590	 /***************************************************************************
1591	  *
1592	  *	Navigation actions
1593	  *	Usage
1594	  *	Action.Navigation.NextPage();
1595	  *
1596	  **************************************************************************/
1597	 Navigation : {
1598
1599	 	NextPage : function() {
1600	 		Cargo.View.Main.NextPage();
1601	 	},
1602
1603	 	PrevPage : function() {
1604	 		Cargo.View.Main.PrevPage();
1605	 	}
1606	 }
1607
1608}
1609
1610/***************************************************************************
1611 *
1612 *	Events
1613 *	This is the events repository
1614 *	Create events for state change by extending backbone
1615 * 	DOM manipulation
1616 *	This file is included in the cargo/init
1617 *
1618 **************************************************************************/
1619
1620/***************************************************************************
1621 *
1622 *	Backbone Events
1623 *
1624 **************************************************************************/
1625
1626 	/**
1627 	 * Example of binding to an event:
1628 	 * <pre class="prettyprint">
1629 	 * Cargo.Event.on("project_load_complete", function( pid ) {
1630 	 *     alert("Project " + pid + " loaded!");
1631 	 * });
1632 	 * </pre>
1633 	 * Example of triggering an event:
1634 	 * <pre class="prettyprint">
1635 	 * Cargo.Event.trigger("add_history", "/Project-title");
1636 	 * </pre>
1637 	 *
1638 	 *	@namespace Events
1639 	 *	@memberOf Cargo
1640 	 *	@extends {Backbone.Events}
1641 	 */
1642
1643	/*
1644	 *	The history object is created in bootstrap
1645	 */
1646	_.extend(Cargo.Event, Backbone.Events);
1647
1648	/**
1649	 *	Adding history
1650	 *	Simply push a single URL onto the history stack
1651	 *	by navigating to that URL
1652	 *
1653	 *	@event add_history
1654	 *	@memberOf Cargo.Events
1655	 *
1656	 *  @param {string} [url] URL to be added to history
1657	 *  @param {string} [id] ID of the project (if there is one)
1658	 *
1659	 *  @example Cargo.Event.on("add_history", function(url, id) {
1660	 *     // Methods after event is triggered
1661	 *});
1662	 */
1663	Cargo.Event.on("add_history", function( url, id ) {
1664		url = Cargo.Helper.NormalizeRouteLink(url);
1665
1666		// Whenever you are, we're already there
1667		if(url === Cargo.Helper.GetCurrentProjectUrl()){
1668			if( !Cargo.Model.DisplayOptions.GetDisableProjectScroll() ) {
1669				Cargo.Helper.ScrollToTop();
1670			}
1671			return;
1672		}
1673
1674		/* Show the loading animation */
1675		if( !Cargo.Helper.IsCurrentRoute(url) ) {
1676			id = (id) ? id : Cargo.Helper.GetPidFromPurl(url);
1677			Cargo.Helper.ShowLoadingAnimation( id, url );
1678		} else {
1679			/* Scroll to top */
1680			if( !Cargo.Model.DisplayOptions.GetDisableProjectScroll() ) {
1681				Cargo.Helper.ScrollToTop();
1682			}
1683		}
1684
1685		/* Navigate to the new URL */
1686		Cargo.Router.navigate( url, { trigger: true });
1687
1688	});
1689
1690	/**
1691	 *	Adding history passively
1692	 *	Change the URL, but don't fire the event
1693	 *
1694	 *  @event add_history_passive
1695	 *  @memberOf Cargo.Events
1696	 *  @private
1697	 *
1698	 *  @param {string} [url] URL to be added to history
1699	 *
1700	 *  @example Cargo.Event.on("add_history_passive", function(url) {
1701	 *     // Methods after event is triggered
1702	 *});
1703	 */
1704	Cargo.Event.on("add_history_passive", function( url ) {
1705		url = Cargo.Helper.NormalizeRouteLink(url);
1706
1707		/* Navigate to the new URL */
1708		Cargo.Router.navigate( url, { trigger: false });
1709
1710	});
1711
1712	/**
1713	 *	Page load when editing a project
1714	 *
1715	 *	@memberOf Cargo.Events
1716	 *
1717	 *	@event admin_edit_load_complete
1718	 *
1719	 *	@example Cargo.Event.on("admin_edit_load_complete", function() {
1720	 *     // Methods after event is triggered
1721	 *});
1722	 */
1723	Cargo.Event.on("admin_edit_load_complete", function(  ) {
1724
1725	});
1726
1727	/**
1728	 *	When the audio player is started / stopped
1729	 *
1730	 *	@event audio_play_pause
1731	 *	@memberOf Cargo.Events
1732	 *
1733	 *  @param {string} [rel] The rel attribute of the button clicked
1734	 *  @param {string}
1734 [which] Which action: play/pause
1735	 *
1736	 *  @example Cargo.Event.on("audio_play_pause", function(rel, which) {
1737	 *     // Methods after event is triggered
1738	 *});
1739	 */
1740	Cargo.Event.on("audio_play_pause", function( rel, which ) {
1741
1742	});
1743
1744	/**
1745	 *	Fires after the columnizer is done updating.
1746	 *	Note this can be triggered often and repeatedly.
1747	 *
1748	 *  @event columnizer_update_complete
1749	 *
1750	 *  @memberOf Cargo.Events
1751	 *
1752	 *  @example Cargo.Event.on("columnizer_update_complete", function() {
1753	 *     // Methods after event is triggered
1754	 *});
1755	 */
1756
1757	Cargo.Event.on("columnizer_update_complete", function() {
1758
1759	});
1760
1761	/**
1762	 *	When on a custom route
1763	 *
1764	 *	@event custom_route_loaded
1765	 *
1766	 *	@memberOf Cargo.Events
1767	 *
1768	 *  @param {string} [location] The route location
1769	 *
1770	 *  @example Cargo.Event.on("custom_route_loaded", function(location) {
1771	 *     // Methods after event is triggered
1772	 *});
1773	 */
1774	Cargo.Event.on("custom_route_loaded", function( location ) {
1775
1776	});
1777
1778	/**
1779	 *	When on a direct link
1780	 *
1781	 *	@event direct_link_loaded
1782	 *
1783	 *	@memberOf Cargo.Events
1784	 *
1785	 *  @param {string} [location] The link loaded
1786	 *
1787	 *  @example Cargo.Event.on("direct_link_loaded", function(location) {
1788	 *     // Methods after event is triggered
1789	 *});
1790	 */
1791	Cargo.Event.on("direct_link_loaded", function( location ) {
1792		$(document).trigger("directLinkLoaded", [ location ]);
1793	});
1794
1795	/**
1796	 *	When the first navigation collection reset event is triggered
1797	 *
1798	 *	@event first_navigation_collection_reset
1799	 *
1800	 *	@memberOf Cargo.Events
1801	 *
1802	 *  @example Cargo.Event.on("first_navigation_collection_reset", function() {
1803	 *     // Methods after event is triggered
1804	 *});
1805	 */
1806	Cargo.Event.on("first_navigation_collection_reset", function(  ) {
1807
1808	});
1809
1810	/**
1811	 *	When the first project collection reset event is triggered
1812	 *
1813	 *	@event first_project_collection_reset
1814	 *
1815	 *	@memberOf Cargo.Events
1816	 *
1817	 *  @example Cargo.Event.on("first_project_collection_reset", function() {
1818	 *     // Methods after event is triggered
1819	 *});
1820	 */
1821	Cargo.Event.on("first_project_collection_reset", function(  ) {
1822		Cargo.API.Config.first_load_finished = true;
1823	});
1824
1825	/**
1826	 *	When the fullscreen mode is closed
1827	 *
1828	 *	@event fullscreen_close
1829	 *
1830	 *	@memberOf Cargo.Events
1831	 *
1832	 *  @example Cargo.Event.on("fullscreen_close", function() {
1833	 *     // Methods after event is triggered
1834	 *});
1835	 */
1836	Cargo.Event.on("fullscreen_close", function(  ) {
1837
1838	});
1839
1840	/**
1841	 *	When the fullscreen mode destroys the hotkeys
1842	 *	rebind any design keys
1843	 *
1844	 *	@event fullscreen_destroy_hotkeys
1845	 *
1846	 *	@memberOf Cargo.Events
1847	 *
1848	 *  @example Cargo.Event.on("fullscreen_destroy_hotkeys", function() {
1849	 *     // Methods after event is triggered
1850	 *});
1851	 */
1852	Cargo.Event.on("fullscreen_destroy_hotkeys", function(  ) {
1853		Cargo.Core.KeyboardShortcut.Add("Esc", 27, function() {
1854			$("a.follow_popup_cancel").click();
1855				if ( ! $("#freshbox").length > 0 ) {
1856				Cargo.app.Rail.Events.trigger("rail:hide");
1857			}
1858		});
1859	});
1860
1861
1862	/**
1863	 *	When the fullscreen mode is initialized
1864	 *
1865	 *	@event fullscreen_open
1866	 *
1867	 *	@memberOf Cargo.Events
1868	 *
1869	 *  @example Cargo.Event.on("fullscreen_open", function() {
1870	 *     // Methods after event is triggered
1871	 *});
1872	 */
1873	Cargo.Event.on("fullscreen_open", function(  ) {
1874
1875	});
1876
1877	/**
1878	 *	When the homepage is loaded
1879	 *
1880	 *	@event homepage_loaded
1881	 *
1882	 *	@memberOf Cargo.Events
1883	 *
1884	 *  @example Cargo.Event.on("homepage_loaded", function() {
1885	 *     // Methods after event is triggered
1886	 *});
1887	 */
1888	Cargo.Event.on("homepage_loaded", function() {
1889		if(Cargo.View.Navigation) {
1890			Cargo.View.Navigation.AddSetActive();
1891		}
1892
1893		// Scroll the page back to the top
1894		if( !Cargo.Model.DisplayOptions.GetDisableProjectScroll() ) {
1895			Cargo.Helper.ScrollToTop();
1896		}
1897	});
1898
1899	/**
1900	 *	When the inspector is unloaded
1901	 *
1902	 *	@event inspector_unload
1903	 *
1904	 *	@memberOf Cargo.Events
1905	 *
1906	 *  @example Cargo.Event.on("inspector_unload", function() {
1907	 *     // Methods after event is triggered
1908	 *});
1909	 */
1910	Cargo.Event.on("inspector_unload", function(  ) {
1911		// Rebind all of the objects again
1912		Cargo.Collection.Projects.reset( Cargo.Collection.Projects.models );
1913		Cargo.Event.Common();
1914	});
1915
1916	/**
1917	 *	When a the loading animations have been replaced
1918	 *
1919	 *	@event loading_anims_replaced
1920	 *
1921	 *	@memberOf Cargo.Events
1922	 *
1923	 *  @example Cargo.Event.on("loading_anims_replaced", function() {
1924	 *     // Methods after event is triggered
1925	 *});
1926	 */
1927	Cargo.Event.on("loading_anims_replaced", function( ) {
1928
1929	});
1930
1931	/**
1932	 *	When the freshbox launches
1933	 *
1934	 *	@event lightbox_open
1935	 *
1936	 *	@memberOf Cargo.Events
1937	 *
1938	 *  @example Cargo.Event.on("lightbox_open", function() {
1939	 *     // Methods after event is triggered
1940	 *});
1941	 */
1942	Cargo.Event.on("lightbox_open", function(  ) {
1943
1944	});
1945
1946	/**
1947	 *	When the freshbox is all done
1948	 *
1949	 *	@event lightbox_close
1950	 *
1951	 *	@memberOf Cargo.Events
1952	 *
1953	 *  @example Cargo.Event.on("lightbox_close", function() {
1954	 *     // Methods after event is triggered
1955	 *});
1956	 */
1957	Cargo.Event.on("lightbox_close", function(  ) {
1958
1959	});
1960
1961	/**
1962	 *	When the navigation content has been reset with a new model
1963	 *
1964	 *	@event navigation_reset
1965	 *
1966	 *	@memberOf Cargo.Events
1967	 *
1968	 *  @example Cargo.Event.on("navigation_reset", function() {
1969	 *     // Methods after event is triggered
1970	 *});
1971	 */
1972	Cargo.Event.on("navigation_reset", function() {
1973
1974	});
1975
1976	/**
1977	 *	When the lightbox has been created and is ready in the window
1978	 *  Triggered by proxying photoswipe events
1979	 *
1980	 *	@event lightbox-init
1981	 *
1982	 *  @param {object} [lightbox] The photoswipe instance
1983	 *	 
1984	 *	@memberOf Cargo.Events
1985	 *
1986	 *  @example Cargo.Event.on("lightbox-init", function() {
1987	 *     // Methods after event is triggered
1988	 *});
1989	 */
1990	Cargo.Event.on('lightbox-init', function(lightbox){
1991
1992	  // hack to prevent lightbox from janking on iOS's slow resize
1993	  if( Cargo.Helper.isMobile()) {
1994	      var resizeTimeout
1995		  $(window).on('resize.lightbox', function(){
1996	          clearTimeout(resizeTimeout);
1997	          resizeTimeout = setTimeout(function(){
1998		          lightbox.updateSize(true);
1999			  }, 500);
2000		  })
2001	  }
2002	});
2003
2004	/**
2005	 *	When the lightbox has been destroyed
2006	 *  Triggered by proxying photoswipe events
2007	 *
2008	 *	@event lightbox-destroy
2009	 *
2010	 *  @param {object} [lightbox] The photoswipe instance
2011	 *	 
2012	 *	@memberOf Cargo.Events
2013	 *
2014	 *  @example Cargo.Event.on("lightbox-init", function() {
2015	 *     // Methods after event is triggered
2016	 *});
2017	 */	
2018	Cargo.Event.on('lightbox-destroy', function(lightbox){
2019	  if( Cargo.Helper.isMobile()) {
2020		  $(window).off('resize.lightbox');
2021	  }
2022	});	
2023
2024	/**
2025	 *	When a set in the navigation is being opened or closed
2026	 *
2027	 *	@event navigation_set_toggle
2028	 *
2029	 *	@memberOf Cargo.Events
2030	 *
2031	 *  @example Cargo.Event.on("navigation_set_toggle", function() {
2032	 *     // Methods after event is triggered
2033	 *});
2034	 */
2035	Cargo.Event.on("navigation_set_toggle", function() {
2036
2037	});
2038
2039	/**
2040	 *	Go to the next page
2041	 *
2042	 *	@event next_page
2043	 *
2044	 *	@memberOf Cargo.Events
2045	 *
2046	 *  @param {number} [page] Page number
2047	 *
2048	 *  @example Cargo.Event.on("next_page", function(page) {
2049	 *     // Methods after event is triggered
2050	 *});
2051	 */
2052	Cargo.Event.on("next_page", function( new_page ) {
2053		$(document).trigger("pageChange", [new_page]);
2054	});
2055
2056	/**
2057	 *	Go to the next project
2058	 *
2059	 *	@event next_project
2060	 *
2061	 *	@memberOf Cargo.Events
2062	 *
2063	 *  @param {number} [pid] PID of the project to be loaded
2064	 *
2065	 *  @example Cargo.Event.on("next_project", function(pid) {
2066	 *     // Methods after event is triggered
2067	 *});
2068	 */
2069	Cargo.Event.on("next_project", function( pid ) {
2070		$(document).trigger("showNextProject", [pid]);
2071	});
2072
2073	/**
2074	 *	When the pagination is done
2075	 *
2076	 *	@event pagination_complete
2077	 *
2078	 *	@memberOf Cargo.Events
2079	 *
2080	 *  @param {number} [page] Page number of the newly loaded page
2081	 *
2082	 *  @example Cargo.Event.on("pagination_complete", function(page) {
2083	 *     // Methods after event is triggered
2084	 *});
2085	 */
2086	Cargo.Event.on("pagination_complete", function( new_page ) {
2087		$(document).trigger("pageChangeComplete", [new_page] );
2088		$(document).trigger("paginationComplete", [new_page] );
2089		Cargo.Helper.HideLoadingAnimation();
2090
2091		// Common events that are fired multiple times
2092		Cargo.Event.Common();
2093
2094		/* Add an active state to the navigation */
2095        if(Cargo.View.Navigation) {
2096            Cargo.View.Navigation.AddActive();
2097            Cargo.View.Navigation.AddSetActive();
2098        }
2099
2100        /* Add an active state to the thumbnail */
2101        if(Cargo.View.Thumbnail) {
2102            Cargo.View.Thumbnail.AddActive();
2103        }
2104
2105	});
2106
2107	/**
2108	 *	When the pagination starts
2109	 *
2110	 *	@event pagination_start
2111	 *
2112	 *	@memberOf Cargo.Events
2113	 *
2114	 *  @param {number} [page] Page number of the page to be loaded
2115	 *
2116	 *  @example Cargo.Event.on("pagination_start", function(page) {
2117	 *     // Methods after event is triggered
2118	 *});
2119	 */
2120	Cargo.Event.on("pagination_start", function( new_page ) {
2121		Cargo.Helper.ShowLoadingAnimation();
2122
2123	});
2124
2125	/**
2126	 *	Go to the previous page
2127	 *
2128	 *	@event prev_page
2129	 *
2130	 *	@memberOf Cargo.Events
2131	 *
2132	 *  @param {number} [page] Page number to go back to
2133	 *
2134	 *  @example Cargo.Event.on("prev_page", function(page) {
2135	 *     // Methods after event is triggered
2136	 *});
2137	 */
2138	Cargo.Event.on("prev_page", function( new_page ) {
2139		$(document).trigger("pageChange", [new_page]);
2140	});
2141
2142	/**
2143	 *	Go to the previous project
2144	 *
2145	 *	@event prev_project
2146	 *
2147	 *	@memberOf Cargo.Events
2148	 *
2149	 *  @param {number} [pid] PID of the previous project
2150	 *
2151	 *  @example Cargo.Event.on("prev_project", function(pid) {
2152	 *     // Methods after event is triggered
2153	 *});
2154	 */
2155	Cargo.Event.on("prev_project", function( pid ) {
2156		$(document).trigger("showPrevProject", [pid]);
2157	});
2158
2159	/**
2160	 *	Fires whenever a project is closing, either to the index or another page/project
2161	 *
2162	 *	@event project_change
2163	 *
2164	 *	@memberOf Cargo.Events
2165	 *
2166	 *  @example Cargo.Event.on("project_change", function() {
2167	 *     // Methods after event is triggered
2168	 *});
2169	 */
2170	Cargo.Event.on("project_change", function() {
2171
2172	});
2173
2174	/**
2175	 *	When a the project collection has loaded
2176	 *
2177	 *	@event project_collection_reset
2178	 *
2179	 *	@memberOf Cargo.Events
2180	 *
2181	 *  @example Cargo.Event.on("project_collection_reset", function() {
2182	 *     // Methods after event is triggered
2183	 *});
2184	 */
2185	Cargo.Event.on("project_collection_reset", function( ) {
2186
2187	});
2188
2189	/**
2190	 *	When the a project is finished loading
2191	 *
2192	 *	@event project_load_complete
2193	 *
2194	 *	@memberOf Cargo.Events
2195	 *
2196	 *  @param {number} [pid] PID of the project to be loaded
2197	 *
2198	 *  @example Cargo.Event.on("project_load_complete", function(pid) {
2199	 *     // Methods after event is triggered
2200	 *});
2201	 */
2202	Cargo.Event.on("project_load_complete", function( pid ) {
2203		$(document).trigger("projectLoadComplete", [ pid ]);
2204
2205		/* Reset the Project Transition */
2206		Cargo.Event.ProjectTransition = new $.Deferred();
2207
2208		/* Common events that are fired multiple times */
2209		Cargo.Event.Common();
2210
2211		/* Scroll the page back to the top */
2212		if( !Cargo.Model.DisplayOptions.GetDisableProjectScroll() ) {
2213			Cargo.Helper.ScrollToTop();
2214		}
2215
2216		/* Change the bg color, if needed */
2217		if(Cargo.View.ProjectDetail) {
2218			Cargo.View.ProjectDetail.ChangeBgColorFromModel();
2219		}
2220
2221		/* Remove the set class from body */
2222		if(Cargo.View.Main && !Cargo.Helper.IsOnStartProject()) {
2223			Cargo.View.Main.RemoveStartProjectClass();
2224		}
2225
2226
2227		/* Add an active state to the navigation */
2228		if(Cargo.View.Navigation) {
2229			Cargo.View.Navigation.AddActive(pid);
2230			Cargo.View.Navigation.AddSetActive();
2231		}
2232
2233		/* Add an active state to the thumbnail */
2234		if(Cargo.View.Thumbnail) {
2235			Cargo.View.Thumbnail.AddActive(pid);
2236			Cargo.View.Thumbnail.CheckAndHideThumbs();
2237		}
2238
2239		/* Hide the loading animations */
2240		Cargo.Helper.HideLoadingAnimation();
2241
2242		/* Set the html page title */
2243		if( !Cargo.Helper.IsOnStartProject() ) {
2244			Cargo.View.Main.SetWindowTitle();
2245		}
2246
2247		/* Set the data-type on the body */
2248		Cargo.View.Main.SetDataType(Cargo.Model.Project.GetType());
2249
2250		// Gracefully track project load as a pageview
2251		if (! Cargo.API.Config.is_direct_link && ! Cargo.Helper.IsOnStartProject()) {
2252			Cargo.Analytics.pageview(true);
2253		}
2254	});
2255
2256	/**
2257	 *	When a project starts to load
2258	 *
2259	 *	@event project_load_start
2260	 *
2261	 *	@memberOf Cargo.Events
2262	 *
2263	 *  @param {string} [purl] Project URL about to be loaded
2264	 *
2265	 *  @example Cargo.Event.on("project_load_start", function(purl) {
2266	 *     // Methods after event is triggered
2267	 *});
2268	 */
2269	Cargo.Event.on("project_load_start", function( purl ) {
2270		Cargo.Event.trigger('project_change');
2271		$(document).trigger("startProjectLoaded", [purl]);
2272
2273		// Set is_direct_link to false once we start loading our projects through JS
2274		Cargo.API.Config.is_direct_link = false;
2275	});
2276
2277	/**
2278	 *	Go to a random project
2279	 *
2280	 *	@event random_project
2281	 *
2282	 *	@memberOf Cargo.Events
2283	 *
2284	 *  @param {number} [pid] PID of a random project to be loaded
2285	 *
2286	 *  @example Cargo.Event.on("random_project", function(pid) {
2287	 *     // Methods after event is triggered
2288	 *});
2289	 */
2290	Cargo.Event.on("random_project", function( pid ) {
2291		$(document).trigger("showRandomProject", [pid]);
2292	});
2293
2294	/**
2295	 *	Triggered once the navigation reseed is complete
2296	 *	Reseeding happens when the admin refreshes the front end via frame
2297	 *
2298	 *	@event reseed_navigation_complete
2299	 *
2300	 *	@memberOf Cargo.Events
2301	 *
2302	 *
2303	 *  @example Cargo.Event.on("reseed_navigation_complete", function() {
2304	 *     // Methods after event is triggered
2305	 *});
2306	 */
2307	Cargo.Event.on("reseed_navigation_complete", function( pid ) {
2308
2309	});
2310
2311	/**
2312	 *	Triggered once the project reseed is complete
2313	 *	Reseeding happens when the admin refreshes the front end via frame
2314	 *
2315	 *	@event reseed_project_complete
2316	 *
2317	 *	@memberOf Cargo.Events
2318	 *
2319	 *
2320	 *  @example Cargo.Event.on("reseed_project_complete", function() {
2321	 *     // Methods after event is triggered
2322	 *});
2323	 */
2324	Cargo.Event.on("reseed_project_complete", function( pid ) {
2325
2326	});
2327
2328	/**
2329	 *	When on a set
2330	 *
2331	 *	@event set_loaded
2332	 *
2333	 *	@memberOf Cargo.Events
2334	 *
2335	 *  @param {string} [location] The URL of the set
2336	 *
2337	 *  @example Cargo.Event.on("set_loaded", function(location) {
2338	 *     // Methods after event is triggered
2339	 *});
2340	 */
2341	Cargo.Event.on("set_loaded", function( location ) {
2342
2343	});
2344
2345	/**
2346	 *	When a the index is completely loaded (project is closed)
2347	 *
2348	 *	@event show_index_complete
2349	 *
2350	 *	@memberOf Cargo.Events
2351	 *
2352	 *  @param {number} [pid] PID of the project recently closed
2353	 *
2354	 *  @example Cargo.Event.on("show_index_complete", function(pid) {
2355	 *     // Methods after event is triggered
2356	 *});
2357	 */
2358	Cargo.Event.on("show_index_complete", function( pid ) {
2359		$(document).trigger("projectCloseComplete", [pid]);
2360		$(document).trigger("projectIndex");
2361
2362		/* Remove active state to the navigation */
2363		if(Cargo.View.Navigation) {
2364			Cargo.View.Navigation.RemoveActive();
2365		}
2366
2367		/* Remove active state to the thumbnails */
2368		if(Cargo.View.Thumbnail) {
2369			Cargo.View.Thumbnail.RemoveActive();
2370			Cargo.View.Thumbnail.ShowThumbs();
2371		}
2372
2373		/* Set the html page title */
2374		Cargo.View.Main.SetWindowTitle(true);
2375
2376		/* Remove the data-type on the body */
2377		Cargo.View.Main.SetDataType(null);
2378
2379		/* Remove the start project class */
2380		Cargo.View.Main.RemoveStartProjectClass();
2381
2382		/* Reset the Project Transition */
2383		Cargo.Event.IndexTransition = new $.Deferred();
2384	});
2385
2386	/**
2387	 *	When the index is about to be shown (project is about to close)
2388	 *
2389	 *	@event show_index_start
2390	 *
2391	 *	@memberOf Cargo.Events
2392	 *
2393	 *  @param {number} [pid] PID of the project currently open
2394	 *
2395	 *  @example Cargo.Event.on("show_index_start", function(pid) {
2396	 *     // Methods after event is triggered
2397	 *});
2398	 */
2399	Cargo.Event.on("show_index_start", function( pid ) {
2400		Cargo.Event.trigger('project_change')
2401		$(document).trigger("projectClose", [pid]);
2402	});
2403
2404	/**
2405	 * Slideshow is resizing
2406	 *
2407	 *	@event slideshow_resize
2408	 *
2409	 *	@memberOf Cargo.Events
2410	 *
2411	 *  @param {object} [el] Slideshow Element
2412	 *  @param {object} [obj] The slideshow instance
2413	 *
2414	 *  @example Cargo.Event.on("slideshow_resize", function(el, obj) {
2415	 *     // Methods after event is triggered
2416	 *});
2417	 */
2418	Cargo.Event.on("slideshow_resize", function( el, obj ) {
2419
2420	});
2421
2422	/**
2423	 *	Slideshow transition is starting
2424	 *
2425	 *	@event slideshow_transition_start
2426	 *
2427	 *	@memberOf Cargo.Events
2428	 *
2429	 *  @param {object} [el] Slideshow Element
2430	 *  @param {object} [obj] The slideshow instance
2431	 *
2432	 *  @example Cargo.Event.on("slideshow_transition_start", function(el, obj) {
2433	 *     // Methods after event is triggered
2434	 *});
2435	 */
2436	Cargo.Event.on("slideshow_transition_start", function( el, obj ) {
2437		$(document).trigger("slideshowTransitionStart");
2438	});
2439
2440	/**
2441	 *	Slideshow transition is finished
2442	 *
2443	 *	@event slideshow_transition_finish
2444	 *
2445	 *	@memberOf Cargo.Events
2446	 *
2447	 *  @param {object} [el] Slideshow Element
2448	 *  @param {object} [obj] The slideshow instance
2449	 *
2450	 *  @example Cargo.Event.on("slideshow_transition_finish", function(el, obj) {
2451	 *     // Methods after event is triggered
2452	 *});
2453	 */
2454	Cargo.Event.on("slideshow_transition_finish", function( el, obj ) {
2455		$(document).trigger("slideshowTransitionFinish");
2456	});
2457
2458	/**
2459	 * When on a solo link
2460	 *
2461	 *	@event solo_link_loaded
2462	 *
2463	 *	@memberOf Cargo.Events
2464	 *
2465	 *  @param {string} [location] URL of the link loaded
2466	 *
2467	 *  @example Cargo.Event.on("solo_link_loaded", function(location) {
2468	 *     // Methods after event is triggered
2469	 *});
2470	 */
2471	Cargo.Event.on("solo_link_loaded", function( location ) {
2472
2473	});
2474
2475	/**
2476	 *	When a template has been loaded
2477	 *
2478	 *	@event template_loaded
2479	 *
2480	 *	@memberOf Cargo.Events
2481	 *
2482	 *  @param {string} [template] Name of the template loaded
2483	 *
2484	 *  @example Cargo.Event.on("template_loaded", function(template) {
2485	 *     // Methods after event is triggered
2486	 *});
2487	 */
2488	Cargo.Event.on("template_loaded", function( template_name ) {
2489
2490	});
2491
2492
2493	/**
2494	 *	Common events that are fired regularly
2495	 *
2496	 *	@method common
2497	 *	@private
2498	 *
2499	 *	@memberOf Cargo.Events
2500	 */
2501	Cargo.Event.Common = function() {
2502		/* Check for, and activate, slideshows */
2503		if(typeof Cargo.Core.Slideshow.InitSlideshow != "undefined") {
2504			Cargo.Core.Slideshow.InitSlideshow();
2505		}
2506
2507		/* Check for, and activate, video fallback */
2508		Cargo.Core.Video.CheckForVideo();
2509
2510		/* Find an init any audio players */
2511		Cargo.Core.Audio.CheckForAudio();
2512
2513		/* Look for a search form in a project or on the page */
2514		Cargo.Core.FindAndLoadSearch();
2515
2516		/* Check and init freshbox, but only on non-permalink */
2517		if( !Cargo.Helper.IsPermalink( Cargo.Model.Project.GetProjectUrl() ) ) {
2518			Cargo.Core.CheckAndInitFreshbox();
2519		}
2520
2521		/* Remove the freshbox */
2522		if( $("#freshbox").length > 0) {
2523			Cargo.View.Fullscreen.Close();
2524		}
2525
2526	}
2527
2528/***************************************************************************
2529 *
2530 *	Project transition event
2531 *
2532 **************************************************************************/
2533 	/**
2534 	 * A global for holding a jquery Deferred object
2535 	 * This is used for project and index transitions
2536 	 *
2537 	 * @type {jQuery}
2538 	 * @method common
2539 	 * @private
2540 	 *
2541 	 * @memberOf Cargo.Events
2542 	 */
2543 	Cargo.Event.ProjectTransition = new $.Deferred();
2544 	Cargo.Event.IndexTransition = new $.Deferred();
2545
2546 	/**
2547 	 * A global method that is meant to be overwritten by designs
2548 	 * This is a method that will run while transitioning a project open
2549 	 * The project opening is deferred until this method resolves itself.
2550 	 *
2551 	 * 	@param {String} pid The project ID
2552 	 * 	@param {String} project_markup The rendered HTML for the requested project
2553 	 * 	@param {Object} project_model The full project model
2554 	 *	@method common
2555	 *	@private
2556	 *
2557	 *	@memberOf Cargo.Events
2558 	 */
2559	Cargo.Event.ShowProjectTransition = function(pid, project_markup, project_model) {
2560		 Cargo.Event.ProjectTransition.resolve();
2561		 return Cargo.Event.ProjectTransition.promise();
2562	}
2563
2564	/**
2565 	 * A global method that is meant to be overwritten by designs
2566 	 * This is a method that will run while transitioning a project close / show index
2567 	 * The index showing is deferred until this method resolves itself.
2568 	 *
2569 	 * 	@param {String} pid The project ID being closed
2570 	 * 	@param {Object} project_model The full project model
2571 	 *	@method common
2572	 *	@private
2573	 *
2574	 *	@memberOf Cargo.Events
2575 	 */
2576	Cargo.Event.ShowIndexTransition = function(pid, project_model) {
2577		Cargo.Event.IndexTransition.resolve();
2578		return Cargo.Event.IndexTransition.promise();
2579	}
2580
2581/***************************************************************************
2582 *
2583 *	jQuery Events
2584 *
2585 **************************************************************************/
2586
2587
2588	/*
2589	 *	This is the close event from Cargo 1.0
2590	 *	When triggered, it will close a project
2591	 *	For now, this remains in this file. Probably should move it
2592	 */
2593	$(document).bind("closeProject", function(e) {
2594		Action.Project.Close();
2595	});
2596
2597
2598
2599/***************************************************************************
2600 *
2601 *	Helpers
2602 *	This contains various helper methods that are used 
2603 *		in various views
2604 *
2605 **************************************************************************/
2606
2607/**
2608 * The Cargo namespace holds all fields and methods
2609 * @namespace Cargo
2610 */
2611 
2612/**
2613 * @namespace o 
2614 * @memberOf Cargo
2615 */
2616
2617/**
2618 * @constructor Cargo.o.Helpers
2619 * @extends {Backbone.View} 
2620 * @memberOf Cargo.o
2621 */
2622
2623
2624
2625Cargo.o.helpers = Backbone.View.extend({
2626	Data : {
2627		is_loading : false,
2628		loading_timout : 1000
2629	},
2630
2631	/**
2632	 *  Checks to see if the browser allows fullscreen
2633	 * 
2634	 *  @public
2635	 *  
2636	 *  @memberOf Cargo.o.Helpers
2637	 *
2638	 *  @return {boolean} True if fullscreen is supported, false if not.
2639	 *  @example Cargo.Helper.AllowsFullScreen();
2640	 */
2641	AllowsFullScreen : function() {
2642		var docElem = document.documentElement;
2643		if (docElem.requestFullscreen || docElem.mozRequestFullScreen || docElem.msRequestFullscreen || docElem.webkitRequestFullScreen) {
2644			return true;
2645		} else {
2646			return false;
2647		}
2648	},
2649
2650	/**
2651	 *	Find Project Collection based on Project URL
2652	 *	
2653	 *	@public 
2654	 *  @memberOf Cargo.o.Helpers
2655	 *
2656	 *	@param {String} [project_url] URL of the Project
2657	 *	
2658	 *	@return {object} Project collection
2659	 *	@example Cargo.Helper.FindCollectionByProjectUrl(url);
2660	 */
2661	FindCollectionByProjectUrl : function( project_url ) {
2662		var found_collection = false;
2663		_.each(Cargo.Collection.Page, function(collection) {
2664			if( !found_collection ) {
2665				var dummy_collection = new Backbone.Collection();
2666				dummy_collection.reset(collection);
2667				var model = dummy_collection.where( { project_url : project_url } );
2668
2669				if(model[0]) {
2670					found_collection = collection;
2671				}
2672			}
2673		});
2674
2675		return found_collection;
2676	},
2677
2678	/**
2679	 * 	Get the active Filter 
2680	 *  
2681	 *  @memberof Cargo.o.Helpers
2682	 *  
2683	 *  @return {string} Filter name
2684	 *  @example Cargo.Helper.GetActiveFilter();
2685	 */
2686	GetActiveFilter : function() {
2687		return Cargo.API.Config.filter; 
2688	},
2689
2690	/**
2691	 * 	Get the active index. This will return
2692	 *  /url/filter/cat OR /url/set OR /url
2693	 *  Whatever the current page's index is
2694	 *  
2695	 *  @memberof Cargo.o.Helpers
2696	 *
2697	 * @param {Boolean} [full_path] Returns the full http://domain.com/path or http://cargocollective.com/url/path
2698	 *  @return {string} Index path
2699	 *  @example Cargo.Helper.GetActiveIndex();
2700	 */
2701	GetActiveIndex : function(full_path) {
2702		var relative_path = Cargo.Helper.GetHomeUrl(true, true);
2703
2704		return (full_path) ? window.location.protocol + '//'+this.GetBaseDomain()+relative_path : relative_path;
2705	},
2706
2707	/**
2708	 *	This is a way to load backbone collections, models and views
2709	 *	without knowing the case. It is useful because of the data-view attributes
2710	 *	that do all the dynamic loading on each page
2711	 *
2712	 *  @public
2713	 *  @memberof Cargo.o.Helpers
2714	 *  
2715	 *	@param {object} [obj] backbone object
2716	 *	@param {string} [model] Name of the requested model
2717	 *	
2718	 *	@return {string} Name actual name of the model
2719	 *	@example Cargo.Helper.GetBackboneCaseInsensitive(obj, model);
2720	 */
2721	GetBackboneCaseInsensitive : function(obj, requested_model) {
2722		var mapped_methods = {};
2723
2724		for(var f in obj){
2725			// console.log(f+" -- "+obj.hasOwnProperty(f)+" -- "+typeof obj[f]);
2726		    if(obj.hasOwnProperty(f) && (typeof obj[f]=='function' || typeof obj[f]=='object')){
2727		        mapped_methods[f.toLowerCase()] = f;
2728		    }
2729		}
2730
2731		return (requested_model) ? mapped_methods[requested_model.toLowerCase()] : "";
2732	},
2733
2734	/**
2735	 *	Get the base URL
2736	 *
2737	 *  @public 
2738	 *  @memberof Cargo.o.Helpers
2739	 *
2740	 *  @param {boolean} [force] Always return the Cargo/url
2741	 *	@param {boolean} [add_slash] If a slash should be returned with Cargo/url
2742	 *	
2743	 *	@return {string} If custom domain, return nothing, otherwise return Cargo/url
2744	 *	@example Cargo.Helper.GetBaseUrl(force, add_slash);
2745	 */
2746	GetBaseUrl : function(force_url, add_slash) {
2747		if( Cargo.API.Config.is_domain ) {
2748			return (add_slash) ? "/" : "";
2749		} else {
2750			var url = (force_url) ? Cargo.API.Config.cargo_url : "";
2751			return (add_slash) ? "/" + url : url;
2752		}
2753	},
2754
2755	/**
2756	 *	The base Cargo domain
2757	 *	
2758	 *  @memberof Cargo.o.Helpers
2759	 *
2760	 *	@return {string} The Cargo root domain
2761	 *	@example Cargo.Helper.GetBaseCargoDomain();
2762	 */
2763	GetBaseCargoDomain : function() {
2764		return (/cargocollective.com/.test(document.location.host) || /persona.co/.test(document.location.host)) ? document.location.host : 'cargocollective.com';
2765	},
2766
2767	/**
2768	 *	The base domain
2769	 *	
2770	 *  @memberof Cargo.o.Helpers
2771	 *
2772	 *	@return {string} The active root domain
2773	 *	@example Cargo.Helper.GetBaseDomain();
2774	 */
2775	GetBaseDomain : function() {
2776		return (/cargocollective.com/.test(document.location.host)) ? 'cargocollective.com' : document.location.host;
2777	},
2778
2779	/**
2780	 *	Gets the current model from the Project collection
2781	 *	
2782	 *  @memberof Cargo.o.Helpers
2783	 *  
2784	 *	@return {object} The active Project Model
2785	 *	@example Cargo.Helper.GetCurrentModelFromProjects();
2786	 */
2787	GetCurrentModelFromProjects : function() {
2788		return Cargo.Collection.Projects.where( { project_url : Cargo.Helper.GetCurrentProjectUrl() } );
2789	},
2790
2791	/**
2792	 *  Get the open Project or Page's type
2793	 *  
2794	 *  @memberof Cargo.o.Helpers
2795	 * 
2796	 *  @return {string/boolean} String of "page" or "project" or false
2797	 *  @example Cargo.Helper.GetCurrentPageType();
2798	 */
2799	GetCurrentPageType : function() {
2800		return Cargo.Model.Project.GetType();
2801	},
2802
2803	/**
2804	 *  Get the open Project's URL
2805	 * 
2806	 *  @memberof Cargo.o.Helpers
2807	 *
2808	 *  @return {string/boolean} String of URL or false
2809	 *  @example Cargo.Helper.GetCurrentProjectUrl();
2810	 */
2811	GetCurrentProjectUrl : function() {
2812		return Cargo.Model.Project.GetProjectUrl();
2813	},
2814
2815	/**
2816	 *  Get the open project's ID
2817	 * 
2818	 *  @memberof Cargo.o.Helpers
2819	 *
2820	 *  @return {string/boolean} String of ID or false
2821	 *  @example Cargo.Helper.GetCurrentProjectId();
2822	 */
2823	GetCurrentProjectId : function() {
2824		return Cargo.Model.Project.GetId();
2825	},
2826
2827	/**
2828	 *  Get the current Backbone route 
2829	 * 
2830	 *  @memberof Cargo.o.Helpers
2831	 *
2832	 *  @return {string} Route name
2833	 *  @example Cargo.Helper.GetCurrentRoute();
2834	 */
2835	GetCurrentRoute : function() {
2836		return Backbone.history.fragment;
2837	},
2838
2839	/**
2840	 * Gets the name of the Design
2841	 *
2842	 *  @memberof Cargo.o.Helpers
2843	 *  
2844	 * 	@return {string} Design name
2845	 *  @example Cargo.Helper.GetDesignName();
2846	 */
2847	GetDesignName : function() {
2848		return Cargo.API.Config.design_name;
2849	},
2850
2851	/**
2852	 *  Get the domain or the Cargo/url
2853	 *  
2854	 *  @memberof Cargo.o.Helpers
2855	 *  
2856	 *  @return {string} Domain or URL
2857	 *  @example Cargo.Helper.GetDomainOrUrl();
2858	 */
2859	GetDomainOrUrl : function() {
2860		if( Cargo.API.Config.is_domain ) {
2861			return this.GetUrl();
2862		} else {
2863			return location.pathname.split("/")[1];
2864		}
2865	},
2866
2867	/**
2868	 *  Gets the homepage URL
2869	 *  Could be Filter, could be search, could be domain. etc
2870	 * 
2871	 *  @memberof Cargo.o.Helpers
2872	 *
2873	 *  @param {boolean} [force] Force return of the Cargo/url
2874	 *  @param {boolean} [add_slash] Prefix a slash to the result
2875	 *  
2876	 *  @return {string} URL
2877	 *  @example Cargo.Helper.GetHomeUrl(force, add_slash);
2878	 */ 
2879	GetHomeUrl : function(force_url, add_slash) {
2880		var slash = (add_slash) ? "/" : "";
2881
2882		// Set and Filter
2883		if( Cargo.API.Config.set && Cargo.API.Config.filter) {
2884			if( Cargo.API.Config.is_domain ) {
2885				return slash + Cargo.API.Config.set + "/filter/" + encodeURIComponent(Cargo.API.Config.filter);
2886			} else {
2887				return this.GetBaseUrl(force_url, add_slash) + "/" + Cargo.API.Config.set + "/filter/" + encodeURIComponent(Cargo.API.Config.filter);
2888			}
2889		}
2890		// Route and Filter
2891		else if( Cargo.API.Config.route && Cargo.API.Config.filter) {
2892			if( Cargo.API.Config.is_domain ) {
2893				return Cargo.Helper.GetRouteUrl() + "/filter/" + encodeURIComponent(Cargo.API.Config.filter);
2894			} else {
2895				return this.GetBaseUrl(force_url, false) + Cargo.Helper.GetRouteUrl() + "/filter/" + encodeURIComponent(Cargo.API.Config.filter);
2896			}
2897		}
2898		// Filter only
2899		else if( Cargo.API.Config.filter ) {
2900			if( Cargo.API.Config.is_domain ) {
2901				return slash + "filter/" + encodeURIComponent(Cargo.API.Config.filter);
2902			} else {
2903				return this.GetBaseUrl(force_url, add_slash) + "/filter/" + encodeURIComponent(Cargo.API.Config.filter);
2904			}
2905			
2906		} 
2907		// Set only
2908		else if( Cargo.API.Config.set ) {
2909			if( Cargo.API.Config.is_domain ) {
2910				return slash + Cargo.API.Config.set;
2911			} else {
2912				return this.GetBaseUrl(force_url, add_slash) + "/" + Cargo.API.Config.set;
2913			}
2914		} 
2915		// Route only
2916		else if( Cargo.API.Config.route ) {
2917			if( Cargo.API.Config.is_domain ) {
2918				return Cargo.Helper.GetRouteUrl();
2919			} else {
2920				// Note: don't send the route with the base because we don't want to load projects
2921				// inside of routes. The projects will redirect to the actual links instead
2922				return this.GetBaseUrl(force_url, add_slash); // + "/" + Cargo.Helper.GetRouteUrl();
2923			}
2924		} 
2925		// Default
2926		else {
2927			return this.GetBaseUrl(force_url, add_slash);
2928		}
2929		
2930	},
2931
2932	/**
2933	 *	Gets the index from one model from the Project collection
2934	 *	Returns the index
2935	 *	
2936	 *  @memberof Cargo.o.Helpers
2937	 *
2938	 *  @param {Backbone.Model} [model] Backbone model
2939	 *  @return {number} Index of the model
2940	 *  @example Cargo.Helper.GetIndexFromModel(model);
2941	 */
2942	GetIndexFromModel : function( model ) {
2943		return Cargo.Collection.Projects.indexOf(model[0]); 
2944	},
2945
2946	/**
2947	 *	Grabs the value of the editor
2948	 *	
2949	 *  @memberof Cargo.o.Helpers
2950	 *  @return {boolean} 
2951	 *  @example Cargo.Helper.GetIsEditor();
2952	 */
2953	GetIsEditor : function() {
2954		return Cargo.API.Config.is_editor;
2955	},
2956
2957	/**
2958	 *	returns the next page offset
2959	 *	Calculates this based on the page * limit
2960	 *	
2961	 *  @memberof Cargo.o.Helpers
2962	 *  @return {number} Offset
2963	 *  @example Cargo.Helper.GetNextPageOffset();
2964	 */
2965	GetNextPageOffset : function() {
2966		var limit 	= this.GetPageLimit();
2967		var page 	= Cargo.API.Config.current_page;
2968
2969		return (page*limit);
2970	},
2971
2972	/**
2973	 *	This returns a new Backbone model of the next Project
2974	 *	It is a convenience method 
2975	 *  
2976	 *  @memberof Cargo.o.Helpers
2977	 *  @return {Backbone.Model} Project model
2978	 *  @example Cargo.Helper.GetNextProjectModel();
2979	 */
2980	GetNextProjectModel : function() {
2981		return Cargo.Collection.Projects.findWhere( { project_url : Cargo.Helper.GetNextProjectUrl() } );
2982	},
2983
2984	/**
2985	 *	Returns the next Project's purl
2986	 *	Note: Will wrap the collection back to 0
2987	 *  
2988	 *  @memberof Cargo.o.Helpers
2989	 *  @return {string} URL
2990	 *  @example Cargo.Helper.GetNextProjectUrl();
2991	 */
2992	GetNextProjectUrl : function() {
2993		var model = this.GetCurrentModelFromProjects();
2994		var index = this.GetIndexFromModel( model ); 
2995		var next_index = (index+1) % Cargo.Collection.Projects.length;
2996		var next_model = Cargo.Collection.Projects.at(next_index);
2997
2998		return next_model.get('project_url');
2999	},
3000
3001	/**
3002	 *	Calculates the page offset based on page
3003	 *	Subtract 1 from the page because the offset starts from 0
3004	 *	but page starts from 1
3005	 *  
3006	 *  @memberof Cargo.o.Helpers
3007	 *  @return {number} Offset
3008	 *  @example Cargo.Helper.GetOffsetFromPage();
3009	 */
3010	GetOffsetFromPage : function( page ) {
3011		var limit 	= this.GetPageLimit();
3012		page--;
3013		return (page*limit);
3014	},
3015
3016	/**
3017	 *	Gets the limit of Projects per page
3018	 *	Note, when there is an Index page (like Feed) this might be different.
3019	 *
3020	 *  @memberof Cargo.o.Helpers
3021	 *  @return {number} Page limit
3022	 *  @example Cargo.Helper.GetPageLimit();
3023	 */
3024	GetPageLimit : function() {
3025		var option_limit = Cargo.Model.DisplayOptions.GetPaginationCount();
3026
3027		if(
3028			option_limit && 
3029			Cargo.Model.DisplayOptions.GetIndexRouteName() && 
3030			Cargo.Model.DisplayOptions.GetIndexRouteName().toUpperCase() == Cargo.Helper.GetRouteName().toUpperCase() &&
3031			Cargo.Model.DisplayOptions.get('index_pagination_count')
3032		) {
3033			option_limit = Cargo.Model.DisplayOptions.get('index_pagination_count');
3034		
3035		} else if(Cargo.Model.DisplayOptions.get('mobile_pagination_count') && this.isMobile()) {
3036			option_limit = Cargo.Model.DisplayOptions.get('mobile_pagination_count');	
3037		}
3038
3039		return (option_limit) ? option_limit : 10;
3040	},
3041
3042	/**
3043	 *	Return the id from the model based on purl
3044	 *  
3045	 *  @memberof Cargo.o.Helpers
3046	 *  @param {string} [purl] URL for the project
3047	 *  @return {number} Project ID
3048	 *  @example Cargo.Helper.GetPidFromPurl(purl);
3049	 */
3050	GetPidFromPurl : function( project_url ) {
3051		// Set the new model
3052		var model = Cargo.Collection.Projects.where( { project_url : project_url } );
3053		
3054		if(model[0]) {
3055			return model[0].get('id');
3056		} else {
3057			return 0;
3058		}
3059	},
3060
3061	/**
3062	 *	Calculates previous page offset
3063	 *  
3064	 *  @memberof Cargo.o.Helpers
3065	 *  @return {number} Offset
3066	 *  @example Cargo.Helper.GetPrevPageOffset();
3067	 */
3068	GetPrevPageOffset : function() {
3069		var limit 	= this.GetPageLimit();
3070		var page 	= Cargo.API.Config.current_page-1;
3071
3072		return (page*limit);
3073	},
3074
3075	/**
3076	 *	This returns a new backbone model of the previous project
3077	 *	It is a convenience method 
3078	 *  
3079	 *  @memberof Cargo.o.Helpers
3080	 *  @return {Backbone.Model} Project model
3081	 *  @example Cargo.Helper.GetPrevProjectModel();
3082	 */
3083	GetPrevProjectModel : function() {
3084		return Cargo.Collection.Projects.findWhere( { project_url : Cargo.Helper.GetPrevProjectUrl() } );
3085	},
3086
3087	/**
3088	 * 	The url for the previous Project
3089	 *  
3090	 *  @memberof Cargo.o.Helpers
3091	 *  @return {string} URL
3092	 *  @example Cargo.Helper.GetPrevProjectUrl();
3093	 */
3094	GetPrevProjectUrl : function() {
3095		var model = this.GetCurrentModelFromProjects();
3096		var index = this.GetIndexFromModel( model );
3097		var next_index = (index > 0) ? index-1 : Cargo.Collection.Projects.length - 1;
3098		var next_model = Cargo.Collection.Projects.at(next_index);
3099
3100		return next_model.get('project_url');
3101	},
3102
3103	/**
3104	 *	Returns a page and a single backbone model based on key
3105	 *	This takes into account the possibility that the model
3106	 *	exists in a different page then the current one
3107	 *
3108	 *  @memberof Cargo.o.Helpers
3109	 *  @return {Object} { "page" : 3, "model" : [Object] }
3110	 *  @example Cargo.Helper.GetProjectModelAndPageByKey(key);
3111	 */
3112	GetProjectModelAndPageByKey : function( key ) {
3113		var offset = this.GetPageLimit();
3114		var current_page =  Cargo.API.Config.current_page;
3115		var page = Math.ceil(key/offset);
3116		var page_offset = 0;
3117		
3118		if( !Cargo.Helper.IsAutopaginate() ) {
3119			page_offset = offset - ((page * offset) - key);
3120			page_offset = (page_offset == offset) ? page_offset-1 : page_offset;
3121			page_offset = (page_offset == Cargo.Collection.Projects.length) ? 0 : page_offset;
3122		} else {
3123			page = 0;
3124			page_offset = (key == Cargo.Collection.Projects.length) ? 0 : key;
3125		}
3126
3127		var model = {};
3128		
3129		// Grab from collection ahead of page 1
3130		if(Cargo.Collection.Page[page] && !Cargo.Helper.IsAutopaginate() ) {
3131			model = new Cargo.Model.BaseProject( Cargo.Collection.Page[page][page_offset] );
3132		}
3133		// Grab from the active collection
3134		else {
3135			model = Cargo.Collection.Projects.at(page_offset);
3136		} 
3137		
3138		return {
3139			"page"  : page,
3140			"model" : model
3141		}
3142
3143	},
3144
3145	/**
3146	 *	Return the purl from the model based on pid
3147	 *  
3148	 *  @memberof Cargo.o.Helpers
3149	 *  @param {number} [pid] Project ID
3150	 *  @return {string} Project URL
3151	 *  @example Cargo.Helper.GetPurlFromPid(pid);
3152	 */
3153	GetPurlFromPid : function( id ) {
3154		// Set the new model
3155		var model = Cargo.Collection.Projects.where( { id : id } );
3156		
3157		if(model[0]) {
3158			return model[0].get('project_url');
3159		} else {
3160			return 0;
3161		}
3162	},
3163
3164	/**
3165	 *  Get the route name 
3166	 * 
3167	 *  @memberof Cargo.o.Helpers
3168	 *
3169	 *  @return {string} Route name
3170	 *  @example Cargo.Helper.GetRouteName();
3171	 */
3172	GetRouteName : function() {
3173		var route_name = Cargo.API.Config.route_name;
3174
3175		if(/display_options/.test(Cargo.API.Config.route)) {
3176			route_name = (/feed/.test(route_name)) ? Cargo.Model.DisplayOptions.GetFeedRouteName() : Cargo.Model.DisplayOptions.GetIndexRouteName();
3177		}
3178
3179		return route_name;
3180	},
3181
3182	/**
3183	 *  Get the route url 
3184	 * 
3185	 *  @memberof Cargo.o.Helpers
3186	 *
3187	 *  @return {string} Route name
3188	 *  @example Cargo.Helper.GetRouteUrl();
3189	 */
3190	GetRouteUrl : function() {
3191		var route_url = Cargo.API.Config.route_name;
3192
3193		if(/display_options/.test(Cargo.API.Config.route) || route_url == "feed" || route_url == "index") {
3194			route_url = (/feed/.test(route_url)) ? Cargo.Model.DisplayOptions.GetFeedRouteUrl() : Cargo.Model.DisplayOptions.GetIndexRouteUrl();
3195		}
3196		
3197		return route_url;
3198	},
3199
3200	/**
3201	 *	Get the currently active set key
3202	 *  
3203	 *  @memberof Cargo.o.Helpers
3204	 *  @return {string} Current set key
3205	 *  @example Cargo.Helper.GetCurrentSetKey();
3206	 */
3207	GetSetKey : function() {
3208	 	return Cargo.Model.Site.GetCurrentSetKey();
3209	},
3210
3211	/**
3212	 * Returns the name of the current set
3213	 *  
3214	 *  @memberof Cargo.o.Helpers
3215	 *  @return {string} Set name
3216	 *  @example Cargo.Helper.GetSetName();
3217	 */
3218	GetSetName : function() {
3219		return Cargo.API.Config.set;
3220	},
3221
3222	/**
3223	 * Returns the start project id
3224	 *  
3225	 *  @memberof Cargo.o.Helpers
3226	 *  @return {number} Start Project ID
3227	 *  @example Cargo.Helper.GetStartProjectId();
3228	 */
3229	GetStartProjectId : function() {
3230		return Math.floor(Cargo.Model.Site.GetStartProjectId());
3231	},
3232
3233	/**
3234	 *	Get the total amount of pages
3235	 *  
3236	 *  @memberof Cargo.o.Helpers
3237	 *  @return {number} Total pages
3238	 *  @example Cargo.Helper.GetTotalPages();
3239	 */
3240	GetTotalPages : function() {
3241	 	return Math.ceil( Cargo.Model.DisplayOptions.GetTotalProjects() / Cargo.Helper.GetPageLimit() );
3242	},
3243
3244	/**
3245	 *	Get the total amount of Projects in memory (used for random)
3246	 *  
3247	 *  @memberof Cargo.o.Helpers
3248	 *  @return {number} Total projects in memory
3249	 *  @example Cargo.Helper.GetTotalProjectsInMemory();
3250	 */
3251	GetTotalProjectsInMemory : function() {
3252	 	var current_count = Cargo.Collection.Projects.length;
3253	 	if( Cargo.Helper.IsAutopaginate() ) {
3254	 		return current_count;
3255	 	}
3256
3257	 	var stored_count = 0;
3258	 	$.each(Cargo.Collection.Page, function() { stored_count += this.length; });
3259	 	return current_count + stored_count;
3260	},
3261
3262	/**
3263	 *	Get the Cargo/url
3264	 *  
3265	 *  @memberof Cargo.o.Helpers
3266	 *  @return {string} This site's Cargo/url
3267	 *  @example Cargo.Helper.GetUrl();
3268	 */
3269	GetUrl : function() {
3270		// return location.pathname.replace(/^\/([^\/]*).*$/, '$1');
3271		return Cargo.API.Config.cargo_url;
3272	},
3273
3274	/**
3275	 *	Go to a URL on this site. Does a full page refresh
3276	 *  
3277	 *  @memberof Cargo.o.Helpers
3278	 *  @param {String} [relative_path] The relative path of the URL 
3279	 *  @example Cargo.Helper.GoToUrl("filter/category");
3280	 */
3281	GoToUrl : function(relative_path) {
3282		relative_path = (relative_path.charAt(0) == "/") ? relative_path.substr(1) : relative_path;
3283		var baseurl = Cargo.Helper.GetBaseUrl(true, true);
3284		var location_path = (Cargo.API.Config.is_domain) ? baseurl + relative_path : baseurl + "/" + relative_path;
3285
3286		top.document.location.href = location_path;
3287	},
3288	
3289	/**
3290	 *	Hides the loading animation
3291	 *	Fires an event
3292	 *	Use with:
3293	 *	<pre>&lt;div data-loading="12345"&gt;&lt;img src="animation.gif"&gt;&lt;/div&gt;</pre>
3294	 *	Or:
3295	 *	<pre>&lt;img src="animation.gif" data-loading="global" /&gt;</pre>
3296	 *  
3297	 *  @memberof Cargo.o.Helpers
3298	 *  @param {number} [pid] Project ID
3299	 *  @example Cargo.Helper.HideLoadingAnimation(pid);
3300	 */
3301	HideLoadingAnimation : function( pid ) {
3302		this.Data.is_loading = false;
3303		$("[data-loading]").hide();
3304
3305		if(typeof Cargo.View.Autopaginate == "object") {
3306			Cargo.View.Autopaginate.$el.hide();
3307		}
3308	},
3309
3310	/**
3311	 *	Checks to see if we are in Admin edit mode
3312	 *  
3313	 *  @memberof Cargo.o.Helpers
3314	 *  @return {boolean}
3315	 *  @example Cargo.Helper.IsAdminEdit();
3316	 */
3317	IsAdminEdit : function() {
3318		var is_admin_path = (/\/adminedit/.test(document.location.pathname) || /\/admin/.test(document.location.pathname));
3319		var has_parent = (parent.location != window.location);
3320		return (is_admin_path || has_parent);
3321	},
3322
3323	/**
3324	 *	Checks to see if we are in aAdmin edit mode
3325	 *  
3326	 *  @memberof Cargo.o.Helpers
3327	 *  @return {boolean}
3328	 *  @example Cargo.Helper.IsAdminEditProject();
3329	 */
3330	IsAdminEditProject : function() {
3331		return Cargo.API.Config.admin_edit_project;
3332	},
3333
3334	/**
3335	 *	Checks to see if this Design autopaginates
3336	 *  
3337	 *  @memberof Cargo.o.Helpers
3338	 *  @return {boolean}
3339	 *  @example Cargo.Helper.IsAutopaginate();
3340	 */
3341	IsAutopaginate : function() {
3342		return ( Cargo.View.Autopaginate || $("[data-view='Autopaginate']").length > 0 || $("[data-view='autopaginate']").length > 0);
3343	},
3344
3345	/**
3346	 *	Matches an incoming route with the current route
3347	 *	Used to ensure we are not trying to double load
3348	 *  
3349	 *  @memberof Cargo.o.Helpers
3350	 *  @param {string} [url] URL
3351	 *  @return {boolean}
3352	 *  @example Cargo.Helper.IsCurrentRoute(url);
3353	 */
3354	IsCurrentRoute : function( url ) {
3355		// Normalize the url
3356		url = this.NormalizeRouteLink(url);
3357
3358		// Now compare
3359		return (Cargo.Helper.GetCurrentRoute() == url);
3360	},
3361
3362	/**
3363	 *  If we are currently on a custom defined route
3364	 *  
3365	 *  @memberof Cargo.o.Helpers
3366	 *  @return {boolean}
3367	 *  @example Cargo.Helper.IsCustomRoute();
3368	 */
3369	IsCustomRoute : function() {
3370		return Cargo.API.Config.is_custom_route;
3371	},
3372
3373	/**
3374	 * If we are currently on a Direct Link
3375	 *  
3376	 *  @memberof Cargo.o.Helpers
3377	 *  @return {boolean}
3378	 *  @example Cargo.Helper.IsDirectLink();
3379	 */
3380	IsDirectLink : function() {
3381		return Cargo.API.Config.is_direct_link;
3382	},
3383
3384	/**
3385	 * If this is a design without Project history
3386	 *  
3387	 *  @memberof Cargo.o.Helpers
3388	 *  @return {boolean}
3389	 *  @example Cargo.Helper.IsFeedDesign();
3390	 */
3391	IsFeedDesign : function() {
3392		return ($("[rel='history']").length == 0) ? true : false;
3393	},
3394
3395	/**
3396	 * If we are currently on a filter link
3397	 *  
3398	 *  @memberof Cargo.o.Helpers
3399	 *  @return {boolean}
3400	 *  @example Cargo.Helper.IsFilter();
3401	 */
3402	IsFilter : function() {
3403		return (this.GetActiveFilter() && this.GetActiveFilter().trim() != "");
3404	},
3405
3406	/**
3407	 *	Test for Firefox. Return true | false
3408	 *  
3409	 *  @memberof Cargo.o.Helpers
3410	 *  @return {boolean}
3411	 *  @example Cargo.Helper.isFirefox();
3412	 */
3413	isFirefox : function() {
3414		return (/Firefox\/\d+\.\d+/.test(navigator.userAgent));
3415	},
3416
3417	/**
3418	 *	Test for IE. Return true | false
3419	 *  
3420	 *  @memberof Cargo.o.Helpers
3421	 *  @return {boolean}
3422	 *  @example Cargo.Helper.isIE();
3423	 */
3424	isIE : function() {
3425		return (/MSIE (\d+\.\d+);/.test(navigator.userAgent));
3426	},
3427		
3428	/**
3429	 *	Test for IE8. Return true | false
3430	 *	Actually, this is just a test for anything < 9
3431	 *  
3432	 *  @memberof Cargo.o.Helpers
3433	 *  @return {boolean}
3434	 *  @example Cargo.Helper.isIE8();
3435	 */
3436	isIE8 : function() {
3437		var rv = -1; // Return value assumes failure.
3438		if (this.isIE())  {
3439			var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
3440			if (re.exec(navigator.userAgent) != null) rv = parseFloat( RegExp.$1 );
3441		}
3442		
3443		return (rv < 9 && rv > 0) ? true : false;
3444	},
3445
3446	/**
3447	 *	Test for iOS.
3448	 *  
3449	 *  @memberof Cargo.o.Helpers
3450	 *  @return {boolean}
3451	 *  @example Cargo.Helper.isIOS();
3452	 */
3453	isIOS : function() {
3454		if (navigator.userAgent.match(/i(Phone|Pod|Pad)/i)) {
3455			return true;	
3456		} else {
3457			return false;
3458		}
3459	},
3460
3461	/**
3462	 *	Test for all mobile.
3463	 *  
3464	 *  @memberof Cargo.o.Helpers
3465	 *  @return {boolean}
3466	 *  @example Cargo.Helper.isMobile();
3467	 */
3468	isMobile : function() {
3469		if(this.isIOS()) return true
3470		else {
3471			var check = false;
3472			(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|
3472sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
3473			return check;
3474		}
3475	},
3476
3477	/**
3478	 * If we are currently on the homepage
3479	 *
3480	 * @memberOf Cargo.o.Helpers
3481	 * @return {boolean} 
3482	 * @example Cargo.Helper.IsOnHome();
3483	 */
3484	IsOnHome : function() {
3485		var on_home = (Cargo.API.Config.set || Cargo.API.Config.filter || Cargo.Helper.GetCurrentRoute() != "") ? false : true;
3486		
3487		return on_home;
3488	},
3489
3490	/**
3491	 * If we are currently on a set
3492	 *  
3493	 *  @memberof Cargo.o.Helpers
3494	 *  @return {boolean}
3495	 *  @example Cargo.Helper.IsOnSet();
3496	 */
3497	IsOnSet : function() {
3498		return (Cargo.API.Config.set) ? true : false;
3499	},
3500
3501	/**
3502	 * If we are currently on a start project
3503	 *  
3504	 *  @memberof Cargo.o.Helpers
3505	 *  @return {boolean}
3506	 *  @example Cargo.Helper.IsOnStartProject();
3507	 */
3508	IsOnStartProject : function() {
3509		return (Cargo.Helper.GetCurrentRoute() == "" && Cargo.API.Config.start_project != null);
3510	},
3511
3512	/**
3513	 *	Test for anything less then IE10
3514	 *  
3515	 *  @memberof Cargo.o.Helpers
3516	 *  @return {boolean}
3517	 *  @example Cargo.Helper.isOldIE();
3518	 */
3519	isOldIE : function() {
3520		var rv = -1; // Return value assumes failure.
3521		if (this.isIE())  {
3522			var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
3523			if (re.exec(navigator.userAgent) != null) rv = parseFloat( RegExp.$1 );
3524		}
3525		
3526		return (rv <= 9 && rv > 0) ? true : false;
3527	},
3528
3529	/**
3530	 *	Called by the router
3531	 *	This method tests to see if a given route is a permalink
3532	 *	(loaded by direct link) or if it is loaded by a click
3533	 *	To determine this, see if the collection data has been loaded yet
3534	 *	If it has, then this is not a permalink
3535	 *  
3536	 *  @memberof Cargo.o.Helpers
3537	 *  @param {string} [purl] Project URL
3538	 *  @param {boolean} [from_router] If we are coming from the router or not
3539	 *  @return {boolean}
3540	 *  @example Cargo.Helper.IsPermalink(purl, from_router);
3541	 */
3542
3543	IsPermalink : function( purl, from_router ) {
3544		var project_or_route = false;
3545		var start_and_no_project = false;
3546		var collection_loaded = false;
3547		var strip_url = (this.GetRouteUrl()) ? this.GetRouteUrl().replace(/\//,'') : '';
3548
3549		// If the incoming PURL is a project or route
3550		if(
3551			this.IsCurrentProject(purl) ||
3552			this.IsCurrentProjectId(purl) ||
3553			this.IsCurrentRoute(purl) ||
3554			strip_url == purl ||
3555			Cargo.Helper.GetRouteName() == strip_url ||
3556			purl == "Website-Offline-message"
3557		) {
3558			project_or_route = true;
3559		}
3560
3561		// If this is a start project
3562		if(
3563			Cargo.Helper.IsOnStartProject() && 
3564			Cargo.Model.Project.GetProjectUrl() != purl
3565		) {
3566			start_and_no_project = true;
3567		}
3568
3569		// If the collection has been loaded from the server yet
3570		if(
3571			Cargo.Collection.Projects.models.length >= 1 && 
3572			Cargo.Collection.Projects.models[0].id
3573		) {
3574			collection_loaded = true;
3575		}
3576
3577		var project_is_project = (Cargo.Model.Project.GetProjectUrl() == purl || Cargo.Model.Project.GetId() == purl || this.GetRouteName() == purl || this.GetCurrentRoute() == purl || purl == "Website-Offline-message");
3578		var start_and_no_project = (Cargo.Helper.IsOnStartProject() && Cargo.Model.Project.GetProjectUrl() != purl);
3579		var _final = (!Cargo.API.Config.first_load_finished && (project_is_project || start_and_no_project) );
3580
3581		/*
3582		 * NOTE: If there are issues, it is because of this. Comment the conditional(s) below out to fix
3583		 */
3584		var _final_new = (!Cargo.API.Config.first_load_finished && !collection_loaded && (project_or_route || start_and_no_project) );
3585		if(!_final_new) {
3586			if(!start_and_no_project && !this.IsAdminEdit() && from_router && !collection_loaded) {
3587				_final = false;
3588			}
3589		}
3590
3591		return _final;
3592
3593	},
3594
3595	/**
3596	 * If we are on a phone
3597	 *  
3598	 *  @memberof Cargo.o.Helpers
3599	 *  @return {boolean}
3600	 *  @example Cargo.Helper.isPhone();
3601	 */
3602	isPhone : function() {
3603		var md = new MobileDetect(window.navigator.userAgent);
3604		return (typeof md.phone() == "string");
3605	},
3606
3607	IsCurrentProject : function(purl) {
3608		return (Cargo.Model.Project.GetProjectUrl() && purl && Cargo.Model.Project.GetProjectUrl().toLowerCase() == purl.toLowerCase());
3609	},
3610
3611	IsCurrentProjectId : function(id) {
3612		return (Cargo.Model.Project.GetId() && id && Cargo.Model.Project.GetProjectUrl() == id);
3613	},
3614
3615	IsCurrentRoute : function(route) {
3616		return (this.GetRouteName() && route && this.GetRouteName().toLowerCase() == route.toLowerCase());
3617	},
3618
3619	/**
3620	 *	A convenience method for determining if this is an HTML 5 browser
3621	 *	Note: the latest version of jQuery does not support browser.version
3622	 *	soooo, we need to figure out something else here
3623	 *  
3624	 *  @memberof Cargo.o.Helpers
3625	 *  @return {boolean}
3626	 *  @example Cargo.Helper.isRecentBrowser();
3627	 */
3628	isRecentBrowser : function() {
3629		// If Firefox, and version 3.6 or less
3630		if( $.browser.mozilla && $.browser.version.slice(0,3) >= 1.9 ) {
3631			return true;
3632		// Check webkit, if version 3 or less
3633		} else if ( $.browser.webkit && $.browser.version.substr(0,1) >= 4 ) {
3634			return true;
3635		} else {
3636			return false;	
3637		}
3638	},
3639
3640	/**
3641	 * If we are on the search route
3642	 *  
3643	 *  @memberof Cargo.o.Helpers
3644	 *  @return {boolean}
3645	 *  @example Cargo.Helper.IsSearch();
3646	 */
3647	IsSearch : function() {
3648		return (this.GetRouteName().toLowerCase() == "search");
3649	},
3650
3651	/**
3652	 * If the incoming string is a set
3653	 *  
3654	 *  @memberof Cargo.o.Helpers
3655	 *  @param {string} [set] The name of a set
3656	 *  @return {boolean}
3657	 *  @example Cargo.Helper.IsSet(set);
3658	 */
3659	IsSet : function(set_name) {
3660		return (Cargo.API.Config.set && Cargo.API.Config.set.toLowerCase() == set_name.toLowerCase());
3661	},
3662	
3663	/**
3664	 * If we are on a solo link
3665	 *  
3666	 *  @memberof Cargo.o.Helpers
3667	 *  @return {boolean}
3668	 *  @example Cargo.Helper.IsSololink();
3669	 */
3670	IsSololink : function() {
3671		return Cargo.API.Config.is_solo;
3672	},
3673
3674	/**
3675	 * If we are on a tablet
3676	 *  
3677	 *  @memberof Cargo.o.Helpers
3678	 *  @return {boolean}
3679	 *  @example Cargo.Helper.isTablet();
3680	 */
3681	isTablet : function() {
3682		var md = new MobileDetect(window.navigator.userAgent);
3683		return (typeof md.tablet() == "string");
3684	},
3685
3686	/**
3687	 *	If the open project is not in the active collection, put it there
3688	 *	This is used primarilly for the next/previous project 
3689	 *  
3690	 *  @memberof Cargo.o.Helpers
3691	 *  @param {string} [url] URL
3692	 *  @return {Backbone.Collection} A collection of projects 
3693	 *  @example Cargo.Helper.MakeCurrentProjectCollectionActive(url);
3694	 */
3695	MakeCurrentProjectCollectionActive : function( url ) {
3696		var project_url = (url) ? url : Cargo.Helper.GetCurrentProjectUrl();
3697		var collection_reset = false;
3698
3699		// If we don't find this project in the collection
3700		if( Cargo.Helper.GetPidFromPurl( Cargo.Helper.GetCurrentProjectUrl() ) == 0) {
3701			var found_collection = this.FindCollectionByProjectUrl( project_url );
3702			if( found_collection ) {
3703				Cargo.Collection.Projects.reset( found_collection );
3704				collection_reset = true;
3705			}
3706		}
3707
3708		// Let the updater know if this was reset
3709		return collection_reset;
3710	},
3711
3712	/**
3713	 *	Makes a direct link to a project from a project URL
3714	 *  
3715	 *  @memberof Cargo.o.Helpers
3716	 *  @param {string} [purl] Project URL
3717	 *  @return {string} The direct link to the project
3718	 *  @example Cargo.Helper.MakeDirectLinkFromPurl(purl);
3719	 */
3720	MakeDirectLinkFromPurl : function( url ) {
3721		var base = Cargo.Helper.GetBaseUrl(true, true);
3722		return  (base == '/') ? base + url : base + '/' + url;
3723	},
3724
3725	/**
3726	 *	Checks to see if getting a next project is possible on this page
3727	 *	Will return true / false
3728	 *  
3729	 *  @memberof Cargo.o.Helpers
3730	 *  @return {boolean}
3731	 *  @example Cargo.Helper.NextProjectNeedsToPaginate();
3732	 */
3733	NextProjectNeedsToPaginate : function() {
3734		var model = this.GetCurrentModelFromProjects();
3735		var index = this.GetIndexFromModel( model );
3736
3737		if( (index+1) == Cargo.Collection.Projects.length && this.GetTotalPages() > 1 ) {
3738			return true;
3739		} else {
3740			return false;
3741		}
3742	},
3743
3744	/**
3745	 *	In order for the router to work with both hash and slash URLs
3746	 *	It needs to have the same links always. This means that we need
3747	 *	to normalize the link by removing the site URL before routing
3748	 *  
3749	 *  @memberof Cargo.o.Helpers
3750	 *  @param {string} [url] URL
3751	 *  @return {string} URL
3752	 *  @example Cargo.Helper.NormalizeRouteLink(url);
3753	 */
3754	NormalizeRouteLink : function( url ) {
3755		if(!url) {
3756			return url;
3757		}
3758
3759		// Sanitize the url
3760		url = url.replace(Cargo.Helper.GetBaseDomain(), "").replace(window.location.protocol + "//","");
3761		if(url.charAt(0) == "/") {
3762			url = url.replace("/", "");
3763		}
3764
3765		/*
3766		 * We should be left with only the route
3767		 * now see if we need to remove the Cargo url
3768		 */
3769		if(url.search(this.GetHomeUrl(true)) == 0) {
3770			url = url.replace(this.GetHomeUrl(true)+"/", "");
3771		}
3772		
3773		return url;
3774	},
3775
3776	/**
3777	 *	Checks to see if getting a prev project is possible on this page
3778	 *  
3779	 *  @memberof Cargo.o.Helpers
3780	 *  @return {boolean}
3781	 *  @example Cargo.Helper.PrevProjectNeedsToPaginate();
3782	 */
3783	PrevProjectNeedsToPaginate : function() {
3784		var model = this.GetCurrentModelFromProjects();
3785		var index = this.GetIndexFromModel( model );
3786
3787		if( index <= 0 && this.GetTotalPages() > 1 ) {
3788			return true;
3789		} else {
3790			return false;
3791		}
3792	},
3793
3794	/**
3795	 *	Scroll to the top of the page
3796	 *	Use a timeout because everything happens too fast without it
3797	 *  
3798	 *  @memberof Cargo.o.Helpers
3799	 *  @example Cargo.Helper.ScrollToTop();
3800	 */
3801	ScrollToTop : function() {
3802		setTimeout(function() {
3803			// If we are further then 50 from the top, jump to 50
3804			if($(window).scrollTop() > 50) {
3805				window.scrollTo(0, 50);	
3806			}
3807
3808			// Smooth scroll the rest of the way
3809			doscroll(0,$(window).scrollTop(),0);
3810		}, 50);
3811	},
3812
3813	/**
3814	 *	Scroll to the left of the page (used for horizontal scroll designs)
3815	 *	Use a timeout because everything happens too fast without it
3816	 *  
3817	 *  @memberof Cargo.o.Helpers
3818	 *  @example Cargo.Helper.ScrollToLeft();
3819	 */
3820	ScrollToLeft : function(scroll_obj) {
3821		setTimeout(function() {
3822			if(typeof scroll_obj == "object") {
3823				if(scroll_obj instanceof jQuery) {
3824					scroll_obj = scroll_obj[0];
3825				}
3826				if($(scroll_obj).scrollLeft() > 100) {
3827					scroll_obj.scrollLeft =100;
3828				}
3829			} else {
3830				if($(window).scrollLeft() > 100) {
3831					window.scrollTo(100, 0);
3832					scroll_obj = window;
3833				}
3834			}
3835
3836			// Smooth scroll the rest of the way
3837			doscroll_left(0, $(scroll_obj).scrollLeft(),0, scroll_obj);
3838		}, 50);
3839	},
3840
3841	/**
3842	 *	Return the model from the collection based on project id
3843	 *  
3844	 *  @memberof Cargo.o.Helpers
3845	 *  @param {number} [id] ID of the Project
3846	 *  @return {Backbone.model} Project model
3847	 *  @example Cargo.Helper.SetActiveProjectFromId(id);
3848	 */
3849	SetActiveProjectFromId : function( id ) {
3850		// remove any appended increments
3851		var id = id.split('-')[0];
3852
3853		// Set the new model
3854		var model = Cargo.Collection.Projects.where( { id : parseInt(id) } );
3855
3856		// Set the model in the global scope
3857		Cargo.Model.Project = model[0].toJSON();
3858		
3859		return Cargo.Model.Project;
3860	},
3861	
3862	/**
3863	 *	Return the model from the collection based on purl
3864	 *  
3865	 *  @memberof Cargo.o.Helpers
3866	 *  @param {string} [purl] URL to the project
3867	 *  @return {Backbone.model} Project model
3868	 *  @example Cargo.Helper.SetActiveProjectFromPurl(purl);
3869	 */
3870	SetActiveProjectFromPurl : function( project_url ) {
3871		// Set the new model
3872		var model = Cargo.Collection.Projects.where( { project_url : project_url } );
3873
3874		// Set the model in the global scope
3875		Cargo.Model.Project = model[0].toJSON();
3876		
3877		return Cargo.Model.Project;
3878	},
3879
3880	/**
3881	 *	Shows the loading animation
3882	 *	Fires an event
3883	 *	Usage:
3884	 *	<pre><div data-loading="12345"><img src="animation.gif"></div></pre>
3885	 *	Or:
3886	 *	<pre><img src="animation.gif" data-loading="global"></pre>
3887	 *  
3888	 *  @memberof Cargo.o.Helpers
3889	 *  @param {number} [pid] Project ID
3890	 *  @param {string} [url] URL to the project
3891	 *  @example Cargo.Helper.ShowLoadingAnimation(pid, url);
3892	 */
3893	ShowLoadingAnimation : function( pid, url ) {
3894		this.Data.is_loading = true;
3895		if( pid ) {
3896			_.delay(function() {
3897				Cargo.Helper.ShowLoadingAnimationById(pid);
3898			}, Cargo.Helper.Data.loading_timout);		
3899		
3900		} else if(url) {
3901			_.delay(function() {
3902				Cargo.Helper.ShowLoadingAnimationById('page');
3903			}, Cargo.Helper.Data.loading_timout);
3904		}
3905		$("*[data-loading=global], *[data-loading=global] .retinaSpinner").show();
3906	},
3907
3908	/**
3909	 *	Shows a specific loading animation based on project ID
3910	 *  
3911	 *  @memberof Cargo.o.Helpers
3912	 *  @param {number} [id] ID of the project
3913	 *  @example Cargo.Helper.ShowLoadingAnimationById(id);
3914	 */
3915	ShowLoadingAnimationById : function(id) {
3916		if(this.Data.is_loading) {
3917			$("*[data-loading="+id+"], *[data-loading="+id+"] .retinaSpinner").show();
3918		}
3919	},
3920
3921	/**
3922	 *	For timeing how long things take internally
3923	 *	Start and stop is the same call Cargo.Helper.Stopwatch()
3924	 *  
3925	 *  @memberof Cargo.o.Helpers
3926	 *  @example Cargo.Helper.Stopwatch();
3927	 *  @private
3928	 */
3929	Stopwatch : function() {
3930		if( !this.startTime ) {
3931			this.startTime = new Date().getMilliseconds();
3932		} else if( !this.endTime ) {
3933			this.endTime = new Date().getMilliseconds();
3934
3935			var total = this.endTime - this.startTime;
3936			Cargo.Core.log(total+"ms");
3937			this.startTime = false;
3938			this.endTime = false;
3939		} 
3940
3941		
3942	},
3943
3944	/**
3945	 * We can't have wrong case in the URL because all routing is based
3946	 * on the correct case. If there is a wrong care
3947	 */
3948	TestUrlCaseAndRedirect: function() {
3949		if( !Cargo.API.Config.is_domain ) {
3950
3951			// Test if the URL is the same case as what we have in the db
3952			var path_url = document.location.pathname.split('/')[1],
3953				url = Cargo.API.Config.cargo_url;
3954
3955			// If there are caps in the url, just keep using what the browser says
3956			if(path_url != url && path_url.toUpperCase() == url.toUpperCase()) {
3957				history.replaceState({}, Cargo.Model.Site.GetWebsiteTitle(), document.location.pathname.replace(path_url, url));
3958			}
3959		}
3960	}
3961
3962});
3963
3964/***************************************************************************
3965 *
3966 *	Router
3967 *
3968 **************************************************************************/
3969Cargo.o.router = Backbone.Router.extend({
3970	
3971	// ==========
3972	// = Routes =
3973	// ==========
3974	
3975	routes: {
3976		""							: "Home",
3977		"adminedit"					: "Home",
3978		"admin"						: "Admin",
3979		"admin/:section"			: "Admin",
3980		"preview/:which"			: "Home",
3981		"filter/:filter"			: "FilterHome",
3982		"filter/:filter/:purl"		: "FilterProjectDetail",
3983		":set/filter/:filter"		: "SetFilterHome",
3984		":set/filter/:filter/:purl"	: "SetFilterProjectDetail",
3985		":purl" 					: "FirstSlashProxy",
3986		":purl/solo" 				: "SoloLink",
3987		":purl/adminedit" 			: "AdminEdit",
3988		":set/:purl" 				: "SetProjectDetail",
3989		"*actions"					: "defaultAction"
3990	},
3991	
3992	// =====================
3993	// = Handler Functions =
3994	// =====================
3995	
3996	
3997	/*
3998	 *	Catch all for an unhandled event
3999	 */
4000	defaultAction: function(actions) {
4001		// if( this.default != actions ) {
4002			// Cargo.Core.log("Default action: "+actions);
4003			this.route(actions);
4004		// }
4005	},
4006
4007	/*
4008	 *	This acts as a router for anything past the first slash
4009	 *	Make sure it's not the URL, needs to be case insensitive
4010	 *	It could be a custom route, or a set, but most likely
4011	 *	it is a project
4012	 *	:purl
4013	 */
4014	FirstSlashProxy : function( url ) {
4015		if( this.RouteIsHome(url) ) {
4016			this.Home();
4017
4018		} else if( Cargo.Helper.IsCustomRoute() ) {
4019			this.CustomRoute( url );
4020		
4021		} else if( Cargo.Helper.IsSet(url) ) {
4022			this.Set( url );
4023		
4024		} else {
4025			this.ProjectDetail( url );
4026		}
4027	},
4028	
4029	/*
4030	 *	Matches the homepage link
4031	 */
4032	Home : function() {
4033		/* Check for a start project
4034		 * Just tell the detail to open */
4035		if( Cargo.API.Config.start_project && Cargo.API.Config.start_project != 0 ) {
4036			/* Cargo.View.ProjectDetail.$el.show(); */
4037			this.ProjectDetail( Cargo.API.Config.start_project );
4038
4039			if(Cargo.View.Main) {
4040				Cargo.View.Main.SetStartProjectClass();
4041			}
4042
4043		} else if(Cargo.API.Config.offline) {
4044			/* Offline page, render load complete */
4045			Cargo.Event.trigger("project_load_complete");
4046
4047		} else {
4048			/*
4049			 *	By setting the id = 0, the project model is changed
4050			 *	which fires the ProjectDetail render function
4051			 *	If the id=0, then the render will close instead of open
4052			 *
4053			 *	Note: this was changed to "clear" and I am not sure
4054			 *	what implications it has 6/28/13
4055			 */
4056			// Cargo.Model.Project.set( { id : 0 } );
4057			Cargo.Model.Project.clear();
4058
4059			Cargo.Event.trigger("homepage_loaded"); 
4060		}
4061	},	
4062
4063	/*
4064	 *	Helper for filter homepage
4065	 *	filter/:filter
4066	 */
4067	FilterHome : function( filter ) {
4068		// Cargo.Core.log("Filter: "+filter);
4069		Cargo.API.Config.filter = filter;
4070		
4071		return this.Home( );
4072	},
4073
4074	Admin : function(section) {
4075		if($("#content_frame").length == 0 && typeof require == "function") {
4076			Cargo.Core.loadEditor();
4077		}
4078	},
4079
4080	/*
4081	 *	Helper for filter detail link
4082	 *	filter/:filter/:purl
4083	 */
4084	FilterProjectDetail : function( filter, purl ) {
4085		// Cargo.Core.log("Filter: "+filter);
4086		Cargo.API.Config.filter = filter;
4087		
4088		return this.ProjectDetail( purl );
4089	},
4090
4091	/*
4092	 *	Helper for set filter homepage
4093	 *	:set/filter/:filter
4094	 */
4095	SetFilterHome : function( set, filter ) {
4096		// Cargo.Core.log("Set: "+set+" Filter: "+filter);
4097		Cargo.API.Config.filter = filter;
4098		
4099		return this.Home( );
4100	},
4101
4102	/*
4103	 *	Helper for set filter detail link
4104	 *	:set/filter/:filter/:purl
4105	 */
4106	SetFilterProjectDetail : function( set, filter, purl ) {
4107		// Cargo.Core.log("Set: "+set+" Filter: "+filter);
4108		Cargo.API.Config.filter = filter;
4109		
4110		return this.ProjectDetail( purl );
4111	},
4112
4113	/*
4114	 *	Helper for set detail link
4115	 *	:set/:purl
4116	 */
4117	SetProjectDetail : function( set, purl ) {
4118		// Cargo.Core.log("Set: "+set+" Project: "+purl);
4119		
4120		// This may trigger when the case is not correct on the set, in that case, go home
4121		return ( Cargo.Helper.IsSet(set) || set.toUpperCase() == Cargo.Helper.GetHomeUrl(true).toUpperCase()) ? this.ProjectDetail( purl ) : this.Home();
4122	},
4123	
4124
4125	/* 
4126	 * 	Project detail 
4127	 *	:purl 
4128	 */
4129	ProjectDetail : function( purl ) {
4130		// Make sure this is a string
4131		purl += "";
4132		
4133		/* Strip any slashes from the purl */
4134		purl = purl.replace(/\//g, "");
4135
4136		// Cargo.Core.log( "Router: ProjectDetail: /"+purl );
4137
4138		var from_router = true;
4139		var is_perma = Cargo.Helper.IsPermalink(purl, from_router);
4140
4141				
4142		if( is_perma ) {
4143			/*
4144			 *	Do the permalink here
4145			 */
4146			if( Cargo.View.ProjectDetail ) {
4147				Cargo.View.ProjectDetail.Permalink(purl);
4148			}
4149		
4150		} else if(purl && purl !== false) {
4151			/*
4152			 *	If there is no project detail element on screen
4153			 *	don't ajax load, direct load by document location
4154			 */
4155			if(!Cargo.View.ProjectDetail && !Cargo.API.Config.offline) {
4156				Cargo.Helper.GoToUrl( purl );
4157			
4158			/*
4159			 *	Get and set the new single project
4160			 *	This will trigger an event that will automatically
4161			 *	update the view
4162			 */
4163			} else {
4164				Cargo.API.GetSingleProject( purl );
4165			}
4166			
4167		}
4168		
4169	},
4170
4171	/*
4172	 *	Custom route
4173	 *	:route
4174	 */
4175	CustomRoute : function( url ) {
4176		// Cargo.Core.log("Custom route: "+url);
4177		Cargo.Event.trigger("custom_route_loaded", url);
4178
4179		if( Cargo.Helper.IsSet(url) ) {
4180			// Cargo.Core.log("Set on custom route: "+url);
4181			Cargo.Event.trigger("set_loaded", url);			
4182		}
4183	},
4184
4185	/*
4186	 *	Set
4187	 *	:set
4188	 */
4189	Set : function( url ) {
4190		// Cargo.Core.log("Set: "+url);
4191		Cargo.Event.trigger("set_loaded", url);			
4192		
4193	},
4194
4195	/*
4196	 *	Solo link is a helper for ProjectDetail
4197	 *	:purl/solo
4198	 */
4199	SoloLink : function( purl ) {
4200		Cargo.API.Config.is_solo = true;
4201		Cargo.Event.trigger("solo_link_loaded", location.pathname); 
4202		return this.ProjectDetail( purl );
4203	},
4204
4205	/*
4206	 *	Admin edit is a helper for ProjectDetail
4207	 *	:purl/adminedit
4208	 */
4209	AdminEdit : function( purl ) {
4210		Cargo.API.Config.admin_edit_project = true;
4211		return this.ProjectDetail(  purl );
4212	},
4213
4214
4215
4216	/*
4217	 *	Rewrite the route Regex to allow for case insensitivity and trailing slash support
4218	 *	Note: This was copied directly from backbone and added the "i" param
4219	 *		  as well as \/? conditional with the second replace(regex)
4220	 */
4221	_routeToRegExp: function (route) {
4222		route = route.replace(/[-{}[\]+?.,\\^$|#\s]/g, '\\$&')
4223					 .replace(/\((.*?)\)/g, '(?:$1)?')
4224					 .replace(/:\w+/g, '([^\/]+\/?)')
4225					 .replace(/\*\w+/g, '(.*?)');
4226		
4227		return new RegExp('^' + route + '$', 'i');
4228	},
4229
4230	/*
4231	 *	Make a shorthand reference for adding a route with Backbone
4232	 *	Also update this to include all variants possible 
4233	 *	This includes trailing slash and query strings
4234	 */
4235	addroute : function( route, name, callback ) {
4236		 var variants = [
4237			'', 				// normal route
4238			'/', 				// trailing slash
4239			'?*querystring', 	// querystrings
4240			'/?*querystring' 	// trailing slash + querystrings
4241		];
4242		
4243		// Bind a route for each of the variants
4244		for (var i=0; i < variants.length; i++) {
4245			var variant = variants[i];
4246			// Backbone.Router.prototype.route.call(this, route+variant, name, callback);
4247			this.route( route+variant, name, callback );
4248		}
4249	},
4250
4251	RouteIsHome : function(url) {
4252
4253		var is_home = false;
4254		// If the incoming url matches the normalized Cargo url
4255		if( !Cargo.API.Config.is_domain && url.toLowerCase() == Cargo.Helper.GetDomainOrUrl().toLowerCase() ) {
4256			// Make sure this is not a project called the same as the URL
4257			if(document.location.pathname.replace("/").split("/").length <= 1) {
4258				is_home = true;
4259			}
4260		}
4261		
4262		return is_home;
4263	}
4264		
4265				
4266});
4267
4268
4269/***************************************************************************
4270 *
4271 *	Templates
4272 *	This is the template manager function file
4273 *	It loads and caches tempaltes
4274 *	Uses require.js to async load in templates
4275 *
4276 **************************************************************************/
4277
4278Cargo.o.templates = Backbone.View.extend({
4279	/* Data store for templates */
4280	Data : { },
4281
4282	/**
4283	 *	Gets a template based on a name
4284	 *	If one is not available in memory, async load it
4285	 */
4286	Get : function( which, file_path ) {
4287		which = Cargo.Helper.GetBackboneCaseInsensitive(this.Data, which);
4288
4289		if( this.Data[which] && this.Data[which].template ) {
4290			return this.Data[which].template;
4291		} else {
4292			this.Load( which, file_path );
4293		}
4294	},
4295
4296	/**
4297	 *	Gets a template element based on a name
4298	 *	If one is not available in memory, async load it
4299	 */
4300	GetEl : function( which ) {
4301		which = Cargo.Helper.GetBackboneCaseInsensitive(this.Data, which);
4302		
4303		if( this.Data[which] ) {
4304			return this.Data[which].element;
4305		} else {
4306			this.Load( which );
4307		}
4308	},
4309
4310	/**
4311	 *	Add a template to the list
4312	 *	Note: This does not load the template view
4313	 */
4314	Add : function( template, element, dataset, setname ) {
4315		Cargo.Template.Data[template] = {
4316			dataset		: dataset,
4317			setname		: setname,
4318			element		: element
4319		}
4320	},
4321
4322	/**
4323	 *	Loops through the dom to find view stubs
4324	 *	Seeks out the "data-view" attribute, which can be associated with any
4325	 *	html element
4326	 */
4327	FindAndAddViewStubs : function( ) {
4328		var self = this;
4329
4330		$("[data-view]").each(function() {
4331			var template = $(this).data("view");
4332			var setname = $(this).data("set");
4333			var el = ( $(this).attr("id") ) ? "#" + $(this).attr("id") : "." + $(this).attr("class");
4334			/*
4335			 *	For the dataset, find if it is collection or model
4336			 *	If it does not exist, then use Default Collection
4337			 */
4338			// var dataset = $(this).data("set");
4339			var dataset = Cargo.Collection.Default;
4340
4341			if(setname) {
4342				var requested_collection = Cargo.Helper.GetBackboneCaseInsensitive(Cargo.Collection, setname);
4343				var requested_model = Cargo.Helper.GetBackboneCaseInsensitive(Cargo.Model, setname);
4344				
4345				if( requested_collection ) {
4346					dataset = Cargo.Collection[requested_collection];
4347
4348				} else if( requested_model ) {
4349					dataset = Cargo.Model[requested_model];
4350				}
4351			}
4352		
4353			/* Add the template to the stack */
4354			Cargo.Template.Add( template, el, dataset, setname );
4355		});
4356	},
4357
4358	/**
4359	 *	Loads a single template into memory
4360	 *
4361	 *	Note: For api routes that are not on this domain (ie: api.cargo)
4362	 *		then we must load the design like a variable with the "true" flag
4363	 *		Otherwise, load the template like normal.
4364	 */
4365	Load : function( which, file_path ) {
4366		if(!file_path) {
4367			var file_name = which + ".handlebars";
4368				// file_path = Cargo.API.GetDesignAndFile(file_name, true);
4369				file_path = Cargo.API.GetDesignAndFile(file_name, false);
4370		}
4371
4372		if( !Cargo.Template.Data[which] || !Cargo.Template.Data[which].loading ) {
4373			if( !Cargo.Template.Data[which] ) {
4374				Cargo.Template.Data[which] = { };
4375			}
4376
4377			Cargo.Template.Data[which].loading = true;
4378			
4379			$.loadFiles( file_path ).done( function( template ) {
4380
4381				/*
4382				 *	If no value for template was passed, require loaded it like a script
4383				 */
4384				if( template ) {
4385					/*
4386					 *	Evaluate the response, it may come as a string
4387					 */
4388					try {
4389						var template = eval(template);
4390					} catch(err) {
4391						// nothing
4392					}
4393					Cargo.Template.Data[which].template = template;
4394				} else {
4395					/*
4396					 *	The name of the output variable is = to the file name, without / & .
4397					 */
4398					var template_var = "templatesdesign" + file_name.replace(/(\/|\.)/g, "");
4399					Cargo.Template.Data[which].template = window[template_var];
4400				}
4401
4402				/* Fire an event notifying of the newly loaded template */
4403				Cargo.Event.trigger("template_loaded", which );	
4404				
4405				return Cargo.Template.Data[which].template;
4406			});
4407		}
4408	},
4409
4410	/**
4411	 *	Load all non-loaded template into memory async
4412	 *	Loops through the template list and loads any template 
4413	 *	that has yet to be loaded (this is determined if the "template" var is blank)
4414	 */
4415	LoadAll : function( ) {
4416		for(key in this.Data) {
4417		    if( typeof(this.Data[key].template) == "undefined" ) {
4418		    	this.Load( key );
4419		    }
4420		}
4421	},
4422
4423	/**
4424	 *	Loops through the templates and checks if they have
4425	 *	A view already loaded for them
4426	 *	If not, it will load a view
4427	 *	@note: this is not really ideal like this
4428	 *	@note: perhaps have a view controller
4429	 */
4430	LoadFoundViews : function() {
4431		/*
4432		 *	Loop through the templates and check to see if a view
4433		 *	has been init yet. If not, do it
4434		 */
4435		for(key in this.Data) {
4436			var requested_view = Cargo.Helper.GetBackboneCaseInsensitive(Cargo.o.view, key);
4437		    if( 
4438		    	typeof(Cargo.o.view[requested_view]) == "function" && 
4439		    	typeof(Cargo.View[requested_view]) == "undefined" 
4440		    ) {
4441
4442				var self = this;
4443		    	/* Handle the case for multiple of the same view on the page */
4444		    	$("[data-view='"+key+"']").each(function(i) {
4445		    		var el = ( $(this).attr("id") ) ? "#" + $(this).attr("id") : "." + $(this).attr("class");
4446		    		var final_view = (i>0) ? requested_view+"_"+i: requested_view;
4447
4448		    		Cargo.View[final_view] = new Cargo.o.view[requested_view]({ 
4449						el    		: el,
4450						collection 	: self.Data[key].dataset,
4451						template 	: self.Data[key].template,
4452						className	: final_view
4453					});
4454		    	});
4455
4456		    } else {
4457		    	if( typeof(Cargo.View[requested_view]) == "function" ) {
4458		    		Cargo.Core.log("A view for "+requested_view+" already loaded");
4459		    	
4460		    	} else if(typeof(Cargo.o.view[requested_view]) == "undefined") {
4461		    		/*	If there is no view, then the user likely
4462		    		 *	just added data-view="something"
4463		    		 *	In this case, make a view based on our default */
4464		    		Cargo.View[key] = new Cargo.o.view.Default({ 
4465						el    		: this.Data[key].element,
4466						collection 	: this.Data[key].dataset,
4467						template 	: key,
4468						datasetname : this.Data[key].setname,
4469						className	: key
4470					});
4471		    	}
4472		    }
4473		}
4474	}
4475});
4476
4477
4478/***************************************************************************
4479 *
4480 *	Addition to Cargo core functionality
4481 *
4482 *	The slideshow class
4483 *	Cargo.Core.Slideshow.InitSlideshow();
4484 *
4485 *	Created on 6/26/13 for 2.0
4486 *
4487 **************************************************************************/
4488
4489Cargo.Core.Slideshow = {
4490
4491	uid: 0,
4492
4493 	InitSlideshow : function() {
4494 		var self = this;
4495
4496 		$(".slideshow").not('[data-slideshow-initialized]').each(function() {
4497 			// Shorthand for display options
4498 			var dop = Cargo.Model.DisplayOptions;
4499 			
4500 			var opts = {
4501				// Transitions
4502			    "transition" 			: dop.GetSlideTransition(),
4503			    "transition_duration"   : dop.GetSlideTransitionDuration(),
4504
4505			    // Autoplay
4506			    "autoplay"          	: dop.GetSlideAutoPlay().enabled,
4507			    "autoplay_duration" 	: dop.GetSlideAutoPlay().delay,
4508
4509			    // Display
4510			    "navigation"        	: dop.GetSlideTextNav().enabled,
4511			    "thumbnails"        	: dop.GetSlideThumbnails().enabled,
4512			    "toggle_thumbnails" 	: dop.GetSlideToggleThumbLink().enabled,
4513			    "default_thumbnails"    : dop.GetSlideDefaultThumbs(),
4514			    "square_thumbnails" 	: false
4515			};
4516			
4517			$(this).attr('data-id', $(this).attr('data-id') + '-' + (self.uid++));
4518			$(this).cargoSlideshow(opts);
4519
4520 		});
4521 	},
4522
4523 	ShowCaption : function() {
4524
4525 	},
4526
4527 	// this is where slideshows register theirselves
4528 	SlideshowObjects : {
4529
4530 	}
4531}
4532
4533/**************************************************************************
4534 *
4535 *	Addition to Cargo core functionality
4536 *
4537 *	The handlebars rendering class
4538 *
4539 *	Usage: 
4540 *	Cargo.Core.Handlebars.Render();
4541 *
4542 **************************************************************************/
4543
4544Cargo.Core.Handlebars = {
4545	/**
4546	 *	Uses handlebars to render a template and data model
4547	 *	registers any helpers that need to go with the render
4548	 *	used by all views that render data
4549	 *	returns the compiled markup
4550	 */
4551	Render : function(template, data_model) {
4552		// Get the compiled template
4553		var compiled = Handlebars.compile( template );
4554		
4555		/*
4556		 * Register partials
4557		 */
4558		Handlebars.registerPartial("loading_animation", $("#loading_animation").html());
4559
4560		/*
4561		 * Register all helpers
4562		 */
4563		this.RegisterHelper_Asset();
4564		this.RegisterHelper_AssetContents();
4565		this.RegisterHelper_Print();
4566		this.RegisterHelper_IfAny();
4567		this.RegisterHelper_IfAll();
4568		this.RegisterHelper_IfEqual();
4569		this.RegisterHelper_UnlessAny();
4570		this.RegisterHelper_UnlessAll();
4571		this.RegisterHelper_IfOption();
4572		this.RegisterHelper_UnlessOption();
4573		this.RegisterHelper_DisplayOptions();
4574		this.RegisterHelper_Truncate();
4575
4576		/*
4577		 *	Finalize with the model
4578		 */
4579		var markup = compiled( data_model );
4580
4581		return markup;
4582	},
4583
4584	/**
4585	 *	This method is just the same as above
4586	 *	However, this render only uses the assets helper and
4587	 *	nothing else. It is used for rendering the CSS content
4588	 *	from the preview panel
4589	 */
4590	RenderOnlyAssets : function(template) {
4591		var compiled = Handlebars.compile( template );
4592		this.RegisterHelper_Asset();
4593		var markup = compiled();
4594		return markup;
4595	},
4596
4597	/*
4598	 * Register a helper for assets
4599	 */
4600	RegisterHelper_Asset : function() {
4601		Handlebars.registerHelper('asset', function(options) {
4602			var asset = options.fn(this);
4603			var model = Cargo.Collection.Assets.where( { filename : asset } )[0];
4604			
4605			return (model) ? Cargo.Model.Site.GetAssetPath(model.get('filename'), model.get('id')) : "";
4606		});
4607	},
4608
4609	/*
4610	 * Register a helder for assets
4611	 */
4612	RegisterHelper_AssetContents : function() {
4613		Handlebars.registerHelper('asset_contents', function(options) {
4614			var asset = options.fn(this);
4615			var model = Cargo.Collection.Assets.where( { filename : asset } )[0];
4616			
4617			var file = (model) ? Cargo.Model.Site.GetAssetPath(model.get('filename'), model.get('id')) : "";
4618
4619			$.get(file, function(data) {
4620				// Try to replace immediately	
4621				$("file[data-url='"+file+"']").after(data).remove();
4622
4623				// Also try a little later, in case the DOM has not been updated
4624				setTimeout(function() {
4625					$("file[data-url='"+file+"']").after(data).remove();
4626				}, 500);
4627				
4628			}, 'text');
4629
4630			return '<file data-url="'+file+'"></file>';
4631		});
4632	},
4633
4634	/*
4635	 * Register a helper for print
4636	 */
4637	RegisterHelper_Print : function() {
4638		Handlebars.registerHelper('print', function(context, options) {
4639			var wrapper_head = wrapper_tail = "";
4640			if(context == "project.content" || context == "content") {
4641				wrapper_head = "<projectcontent>";
4642				wrapper_tail = "</projectcontent>";
4643			}
4644			context = context.replace("project.","");
4645
4646			var model = (typeof this.project == "object") ? this.project : this;
4647
4648			if(Cargo.Model.Site.GetUseHiRes()) {
4649				if(model[context + '_2x'] != null){
4650					context += "_2x";
4651				}
4652			}
4653
4654			return new Handlebars.SafeString(wrapper_head + model[context] + wrapper_tail);
4655		});
4656	},
4657
4658	/*
4659	 * Register a helper for if_any
4660	 */
4661	RegisterHelper_IfAny : function() {
4662		Handlebars.registerHelper('if_any', function() {
4663			var any = false;
4664			var options = arguments[arguments.length-1];
4665			
4666			for (var i = arguments.length - 2; i >= 0; i--) {
4667				if( arguments[i] ) {
4668					any = true;
4669					break;
4670				}
4671			}
4672			return (any) ? options.fn(this) : options.inverse(this);
4673		});
4674	},
4675
4676	/*
4677	 * Register a helper for if_all
4678	 */
4679	RegisterHelper_IfAll : function() {
4680		Handlebars.registerHelper('if_all', function() {
4681			var all = true;
4682			var options = arguments[arguments.length-1];
4683			
4684			for (var i = arguments.length - 2; i >= 0; i--) {
4685				if( !arguments[i] ) {
4686					all = false;
4687					break;
4688				}
4689			}
4690
4691			return (all) ? options.fn(this) : options.inverse(this);
4692		});
4693	},
4694
4695	/*
4696	 * Register a helper for if_equal
4697	 */
4698	RegisterHelper_IfEqual : function() {
4699		Handlebars.registerHelper('if_equal', function(value_one, value_two, options) {
4700			if (arguments.length != 3) {
4701				return '';
4702			}
4703			return (value_one == value_two) ? options.fn(this) : options.inverse(this);
4704		});
4705	},
4706
4707	/*
4708	 * Register a helper for unless_any
4709	 */
4710	RegisterHelper_UnlessAny : function() {
4711		Handlebars.registerHelper('unless_any', function() {
4712			var any = false;
4713			var options = arguments[arguments.length-1];
4714			
4715			for (var i = arguments.length - 2; i >= 0; i--) {
4716				if( !arguments[i] ) {
4717					any = true;
4718					break;
4719				}
4720			}
4721			return (any) ? options.fn(this) : options.inverse(this);
4722		});
4723	},
4724
4725	/*
4726	 * Register a helper for unless_all
4727	 */
4728	RegisterHelper_UnlessAll : function() {
4729		Handlebars.registerHelper('unless_all', function() {
4730			var all = true;
4731			var options = arguments[arguments.length-1];
4732			
4733			for (var i = arguments.length - 2; i >= 0; i--) {
4734				if( arguments[i] ) {
4735					all = false;
4736					break;
4737				}
4738			}
4739			return (all) ? options.fn(this) : options.inverse(this);
4740		});
4741	},
4742
4743	/*
4744	 * Register a helper for display options
4745	 */
4746	RegisterHelper_DisplayOptions : function() {
4747		Handlebars.registerHelper('displayoptions', function(context, options) {
4748			// Split the context into args
4749			var args = context.replace('display_options.','').split(".");
4750			// Find out how many options
4751			var count = args.length;
4752			// Assume we are not passing this condition
4753			var condition = false;
4754
4755			if(count == 1) {
4756				condition = Cargo.Model.DisplayOptions.get(args[0]);
4757			} else if(count == 2) {
4758				condition = Cargo.Model.DisplayOptions.get(args[0])[args[1]];
4759			} else if(count == 3) {
4760				condition = Cargo.Model.DisplayOptions.get(args[0])[args[1]][args[2]];
4761			} else {
4762				condition = Cargo.Model.DisplayOptions.get(args[0]);
4763			}
4764
4765			return (condition) ? options.fn(this) : options.inverse(this);
4766		});
4767
4768	},
4769
4770	/*
4771	 * Register a helper for display options
4772	 */
4773	RegisterHelper_IfOption : function() {
4774		Handlebars.registerHelper('if_option', function(context, options) {
4775			// Split the context into args
4776			var args = context.replace('display_options.','').split(".");
4777			// Find out how many options
4778			var count = args.length;
4779			// Assume we are not passing this condition
4780			var condition = false;
4781
4782			if(count == 1) {
4783				condition = Cargo.Model.DisplayOptions.get(args[0]);
4784			} else if(count == 2) {
4785				condition = Cargo.Model.DisplayOptions.get(args[0])[args[1]];
4786			} else if(count == 3) {
4787				condition = Cargo.Model.DisplayOptions.get(args[0])[args[1]][args[2]];
4788			} else {
4789				condition = Cargo.Model.DisplayOptions.get(args[0]);
4790			}
4791
4792			return (condition) ? options.fn(this) : options.inverse(this);
4793		});
4794
4795	},
4796
4797	/*
4798	 * Register a helper for display options
4799	 */
4800	RegisterHelper_UnlessOption : function() {
4801		Handlebars.registerHelper('unless_option', function(context, options) {
4802			// Split the context into args
4803			var args = context.replace('display_options.','').split(".");
4804			// Find out how many options
4805			var count = args.length;
4806			// Assume we are not passing this condition
4807			var condition = false;
4808
4809			if(count == 1) {
4810				condition = Cargo.Model.DisplayOptions.get(args[0]);
4811			} else if(count == 2) {
4812				condition = Cargo.Model.DisplayOptions.get(args[0])[args[1]];
4813			} else if(count == 3) {
4814				condition = Cargo.Model.DisplayOptions.get(args[0])[args[1]][args[2]];
4815			} else {
4816				condition = Cargo.Model.DisplayOptions.get(args[0]);
4817			}
4818
4819			return (!condition) ? options.fn(this) : options.inverse(this);
4820		});
4821
4822	},
4823
4824	RegisterHelper_Truncate : function() {
4825		Handlebars.registerHelper ('truncate', function (str, len) {
4826			if (str.length > len && str.length > 0) {
4827		        var new_str = str + " ";
4828		        new_str = str.substr (0, len);
4829		        new_str = str.substr (0, new_str.lastIndexOf(" "));
4830		        new_str = (new_str.length > 0) ? new_str : str.substr (0, len);
4831					
4832		        return new Handlebars.SafeString ( new_str +'...' ); 
4833		    }
4834		    return str;
4835		});
4836	}
4837}
4838
4839/**************************************************************************
4840 *
4841 *	Addition to Cargo core functionality
4842 *
4843 *	The video class
4844 *	This method is a replica of the one found in site.package
4845 *	The "reverse fallback" -- Flash object and HTML5 video tags are both housed
4846 *	within a div.video_component. The CheckForVideo() method determines who gets
4847 *	to stay, in essence, and removes the unused tags.
4848 *
4849 *	Usage: 
4850 *	Cargo.Core.Video.CheckForVideo();
4851 *
4852 **************************************************************************/
4853
4854Cargo.Core.Video = {
4855	Config: {
4856		DEBUG: false,
4857		dependencies: [
4858			'/_css/mediaplayer.css',
4859			'/_jsapps/_libs/jquery-ui-1.10.3.custom.min.js',
4860			'/_js/cargo.video.package.js'
4861		],
4862		volume_default: 0.75,
4863		volume: 1,
4864		muted: false
4865	},
4866
4867/**
4868 * DEBUG
4869 *
4870 * Controlled by Config.DEBUG, MightForceHTML5() and IsForcingHTML5() modify,
4871 * dependent upon the presence of the cargo.video.forcedHTML5 cookie, the
4872 * behavior of DetectPlugin() and DestroyFallbackTags() - always displaying
4873 * the video elements.
4874 *
4875 * ForceHTML5() controls the cargo.video.forcedHTML5 cookie and enables video
4876 * elements accordingly.
4877 */
4878	MightForceHTML5: function() {
4879		return this.Config.DEBUG;
4880	},
4881
4882	IsForcingHTML5: function() {
4883		if (this.Config.is_forcing_HTML5 !== undefined)
4884			return this.Config.is_forcing_HTML5;
4885
4886		var forcing_HTML5 = $.cookie('cargo.video.forcedHTML5') || false;
4887		if (forcing_HTML5)
4888			this.Config.has_flash_plugin = false;
4889
4890		this.Config.is_forcing_HTML5 = forcing_HTML5;
4891		return forcing_HTML5;
4892	},
4893
4894	ForceHTML5: function() {
4895		var should_force_HTML5 = $.cookie('cargo.video.forcedHTML5') || false;
4896		this.Config.force_HTML5 = ! should_force_HTML5;
4897		if (this.Config.force_HTML5) {
4898			$.cookie('cargo.video.forcedHTML5', true, {expires: 30, path: '/'});
4899			this.Config.has_flash_plugin = false;
4900			this.CheckForVideo();
4901		} else {
4902			$.cookie('cargo.video.forcedHTML5', null, {path: '/'});
4903			window.location.reload();
4904		}
4905	},
4906
4907/**
4908 * Primary method, determines if there are videos present and, if so, if there
4909 * is a need to "reverse fallback" to an HTML5 player. Upon fallback, loads the
4910 * related CSS and javascript should they not already exist, calling back to
4911 * itself recursively, rearranging and removing Flash and HTML5 elements, and 
4912 * finally applying the HTML5 player controls.
4913 *
4914 * @param	video_js_has_loaded	Boolean, true on recursive call after load of external CSS and javascript files
4915 */
4916	CheckForVideo: function(video_js_has_loaded) {
4917		if (!this.DetectVideos())
4918			return;
4919
4920		if (this.DetectPlugin()) {
4921			this.DestroyFallbackTags();
4922			return;
4923		}
4924
4925		// Load video-related javascript here, but only once
4926		if (this.Config.has_video_js === undefined) {
4927			if (video_js_has_loaded) {
4928				this.Config.has_video_js = true;
4929			} else {
4930				if (this.Config.is_loading_video_js !== undefined)
4931					return;
4932
4933				this.Config.is_loading_video_js = true;
4934
4935				// Actual loading of external dependencies
4936				$.loadFiles(this.Config.dependencies).done(function() {
4937					Cargo.Core.Video.CheckForVideo(true);
4938				});
4939
4940				return;
4941			}
4942		}
4943
4944		// Culling of the object tags, displaying of the video tags
4945		$('.video_component').each(function() {
4946			$(this).children('object').remove();
4947
4948			var video_tag = $(this).children('.video_tag');
4949			var player = video_tag.children();
4950			video_tag.css({
4951				display: 'inline-block',
4952				width: player.attr('data-video-width'),
4953				height: player.attr('data-video-height')
4954			});
4955
4956			// Apply jMediaelement controls after javascript has loaded
4957			if (Cargo.Core.Video.Config.has_video_js)
4958				Cargo.Core.Video.ConfigureMediaPlayer(player);
4959		});
4960	},
4961
4962/**
4963 * Application of jMediaelement controls to video tags
4964 */
4965	ConfigureMediaPlayer: function(player) {
4966		// Don't call video player methods more than once
4967		if (player.attr('data-video-configured') !== undefined)
4968			return;
4969
4970		player.attr('data-video-configured', 'true');
4971
4972		// Grab volume settings from cookies or defaults
4973		this.Config.volume = $.cookie('cargo.video.volume') || this.Config.volume_default;
4974		this.Config.muted = ($.cookie('cargo.video.muted')) ? true : false;
4975
4976		// Apply jMediaelement controls, custom volume logic
4977		player.jmeInit().jmeProp('controlbar', true);
4978		player.children('video').each(function(index, video) {
4979			$(video).bind('volumechange', function() {
4980				var control = $('.mute-unmute');
4981				var volume = Number($(this).jmeValue('volume'));
4982				var muted = $(this).jmeValue('muted');
4983
4984				if (volume > 0.66) {
4985					control.removeClass('volume-medium volume-low').addClass('volume-high');
4986				} else if (volume <= 0.66 && volume > 0.33) {
4987					control.removeClass('volume-high volume-low state-mute state-unmute').addClass('volume-medium');
4988				} else if (volume <= 0.33 && volume > 0.01) {
4989					control.removeClass('volume-high volume-medium state-mute state-unmute').addClass('volume-low');
4990				} else {
4991					control.removeClass('volume-high volume-medium volume-low').addClass('state-unmute');
4992				}
4993			
4994				if (muted) {
4995					control.addClass('state-unmute');
4996					$.cookie('cargo.video.muted', true, {expires: 30, path: '/'});
4997				} else {
4998					$.cookie('cargo.video.muted', null, {path: '/'});
4999				}
5000			
5001				$.cookie('cargo.video.volume', volume, {expires: 30, path: '/'});
5002			});
5003
5004			video.volume = Cargo.Core.Video.Config.volume;
5005			video.muted = Cargo.Core.Video.Config.muted;
5006		});
5007	},
5008
5009/**
5010 * Determine if the browser supports and has the Flash plugin installed
5011 *
5012 * @return	Boolean, whether detected
5013 */
5014	DetectPlugin: function() {
5015		// Only check for Flash once
5016		if (this.Config.has_flash_plugin !== undefined)
5017			return this.Config.has_flash_plugin;
5018
5019		// During debug, always report a lack of Flash
5020		if (this.MightForceHTML5()) {
5021			if (this.IsForcingHTML5())
5022				return false;
5023		}
5024
5025		var flash_was_detected = false;
5026		if (typeof navigator.plugins != 'undefined' && typeof navigator.plugins['Shockwave Flash'] == 'object') {
5027			var d = navigator.plugins['Shockwave Flash'].description;
5028			if (d && !(typeof navigator.mimeTypes != 'undefined' && navigator.mimeTypes['application/x-shockwave-flash'] && !navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin))
5029				flash_was_detected = true;
5030		} else if (typeof window.ActiveXObject != 'undefined') {
5031			try {
5032				if ((new ActiveXObject('ShockwaveFlash.ShockwaveFlash')))
5033					flash_was_detected = true;
5034			} catch(e) {}
5035		}
5036
5037		this.Config.has_flash_plugin = flash_was_detected;
5038		return flash_was_detected;
5039	},
5040
5041/**
5042 * Get a count of the div.video_component elements, confirm if there is more
5043 * than one present
5044 *
5045 * @return	Boolean, true if one or more
5046 */
5047	DetectVideos: function() {
5048		return ($('.video_component').length > 0);
5049	},
5050
5051/**
5052 * Remove the video tag parent div, even though it is not displayed, just to be
5053 * extra, extra safe
5054 */
5055	DestroyFallbackTags: function() {
5056		// During debug, do not remove the video tags
5057		if (this.MightForceHTML5())
5058			return;
5059
5060		if (this.Config.was_destroyed !== undefined)
5061			return;
5062
5063		this.Config.was_destroyed = true;
5064		$('.video_component .video_tag').each(function(index, value) {
5065			$(this).remove();
5066		});
5067	}
5068}
5069/*
5070 * End Cargo.Core.Video
5071 */
5072 
5073
5074/**************************************************************************
5075 *
5076 *	Addition to Cargo core functionality
5077 *
5078 *	The audio class
5079 *	This method is a replica of the one found in site.package
5080 *
5081 *	Usage: 
5082 *	Cargo.Core.Audio.CheckForAudio();
5083 *
5084 **************************************************************************/
5085
5086// This value is needed by sound manager and is global because of legacy
5087var start_sound = 
5088	this_play = false;
5089
5090Cargo.Core.Audio = {
5091	Config: {
5092		has_audio_js : false,
5093		dependencies: [
5094			'/_jsapps/_libs/jquery-ui-1.10.3.custom.min.js',
5095			'/_js/soundmanager/api/soundmanager2-nodebug-jsmin.js'
5096		]
5097	},
5098
5099	/**
5100	 * Primary method, determines if there are audio players present and, if so, load them
5101	 *
5102	 * @param	audio_js_has_loaded	Boolean, true on recursive call after load of external CSS and javascript files
5103	 */
5104	CheckForAudio : function() {
5105		if (!this.DetectAudioPlayer())
5106			return;
5107
5108		this.UnloadAudio();
5109		this.InitButtons();
5110		this.InitPlayerSize();
5111		if(Cargo.Helper.isMobile()){
5112			// preload audio JS because of IOS issues
5113			// Sound play() calls are blocked by the OS unless in direct response to a user event like touch or click
5114			// http://www.schillmania.com/projects/soundmanager2/doc/technotes/#mobile-device-limitations
5115			Cargo.Core.Audio.LoadDependancies(null, function(){
5116				$(window).ready(function(){
5117					setupSoundManager();
5118				});
5119			});
5120		}
5121	},
5122
5123	InitButtons : function() {
5124
5125		$(".audio_component .play_pause").off('click').on('click', function(e) { 
5126			e.preventDefault();
5127			
5128			var force = false;
5129			if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) { 
5130				var ffversion=new Number(RegExp.$1); 
5131				if(ffversion < 3.6) { force = true; }
5132			}
5133			var rel = $(this).attr("rel");
5134			var which = $(this).attr("class").replace("play_pause ", "");
5135
5136			// Trigger the event of playing the audio
5137			Cargo.Event.trigger("audio_play_pause", rel, which );
5138				
5139			// Load dependancies, or toggle the sound
5140			if( !Cargo.Core.Audio.Config.has_audio_js ) {
5141				// console.log("load");
5142				Cargo.Core.Audio.LoadDependancies($(this), function(){
5143					setupSoundManager();
5144				});
5145			} else {
5146				// console.log("toggle");
5147				Cargo.Core.Audio.ToggleSound($(this));
5148			}
5149		});
5150		
5151		if($("#DroidFont").length <= 0) {
5152			 // Load the external font from the google api
5153			  var droidfont=document.createElement('link');
5154			  droidfont.id='DroidFont';
5155			  droidfont.setAttribute('type','text/css');
5156			  droidfont.setAttribute('rel','stylesheet');
5157			  droidfont.setAttribute('href','https://fonts.googleapis.com/css?family=Droid+Sans+Mono');
5158			  
5159			  var hd=document.getElementsByTagName('head')[0];
5160			  hd.appendChild(droidfont);
5161		};
5162
5163		$(".progress").css("display","none");
5164	},
5165
5166	/**
5167	 *	Set the player size of the audio component
5168	 *	Also set the position of each element
5169	 */
5170	InitPlayerSize : function() {
5171		var total_players = $(".audio_component").length;
5172		var zero_count = 0;
5173		
5174		$(".audio_component").each(function(){
5175			if($(this).width() > 0) {
5176				// Setup playhead/progress width 
5177			   //$('.Href', this).hide();            
5178			   var position = $(".position", this);
5179			   var playhead = $(".playhead", this);
5180			   // This makes the player correctly sized based on the .Sound width property
5181			   var soundWidth = $(this).width();
5182			   var soundBordersWidth = $(".border", this).outerWidth() - $(".border", this).width();
5183			   var controlsWidth = $(".controls", this).width(soundWidth - soundBordersWidth).width();
5184			   var volumeWidth = $(".volume", this).width();
5185			   var playpauseWidth = $(".play_pause", this).width();
5186			   var spacerWidths = $(".vertical_border", this).width() * 2;
5187			   $(".border", this).width(soundWidth - soundBordersWidth);
5188			   $(".info", this).width(controlsWidth - volumeWidth - playpauseWidth - spacerWidths);
5189			  
5190			   var refWidth = $(".info", this).width();
5191			   position.width(refWidth);
5192				 
5193			   playhead
5194				 .css("width", refWidth+"px")
5195				 .css("left", 0);
5196		
5197			   var progressBordersWidth = $(".progress", this).outerWidth() - $(".progress", this).width();
5198			   $(".progress")
5199				 .css("width", (refWidth - progressBordersWidth) +"px")
5200				 .css("left", 0);
5201				
5202				if($("#home_content").length > 0) var prog_left = "35px";
5203				else var prog_left = "0px";
5204				
5205			   $(".progress_clip")
5206				 .css("position", "absolute")
5207				 .css("overflow", "hidden")
5208				 .css("width", refWidth+"px")
5209				 .css("left", prog_left);
5210			   
5211			   $(".position")
5212				 .css("left", prog_left);
5213		
5214			   $(".playhead_container", this)
5215				 .css("position", "relative")
5216				 .css("width", (playhead.outerWidth() * 2) + "px")
5217				 .css("left", ((playhead.outerWidth() * -1)) + "px");
5218		
5219			   $(".progress_container", this)
5220				 .css("position", "absolute")
5221				 .css("z-index", "-1")
5222				 .css("width", (playhead.outerWidth() * 2) + "px")
5223				 .css("left", ((playhead.outerWidth() * -1)) + "px");
5224			
5225			} // end width check
5226			else {
5227				zero_count++;
5228			}
5229		});
5230		
5231		if( zero_count > 0 && zero_count == total_players) {
5232			// This content is not yet loaded because the container is faded out
5233			// Pause for 1 sec, then enable
5234			setTimeout("Cargo.Core.Audio.InitPlayerSize()", 500);
5235		}
5236	},
5237
5238	/**
5239	 *	Load all javascript dependancies
5240	 */
5241	LoadDependancies : function(el, callback) {
5242		// Load audio-related javascript here, but only once
5243		if (this.Config.has_audio_js === false) {
5244			
5245			$.loadFiles(this.Config.dependencies).done(function() {
5246				Cargo.Core.Audio.ToggleSound(el);
5247				Cargo.Core.Audio.Config.has_audio_js = true;
5248				if(typeof callback == "function"){
5249					callback();
5250				}
5251			});
5252
5253			return;
5254		}
5255
5256	},
5257
5258	/**
5259	 *	When the play/pause button is pressed
5260	 */
5261	ToggleSound : function(el) {
5262		start_sound = $(el).parents(".audio_component").attr("id");
5263		this_play = $(el);
5264	},
5265
5266	/**
5267	 * Get a count of the div.audio_component elements, confirm if there is more
5268	 * than one present
5269	 *
5270	 * @return	Boolean, true if one or more
5271	 */
5272	DetectAudioPlayer: function() {
5273		return ($('.audio_component').length > 0);
5274	},
5275
5276	/**
5277	 *	Removes any sound object from the page
5278	 */
5279	UnloadAudio : function() {
5280		$(".audio_component").each(function() {
5281			if(typeof soundManager == "object") {
5282				soundManager.unload( $(this).attr("id") );
5283			}
5284		});
5285	}
5286
5287};
5288
5289
5290
5291
5292/**************************************************************************
5293 *
5294 *	Addition to Cargo "core" functionality
5295 *
5296 *	The social media class
5297 *	This method is a replica of the one found in site.package
5298 *
5299 *	Usage: 
5300 *	Cargo.Core.SocialMedia.Init();
5301 *
5302 **************************************************************************/
5303
5304/**
5305 *	  This is the main social media app
5306 *    It will contain the various services
5307 *	NOTE:
5308 *		The var "Services" sits at the bottom of this method
5309 *		this defines each socail network and their dependencies
5310 *		it must sit at the bottom, not the top
5311 */
5312Cargo.Core.SocialMedia = {
5313    
5314    /*
5315     *	Pre-define the services and their respective scripts that need loading
5316     */
5317    "Services"   : { 
5318    	"facebook"   : {
5319    		"script" : false,	// script url for inclusion at write time
5320    		"inuse"	 : false	// if this is in use or not, always default false
5321    	},
5322    	
5323    	"twitter"    : {
5324    		"script" : "http://platform.twitter.com/widgets.js",
5325    		"inuse"	 : false
5326    	},
5327    	
5328    	"pinterest"  : {
5329    		"script" : false,
5330    		"inuse"	 : false
5331    	},
5332    	
5333    	"googleplus" : {
5334    		"script" : "https://apis.google.com/js/plusone.js",
5335    		"inuse"	 : false
5336    	},
5337    	
5338    	"stumbleupon": {
5339    		"script" : "http://platform.stumbleupon.com/1/widgets.js",
5340    		"inuse"	 : false
5341    	},
5342    	
5343    	"reddit"     : {
5344    		"script" : "http://www.reddit.com/buttonlite.js?i=5",
5345    		"inuse"	 : false
5346    	}
5347    
5348    
5349    },
5350    
5351    "Options"       	   : { 
5352    	"element"   	   : ".project_footer",	// The selector element to bind the html to
5353    	"show_count"       : false,				// Show the "like" count or not
5354    	"twitter_username" : false				// Twitter username for @reply
5355    },
5356    
5357    "OutputHTML" : "",						// The HTML that will be printed to the element
5358    
5359	
5360	
5361	/*
5362	 *	Set the defaults of the socail media elements
5363	 */
5364    SetDefaults : function( which, value ) {
5365        if(which == "element" || which == "show_count" || which == "twitter_username") {
5366            this.Options[which] = value;
5367        } else {
5368        	// set the "inuse" value for the default 
5369            this.Services[which].inuse = value;
5370        }
5371    },
5372    
5373    /*
5374     *	Initialize the services
5375     */
5376    Init : function() {
5377        // Set a delay to listen to the collection loaded
5378    	if(Cargo.Helper.IsFeedDesign()) {
5379            Cargo.Event.on('project_collection_reset', function() {
5380                Cargo.Core.SocialMedia.Start();
5381            });
5382        } else {
5383            this.Start();
5384        }
5385    },
5386
5387    Start : function() {
5388        // Reset the output values
5389        this.OutputHTML = "";
5390        
5391        // Create the HTML
5392        this.CreateHTML();
5393        
5394        // Write the HTML
5395        this.WriteHTML();
5396    },
5397    
5398    /*
5399	 *	Gets a project title
5400	 */
5401    AddHTML : function( html ) {
5402    	this.OutputHTML += html;
5403    },
5404    
5405    /*
5406     *	Takes the HTML that was created by the methods below
5407     *	Replaces the stubs with real content
5408     *	Stubs are for direct link / encoded direct link / title
5409     *	Input is a PID of the element we are binding
5410     *	If no PID is passed, it will default to site link / html title
5411     */
5412    GetFormattedHTML : function( pid ) {
5413    	var final_output = this.OutputHTML;
5414    	var project_model = Cargo.Collection.Projects.where( { id : pid } )[0];
5415
5416        project_model = (typeof project_model == "undefined") ? Cargo.Model.Project : project_model;
5417    	
5418    	// Direct link
5419    	final_output = final_output.replace(/CARGO_DIRECT_LINK/g, project_model.get('direct_link') );
5420    	
5421    	// Encoded direct link
5422    	final_output = final_output.replace(/CARGO_ENCODED_DIRECT_LINK/g, encodeURIComponent(project_model.get('direct_link')) );
5423
5424        // Title
5425    	final_output = final_output.replace(/CARGO_PROJECT_TITLE/g, project_model.get('title_no_html') );
5426    	
5427    	// Return the results
5428    	return final_output;
5429    },
5430    
5431     /*
5432     *	Write the buttons to the choosen element
5433     */
5434    WriteHTML : function() {
5435    	
5436    	// Attach the content to the choosen element/selector
5437    	$(this.Options.element)
5438	    	.each(function() {
5439	    		// Add to reclusive elements. Make them social, but skip ones that already are social.
5440	    		if($(this).data("has_social") !== true) {
5441		    		$(this)
5442		    			.append('<div class="social_media">' + Cargo.Core.SocialMedia.GetFormattedHTML( Cargo.Core.SocialMedia.GetNearestPid( $(this) ) ) + '</div>')
5443		    			.data("has_social", true);
5444		    	}
5445	    	});
5446    	
5447    	
5448    	// Loop one more time, this time include scripts
5449		$.each(this.Services, function(service, options) {
5450			if( options.inuse === true && options.script !== false ) {
5451				// Remove any old scripts
5452				$("script#"+service).remove();
5453				
5454				// Add the new script
5455				addScript( options.script, service );
5456			}
5457		});
5458    
5459    },
5460    
5461    /*
5462	 *	Quick and dirty. 
5463	 *	Finds the pid of the most active element
5464	 *	On feed design, that is probably it's parent. Find the entry
5465	 */
5466	GetNearestPid : function( this_elem ) {
5467		if(Cargo.Helper.IsFeedDesign()) {
5468			return $(this_elem).closest('[data-id]').attr('data-id');
5469		} else {
5470			return Cargo.Helper.GetPidFromPurl(Cargo.Helper.GetCurrentProjectUrl());
5471		}
5472	},
5473    
5474    /*
5475     *	Create the final output HTML by adding individual services
5476     */
5477    CreateHTML : function() { 
5478    	// Loop through and add each to the selector
5479       for (var service in this.Services) {
5480            // Facebook
5481            if(service == "facebook" && this.Services[service].inuse ) {
5482               this.AddHTML( this.Facebook() );
5483            }
5484            // Twitter
5485            else if(service == "twitter" && this.Services[service].inuse ) {
5486               this.AddHTML( this.Twitter() );
5487            }
5488            // Pinterest
5489            else if(service == "pinterest" && this.Services[service].inuse ) {
5490               this.AddHTML( this.Pinterest() );
5491            }
5492            // Google Plus
5493            else if(service == "googleplus" && this.Services[service].inuse ) {
5494               this.AddHTML( this.GooglePlus() );
5495            }
5496            // StumbleUpon
5497            else if(service == "stumbleupon" && this.Services[service].inuse ) {
5498               this.AddHTML( this.StumbleUpon() );
5499            }
5500            // Reddit
5501            else if(service == "reddit" && this.Services[service].inuse ) {
5502               this.AddHTML( this.Reddit() );
5503            }
5504            
5505        }
5506    },
5507    
5508    
5509    /*
5510     *    All services below here
5511     *	Note, string replacements are made for the following:
5512     *		CARGO_DIRECT_LINK
5513     *		CARGO_ENCODED_DIRECT_LINK
5514     *		CARGO_PROJECT_TITLE
5515     */
5516    Facebook : function() {
5517    
5518        // Cargo.log("Should we show comments? "+this.Options.show_count);
5519        
5520        return '<div class="facebook_like" style="float: left; margin-right: 8px;"><iframe src="http://www.facebook.com/plugins/like.php?href=CARGO_ENCODED_DIRECT_LINK&amp;send=false&amp;layout=button_count&amp;width=50&amp;show_faces=false&amp;action=like&amp;colorscheme=light&amp;font=arial&amp;height=21&amp;appId=150492405019861" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:46px; height:21px;" allowTransparency="true"></iframe></div>';
5521        
5522    },
5523    
5524    Twitter : function() {
5525        
5526        var output = '<div class="tweet_this" style="float: left; margin-right: 8px;"><a href="https://twitter.com/share" class="twitter-share-button"';
5527        
5528        if (this.Options.twitter_username !== false) {
5529        	output += ' data-via="'+ this.Options.twitter_username +'"';
5530        }
5531        
5532        output += ' data-url="CARGO_ENCODED_DIRECT_LINK" data-text="CARGO_PROJECT_TITLE: CARGO_DIRECT_LINK" data-count="none">Tweet</a></div>';
5533        
5534        return output;
5535
5536    },
5537    
5538    Pinterest : function() {
5539    	
5540    	$("a.pin_it_link")
5541    		.on("click", function() {
5542    			addScript("http://assets.pinterest.com/js/pinmarklet.js?r=" + Math.random()*99999999);
5543    			return false;
5544    		});
5545    
5546    	return '<div class="pin_it" style="float: left; margin-right: 8px;"><a href="#" class="pin_it_link" style="background: none; border: 0;"><img src="/_gfx/pinit.png" /></a></div>';
5547    	
5548    },
5549    
5550    GooglePlus : function() {
5551    	
5552		return '<div class="google_plus" style="float: left; margin-right: 8px;"><g:plusone size="medium" annotation="none" href="CARGO_DIRECT_LINK"></g:plusone></div>';
5553    	
5554    },
5555    
5556    StumbleUpon : function() {
5557    
5558		return '<div class="stumbleupon" style="float: left; margin-right: 8px;"><su:badge layout="4" location ="CARGO_DIRECT_LINK"></su:badge></div>';
5559    	
5560    },
5561    
5562    Reddit : function() {
5563    	
5564    	return '<div class="reddit" style="float: left; margin-right: 8px;"><a href="http://www.reddit.com/submit?url=CARGO_ENCODED_DIRECT_LINK" target="_blank"><img src="http://www.reddit.com/static/spreddit5.gif" alt="submit to reddit" border="0" /></a></div>';
5565    	
5566    }
5567    
5568    
5569}; // End SocialMedia
5570
5571Cargo.Core.Inspector3 = {
5572
5573	files: {
5574		corePackage        	: '/_jsapps/inspector/3.0.7/inspector-core.min.js?02.16.17',
5575		styleSheet 			: '/_jsapps/inspector/3.0.7/inspector-frontend.min.css?02.16.17',
5576		frontendPackage 	: '/_jsapps/inspector/3.0.7/inspector-frontend.backbone.min.js?02.16.17'
5577	},
5578	
5579	load: function(customOptions){
5580
5581		var self = this;
5582
5583		var injectFrontendPackage = function(){
5584			if(window.frameElement !== null && window.frameElement !== undefined){
5585					// we're in a frame. That's ok but test if it's
5586					// not a frame that we just created.
5587					
5588					if(window.frameElement.getAttribute('data-inspector') !== null){
5589
5590						// we are in the frame that we've created in this frame's parent. 
5591						// No need to do anything.
5592						return false;
5593					} else {
5594
5595						parent.location = document.location.href.replace(/\/adminedit/gi, '') + '?inspector';
5596						
5597					}
5598				}
5599
5600				var script = document.createElement('script');
5601				script.src = self.files.frontendPackage;
5602				document.head.appendChild(script);
5603
5604				//document.body.style.visibility = 'hidden';
5605
5606				script.onload = function(){
5607
5608					setupInspectorFrame();
5609					
5610				}
5611		}
5612
5613		var setupInspectorFrame = function(){
5614			// only do this when we're not in an iframe.
5615			document.body.setAttribute('style', '');
5616			document.body.innerHTML = '';
5617
5618			$(document.body).append('<img class="ci-loader" style="left: 40px; top: 40px; position: absolute;" src="/_gfx/loadingAnim.gif" />');
5619
5620			if(Cargo.ReplaceLoadingAnims === undefined){
5621				Cargo.Core.ReplaceLoadingAnims.init();
5622			} else {
5623				Cargo.ReplaceLoadingAnims();
5624			}
5625
5626			// remove styleSheets
5627			var styles = document.head.querySelectorAll('link, style');
5628
5629			for(var i = 0; i < styles.length; i++){
5630				document.head.removeChild(styles[i]);
5631			}
5632
5633			// add our styleSheet
5634			var style = document.createElement('link');
5635			style.href = self.files.styleSheet;
5636			style.rel = 'stylesheet';
5637			document.head.appendChild(style);
5638
5639
5640			var frame = document.createElement('iframe');
5641
5642			frame.className = 'bodyframe';
5643			frame.setAttribute('data-inspector', 'true');
5644			frame.setAttribute('frameborder', '0');
5645
5646			document.body.appendChild(frame);
5647
5648			$(frame).css('opacity', 0);
5649
5650			// url must be different from the current one to make it work in FF. 
5651			var newSrc = window.location.href.replace(/\?inspector|\/admin\/css/gi, '');
5652
5653			if(newSrc.indexOf('?') === -1){
5654				newSrc += '?inspecting';
5655			} else {
5656				newSrc += '&inspecting';
5657			}
5658
5659			frame.src = newSrc;
5660
5661			// frame loaded. We can access it's DOM now.
5662			frame.onload = function(){
5663
5664				injectInspectorCore(frame);
5665
5666				//frame.style.visibility = 'hidden';
5667			}
5668
5669		}
5670
5671		var injectInspectorCore = function(frame){
5672			// inject the inspector API into the frame
5673			var script = frame.contentDocument.createElement('script');
5674			script.src = self.files.corePackage;
5675			frame.contentDocument.head.appendChild(script);
5676
5677			// once the API has loaded we can instanciate the editor.
5678			script.onload = function(){
5679				
5680				if(window.editor === undefined){
5681					initializeInspector(frame);
5682				} else {
5683					window.editor.reloadFrameReferences();
5684				}
5685			}
5686		}
5687
5688		var initializeInspector = function(frame){
5689			 // instanciate the editor and tell it to inspect the frame instead of the 
5690			// current document by passing the iframe object.
5691			
5692			$("#follow_popup, .ci-loader").remove();
5693
5694			var options = $.extend({
5695
5696				iframe: frame,
5697
5698				styleSheetFilter: function(styleSheet){
5699
5700					/*if(styleSheet.ownerNode.nodeName === "STYLE"){
5701						return false;
5702					}
5703
5704					// adblock inserts style nodes with script tags in it...
5705					if(styleSheet.ownerNode !== null){
5706						if($(styleSheet.ownerNode).find('script').length > 0){
5707							return false;
5708						}
5709					}
5710
5711					if(styleSheet.href !== null) {
5712						if(styleSheet.href.indexOf('global.css') !== -1){
5713							return false;
5714						}
5715					}*/
5716
5717					if(styleSheet.ownerNode !== null){
5718						if(styleSheet.ownerNode.id.indexOf('member_stylesheet') !== -1 || styleSheet.ownerNode.id.indexOf('user_stylesheet') !== -1 ){
5719							return true;
5720						}
5721					}
5722
5723					return false;
5724				},
5725
5726				styleSheetSavableFilter: function(styleSheet){
5727					
5728					if(styleSheet.ownerNode !== null){
5729						if(styleSheet.ownerNode.id.indexOf('member_stylesheet') !== -1 || styleSheet.ownerNode.id.indexOf('user_stylesheet') !== -1 ){
5730							return true;
5731						}
5732					}
5733					
5734					return false;
5735				},
5736
5737				onResize: function(){
5738					$(window).resize();
5739				},
5740
5741				onInspectorClick: function(e){
5742
5743					// don't preventDefault if a splash page has been clicked.
5744					if(e.target.id !== "splash"){
5745						e.preventDefault();
5746					}
5747
5748					if( $(e.target).closest('#toolset').length > 0 ) {
5749						return false;
5750					}
5751
5752					if(e.target.nodeName === "HTML") {
5753						e.target = document.body;
5754					}
5755					
5756					// return the element that is going to be inspected.
5757					return e.target;
5758				},
5759
5760				onSave: function(button, modifiedFiles){
5761
5762					var styleSheetPostArray = [];
5763
5764					var self = this;
5765
5766					for(var i = 0; i < modifiedFiles.length; i++){
5767						styleSheetPostArray.push({
5768							filename: encodeURIComponent(modifiedFiles[i].filename),
5769							stylesheet: modifiedFiles[i].cssString,
5770						});
5771					}
5772
5773					$.ajax({
5774						type: "POST",
5775						url: '/dispatch/admin/saveInspectorCSS',
5776						data: {
5777							stylesheet: JSON.stringify( styleSheetPostArray ),
5778							url: ( Cargo.Helper ? Cargo.Helper.GetUrl() : frame.contentWindow.Cargo.Location.GetCargoUrl() )
5779						},
5780						success: function(response){
5781							if(response.error.length > 0){
5782								button.$el.removeClass('saving');
5783								
5784								if(response.error[0].message) {
5785									alert(response.error[0].message);
5786								} else  {
5787									alert('Save failed');
5788								}
5789							} else {
5790
5791								for(var i = 0; i < modifiedFiles.length; i++){
5792									modifiedFiles[i].CSSStyleSheet.markAsSaved();
5793								}
5794
5795							}
5796						},
5797						always: function(){
5798							button.$el.removeClass('saving');
5799						},
5800						dataType: 'json'
5801					});
5802
5803				}
5804
5805			}, customOptions);
5806
5807			window.editor = new CSSEditor(options);
5808
5809			document.body.appendChild(window.editor.getEditorElement());
5810			
5811			window.editor.getEditorElement().style.setProperty('visibility', 'hidden', 'important');
5812
5813			var init = true;
5814
5815			$(window).resize(function(){
5816				
5817				var editorWidth = editor.getEditorElement().offsetWidth;
5818				windowWidth = document.documentElement.offsetWidth;
5819
5820				frame.style.width = (windowWidth - editorWidth) + 'px';
5821				frame.style.left = editorWidth + 'px';
5822
5823				if(init){
5824					$(frame).css('opacity', '');
5825					window.editor.getEditorElement().style.removeProperty('visibility');
5826					init = false;
5827				}
5828
5829			});
5830			
5831			$(window).resize();
5832			//frame.style.visibility = '';
5833			//
5834			var c = window.editor.options.coreWindow.Cargo;
5835
5836			if(c.hasOwnProperty('Event')){
5837				c.Event.trigger('inspector_open');
5838			}
5839
5840		}
5841
5842		
5843		var adminURLIndex = document.location.href.indexOf('/admin/css');
5844
5845		if( (Cargo.Helper ? Cargo.Helper.isMobile() : Cargo.Config.isMobile()) ) {
5846
5847			alert('Sorry, the Inspector does not support iOS (yet).');
5848
5849			if(adminURLIndex !== -1){
5850				document.location.href = document.location.href.substring(0, adminURLIndex);
5851			}
5852
5853			return false;
5854			
5855		}
5856
5857		if( (Cargo.Helper ? Cargo.Helper.isIE8() : Cargo.Config.isIE8()) ) {
5858
5859			alert('Sorry, the Inspector does not support your browser version.');
5860
5861			if(adminURLIndex !== -1){
5862				document.location.href = document.location.href.substring(0, adminURLIndex);
5863			}
5864
5865			return false;
5866			
5867		}
5868
5869		Cargo.InspectorLoading = true;
5870		$("#follow_popup").remove();
5871
5872		$.post("/dispatch/admin/authenticateInspector", {url: ( Cargo.Helper ? Cargo.Helper.GetUrl() : Cargo.Location.GetCargoUrl() ) }, function(authenticationData) {
5873
5874			if(authenticationData.error.length === 0) {
5875
5876				injectFrontendPackage();
5877
5878			} else if(authenticationData.error[0].message) {
5879				alert(authenticationData.error[0].message);
5880
5881			} else {
5882				alert("You must be logged in to use the Inspector.");
5883
5884				if(adminURLIndex !== -1){
5885					document.location.href = document.location.href.substring(0, adminURLIndex);
5886				}
5887			}
5888		}, 'json');
5889
5890		//injectFrontendPackage();
5891
5892	}
5893}
5894
5895Cargo.utils = Cargo.utils || {};
5896
5897/**
5898 *	Cargo.Core.CargoXHR wraps the native XMLHttpRequest with some extra features like retry.
5899 *
5900 *  Kinda implements the jQuery (http://api.jquery.com/jquery.ajax/) ajax options.
5901 *
5902 * @author Aart Röst / Cargo Collective
5903 * 
5904 */
5905
5906Cargo.utils.CargoXHR = function(options){
5907
5908	var self = this;
5909
5910	$.extend(this.options = {}, this.defaultOptions, options);
5911
5912	if(this.options.context === undefined){
5913		this.options.context = this;
5914	}
5915
5916	if(this.options.url === undefined){
5917		throw 'no URL given, define "url" in the contructor\'s options object';
5918	}
5919
5920	this.retries = 0;
5921
5922	this.xhr = new XMLHttpRequest();
5923
5924	this.xhr.addEventListener("abort", function(){
5925		self.options.abort.apply(self.options.context, arguments);
5926	}, false);
5927	
5928	this.xhr.addEventListener("error", function(){
5929		self.options.error.apply(self.options.context, arguments);
5930	}, false);
5931	
5932	this.xhr.addEventListener("load", function(){
5933		self.options.success.apply(self.options.context, arguments);
5934	}, false);
5935
5936	this.xhr.addEventListener("loadend", function(){
5937		self.options.always.apply(self.options.context, arguments);
5938	}, false);
5939
5940	this.xhr.upload.addEventListener("progress", function(e){
5941		self.options.progress.apply(self.options.context, arguments);
5942	}, false);
5943
5944	this.send();
5945
5946}
5947
5948Cargo.utils.CargoXHR.prototype.send = function(){
5949
5950	this.xhr.open(this.options.type, this.options.url, this.options.async);
5951
5952	for(var key in this.options.headers){
5953		this.xhr.setRequestHeader(key, this.options.headers[key]);
5954	}
5955	
5956	this.xhr.responseType = this.options.dataType;
5957
5958	this.options.beforeSend.call(this, this.xhr);
5959
5960	this.xhr.send(this.options.data);
5961
5962}
5963
5964Cargo.utils.CargoXHR.prototype.retry = function(){
5965
5966	if(this.retries++ < this.options.maxRetries){
5967
5968		Cargo.utils.CargoXHR.prototype.send.apply(this, arguments);
5969
5970	} else {
5971		
5972		this.abort();
5973		return false;
5974
5975	}
5976
5977}
5978
5979Cargo.utils.CargoXHR.prototype.abort = function(){
5980
5981	if(typeof this.xhr === 'object'){
5982		this.xhr.abort();
5983	}
5984
5985}
5986
5987Cargo.utils.CargoXHR.prototype.defaultOptions = {
5988	async: true,
5989	beforeSend: function(XHR){
5990
5991	},
5992	always: function(e){
5993
5994	},
5995	success: function(e){
5996
5997	},
5998	progress: function(e){
5999
6000	},
6001	error: function(e) {
6002
6003	},
6004	abort: function(e) {
6005
6006	},
6007	type: 'GET',
6008	url: undefined,
6009	dataType: 'text',
6010	headers: {},
6011	data: null,
6012	context: undefined,
6013	maxRetries: 5
6014}
6015
6016Cargo.utils = Cargo.utils || {};
6017
6018/**
6019 * @todo: process fields: totalBytes, uploadedBytes
6020 * 		  global progress percentage calculated with the progress from all processes.
6021 * 
6022 */
6023
6024Cargo.utils.ImageUploader = function(options){
6025
6026	$.extend(this.options = {}, this.defaultOptions, options);
6027
6028	this.activeProcesses = [];
6029
6030}
6031
6032Cargo.utils.ImageUploader.prototype = {
6033
6034	upload: function(files, pid, customParameters){
6035
6036		if(files === undefined){
6037			return undefined;
6038		}
6039
6040		// TODO: handle either a single file or an array of files
6041		if(files.length === 0) {
6042			return undefined;
6043		};
6044
6045		var _this = this;
6046
6047		// wrap all files in a process. This allows for 
6048		// global progress tracking when uploading an array
6049		// of files and easier management of multiple 
6050		// concurrent processes. Also allows you to swap out processes
6051		// between uploader instances or re-upload/re-use a certain process.
6052		
6053		var uploadProcess = {
6054			uid: Cargo.Model.Site.GetId(),
6055			url: Cargo.Helper.GetUrl(),
6056			pid: (pid !== undefined ? pid : Cargo.Helper.GetCurrentProjectId()),
6057			fileIndex: -1,
6058			aborted: false,
6059			totalBytes: 0,
6060			uploadedBytes: 0,
6061			customParameters: customParameters,
6062			getUploadProgress: function(){
6063				return (this.uploadedBytes / this.totalBytes) * 100;
6064			},
6065			files: [],
6066			events: {
6067				onFileComplete: function(file, event){},
6068				onFileProgress: function(file, event){},
6069				onFileFail: function(file, event){},
6070				onFileError: function(file, event){},
6071				onAbort: function(uploadProcess){},
6072				onComplete: function(uploadProcess){}
6073			},
6074			getNextFile: function(){
6075
6076				if(this.fileIndex < this.files.length - 1 && this.aborted !== true){
6077					return this.files[++this.fileIndex];
6078				} 
6079
6080				return undefined;
6081			},
6082			getCurrentFile: function(){
6083				return this.files[this.fileIndex];
6084			},
6085			abort: function(){
6086
6087				if(this.xhr !== undefined){
6088
6089					this.aborted = true;
6090					this.xhr.abort();
6091					this.events.onAbort(this);
6092
6093					var processIndex = _this.activeProcesses.indexOf(this);
6094
6095					if(processIndex !== -1){
6096						_this.activeProcesses.splice(processIndex, 1);
6097					}
6098
6099				}
6100				
6101			},
6102			addFile: function(file){
6103				
6104				if(this.files.indexOf(file) === -1) {
6105
6106					if(_this.options.acceptFileTypes.test(file.type) && _this.options.acceptFileTypes.test(file.name)){
6107				
6108						uploadProcess.files.push(file);
6109
6110						this.totalBytes += file.size;
6111
6112					} else {
6113
6114						// TODO: Think this through again.
6115						uploadProcess.events.onFileError(file, {
6116							message: 'File type not allowed'
6117						});
6118
6119					}
6120
6121				}
6122
6123			}
6124		};
6125
6126		[].forEach.call(files, function(file){
6127			uploadProcess.addFile(file);
6128		});
6129
6130		if(uploadProcess.files.length === 0){
6131			uploadProcess = null;
6132			return undefined;
6133		}
6134
6135		this.activeProcesses.push(uploadProcess);
6136
6137		this.handleUploadProcess(uploadProcess);
6138
6139		return uploadProcess;
6140
6141	},
6142
6143	handleUploadProcess: function(uploadProcess){
6144
6145		var file = uploadProcess.getNextFile(),
6146			_this = this;
6147
6148		if(file === undefined) {
6149
6150			// no more files.
6151			uploadProcess.xhr = undefined;
6152			uploadProcess.events.onComplete(uploadProcess);
6153
6154			uploadProcess.fileIndex = -1;
6155
6156			var processIndex = _this.activeProcesses.indexOf(uploadProcess);
6157
6158			if(processIndex !== -1){
6159				_this.activeProcesses.splice(processIndex, 1);
6160			}
6161
6162			return;
6163		}
6164
6165		file.lastProgress = 0;
6166
6167		var formData = new FormData();
6168
6169		formData.append('Filedata[]', file);
6170
6171		formData.append('pid', uploadProcess.pid);
6172		formData.append('url', uploadProcess.url);
6173		formData.append('uid', uploadProcess.uid);
6174
6175		if(typeof uploadProcess.customParameters === "object"){
6176
6177			Object.keys(uploadProcess.customParameters).forEach(function(key){
6178				
6179				formData.append(key, uploadProcess.customParameters[key]);
6180
6181			});
6182
6183		}
6184		
6185		uploadProcess.xhr = new Cargo.utils.CargoXHR({
6186
6187			url: this.options.posturl,
6188			type: 'POST',
6189			data: formData,
6190			progress: function(e){
6191				
6192				if(e.lengthComputable){
6193					
6194					uploadProcess.uploadedBytes += e.loaded - file.lastProgress;
6195					file.lastProgress = e.loaded;
6196
6197				}
6198
6199				uploadProcess.events.onFileProgress(file, e);
6200
6201			},
6202			success: function(e){
6203				
6204				if (this.xhr.status == 200) {
6205
6206					var response = JSON.parse(this.xhr.responseText);
6207					uploadProcess.events.onFileComplete.call(this, file, e);
6208
6209				} else {
6210
6211					uploadProcess.events.onFileFail.call(this, file, e);
6212
6213				}
6214
6215				// keep going until we run out of files to upload
6216				_this.handleUploadProcess(uploadProcess);
6217
6218			}
6219
6220		});
6221
6222	},
6223
6224	defaultOptions: {
6225		acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
6226		posturl: '/dispatch/adminproject/handleMultiFileUpload'
6227	}
6228};
6229
6230 /***************************************************************************
6231  *
6232  *	Helper functions that are not in a class
6233  *
6234  *	These are one off functions that do something, but are not tied
6235  *	to any particular class
6236  *
6237 **************************************************************************/
6238
6239/**
6240 *	Replication of the php in_array function
6241 *	http://phpjs.org/functions/in_array/
6242 */
6243function in_array (needle, haystack, argStrict) {
6244    var key = '',
6245        strict = !! argStrict;
6246
6247    if (strict) {
6248        for (key in haystack) {
6249            if (haystack[key] === needle) {
6250                return true;
6251            }
6252        }
6253    } else {
6254        for (key in haystack) {
6255            if (haystack[key] == needle) {
6256                return true;
6257            }
6258        }
6259    }
6260
6261    return false;
6262}
6263
6264/**
6265 *  Return the first letter of a word capitolized 
6266 */
6267function ucFirst(str) {
6268  return (str) ? str.charAt(0).toUpperCase() + str.slice(1) : str;
6269}
6270
6271/**
6272 *  Legacy function 
6273 *  CLoses projects
6274 */
6275function closeThisPr() {
6276  Action.Project.Close();
6277}
6278
6279/**
6280 *  Legacy function 
6281 *  CLoses projects
6282 */
6283Projects = {
6284  CloseProject :function() {
6285    Action.Project.Close();
6286  }
6287}
6288
6289/**
6290 *  Legacy function
6291 *  Gets a filter
6292 */
6293Cargo.Location = {
6294  GetUserFilter :function() {
6295    // return Cargo.Model.Site.GetFilter();
6296    return Cargo.API.Config.filter;
6297  },
6298  GetDomainOrUrl : function() {
6299    return Cargo.Helper.GetUrl();
6300  }
6301}
6302
6303/**
6304 *  Legacy function 
6305 *  Loads the inspector
6306 */
6307function loadInspector() {
6308  Cargo.Core.LoadInspector();
6309}
6310
6311/**
6312 *  Toggles the header into hi-res our out
6313 *  Called from the admin frame
6314 */
6315function ToggleHeaderHiRes(which, url, firstval) {
6316  if($('img[src$="/'+url+'"]').length > 0) {
6317    if(which == "2x") {
6318      if(firstval == "2x") {
6319        $('img[src$="/'+url+'"]').width( $('img[src$="/'+url+'"]').attr('width') );
6320        $('img[src$="/'+url+'"]').height( $('img[src$="/'+url+'"]').attr('height') );
6321      } else {
6322        $('img[src$="/'+url+'"]').width( Math.floor($('img[src$="/'+url+'"]').attr('width') / 2) );
6323        $('img[src$="/'+url+'"]').height( Math.floor($('img[src$="/'+url+'"]').attr('height') / 2) );
6324      }
6325     
6326    } else {
6327      if(firstval == "1x") {
6328        $('img[src$="/'+url+'"]').width( $('img[src$="/'+url+'"]').attr('width') );
6329        $('img[src$="/'+url+'"]').height( $('img[src$="/'+url+'"]').attr('height') );
6330      } else {
6331        $('img[src$="/'+url+'"]').width( Math.floor($('img[src$="/'+url+'"]').attr('width') * 2) );
6332        $('img[src$="/'+url+'"]').height( Math.floor($('img[src$="/'+url+'"]').attr('height') * 2) );
6333      }
6334    }
6335  }
6336}
6337
6338/**
6339 *    Social media class
6340 *
6341 *    This is the main social media function
6342 *    It will init the rest of the application
6343 *
6344 *  Cargo.IncludeSocialMedia( {
6345 *    element     : ".project_footer",
6346 *    show_count  : true,
6347 *
6348 *    facebook    : true,
6349 *    twitter     : true,
6350 *    stumbleupon : false,
6351 *    pintrest    : true
6352 *  } );
6353 *
6354 *
6355 *  .social_media {
6356 *    margin: 30px 0 100px 0; height: 20px;
6357 *   }
6358 *
6359 */
6360Cargo.IncludeSocialMedia = function( dataObj ) {
6361    // Loop through each to set the default options
6362    $.each(dataObj, function(key, value) {
6363        Cargo.Core.SocialMedia.SetDefaults(key, value);
6364    });
6365
6366    // Set the project open/ready states
6367    $(document).ready(function() { 
6368        Cargo.Core.SocialMedia.Init(); 
6369    });
6370
6371    Cargo.Event.on('project_load_complete', function( pid ) {
6372        Cargo.Core.SocialMedia.Init();
6373    });
6374    
6375    Cargo.Event.on('pagination_complete', function( new_page ) {
6376        Cargo.Core.SocialMedia.Init();
6377    });
6378
6379};
6380
6381/**
6382 *  Legacy function
6383 *  Smothly scroll
6384 */
6385var lastT = 0;
6386var timer = null;
6387function doscroll(targetYPos,y,t) {
6388  var speed = 6;
6389  var diff = targetYPos - y;
6390  var steps = diff/(speed*speed);
6391  t += (t+steps)/diff;
6392  newY = (t==1) ? y+diff : y + (diff * (-Math.pow(2, -20 * t/1) + 1));
6393  if(t) window.scrollTo(0, newY);
6394  
6395  if(t >= 1 || lastT == t || !t) clearTimeout(timer);
6396  else timer=setTimeout("doscroll("+targetYPos+","+y+","+t+")",1);
6397  
6398  lastT = t;
6399  
6400  return false;
6401}
6402
6403function doscroll_left(targetXPos,x,t,scroll_obj) {
6404  var speed = 6;
6405  var diff = targetXPos - x;
6406  var steps = diff/(speed*speed);
6407  t += (t+steps)/diff;
6408  newX = (t==1) ? x+diff : x + (diff * (-Math.pow(2, -20 * t/1) + 1));
6409  if(t) {
6410    if(scroll_obj === window) {
6411      window.scrollTo(newX, 0);
6412    } else {
6413      scroll_obj.scrollLeft = newX;
6414    }
6415  }
6416  
6417  if(t >= 1 || lastT == t || !t) clearTimeout(timer);
6418  else timer=setTimeout(function() {
6419    doscroll_left(targetXPos,x,t,scroll_obj);
6420  },1);
6421  
6422  lastT = t;
6423  
6424  return false;
6425}
6426
6427/**
6428 *  Legacy function to add a script into the head
6429 *  Used by tracemarks
6430 */
6431function addScript(script_file, id) {
6432  script_file = (script_file == "/_js/cargo.tracemarklet.js") ? "/_js/cargo.tracemarklet-api.js" : script_file;
6433  var scrpt=document.createElement('script');
6434    scrpt.setAttribute('type','text/javascript');
6435    scrpt.setAttribute('src',script_file);   
6436    if( id ) scrpt.setAttribute('id',id);   
6437  var hd=document.getElementsByTagName('head')[0];
6438    hd.appendChild(scrpt);
6439}
6440
6441/**
6442 *  Legacy function for adding Facebook comments
6443 *
6444 */
6445/////////////////////////////////////////////////////////////////////////////////////////////////
6446// Facebook comments
6447function fbComments(variant, fbwidth, fbnum){
6448    addScript("http://connect.facebook.net/en_US/all.js#xfbml=1");
6449    var f_url =
6450        padding = "";
6451    var margin = "30px 0 30px 0";
6452    var colorscheme = (variant == "dark") ? "dark" : "light";
6453
6454    fbwidth = (!fbwidth) ? 560 : fbwidth;
6455    fbnum = (!fbnum) ? 15 : fbnum;
6456    
6457    Cargo.Event.on("project_load_complete", function( pid ) { 
6458      fbCommentLoad(pid, variant, fbwidth, fbnum);
6459    });
6460}
6461/////////////////////////////////////////////////////////////////////////////////////////////////
6462// Load FaceBook comments for non-feed designs
6463function fbCommentLoad(pid, variant, fbwidth, fbnum) {
6464  var margin = "30px 0 30px 0";
6465  var timeout = 500;
6466  var colorscheme = (variant == "dark") ? "dark" : "light";
6467  var position = "before";
6468  var element= $("directlink",Cargo.View.ProjectDetail.$el);
6469
6470  if( !pid ) {
6471    // get the open project
6472    if( $("#current_open").length > 0 && $("#current_open").val() != "none") {
6473      pid = $("#current_open").val();
6474      
6475    } else {
6476      return false;
6477    }
6478  }
6479
6480  // If you're on a project
6481  if(Cargo.Model.Project.GetType() == "project"){
6482    // Load the Facebook comments script again, for whatever reason(s)
6483    $.getScript("http://connect.facebook.net/en_US/all.js#xfbml=1", function() { 
6484      var f_url = Cargo.Model.Project.GetDirectLink();
6485
6486      if(position == "after") {
6487        $(element).after('<div class="fb_comments" style="width: '+fbwidth+'; margin: '+margin+'; padding: '+padding+';"><fb:comments href="'+f_url+'" width="'+fbwidth+'" num_posts="'+fbnum+'" colorscheme="'+colorscheme+'"></fb:comments></div>');
6488      } else if(position == "inside") {
6489        $(element).append('<div class="fb_comments" style="width: '+fbwidth+'; margin: '+margin+'; padding: '+padding+';"><fb:comments href="'+f_url+'" width="'+fbwidth+'" num_posts="'+fbnum+'" colorscheme="'+colorscheme+'"></fb:comments></div>');
6490      } else {
6491        $(element).before('<div class="fb_comments" style="width: '+fbwidth+'; margin: '+margin+'; padding: '+padding+';"><fb:comments href="'+f_url+'" width="'+fbwidth+'" num_posts="'+fbnum+'" colorscheme="'+colorscheme+'"></fb:comments></div>');
6492      }
6493      
6494      // Force FB to re-parse the comments
6495      FB.XFBML.parse();
6496      
6497    });
6498  }
6499}
6500
6501	
6502	Cargo.o.model.project = Backbone.Model.extend({
6503		GetId : function() {
6504			return this.get("id"); 
6505		},
6506		
6507		GetSiteId : function() {
6508			return this.get("site_id");
6509		},
6510		
6511		GetUrl : function() {
6512			return this.get("url"); 
6513		},
6514		
6515		GetProjectUrl : function() {
6516			return this.get("project_url");
6517		},
6518		
6519		GetDirectLink : function() {
6520			return this.get("direct_link");
6521		},
6522		
6523		GetType : function() {
6524			return (this.get("type") && this.get("id") != 0) ? this.get("type") : false; 
6525		},
6526		
6527		GetSetId : function() {
6528			return this.get("set_id"); 
6529		},
6530
6531		GetSetName : function() {
6532			return this.get("set_name"); 
6533		},
6534
6535		GetSetUrl : function() {
6536			return this.get("set_url"); 
6537		},
6538
6539		GetSetCount : function() {
6540			return this.get("set_count"); 
6541		},
6542
6543		GetSetStart : function() {
6544			return this.get("set_start"); 
6545		},
6546
6547		GetSetEnd : function() {
6548			return this.get("set_end"); 
6549		},
6550
6551		GetTitle : function() {
6552			return this.get("title"); 
6553		},
6554
6555		GetTitleNoHtml : function() {
6556			return this.get("title_no_html"); 
6557		},
6558
6559		GetShowTitle : function() {
6560			return this.get("show_title"); 
6561		},
6562		
6563		GetTags : function() {
6564			return this.get("tags");
6565		},
6566		
6567		GetDisplay : function() {
6568			return this.get("display");
6569		},
6570		
6571		GetExcerpt : function() {
6572			return this.get("excerpt");
6573		},
6574		
6575		GetContent : function() {
6576			return this.get("content");
6577		},
6578
6579		GetContent2x : function() {
6580			return this.get("content_2x");
6581		},
6582		
6583		GetContentNoHtml : function() {
6584			return this.get("content_no_html");
6585		},
6586
6587		GetContentPartialHtml : function() {
6588			return this.get("content_partial_html");
6589		},
6590		
6591		GetViews : function() {
6592			return this.get("views");
6593		},
6594		
6595		GetDate : function() {
6596			return this.get("date");
6597		},
6598		
6599		GetBgColor : function() {
6600			return (this.get("bgcolor") == "") ? false : "#"+this.get("bgcolor");
6601		},
6602
6603		GetCommentCount : function() {
6604			return this.get("comment_count");
6605		},
6606		
6607		GetThumbUrl : function() {
6608			return this.get("thumb_url"); 
6609		},
6610
6611		GetThumbUrl2x : function() {
6612			return this.get("thumb_url_2x"); 
6613		},
6614		
6615		GetThumbWidth : function() {
6616			return this.get("thumb_width");
6617		},
6618		
6619		GetThumbHeight : function() {
6620			return this.get("thumb_height");
6621		},
6622		
6623		GetDefaultThumbUrl : function() {
6624			return this.get("default_thumb_url");
6625		},
6626
6627		GetDefaultThumb2xUrl : function() {
6628			return this.get("default_thumb_url_2x");
6629		},
6630		
6631		GetDefaultThumbWidth : function() {
6632			return this.get("default_thumb_width");
6633		},
6634		
6635		GetDefaultThumbHeight : function() {
6636			return this.get("default_thumb_height");
6637		},
6638		
6639		GetCustomThumbUrl : function() {
6640			return this.get("custom_thumb_url");
6641		},
6642
6643		GetCustomThumb2xUrl : function() {
6644			return this.get("custom_thumb_url_2x");
6645		},
6646		
6647		GetCustomThumbWidth : function() {
6648			return this.get("custom_thumb_width");
6649		},
6650		
6651		GetCustomThumbHeight : function() {
6652			return this.get("custom_thumb_height");
6653		},
6654		
6655		GetImages : function() {
6656			return this.get("images");
6657		},
6658
6659		initialize : function() {
6660			//Cargo.Core.log(this);
6661			
6662		},
6663
6664		fetch : function(options) {
6665			if ( options.reset && $("script[data-set='ProjectDetail']").html() ) {
6666				this.set( $.parseJSON( $("script[data-set='ProjectDetail']").html() ), options );
6667
6668			} else {
6669				Backbone.Model.prototype.fetch.apply(this, arguments);    
6670			}
6671		},
6672
6673		/**
6674		 * Update the parent collection model on parse (save response)
6675		 */
6676		parse: function(resp, options) {
6677			var project_model = Cargo.Collection.Projects.findWhere( { project_url : this.GetProjectUrl() } );
6678			var page_model = Cargo.Collection.Pages.findWhere( { project_url : this.GetProjectUrl() } );
6679			var model = (page_model) ? page_model : project_model;
6680			if(model) model.set(resp);
6681			return resp;
6682		},
6683
6684		url : function() {
6685			return Cargo.API.GetSingleProjectPath();
6686		}
6687
6688	});
6689
6690	
6691
6692/**
6693 *	Default model for constructing display options
6694 *	Because these are nessesary for first load of project collection
6695 *	This data may be preloaded on the page. For this reason,
6696 *	we extend the fetch function to check to see if this is the case
6697 *	If it is not the case, then fetch from the API url
6698 *
6699 *	Usage:
6700 *	<script type="text/json" data-set="DisplayOptions">{options}</script>
6701 */
6702	
6703	Cargo.o.model.display_options = Backbone.Model.extend({
6704		
6705		fetch : function(options) {         
6706			if ( $("script[data-set='DisplayOptions']").html() ) {
6707				this.set( $.parseJSON( $("script[data-set='DisplayOptions']").html() ), options );
6708
6709			} else {
6710				Backbone.Model.prototype.fetch.apply(this, arguments);    
6711			}
6712		},
6713
6714		url : function() {
6715			return Cargo.API.GetDisplayOptionsPath();
6716		},
6717
6718	
6719		GetUserId : function() {
6720			return this.get("user_id");
6721		},
6722
6723		GetPaginationCount : function() {
6724			return this.get("pagination_count");
6725		},
6726
6727		GetTotalProjects : function() {
6728			/* Get the total amount of projects, which may be filtered */
6729			var total = (Cargo.API.Config.filter_total) ? Math.floor(Cargo.API.Config.filter_total) : Math.floor(this.get("total_projects"));
6730			return total;
6731		},
6732
6733		GetFollowLinkInNav : function() {
6734			return this.get("following_link_in_nav");
6735		},
6736
6737		GetDisableProjectScroll : function() {
6738			return this.get("disable_project_scroll");
6739		},
6740
6741		GetHeaderLinkToIndex : function() {
6742			return this.get("header_link_to_index");
6743		},
6744
6745		GetHeader2x : function() {
6746			return this.get("header_2x");
6747		},
6748
6749		GetTagsInThumb : function() {
6750			return this.get("tags_in_thumb");
6751		},
6752
6753		GetTitleInThumb : function() {
6754			return this.get("title_in_thumb");
6755		},
6756
6757		GetThumbSize : function() {
6758			return this.get("thumb_size");
6759		},
6760
6761		GetThumbsBelowProjects : function() {
6762			return this.get("thumbs_below_projects");
6763		},
6764
6765		GetThumbsBelowPages : function() {
6766			return this.get("thumbs_below_pages");
6767		},
6768
6769		GetViewCountInProject : function() {
6770			return this.get("view_count_in_project");
6771		},
6772
6773		GetTitleInProject : function() {
6774			return this.get("title_in_project");
6775		},
6776
6777		GetFooterInProject : function() {
6778			return this.get("footer_in_project");
6779		},
6780
6781		GetPagesInTextNav : function() {
6782			return this.get("pages_in_text_nav");
6783		},
6784
6785		GetProjectsInTextNav : function() {
6786			return this.get("projects_in_text_nav");
6787		},
6788
6789		GetUseSets : function() {
6790			return this.get("use_sets");
6791		},
6792
6793		GetSetsOpenByDefault : function() {
6794			return this.get("sets_open_by_default");
6795		},
6796
6797		GetSetsCanToggle : function() {
6798			return this.get("sets_can_toggle");
6799		},
6800
6801		GetSetsOneAtATime : function() {
6802			return this.get("sets_one_at_a_time");
6803		},
6804
6805		GetUseSetLinks : function() {
6806			return this.get("use_set_links");
6807		},
6808
6809		GetSetLinksPosition : function() {
6810			return this.get("set_links_position");
6811		},
6812
6813		GetRecentProjectAsStart : function() {
6814			return this.get("recent_project_as_start");
6815		},
6816
6817		GetExcerptInThumb : function() {
6818			return this.get("excerpt_in_thumb");
6819		},
6820
6821		GetLightboxView : function() {
6822			return this.get("lightbox_view");
6823		},
6824
6825		GetImageWidth : function() {
6826			return this.get("image_width");
6827		},
6828
6829		// Slideshows
6830		GetSlideTransition : function() {
6831			return this.get("slide_transition");
6832		},
6833
6834		GetSlideTransitionDuration : function() {
6835			return this.get("slide_transition_duration");
6836		},
6837
6838		GetSlideAutoPlay : function() {
6839			return this.get("slide_auto_play");
6840		},
6841
6842		GetSlideTextNav : function() {
6843			return this.get("slide_text_nav");
6844		},
6845
6846		GetSlideResponsive : function() {
6847			return this.get("slideshow_responsive");
6848		},
6849
6850		GetSlideImagePosition : function() {
6851			return this.get("slide_image_position");
6852		},
6853
6854		GetSlideNavPosition : function() {
6855			return this.get("slide_nav_position");
6856		},
6857
6858		GetSlideThumbnails : function() {
6859			return this.get("slide_thumbnails");
6860		},
6861
6862		GetSlideDefaultThumbs : function() {
6863			return this.get("slide_default_thumbs");
6864		},
6865
6866		GetSlideToggleThumbLink : function() {
6867			return this.get("slide_toggle_thumb_link");
6868		},
6869
6870		GetIndexRouteUrl : function() {
6871			return this.get("index_route_url");
6872		},
6873
6874		GetIndexRouteName : function() {
6875			return this.get("index_route_name");
6876		},
6877
6878		GetFeedRouteUrl : function() {
6879			return this.get("feed_route_url");
6880		},
6881
6882		GetFeedRouteName : function() {
6883			return this.get("feed_route_name");
6884		},
6885
6886		GetHomeRoute : function() {
6887			return this.get("home_route");
6888		}
6889
6890		
6891	});
6892	
6893
6894
6895	
6896	Cargo.o.model.site = Backbone.Model.extend({
6897		
6898		fetch : function(options) {         
6899			if ( $("script[data-set='Site']").html() ) {
6900				this.set( $.parseJSON( $("script[data-set='Site']").html() ), options );
6901
6902			} else {
6903				Backbone.Model.prototype.fetch.apply(this, arguments);    
6904			}
6905		},
6906
6907		url : function() {
6908			var path = Cargo.API.GetSitePath();
6909
6910			if( Cargo.API.Config.filter != "" ) {
6911				path += "?filter=" + encodeURIComponent(Cargo.API.Config.filter);
6912			}
6913			return path;
6914		},
6915
6916		GetId : function() {
6917			return this.get("id");
6918		},
6919
6920		GetDirectLink : function() {
6921			return this.get("direct_link");
6922		},
6923
6924		GetDisplayUrl : function() {
6925			return this.get("display_url");
6926		},
6927
6928		GetAssetPath : function(asset, id) {
6929			if(asset && id) {
6930				return this.get("asset_path") + "/" + asset + "?" + id;
6931			} else {
6932				return this.get("asset_path");
6933			}
6934		},
6935
6936		GetWebsiteTitle : function() {
6937			return this.get("website_title");
6938		},
6939
6940		GetMetaTags : function() {
6941			return this.get("meta_tags");
6942		},
6943
6944		GetMetaDescription : function() {
6945			return this.get("meta_description");
6946		},
6947
6948		GetMetaHead : function() {
6949			return this.get("meta_head");
6950		},
6951
6952		GetStartProjectId : function() {
6953			return this.get("start_project_id");
6954		},
6955
6956		GetThumbUrl : function() {
6957			return this.get("thumb_url");
6958		},
6959
6960		GetCssUrl : function() {
6961			return this.get("css_url");
6962		},
6963
6964		GetFaviconUrl : function() {
6965			return this.get("favicon_url");
6966		},
6967
6968		GetHeaderUrl : function() {
6969			return this.get("header_url");
6970		},
6971
6972		GetHeaderWidth : function() {
6973			return this.get("header_width");
6974		},
6975
6976		GetHeaderHeight : function() {
6977			return this.get("header_height");
6978		},
6979		
6980		GetFilter : function() {
6981			return (this.get("filter")) ? this.get("filter") : Cargo.API.Config.filter;
6982		},
6983
6984		GetIsEditor : function() {
6985			return this.get("is_editor");
6986		},
6987
6988		GetUseHiRes : function() {
6989			return this.get("use_hi_res");
6990		},
6991
6992		GetDisplayOption : function( which ) {
6993			if(typeof this.get("display_options") != "undefined" ) {
6994				return this.get("display_options")[which];	
6995			} else {
6996				return false;
6997			}
6998			
6999		},
7000
7001		GetFileUrl : function( which ) {
7002			if(typeof this.get("files") != "undefined" ) {
7003				return this.get("files")[which];	
7004			} else {
7005				return false;
7006			}
7007			
7008		},
7009
7010		GetCurrentSetKey : function() {
7011			return this.get("current_set_key");
7012		},
7013
7014		GetResourceUrl : function() {
7015			return this.get("resource_url");
7016		},
7017
7018		/**
7019		 *	Overwrite the parse function because the api
7020		 *	can not return the proper cookie value for editor
7021		 */
7022		parse : function (response) {
7023			if( !Cargo.Helper.IsAdminEdit() ) {
7024				response.is_editor = Cargo.Helper.GetIsEditor();
7025			}
7026			return response;
7027		}
7028
7029
7030	});
7031	
7032
7033/**
7034 *	Default model for constructing the Rail content
7035 */
7036	
7037	Cargo.o.model.rail = Backbone.Model.extend({
7038		
7039		url : function() {
7040			return Cargo.API.GetRailPath();
7041		},
7042
7043		GetTemplates : function() {
7044			return this.get('templates');
7045		},
7046
7047		GetModels : function() {
7048			return this.get('models');
7049		},
7050
7051		GetPopup : function() {
7052			return this.get('jdata')['popup'];
7053		}
7054
7055		
7056	});
7057	
7058
7059
7060/**
7061 *	Default model for assets
7062 */
7063	
7064Cargo.o.model.asset = Backbone.Model.extend({
7065	
7066});
7067	
7068
7069
7070/**
7071 *	Default model for images
7072 */
7073	
7074Cargo.o.model.images = Backbone.Model.extend({
7075	
7076});
7077	
7078
7079
7080
7081/**
7082 *	Default undefined model
7083 */
7084	
7085Cargo.o.model.undefined_default = Backbone.Model.extend({
7086	
7087});
7088	
7089
7090
7091
7092	Cargo.o.collection.projects =  Backbone.Collection.extend({
7093		
7094		initialize : function() {
7095			
7096		},
7097
7098		url		: function() {
7099			return Cargo.API.GetProjectsPath();
7100		}		
7101		
7102	});
7103
7104
7105
7106	Cargo.o.collection.pages =  Backbone.Collection.extend({
7107		
7108		initialize : function() {
7109			
7110		},
7111
7112		url		: function() {
7113			return Cargo.API.GetProjectsPath(null, null, 'page');
7114		}		
7115		
7116	});
7117
7118
7119
7120	Cargo.o.collection.navigation =  Backbone.Collection.extend({
7121		
7122		initialize : function() {
7123			
7124		},
7125
7126		url		: function() {
7127			return Cargo.API.GetNavigationCargoPath();
7128		}
7129		
7130	});
7131
7132
7133
7134	Cargo.o.collection.assets =  Backbone.Collection.extend({
7135		
7136		initialize : function() {
7137			
7138		},
7139
7140		fetch : function(options) {         
7141			if ( $("script[data-set='Assets']").html() ) {
7142				this.set( $.parseJSON( $("script[data-set='Assets']").html() ), options );
7143
7144			}
7145		}
7146		
7147	});
7148
7149
7150
7151	Cargo.o.collection.undefined_default =  Backbone.Collection.extend({
7152		
7153		initialize : function() {
7154			
7155		},
7156
7157		url		: function() {
7158			
7159		}		
7160		
7161	});
7162
7163
7164
7165	Cargo.o.collection.sets =  Backbone.Collection.extend({
7166		
7167		initialize : function() {
7168			
7169		},
7170
7171		url		: function() {
7172			return Cargo.API.GetSetsPath(0, 9999);
7173		}		
7174		
7175	});
7176
7177
7178
7179	Cargo.o.collection.images =  Backbone.Collection.extend({
7180		
7181		initialize : function() {
7182			
7183		},
7184
7185		url		: function() {
7186			var offset = this.options.offset;
7187			var limit = this.options.limit;
7188			var sort = this.options.sort;
7189			return Cargo.API.GetImagesPath(offset, limit, sort);
7190		},
7191
7192		fetch : function(options) {         
7193			this.options = options;
7194			Backbone.Model.prototype.fetch.apply(this, arguments);    
7195		}
7196		
7197	});
7198
7199
7200
7201	Cargo.o.collection.search =  Backbone.Collection.extend({
7202		
7203		initialize : function() {
7204			
7205		},
7206
7207		url	: function() {
7208			var term = (this.options && this.options.term) ? this.options.term : false;
7209			return Cargo.API.GetSearchPath(term);
7210		},
7211
7212		fetch : function(options) {         
7213			this.options = options;
7214			Backbone.Model.prototype.fetch.apply(this, arguments);    
7215		}
7216		
7217	});
7218
7219
7220	Cargo.o.view.Autopaginate = Backbone.View.extend({
7221		
7222		initialize: function() {
7223			this.Data = {
7224				"defered_count"		: 0,			// count how many defered events have been fired
7225				"is_updating"		: false,		// are we currently waiting on ajax?
7226				"within_bounds"		: false,		// within the bounds of paginating?
7227				"preload_distance"	: 500,			// Distance before the end of the page to fire
7228				"model_count" 		: 0,			// How many models do we have
7229				"direction"			: 'vertical'	// Which direction to check distance - vertical || horizontal
7230			};
7231
7232			var self = this;
7233
7234			self.tallProjectFix = Cargo.Helper.IsFeedDesign() && $('[data-view="Autopaginate"]').length == 1;
7235
7236			/* Set an event to wait for the model to change */
7237			this.listenTo(this.collection, "reset", this.reset);
7238			this.listenTo(this.collection, "add", this.add);
7239
7240			/* Loop through the data attributes and update the defaults */
7241			$.each(this.$el.data(), function(i, v) {
7242			   self.Data[i] = v;
7243			});
7244
7245			/* Set the element for the scroll container and the height continer */
7246			this.Data.scroll_el = (this.Data.container) ? $($("#"+this.Data.container).parents('div')[0]) : $(document);
7247			this.Data.size_el   = (this.Data.container) ? $("#"+this.Data.container) : $(document);
7248
7249			this.Data.direction = ($(this.$el).data('direction')) ? $(this.$el).data('direction') : this.Data.direction;
7250
7251
7252			/* Bind the scroll */
7253			_.delay(function() {
7254				self.Data.scroll_el.bind("scroll", function(event) {
7255					_.delay(function() {  
7256						self.CheckForPagination();
7257					}, 100);
7258				});
7259			}, 200);
7260
7261			/* Defer the toggle of the project until after the collection is reset */
7262			Cargo.Event.on("project_collection_reset", function( ) { 
7263				/* Tell the updater that we are done */
7264				self.Data.is_updating = false;
7265
7266				/* Hide the spinner */
7267				self.$el.hide();
7268
7269			});
7270
7271			/* Defer the checking of the first load pagination until after collection and template */
7272			Cargo.Event.on("project_collection_reset template_loaded", function(template) {
7273				/* Check to see if we have enough page 
7274				 * height to fire pagination right now 
7275				 * but only do this on first load 
7276				 * and only after the project collection is reset
7277				 * and only after the thumbnail template has been loaded */
7278				if( template == "Thumbnail") {
7279					_.delay(function() { 
7280						self.CheckForPagination();
7281					}, 100);
7282				}
7283			});
7284
7285			$(function(){
7286				self.last_project = $('.project_container').last();
7287				self.CheckForPagination();
7288			});
7289		},
7290
7291		reset : function() {
7292			Cargo.Event.trigger("pagination_complete", Cargo.API.Config.current_page );
7293			this.last_project = $('.project_container').last();
7294		},
7295
7296		add : function() {
7297			this.Data.model_count++;
7298
7299			// Make sure we have added all the models before firing the complete event
7300			if(this.collection.length == this.Data.model_count) {
7301				Cargo.Event.trigger("pagination_complete", Cargo.API.Config.current_page );
7302				this.last_project = $('.project_container').last();
7303
7304				/* Tell the updater that we are done */
7305				this.Data.is_updating = false;
7306			}
7307		},
7308
7309		
7310		CheckForPagination : function() {
7311			if(this.Data.direction == "horizontal") {
7312				this.CheckPageWidth(); 
7313			} else {
7314				this.CheckPageHeight();
7315			}
7316		},
7317		
7318		/*
7319		 *	Checks if we are within bounds of paginating
7320		 */
7321		CheckPageHeight : function() {
7322			// Don't do this if we're editing a project
7323			if( Cargo.Helper.IsAdminEditProject() ) {
7324				// Do nothing
7325				return false;
7326			}
7327
7328			var last_project_height = 0,
7329				window_height = $(window).height(),
7330				window_scroll = this.Data.scroll_el.scrollTop() + window_height,
7331				document_size = this.Data.size_el.height()-this.Data.preload_distance;
7332
7333			if(this.last_project && this.last_project.length > 0 && this.tallProjectFix){
7334				last_project_height = this.last_project.height();
7335				if(last_project_height > window_height - 10) {
7336					document_size -= (last_project_height - window_height);
7337				}
7338			}
7339
7340			if( window_scroll > document_size ) {
7341				this.Data.within_bounds = true;
7342			} else {
7343				this.Data.within_bounds = false;
7344			}
7345			
7346			/* Make sure there are more pages and the container is visible */
7347			if( 
7348				Cargo.Helper.GetTotalPages() > Cargo.API.Config.current_page &&
7349				(
7350					( typeof Cargo.View.Thumbnail != "undefined" && Cargo.View.Thumbnail.$el.is(":visible") ) ||
7351					typeof Cargo.View.Thumbnail == "undefined" 
7352				) &&
7353				!Cargo.Helper.IsSololink()
7354
7355			) {
7356				if (this.Data.within_bounds && !this.Data.is_updating) {
7357					Cargo.Event.trigger("pagination_start");
7358					this.Data.is_updating 	= true;
7359					this.Data.within_bounds = false;
7360					this.$el.show();
7361					Cargo.View.Main.NextPage();
7362				}
7363			}
7364		
7365		},
7366
7367		/*
7368		 *	Checks if we are within bounds of paginating
7369		 */
7370		CheckPageWidth : function() {
7371			// Don't do this if we're editing a project
7372			if( Cargo.Helper.IsAdminEditProject() ) {
7373				// Do nothing
7374				return false;
7375			}
7376
7377			var last_project_width = 0,
7378				window_width = $(window).width(),
7379				window_scroll = this.Data.scroll_el.scrollLeft() + window_width,
7380				document_size = this.Data.size_el.width()-this.Data.preload_distance;
7381
7382			if(this.last_project.length > 0 && this.tallProjectFix){
7383				last_project_width = this.last_project.width();
7384				if(last_project_width > window_width - 10) {
7385					document_size -= (last_project_width - window_width);
7386				}
7387			}
7388
7389			if( window_scroll > document_size ) {
7390				this.Data.within_bounds = true;
7391			} else {
7392				this.Data.within_bounds = false;
7393			}
7394
7395			/* Make sure there are more pages and the container is visible */
7396			if( 
7397				Cargo.Helper.GetTotalPages() > Cargo.API.Config.current_page &&
7398				(
7399					( typeof Cargo.View.Thumbnail != "undefined" && Cargo.View.Thumbnail.$el.is(":visible") ) ||
7400					typeof Cargo.View.Thumbnail == "undefined" 
7401				) &&
7402				!Cargo.Helper.IsSololink()
7403
7404			) {
7405				if (this.Data.within_bounds && !this.Data.is_updating) {
7406					Cargo.Event.trigger("pagination_start");
7407					this.Data.is_updating 	= true;
7408					this.Data.within_bounds = false;
7409					this.$el.show();
7410					Cargo.View.Main.NextPage();
7411				}
7412			}
7413		
7414		}
7415
7416	});
7417
7418/**
7419 *	This view handles the main website's view
7420 *	It binds to the body element and is loaded only once on page load
7421 *	This is mostly to handle the global click events for history and close
7422 *	However, maybe it will handle other things as well
7423 *	If we would render the entire app from JS, then this would render the init design
7424 */
7425
7426	Cargo.o.view.Main =  Backbone.View.extend({
7427			el: 'body',
7428
7429			initialize: function() {
7430				// Set an event to wait for the model to change
7431				this.collection.bind("change", this.render, this);
7432
7433				/*
7434				 *	If there is a custom HTML/CSS preview
7435				 */
7436				if( Cargo.API.Config.preview == "html" ) {
7437					this.WriteCustomHtmlPreview();
7438				} else if( Cargo.API.Config.preview == "css" ) {
7439					this.WriteCustomCSSPreview();
7440				}
7441
7442				/*
7443				 *	Init the Rail and Following, but not in admin or mobile mode
7444				 */
7445				if( !Cargo.Helper.IsAdminEdit() && typeof Mobile != "object") {
7446					Cargo.View.Rail = new Cargo.o.view.Rail({ 
7447						el    		: "body",
7448						collection 	: Cargo.Model.Rail 
7449					});
7450
7451					Cargo.View.Following = new Cargo.o.view.Following({ el : "body" });
7452				}
7453
7454			},
7455			
7456			events: {
7457			    "click [rel='history']"			: "HistoryPush",
7458			    "click [rel='show_index']"		: "ShowIndex",
7459			    "click [rel='close_project']"	: "ShowIndex", // API 0.1 legacy support
7460			    "click [rel='show_home']"		: "ShowHome",
7461			    "click [rel='next_project']"	: "NextProject",
7462			    "click [rel='prev_project']"	: "PrevProject",
7463			    "click [rel='random_project']"	: "RandomProject",
7464			    "click [rel='next_page']" 		: "NextPage",
7465			    "click [rel='prev_page']" 		: "PrevPage"
7466			},
7467
7468			/**
7469			 *	Site model has loaded, init from here
7470			 */
7471			render : function() {
7472				var model = this.collection;
7473			},
7474			
7475			/*
7476			 *	Pushes a history item onto the stack by navigating to 
7477			 *	the url found in the href property
7478			 *	Triggers the global event history add_history
7479			 *	Works with any tag
7480			 *	Usage:
7481			 *	<div data-href="/some/link" rel="history">Click me</div>
7482			 */
7483			HistoryPush : function( e, target ) {
7484				if( e ) {
7485					/* Remove default action */
7486					e.preventDefault();
7487					target = e.currentTarget;
7488				}
7489
7490				/* Get the absolute anchor href */
7491				var href = ( $(target).attr("href") ) ? $(target).attr("href") : $(target).data("href");
7492				var id   = ( $(target).attr("data-id") ) ? $(target).attr("data-id") : $(target).data("id");
7493
7494				/* ctrl+click */
7495				if (e.metaKey) {
7496					window.open( href );
7497				} else {
7498					/* Navigate to the new URL */
7499					Cargo.Event.trigger("add_history", href, id);
7500				}
7501			},
7502
7503			/*
7504			 *	Closes an open project by navigating to the homepage 
7505			 *	Works with any tag
7506			 *	Usage:
7507			 *	<a href="#" rel="show_index">Close</a>
7508			 *	Or
7509			 *	Cargo.View.Main.ShowIndex();
7510			 */
7511			ShowIndex : function( e ) {
7512				if( e ) {
7513					/* Remove default action (if there is one) */
7514					e.preventDefault();
7515				}
7516				
7517				/* If this is a feed based design, then there will be an index_route
7518				 * if that is the case, go there */
7519				if( Cargo.Model.DisplayOptions.GetIndexRouteName() ) {
7520					Cargo.Helper.GoToUrl( Cargo.Model.DisplayOptions.GetIndexRouteUrl() );
7521					return;
7522				} 
7523
7524				/* If this project is the start project, close passively
7525				 * Otherwise, close normally */
7526				if( Cargo.API.Config.start_project ) {
7527					Cargo.Event.trigger("add_history_passive", "" );
7528
7529					Cargo.API.Config.start_project = null;
7530					
7531					/* Update the data model to remove the last project */
7532					Cargo.Model.Project.set( { id : 0, project_url : null } );
7533
7534
7535				} else {
7536					/* Navigate to the Home route */
7537					var home_route = ""; //(Cargo.API.Config.set) ? Cargo.API.Config.set : "";
7538					Cargo.Event.trigger("add_history", home_route );
7539				}
7540
7541			},
7542
7543			/**
7544			 * Closes an open project, navigates to the root
7545			 * and re-opens the start page
7546			 *	Usage:
7547			 *	<a href="#" rel="show_home">Home</a>
7548			 *	Or
7549			 *	Cargo.View.Main.ShowHome();
7550			 */
7551			ShowHome : function(e) {
7552				if( e ) {
7553					/* Remove default action (if there is one) */
7554					e.preventDefault();
7555				}
7556
7557				/* If this is a feed based design, then there will be an index_route
7558				 * if that is the case, go there */
7559				if( Cargo.Model.DisplayOptions.GetIndexRouteName() ) {
7560					Cargo.Helper.GoToUrl( Cargo.Model.DisplayOptions.GetIndexRouteUrl() );
7561					return;
7562				} 
7563
7564				/* If this project is the start project, close passively
7565				 * Otherwise, close normally */
7566				if( Cargo.API.Config.start_project ) {
7567					Cargo.Event.trigger("add_history_passive", "" );
7568
7569					/*
7570					 *	Try and get the model from memory first
7571					 */
7572					var project_model = Cargo.Collection.Projects.findWhere( { id : Cargo.Helper.GetStartProjectId() } );
7573					var page_model = Cargo.Collection.Pages.findWhere( { id : Cargo.Helper.GetStartProjectId() } );
7574					var home_model = (typeof Persona.Model.HomeProject == "object") ? Persona.Model.HomeProject : false;
7575					var model = (page_model) ? page_model : (home_model) ? home_model : project_model;
7576
7577					if(model) {
7578						Cargo.Model.Project.set( model.toJSON() );
7579
7580					} else {
7581						// If there is no model, then load one async
7582						Cargo.Model.Project.fetch( { data : { pid : Cargo.Helper.GetStartProjectId() }} );
7583					}
7584
7585
7586				} else {
7587					/* Navigate to the Home route */
7588					var home_route = ""; //(Cargo.API.Config.set) ? Cargo.API.Config.set : "";
7589					Cargo.Event.trigger("add_history", home_route );
7590				}
7591			},
7592
7593			/**
7594			 *	Next project
7595			 *	If we are on the last project of a page, go to the next page
7596			 *	If we are on the last project of the last page, go to page 1
7597			 *	Works with any tag
7598			 *	Usage:
7599			 *	<a href="#" rel="next_project">Next project</a>
7600			 *	Or
7601			 *	Cargo.View.Main.NextProject();
7602			 */
7603			 NextProject : function( e, dont_check ) {
7604			 	if( e ) {
7605					/* Remove default action (if there is one) */
7606					e.preventDefault();
7607				}
7608
7609				/*
7610				 *	If the next project needs to paginated
7611				 *	then paginate and delay this method until after 
7612				 *	the collection resets itself
7613				 */
7614				if( Cargo.Helper.NextProjectNeedsToPaginate() && !dont_check ) {
7615					
7616					/* Delay this event, but only fire it once, so remove it too  
7617					 * Note: Stays above the Pagination because memory fires too quick */
7618					Cargo.Event.on("pagination_complete", function() { 
7619					   Cargo.View.Main.NextProject();
7620					   this.off("pagination_complete");
7621					});	
7622
7623					if( Cargo.Helper.GetTotalPages() == Cargo.API.Config.current_page ) {
7624						if( Cargo.Helper.IsAutopaginate() ) {
7625							/* If we reached the end and it is autopaginated, go back to the start */
7626							this.NextProject(false, true);
7627						} else {
7628							/* If we are on the last page, go to first */
7629							Cargo.View.Main.GoToPage(1);
7630						}
7631
7632					} else {
7633						/* Get the next page */
7634						Cargo.View.Main.NextPage();	
7635					}
7636					
7637
7638				} else {
7639					/* Get the next purl */
7640					var purl = Cargo.Helper.GetNextProjectUrl();
7641					var href = "/" + purl;
7642
7643					/* Navigate to the new route */
7644					if( Cargo.View.hasOwnProperty('ProjectDetail') ) {
7645						Cargo.Event.trigger("add_history", href);
7646					} else {
7647						document.location.href = Cargo.Helper.GetBaseUrl(true, true) + href;
7648					}
7649
7650					/* Cargo events */
7651					Cargo.Event.trigger("next_project", Cargo.Helper.GetPidFromPurl(purl));	
7652				}
7653				
7654			 },
7655
7656			 /**
7657			  *	Previous project
7658			  *	If we are on the first project of a page, go to the previous page
7659			  *	If we are on the first project of the first page, go to last page
7660			  *	Works with any tag
7661			  *	Usage:
7662			  *	<a href="#" rel="prev_project">Next project</a>
7663			  *	Or
7664			  *	Cargo.View.Main.PrevProject();
7665			  */
7666			 PrevProject : function( e, dont_check ) {
7667			 	if( e ) {
7668					/* Remove default action (if there is one) */
7669					e.preventDefault();
7670				}
7671
7672				/*
7673				 *	If the next project needs to paginated
7674				 *	then paginate and delay this method until after 
7675				 *	the collection resets itself
7676				 */
7677				if( Cargo.Helper.PrevProjectNeedsToPaginate() && !dont_check && !Cargo.Helper.IsAutopaginate() ) {
7678					/* Delay this event, but only fire it once, so remove it too  
7679					 * Note: Stays above the Pagination because memory fires too quick */
7680					Cargo.Event.on("pagination_complete", function() { 
7681						/* Set value to true to not perform this check next time */
7682					   Cargo.View.Main.PrevProject( e, true );
7683					   this.off("pagination_complete");
7684					});	
7685
7686					if( Cargo.API.Config.current_page == 1 ) {
7687						/* If we are on the first page, go to last */
7688						Cargo.View.Main.GoToPage( Cargo.Helper.GetTotalPages() );
7689
7690					} else {
7691						/* Get the prev page */
7692						Cargo.View.Main.PrevPage();
7693					}
7694
7695
7696				} else {
7697					/* Get the prev purl */
7698					var purl = Cargo.Helper.GetPrevProjectUrl();
7699					var href = "/" + purl;
7700					
7701					/* Navigate to the new route */
7702					if( Cargo.View.hasOwnProperty('ProjectDetail') ) {
7703						Cargo.Event.trigger("add_history", href);
7704					} else {
7705						document.location.href = Cargo.Helper.GetBaseUrl(true, true) + href;
7706					}
7707
7708
7709					/* Cargo events */
7710					Cargo.Event.trigger("prev_project", Cargo.Helper.GetPidFromPurl(purl));
7711				}
7712			 },
7713
7714			/**
7715			 *	Random project
7716			 *	Works with any tag
7717			 *	Usage:
7718			 *	<a href="#" rel="random_project">Random project</a>
7719			 *	Or
7720			 *	Cargo.View.Main.RandomProject();
7721			 */
7722			 RandomProject : function( e ) {
7723			 	if( e ) {
7724					/* Remove default action (if there is one) */
7725					e.preventDefault();
7726				}
7727
7728				if( Cargo.View.hasOwnProperty('ProjectDetail') ) {
7729					Cargo.API.GetRandomProject();
7730					
7731				} else {
7732					// Get a random number
7733					var random = Math.floor(Math.random() * Cargo.Helper.GetTotalProjectsInMemory()) + 1;
7734					var obj = Cargo.Helper.GetProjectModelAndPageByKey(random);
7735					var cur_pid = Cargo.Helper.GetPidFromPurl( Cargo.Model.Project.GetProjectUrl() );
7736
7737					// If there is a project open, make sure this is not that project
7738					if( obj.model && cur_pid == obj.model.get('id') ) {
7739						// Randomize agail
7740						random = Math.floor(Math.random()*Cargo.Helper.GetTotalProjectsInMemory());
7741						obj = Cargo.Helper.GetProjectModelAndPageByKey(random);
7742					}
7743					
7744					if( obj.model && obj.model.get('id') && cur_pid != obj.model.get('id') ) {
7745						document.location.href = obj.model.get('url');
7746
7747						// Cargo events 
7748						Cargo.Event.trigger("random_project", obj.model.get('id') );
7749					
7750					} else {
7751						this.RandomProject();
7752					}
7753				}
7754			 },
7755
7756
7757			/**
7758			  *	Next page
7759			  *	Works with any tag
7760			  *	Usage:
7761			  *	<a href="#" rel="next_page">Next project</a>
7762			  *	Or
7763			  *	Cargo.View.Main.NextPage();
7764			  */
7765			 NextPage : function( e ) {
7766			 	if( e ) {
7767					/* Remove default action (if there is one) */
7768					e.preventDefault();
7769				}
7770
7771				var self = this;
7772
7773				/* Set the next page */
7774				var current_page 	= Cargo.API.Config.current_page;
7775				var next_page 		= current_page+1;
7776
7777				/* Call the API for the next page */
7778				this.GoToPage(next_page);
7779				
7780				/* Cargo events */
7781				Cargo.Event.trigger("next_page", next_page );
7782
7783			 },
7784
7785			 /**
7786			  *	Previous page
7787			  *	Works with any tag
7788			  *	Usage:
7789			  *	<a href="#" rel="prev_page">Next project</a>
7790			  *	Or
7791			  *	Cargo.View.Main.PrevPage();
7792			  */
7793			 PrevPage : function( e ) {
7794			 	if( e ) {
7795					/* Remove default action (if there is one) */
7796					e.preventDefault();
7797				}
7798
7799				/* Set the prev page */
7800				var current_page 	= Cargo.API.Config.current_page;
7801				var prev_page 		= current_page-1;
7802				
7803				/* Call the API for the next page */
7804				this.GoToPage(prev_page);
7805				
7806				/* Cargo events */
7807				Cargo.Event.trigger("prev_page", prev_page );
7808			 },
7809
7810			/**
7811			 *	Get a specific page
7812			 */
7813			GoToPage : function( page ) {
7814				/* Show the loading animation */
7815				Cargo.Helper.ShowLoadingAnimation( );
7816
7817			 	/* Set the prev page */
7818				var current_page 	= Cargo.API.Config.current_page;
7819				
7820				/* Call the API for the next page */
7821				Cargo.API.SetProjectsPage( page, current_page );
7822				Cargo.API.SetNavigationPage( page, current_page );
7823			},
7824
7825			/**
7826			 *	This method grabs the custom HTML out of the
7827			 *	edit frame 
7828			 */
7829			WriteCustomHtmlPreview : function() {
7830
7831				if(parent.editframe !== undefined){
7832					var customhtml = parent.editframe.document.getElementById("syntax_highlight").value;
7833					customhtml = Cargo.Core.Handlebars.RenderOnlyAssets(customhtml);
7834					$("customhtml").html(customhtml);
7835				} else if(typeof parent.Cargo !== "undefined" && parent.Cargo.hasOwnProperty('Editor')) {
7836
7837					if(parent.Cargo.Editor.previewCustomHTML !== undefined) {
7838
7839						var customhtml = parent.Cargo.Editor.previewCustomHTML;
7840						customhtml = Cargo.Core.Handlebars.RenderOnlyAssets(customhtml);
7841						$("customhtml").html(customhtml);
7842						
7843					}
7844
7845				}
7846
7847			},
7848
7849			/**
7850			 *	This method grabs the CSS out of the
7851			 *	edit frame 
7852			 */
7853			WriteCustomCSSPreview : function() {
7854				var stylesheet = parent.editframe.document.getElementById("syntax_highlight").value;
7855				stylesheet = Cargo.Core.Handlebars.RenderOnlyAssets(stylesheet);
7856				$("body").append('<style>'+stylesheet+'</style>');
7857				$("head link[rel='stylesheet']").remove();
7858			},
7859
7860			/*
7861			 *	For ajax history, set the title of the page
7862			 */
7863			SetWindowTitle : function(is_unload) {
7864				if(!is_unload) {
7865					if(Cargo.Model.Project.GetTitleNoHtml()) {
7866						document.title = Cargo.Model.Project.GetTitleNoHtml() + " - " + Cargo.Model.Site.GetWebsiteTitle();
7867					}
7868				
7869				} else {
7870					document.title = Cargo.Model.Site.GetWebsiteTitle();	
7871				}
7872			},
7873
7874			/*
7875			 *	For ajax history, set the title of the page
7876			 */
7877			SetDataType : function(type) {
7878				this.$el.attr("data-pagetype", type);
7879				if(type) this.$el.addClass("open");
7880				else this.$el.removeClass("open");
7881			},
7882
7883			/**
7884			 *	Set a class for the start project, if this is one
7885			 *	Is called from the router when we find out it is the home route
7886			 *	and this is a start project
7887			 */
7888			SetStartProjectClass : function() {
7889				if( Cargo.API.Config.start_project ) {
7890					this.$el.addClass('start_project');
7891				}
7892			},
7893
7894			/**
7895			 *	This method is called every project open
7896			 */	
7897			RemoveStartProjectClass : function() {
7898				this.$el.removeClass('start_project');
7899			}
7900			
7901		});
7902
7903
7904
7905
7906/**
7907 *	This Feed view is here to kick ass and chew bubblegum. Let's hope it's not out of bubble gum.
7908 *
7909 */
7910
7911	Cargo.o.view.Feed = Backbone.View.extend({
7912			
7913		initialize: function(options) {
7914
7915			var self = this;
7916
7917			this.options = options;
7918			this.initialized = false;
7919
7920			if( typeof this.collection == "object" ) {
7921
7922				this.listenTo(this.collection, "add", this.render);
7923				this.listenTo(this.collection, "change", this.render);
7924				this.listenTo(this.collection, 'reset', this.reset);
7925
7926			}
7927
7928			Cargo.Event.on('first_project_collection_reset', function(){
7929				self.initialized = true;
7930			});
7931
7932		},
7933
7934		render : function(model) {
7935
7936			/* Get the template for this view (Prevents first load render) */
7937			var template 	= Cargo.Template.Get('Feed'),
7938				pid 		= (model.get('id') !== undefined ? model.get('id') : false),
7939				projectContainer = $("[data-id='" + pid + "']", this.$el);
7940
7941			/*
7942			 *	If a template exists, render it
7943			 */
7944
7945			if( template && this.initialized === true) {
7946				
7947				/* Render the template and get the markup */
7948				var data_model = Cargo.API.GetDataPackage( 'projects', model.toJSON() ),
7949					markup = Cargo.Core.Handlebars.Render( template, data_model);
7950
7951				if( projectContainer.length > 0 ){
7952					
7953					// update project
7954					projectContainer[0].outerHTML = markup;
7955
7956				} else {
7957
7958					// create new project
7959					this.$el.append(markup);
7960
7961				}
7962
7963				// setting outerHTML loses the reference & 
7964				// creating a new project requires looking up the new el too.
7965				projectContainer = $("[data-id='" + pid + "']", this.$el);
7966
7967			}
7968
7969			this.markNodesAsOriginal(projectContainer, pid);
7970
7971		},
7972
7973		reset : function() {
7974			
7975			/* Make a reference to self */
7976			var self 		= this;
7977
7978			/* Get the template for this view (Prevents first load render) */
7979			var template 	= Cargo.Template.Get('Feed');
7980
7981			/*
7982			 *	If a template exists, render it
7983			 */
7984			if( template ) {
7985				
7986				/* Create a package of data for the render */
7987				var data_model = Cargo.API.GetDataPackage( 'projects', this.collection.toJSON() );
7988
7989				/* Render the template and get the markup */
7990				var markup = Cargo.Core.Handlebars.Render( template, data_model );
7991
7992				/* Empty the container */
7993				this.$el.empty();
7994				this.$el.append( markup );
7995
7996				var i, 
7997					pid;
7998
7999				for(i = 0; i < data_model.projects.length; i++){
8000					
8001					pid = data_model.projects[i].id;
8002					this.markNodesAsOriginal($("[data-id='" + pid + "']", this.$el), pid);
8003
8004				}
8005			} // end template
8006
8007		},
8008
8009		markNodesAsOriginal: function(projectContainer, pid){
8010
8011			var projectContent = projectContainer.find('projectcontent')[0];
8012
8013			if(projectContent !== undefined){
8014
8015				if(Node.prototype.hasOwnProperty('setSaveable')){
8016					this.getAllDescendants(projectContent, function(node){
8017						node.setSaveable(true);
8018					});
8019				}
8020
8021				var parent = projectContent.parentNode;
8022
8023				// fyi: ce = cargo editor
8024				parent.setAttribute('data-ce-host', 'true');
8025				parent.setAttribute('data-ce-model-id', pid);
8026
8027				while (projectContent.childNodes.length > 0) {
8028					parent.insertBefore(projectContent.childNodes[0], projectContent);
8029				}
8030
8031				parent.removeChild(projectContent);
8032
8033			}
8034
8035		},
8036
8037		getAllDescendants: function(node, callback) {
8038
8039			// recursively finds all childnodes and fires a callback for every node found
8040			for (var i = 0; i < node.childNodes.length; i++) {
8041
8042				var child = node.childNodes[i],
8043					result = callback(child);
8044
8045				if(result === false){
8046					return false;
8047				}
8048
8049				// if "continue" is being returned from the callback we don't go down
8050				// the subtree of the current child.
8051				if(result !== "continue"){
8052					// recursively go down the tree.
8053					this.getAllDescendants(child, callback);
8054				}
8055
8056			}
8057
8058		}
8059
8060	});
8061
8062	Cargo.o.view.ProjectDetail = Backbone.View.extend({
8063			el: '#maincontainer',
8064
8065			initialize: function() {
8066				this.Data = {
8067					"total_tries" : 0,
8068					"max_tries" : 5,
8069					"first_load" : true
8070				};
8071
8072				// Set an event to wait for the model to change
8073				//this.collection.bind("empty", this.CloseProject, this);
8074
8075				// Set an event to wait for the model to change
8076				this.collection.bind("change", this.render, this);
8077
8078				var model = this.collection,
8079					pid   = (model && model.GetId()) ? model.GetId() : false;
8080
8081				this.markNodesAsOriginal(pid);
8082
8083				/*
8084				 *	If we are editing a project in the admin
8085				 */
8086				// if( Cargo.API.Config.adminedit ) {
8087				// 	this.AdminEditProject();
8088				// }
8089
8090			},
8091
8092			render : function(e) {
8093
8094				if(this.preventRender === true) {
8095					console.log('Prevented project render');
8096					return;
8097				}
8098
8099				// Set the model for shorthand
8100				var model = this.collection,
8101					_self = this;
8102
8103				/* If there is no model, and no model.id, close the project */
8104				if( model && !model.GetId() ) {
8105					this.CloseProject();
8106					
8107					/* Show the loading animations */
8108					Cargo.Helper.HideLoadingAnimation();
8109
8110					return;
8111				}
8112
8113				/* Make sure we have a model */
8114				else if( model ) {
8115					var self 		= this;
8116					var pid			= (model && model.GetId()) ? model.GetId() : false;
8117					var template 	= Cargo.Template.Get( "ProjectDetail" );
8118
8119					this.Data.pid = pid;
8120
8121					/*
8122					 *	Probably this is being called before the template has been loaded
8123					 *	set a timeout and recall this method when the template is loaded
8124					 *	However, there might be something really wrong here, so keep track
8125					 *	of how many times we try. If it is too many, reload the whole page
8126					 *	at the direct link
8127					 */
8128					if( !template ) {
8129						if( Cargo.View.ProjectDetail.Data.total_tries >= Cargo.View.ProjectDetail.Data.max_tries ) {
8130							document.location.href = model.GetDirectLink();
8131						
8132						} else {
8133							setTimeout(function() {
8134								Cargo.View.ProjectDetail.Data.total_tries++;
8135								Cargo.View.ProjectDetail.render();
8136							},100);
8137
8138						}
8139						return false;
8140					}
8141					
8142					/* Create a json object for the model wrapped in the project label */
8143					var data_model = Cargo.API.GetDataPackage( "project", model.toJSON() );
8144					
8145					/* Render the template and get the markup */
8146					var markup = Cargo.Core.Handlebars.Render( template, data_model );
8147
8148					/* Wait for the deferred project transition event to complete */
8149					$.when( Cargo.Event.ShowProjectTransition(pid, markup, model) ).then(function() {
8150						/* Write the content */
8151						self.$el.html( markup ).show();	
8152
8153						_self.markNodesAsOriginal(pid);
8154
8155						/* Fire the Cargo event on complete */
8156						Cargo.Event.trigger("project_load_complete", pid);
8157
8158						/* Increment the view count */
8159						self.IncrementView();
8160
8161						/* Reset the total tries count */
8162						Cargo.View.ProjectDetail.Data.total_tries = 0;
8163
8164						self.Data.first_load = false;
8165					});
8166
8167				}
8168
8169			},
8170
8171			/**
8172			 *	Remove the main view
8173			 */
8174			CloseProject : function() {
8175				// Set the model for shorthand
8176				var self = this;
8177				var model = this.collection;
8178
8179				// On first load, do not use transitions
8180				if(this.Data.first_load === true) {
8181					this.DoCloseActions(this.Data.pid);
8182				} else {
8183					/* Wait for the deferred project transition event to complete */
8184					$.when( Cargo.Event.ShowIndexTransition(this.Data.pid, model) ).then(function() {
8185						self.DoCloseActions(self.Data.pid);
8186					});
8187				}
8188				
8189			},
8190
8191			/**
8192			 * Do the actions to actually close the project
8193			 * This is offloaded so there is a way of bypassing the transition deferment 
8194			 * @param {Number} pid Project ID
8195			 */
8196			DoCloseActions: function(pid) {
8197				/* Before close events */
8198				Cargo.Event.trigger("show_index_start", pid );  
8199
8200				this.$el.empty().hide(0);
8201				
8202				/* After close events */
8203				Cargo.Event.trigger("show_index_complete", pid );
8204
8205				this.Data.first_load = false;
8206			},
8207
8208
8209			/**
8210			 *	This is what happens on a permalink
8211			 */
8212			Permalink : function(purl) {
8213				/*
8214				 *	If this is an old browser #link, do a different permalink
8215				 */
8216				if(Cargo.Helper.isOldIE()) {
8217					this.IePermalink(purl);
8218				} else {
8219					this.el.style.display = "block";
8220					
8221					Cargo.Event.trigger("direct_link_loaded", Cargo.Model.Project.GetId(), location.pathname); 
8222					Cargo.Event.trigger("project_load_complete", Cargo.Model.Project.GetId());
8223				}
8224
8225				// Cargo.API.Config.is_direct_link = false;
8226
8227				this.Data.first_load = false;
8228			},
8229
8230			/**
8231			 *	When an old browser loads a permalink, 
8232			 *	it comes in the form or #Project and so the data
8233			 *	is not yet loaded because PHP does not know about it
8234			 *	So we need to load the data
8235			 */
8236			IePermalink : function(purl) {
8237				/*
8238				 *	Not sure why, but this has to sit in a timeout
8239				 */
8240				setTimeout(function() {					
8241					/*
8242					 *	Get and set the new single project
8243					 *	This will trigger an event that will automatically
8244					 *	update the view
8245					 */
8246					Cargo.API.GetSingleProject( purl );
8247
8248				}, 500);
8249			},
8250			
8251			/**
8252			 *	Change the background color based on project model
8253			 *	This is called from the projectloadcomplete event
8254			 * 	Inject the project detail element with a style tag
8255			 *	This is done because when the element is removed on close
8256			 *	the style tag will go with it. So we don't need to remember
8257			 *	states
8258			 */
8259			ChangeBgColorFromModel : function() {
8260				var model = this.collection;
8261
8262				if( model && !model.GetId() ) {
8263					return;
8264
8265				} else if( model.GetBgColor() ) {
8266					var tag = '<style id="site_bg_color"> body { background : ' + model.GetBgColor() + ' } </style>';
8267					this.$el.append(tag);
8268				}
8269			},
8270
8271			/**
8272			 *	This method removes all content but the project detail
8273			 *	It is for when editing a project in the admin
8274			 */
8275			AdminEditProject : function() {
8276				// Find any object with the adminedit flag and include that
8277				// var options = $("[data-options]").clone();
8278				// $("body").css("margin", "35px");
8279				// $("body").html(this.$el).append(options);
8280
8281				// Cargo.Event.trigger("admin_edit_load_complete");
8282			},
8283
8284			/**
8285			 * This method is triggered by the admin editor
8286			 * It expects project content to be passed to it
8287			 */
8288			SaveContent : function(content) {
8289				// Set the local content
8290				var model = this.collection;
8291				model.set('content', content, { silent : true });
8292
8293				var collection = null;
8294
8295				// Get the right collection
8296				if(model.get('type') == "page") {
8297					collection = Cargo.Collection.Pages;
8298				} else {
8299					collection = Cargo.Collection.Projects;
8300				}
8301
8302				// Update the right model in the collection
8303				var collection_model = collection.where({ id : model.get('id') })[0];
8304
8305				if(collection_model) {
8306					collection_model.set('content', content, { silent : true });
8307				}
8308
8309			},
8310
8311			/**
8312			 *	Increment the view count on project open
8313			 */
8314			IncrementView : function() {
8315				var self = this;
8316
8317				/* Put a timeout on this so that the browser can do render
8318				 * before it has to do this ajax
8319				 */
8320
8321				setTimeout(function() {
8322					if( self.collection.GetId() != "none" ) {
8323						$.post('/dispatch/project/addview', { pid: self.collection.GetId() });
8324					}
8325				}, 500);
8326			},
8327
8328			markNodesAsOriginal: function(pid){
8329
8330				var projectContent = this.$el.find('projectcontent')[0];
8331
8332				if(projectContent !== undefined){
8333
8334						if(Node.prototype.hasOwnProperty('setSaveable')){
8335							this.getAllDescendants(projectContent, function(node){
8336								node.setSaveable(true);
8337							});
8338						}
8339
8340						var parent = projectContent.parentNode;
8341
8342						// fyi: ce = cargo editor
8343						parent.setAttribute('data-ce-host', 'true');
8344						parent.setAttribute('data-ce-model-id', pid);
8345
8346						while (projectContent.childNodes.length > 0) {
8347							parent.insertBefore(projectContent.childNodes[0], projectContent);
8348						}
8349
8350						parent.removeChild(projectContent);
8351				}
8352
8353			},
8354
8355			getAllDescendants: function(node, callback) {
8356
8357				// recursively finds all childnodes and fires a callback for every node found
8358				for (var i = 0; i < node.childNodes.length; i++) {
8359
8360					var child = node.childNodes[i],
8361						result = callback(child);
8362
8363					if(result === false){
8364						return false;
8365					}
8366
8367					// if "continue" is being returned from the callback we don't go down
8368					// the subtree of the current child.
8369					if(result !== "continue"){
8370						// recursively go down the tree.
8371						this.getAllDescendants(child, callback);
8372					}
8373
8374				}
8375
8376			}
8377	});
8378
8379
8380
8381
8382	Cargo.o.view.Pagination = Backbone.View.extend({
8383			initialize: function() {
8384
8385				/* Defer the toggle of the project until after the collection is reset */
8386				Cargo.Event.on("project_collection_reset", function( ) { 
8387					Cargo.View.Pagination.ChangePage( Cargo.API.Config.current_page );
8388				});
8389
8390			},
8391
8392			/*
8393			 *	Receives a model input (probably from the ChangePage method)
8394			 *	Renders the template
8395			 */
8396			render : function( model ) {
8397				
8398				/* Get the template for this view (Prevents first load render) */
8399				var template 	= Cargo.Template.Get("Pagination");
8400
8401				/* Only render if we found a template */
8402				if( template ) {
8403					
8404					/* Render the template and get the markup */
8405					var markup = Cargo.Core.Handlebars.Render( template, model );
8406
8407					this.$el.html( markup ); //.show();
8408					
8409				}
8410
8411				/* Cargo event */
8412				Cargo.Event.trigger("pagination_complete", Cargo.API.Config.current_page );
8413			},
8414
8415			/**
8416			 *	Handle a page change
8417			 *
8418			 */
8419			ChangePage : function( next_page ) {
8420				var total = Cargo.Helper.GetTotalPages();
8421
8422				Cargo.Helper.ShowLoadingAnimation( );
8423
8424				/*
8425				 *	Determine what type of pagination message to show
8426				 */
8427				// If there is only one page, don't show anything
8428				if( next_page == 1 && total == 1) {
8429					pagination = "";
8430				} 
8431				// If we are on the first page and more then one page
8432				else if(next_page == 1 && total > 1) {
8433					pagination = {
8434						"next_page" : { 
8435							"current" : next_page, 
8436							"total" : total 
8437						}
8438					};
8439				}
8440				// If we not on the last page
8441				else if( next_page == total ) {
8442					pagination = {
8443						"prev_page" : { 
8444							"current" : next_page, 
8445							"total" : total 
8446						}
8447					};
8448				}
8449				// If we are past the first page, before the last page
8450				else if( next_page > 1 && next_page < total ) {
8451					pagination = {
8452						"next_prev_page" : { 
8453							"current" : next_page, 
8454							"total" : total 
8455						}
8456					};
8457				}
8458
8459				
8460				this.render(pagination);
8461			}
8462
8463
8464		});
8465
8466
8467
8468	Cargo.o.view.Thumbnail = Backbone.View.extend({
8469			
8470			initialize: function() {
8471				this.Data = {
8472					/* How many models do we have */
8473					model_count : 0
8474				};
8475
8476				/* Set an event to wait for the model to change */
8477				this.listenTo(this.collection, "reset", this.render);
8478				this.listenTo(this.collection, "add", this.add);
8479			},
8480
8481			/**
8482			 *	This method will re-render the entire contents of the container
8483			 *	this is meant for replacing all content in a standard pagination
8484			 */
8485			render : function( ) {
8486
8487				/* Make a reference to self */
8488				var self 		= this;
8489
8490				/* Get the template for this view (Prevents first load render) */
8491				var template 	= Cargo.Template.Get("Thumbnail");
8492
8493				/*
8494				 *	If a template exists, render it
8495				 */
8496				if( template ) {
8497					/* Create a package of data for the render */
8498					var data_model = Cargo.API.GetDataPackage( "projects", this.collection.toJSON() );
8499					
8500					/* Render the template and get the markup */
8501					var markup = Cargo.Core.Handlebars.Render( template, data_model );
8502
8503					/* Empty the container */
8504					this.$el.empty();
8505					this.$el.append( markup );
8506
8507					Cargo.Event.trigger("project_collection_reset");
8508				} 
8509
8510			},
8511
8512			/**
8513			 *	The add event is for adding a single item into the collection
8514			 *	this is intended for autopagination
8515			 */
8516			add : function( model ) {
8517				var self = this;
8518				
8519				/* Count how many models we have for autopagination */
8520				this.Data.model_count++;
8521
8522				/* Get the template for this view  */
8523				var template 	= Cargo.Template.Get("Thumbnail");
8524
8525				var in_thumb = ($("[href='"+model.get('url')+"']", self.$el).length > 0);
8526				var in_data  = ($("[data-id='"+model.get('id')+"']", self.$el).length > 0);
8527
8528				/* Verify that this model is not already onscreen */
8529				if( template && !in_thumb && !in_data ) {
8530					/* Create a package of data for the render */
8531					var data_model = Cargo.API.GetDataPackage( "projects", model.toJSON() );
8532
8533					/* Render the template and get the markup */
8534					var markup = Cargo.Core.Handlebars.Render( template, data_model );
8535
8536					/* Check if this is in a set and needs to be added to one */
8537					if(
8538						Cargo.Model.DisplayOptions.GetUseSets() && 
8539						model.get("set_id") != 0 &&
8540						$("[data-setid='"+model.get("set_id")+"']", self.$el).length > 0
8541					) {
8542						$("[data-setid='"+model.get("set_id")+"']", self.$el).append( markup );
8543					}
8544					/* Prepend this to the autopagination handle */
8545					else {
8546						this.$el.append( markup );
8547					}
8548				}
8549
8550				/* Only fire this if the Pagination view is not loaded to prevent double calls 
8551				 * and when the amount of projects on screen is the same as in the collection
8552				 */
8553				if( !Cargo.View.Pagination && this.collection.length == this.Data.model_count) {
8554					Cargo.Event.trigger("project_collection_reset"); 	
8555				}
8556			},
8557
8558			/**
8559			 *	Add an active state to the navigation
8560			 *	remove old active states first
8561			 */
8562			AddActive : function(pid) {
8563				var self = this;
8564				if( !pid ) {
8565					pid = Cargo.Model.Project.GetId();
8566				}
8567				this.RemoveActive();
8568				$("[data-id='"+pid+"']", self.$el).addClass("active");
8569			},
8570
8571			/**
8572			 *	Remove active state to the navigation
8573			 */
8574			RemoveActive : function() {
8575				var self = this;
8576				$(".active", self.$el).removeClass("active");
8577			},
8578
8579			/**
8580			 *	Checks the display options for hidden thumbs below projects and/or pages
8581			 *	and if it is set, then hide them
8582			 */
8583			CheckAndHideThumbs : function() {
8584				var type = Cargo.Helper.GetCurrentPageType(); 
8585				if( type == "project" && !Cargo.Model.DisplayOptions.GetThumbsBelowProjects() ) {
8586					this.$el.hide();
8587
8588				} else if( type == "page" && !Cargo.Model.DisplayOptions.GetThumbsBelowPages() ) {
8589					this.$el.hide();
8590
8591				} else {
8592					this.ShowThumbs();
8593				}
8594			},
8595
8596			/**
8597			 *	Show the thumbnails again
8598			 */
8599			ShowThumbs : function() {
8600				this.$el.show();
8601			}
8602	});
8603
8604	Cargo.o.view.Navigation = Backbone.View.extend({
8605			
8606			Data : {
8607				first_load_complete : false
8608			},
8609
8610			initialize: function() {
8611				/* Set an event to wait for the model to change */
8612				this.listenTo(this.collection, "reset", this.render);
8613				this.listenTo(this.collection, "change", this.render);
8614				this.listenTo(this.collection, "add", this.add);
8615
8616				Cargo.Event.on('first_navigation_collection_reset', function() { 
8617					Cargo.View.Navigation.Data.first_load_complete = true;
8618				});
8619			},
8620
8621			events: {
8622			    "click .set_link > a[href='#']"	: "setClick"
8623			},
8624
8625			/**
8626			 *	This method will re-render the entire contents of the container
8627			 *	this is meant for replacing all content in a standard pagination
8628			 */
8629			render : function( ) {
8630				
8631				/* Make a reference to self */
8632				var self 		= this;
8633
8634				/* Get the template for this view (Prevents first load render) */
8635				var template 	= Cargo.Template.Get("Navigation");
8636
8637				/*
8638				 *	If a template exists, render it
8639				 */
8640				if( template ) {
8641					/* Create a package of data for the render */
8642					var data_model = Cargo.API.GetDataPackage( "navigation", this.collection.toJSON() );
8643
8644					/* Render the template and get the markup */
8645					var markup = Cargo.Core.Handlebars.Render( template, data_model );
8646
8647					/* Empty the container */
8648					this.$el.empty();
8649					this.$el.append( markup );
8650					
8651				}
8652
8653				Cargo.Event.trigger("navigation_reset");
8654
8655			},
8656
8657			/**
8658			 *	The add event is for adding a single item into the collection
8659			 *	this is intended for autopagination
8660			 */
8661			add : function( model ) {
8662				var self = this;
8663				
8664				/* Get the template for this view (Prevents first load render) */
8665				var template 	= Cargo.Template.Get("Navigation");
8666
8667				/* Verify that this model is not already onscreen */
8668				if( 
8669					this.Data.first_load_complete && 
8670					$("[href='"+model.get('url')+"']", self.$el).length == 0 &&
8671					$("[id$='"+model.get('id')+"']", self.$el).length == 0
8672				) {
8673					
8674					/* Create a package of data for the render */
8675					var data_model = Cargo.API.GetDataPackage( "navigation", model.toJSON() );
8676
8677					/* Render the template and get the markup */
8678					var markup = Cargo.Core.Handlebars.Render( template, data_model );
8679
8680					/* Check if this is in a set and needs to be added to one */
8681					if(
8682						Cargo.Model.DisplayOptions.GetUseSets() && 
8683						model.get("set_id") != 0 &&
8684						$("[data-setid='"+model.get("set_id")+"']", self.$el).length > 0
8685					) {
8686						$("[data-setid='"+model.get("set_id")+"']", self.$el).append( markup );
8687					}
8688					/* Prepend this to the autopagination handle */
8689					else {
8690						this.$el.append( markup );
8691					}
8692				}
8693			},
8694
8695			setClick : function(e) {
8696				
8697				if( e ) {
8698					/* Remove default action */
8699					e.preventDefault();
8700
8701					if(Cargo.Model.DisplayOptions.GetSetsCanToggle()){
8702						var set = $(e.currentTarget).closest('li.set_link');
8703						var nav = this.el;
8704
8705						if(set.hasClass('open')){
8706							set.removeClass('open').addClass('closed');
8707						} else if(set.hasClass('closed')){
8708							if(Cargo.Model.DisplayOptions.GetSetsOneAtATime()){
8709								$(nav).find('li.set_link').removeClass('open').addClass('closed');
8710							}
8711							set.removeClass('closed').addClass('open');
8712						} else {
8713							// assume it's open
8714							set.addClass('closed');
8715						}
8716
8717						Cargo.Event.trigger("navigation_set_toggle");
8718					}
8719				}
8720			},
8721
8722
8723			/**
8724			 *	Add an active state to the navigation
8725			 *	remove old active states first
8726			 */
8727			AddActive : function(pid) {
8728				var self = this;
8729				if( !pid ) {
8730					pid = Cargo.Model.Project.GetId();
8731				}
8732				this.RemoveActive();
8733				$("[data-id='"+pid+"']", self.$el).addClass("active");
8734			},
8735
8736			/**
8737			 *	Remove active state to the navigation
8738			 */
8739			RemoveActive : function() {
8740				var self = this;
8741				$(".active", self.$el).removeClass("active");
8742			},
8743
8744			/**
8745			 *	Add an active state to the set
8746			 */
8747			AddSetActive : function() {
8748				var self = this;
8749				var set_id = Cargo.API.Config.set_id ? Cargo.API.Config.set_id : Cargo.Model.Project.GetSetId();
8750				if( set_id ) {
8751					$("[data-id='"+set_id+"']", self.$el).addClass("active");
8752				}
8753			},
8754
8755			/**
8756			 *	Add an active state to the set
8757			 */
8758			RemoveSetActive : function() {
8759				var self = this;
8760				var set_id = Cargo.API.Config.set_id ? Cargo.API.Config.set_id : Cargo.Model.Project.GetSetId();
8761				if( set_id ) {
8762					$("[data-id='"+set_id+"']", self.$el).removeClass("active");
8763				}
8764			}
8765	});
8766
8767
8768	Cargo.o.view.Freshbox = Backbone.View.extend({
8769
8770		events: {
8771			"click #fullscreen"			: "InitFullscreen",
8772			"click #fullscreen_prev"	: "Prev",
8773			"click #fullscreen_next"	: "Next",
8774			"click #fullscreen_close" 	: "Close",
8775			"click #fullscreen_zoom" 	: function(e){
8776				this.toggleZoom();
8777				this.Data.zoomEnabled =! this.Data.zoomEnabled;
8778				return false;
8779			},
8780			"click #freshbox"			: "Close",
8781			"click img"					: "LightboxClick",
8782			"mouseover img" 			: "LightboxHover"
8783		},
8784
8785		prevent_smart_image_picking: false,
8786
8787		initialize: function() {
8788
8789			Cargo.Event.on('lightbox-init', function(){
8790
8791				this.InitKeyboardNav();
8792
8793				if(this.isFullscreen) {
8794					Cargo.Event.trigger("fullscreen_open");
8795				} else {
8796					Cargo.Event.trigger("lightbox_open");
8797				}
8798
8799			}, this);
8800
8801			Cargo.Event.on('lightbox-destroy', function(){
8802
8803				this.DestroyKeyboardNav();
8804				
8805				Cargo.Event.trigger("fullscreen_destroy_hotkeys");
8806
8807				if(this.isFullscreen) {
8808					Cargo.Event.trigger("fullscreen_close");
8809				} else {
8810					Cargo.Event.trigger("lightbox_close");
8811				}
8812
8813			}, this);
8814
8815			Cargo.Event.trigger('fullscreen-initialized', this);
8816
8817		},
8818
8819		/**
8820		 *	Write out the freshbox element to the page
8821		 *	Uses the template that was already loaded
8822		 */
8823		render : function() {
8824			
8825		},
8826
8827		/**
8828		 * Fullscreen
8829		 *	Called from ProjectDetail
8830		 *	Opened by clicking the fulllscreen button
8831		 *	Opens images fullscreen via HTML5
8832		 */
8833		InitFullscreen : function( e ) {
8834
8835			if( e ) {
8836				e.preventDefault();
8837				this.clicked_img = $(e.currentTarget);
8838			}
8839
8840			this.isFullscreen = true;
8841
8842			this.InitSlideshow();
8843
8844			/* Remove default action */
8845			/*if( e ) e.preventDefault(); 
8846
8847			this.clicked_img = $(e.currentTarget);
8848
8849			this.InitSlideshow();
8850
8851			// Hide until open if not IE or iOS or an old browser
8852			if( ! Cargo.Helper.isIE() && (! Cargo.Helper.isIOS() || ! Cargo.Helper.AllowsFullScreen()) ) {
8853				$("#freshbox").css("visibility", "hidden");
8854			}
8855
8856			this.OpenFullscreen();
8857
8858			this.Next();*/
8859
8860		},
8861
8862		/**
8863		 *	Lightbox view
8864		 *	Opened by clicking on an img tag inside the content
8865		 */
8866		LightboxClick : function( e ) {
8867
8868			this.clicked_img = $(e.currentTarget);
8869
8870			/* Don't allow this behavior on slideshow thumbs */
8871			if( $(this.clicked_img).closest('.slideshow_thumbnails').length > 0 ) {
8872				return false;
8873			}
8874
8875			/* Always verify that we want the lightbox */
8876			if( Cargo.Model.DisplayOptions.GetLightboxView() ) {
8877				/* Verify there is a larger version available (and is a Cargo image) */
8878				if( typeof $(this.clicked_img).attr("src_o") != "undefined" ) {
8879					
8880					if( $(this.clicked_img).parent("a").length == 0 ) {
8881						/* Remove default action */
8882						if( e ) e.preventDefault(); 
8883						
8884						this.isFullscreen = false;
8885
8886						this.InitSlideshow(e.currentTarget);
8887
8888						var self = this;
8889
8890						/* Set the first image to be the clicked image 
8891						 * by looping through all images and finding a match
8892						 * Any image that is not matched, gets moved to the back of the stack */
8893						$("#fullscreen_imgs img").each(function() {
8894							if( $(this).attr("src_o") == $(e.currentTarget).attr("src_o") ) {
8895								/* Show the first image by going "next" */
8896								self.Next();
8897								return false;
8898
8899							} else {
8900								/* This is not it, so progress to the next frame */
8901								self.Next();
8902							}
8903							
8904						});
8905
8906						Cargo.Event.trigger("lightbox_open");
8907
8908					}
8909				}
8910			}
8911			
8912		},
8913
8914		/**
8915		 *	Handles the hover over images for the lightbox
8916		 *	Used the backbone event
8917		 */
8918		LightboxHover : function( e ) {
8919			/* Don't allow this behavior on slideshow thumbs */
8920			if( $(e.currentTarget).closest('.slideshow_thumbnails').length > 0 || !$(e.currentTarget).attr('src_o') ) {
8921				return false;
8922			}
8923			
8924			/* Always verify that we want the lightbox */
8925			if( Cargo.Model.DisplayOptions.GetLightboxView() ) {
8926				/* Verify there is a larger version available (and is a Cargo image) */
8927				if( typeof $(e.currentTarget).attr("src_o") != "undefined" ) {
8928					/* Make sure the is no parent <a> */
8929					if( $(e.currentTarget).parent("a").length == 0 ) {
8930						/* Make the hover */
8931						if($(e.currentTarget).parent("#freshbox").length == 0) {
8932							/* Normal project view */
8933							$(e.currentTarget).css("cursor", function() {
8934								if ($.browser.mozilla) {
8935									return '-moz-zoom-in';
8936								} else if(window.navigator.userAgent.indexOf("Edge") > -1) {
8937									return 'zoom-in'; 
8938								} else if ($.browser.webkit) {
8939									return '-webkit-zoom-in';
8940								} else {
8941									return 'pointer'; 
8942								}
8943							});
8944						} else {
8945
8946							$(e.currentTarget).css("cursor", 'pointer');
8947
8948						}
8949					}
8950				}
8951			}
8952		},
8953
8954		/**
8955		 *	Starts the slideshow inside of the freshbox
8956		 *	by rendering the freshbox with the current model
8957		 *	Called by both lightbox click and fullscreen click
8958		 */
8959		InitSlideshow : function(clicked_image) {
8960
8961			var active_container, fullscreenImages, node;
8962
8963			if(Cargo.View.hasOwnProperty('ProjectDetail')){
8964				// if we're in a project
8965				active_container = Cargo.View.ProjectDetail.$el;
8966			} else {
8967				// if we're not in a project (index on feed based designs)
8968				// check if in slideshow
8969				var slideshowParent = this.clicked_img.closest('.slideshow');
8970
8971				if(slideshowParent.length > 0){
8972					active_container = slideshowParent.closest('[data-id]:not(.slideshow)');
8973				} else {
8974					active_container = this.clicked_img.closest('[data-id]');
8975				}
8976			}
8977
8978			fullscreenImages = [];
8979
8980			$("img", active_container).each(function() {
8981				// If it's a Cargo uploaded image
8982				if ( $(this).is("[src_o]") && $(this).closest('.slideshow_thumbnails').length === 0 ) {
8983					
8984					fullscreenImages.push(this);
8985
8986				}
8987			});
8988
8989
8990			if(clicked_image === undefined && this.prevent_smart_image_picking !== true) {
8991
8992				var halfScreenHeight = window.innerHeight / 2,
8993					smallestOffset = 9e9;
8994
8995				_.each(fullscreenImages, function(img){
8996
8997					var rect = img.getBoundingClientRect(),
8998						imgCenter = rect.top + (rect.bottom - rect.top) / 2,
8999						imgCenterOffset = Math.abs(imgCenter - halfScreenHeight);
9000
9001					if(rect.width !== 0 && imgCenterOffset < smallestOffset) {
9002						clicked_image = img;
9003						smallestOffset = imgCenterOffset;
9004					}
9005
9006				});
9007
9008			}
9009
9010			if(fullscreenImages.length > 0) {
9011				var options = {};
9012
9013				if(this.isFullscreen) {
9014					options.showAnimationDuration = 0;
9015				}
9016
9017				var gallery = lightbox.openImageArray(fullscreenImages, clicked_image, options);
9018
9019				if(gallery !== undefined && this.isFullscreen) {
9020					
9021					gallery.ui.toggleFullscreen();
9022					
9023				}
9024			}
9025
9026
9027		},
9028
9029		Remove : function() {
9030			// Remove the freshbox
9031			$("#freshbox").remove();
9032			$("#fullscreen_imgs").remove();
9033
9034		},
9035
9036		noop: function(){},
9037
9038		InitKeyboardNav : function() {
9039
9040			this.DestroyKeyboardNav();
9041
9042			Cargo.Core.KeyboardShortcut.Add("Up", 38, this.noop);  
9043			Cargo.Core.KeyboardShortcut.Add("Down", 36, this.noop);
9044			Cargo.Core.KeyboardShortcut.Add("Left", 37, this.noop);
9045			Cargo.Core.KeyboardShortcut.Add("Right", 39, this.noop);
9046			Cargo.Core.KeyboardShortcut.Add("Esc",  2, this.noop);
9047
9048		},
9049
9050		DestroyKeyboardNav : function() {
9051			Cargo.Core.KeyboardShortcut.Remove("Up");
9052			Cargo.Core.KeyboardShortcut.Remove("Down"); 
9053			Cargo.Core.KeyboardShortcut.Remove("Left"); 
9054			Cargo.Core.KeyboardShortcut.Remove("Right"); 
9055			Cargo.Core.KeyboardShortcut.Remove("Esc");
9056		}
9057
9058	});
9059
9060/**
9061 *	This is a dummy default view
9062 *	The purpose of this is to bind to a model / collection and 
9063 *	This will be loaded by the template loader if there is no view
9064 *	already. The reason this may happen is a user adds date-view="something"
9065 *	But did not create a view for it. In this case, use this default view
9066 *	Which simply renders on reset
9067 */
9068	Cargo.o.view.Default = Backbone.View.extend({
9069			
9070			initialize: function(options) {
9071				this.options = options;
9072
9073				if( typeof this.collection == "object" ) {
9074					/* Set an event to wait for the model to change */
9075					this.listenTo(this.collection, "reset", this.render);
9076					this.listenTo(this.collection, "add", this.add);
9077				}
9078			},
9079
9080			render : function( ) {
9081				
9082				/* Make a reference to self */
9083				var self 		= this;
9084
9085				/* Get the template for this view (Prevents first load render) */
9086				var template 	= Cargo.Template.Get(this.options.template);
9087
9088				/*
9089				 *	If a template exists, render it
9090				 */
9091				if( template ) {
9092					
9093					/* Create a package of data for the render */
9094					var data_model = Cargo.API.GetDataPackage( this.options.datasetname.toLowerCase(), this.collection.toJSON() );
9095
9096					/* Render the template and get the markup */
9097					var markup = Cargo.Core.Handlebars.Render( template, data_model );
9098
9099					/* Empty the container */
9100					this.$el.empty();
9101					this.$el.append( markup );
9102
9103				} // end template
9104
9105
9106			},
9107
9108			/**
9109			 *	The add event is for adding a single item into the collection
9110			 *	this is intended for autopagination
9111			 */
9112			add : function( model ) {
9113				/* Get the template for this view */
9114				var template 	= Cargo.Template.Get(this.options.template);
9115				var self = this;
9116				var in_thumb = ($("[href='"+model.get('url')+"']", self.$el).length > 0);
9117				var in_data  = ($("[data-id='"+model.get('id')+"']", self.$el).length > 0);
9118
9119				/* Verify that this model is not already onscreen */
9120				if( template && !in_thumb && !in_data ) {
9121					/* Create a package of data for the render */
9122					var data_model = Cargo.API.GetDataPackage( this.options.datasetname.toLowerCase(), model.toJSON() );
9123
9124					/* Render the template and get the markup */
9125					var markup = Cargo.Core.Handlebars.Render( template, data_model );
9126
9127					/* Prepend this to the autopagination handle */
9128					this.$el.append( markup );
9129				}
9130			}
9131	});
9132
9133
9134	Cargo.o.view.Rail = Backbone.View.extend({
9135		
9136		initialize: function() {
9137			/*
9138			 *	Only do this when not in the admin
9139			 */
9140			if( !Cargo.Helper.IsAdminEdit() ) {
9141				/* Set an event to wait for the model to change */
9142				this.collection.bind("change", this.render, this);
9143
9144				/* Load in the CSS dependancy */
9145				var dependencies = [
9146					"/_jsapps/rail/rail.css"
9147				];
9148
9149				$.loadFiles(dependencies).done(function() {
9150					// Done loading the CSS
9151				});
9152			}
9153			
9154		},
9155
9156		
9157		
9158		render : function() {
9159			var templates = this.collection.GetTemplates();
9160			var models = this.collection.GetModels();
9161			
9162			/* Write to the page */
9163			this.$el.append(templates.masterview_tpl);
9164
9165			this.$el.append(this.collection.GetPopup());
9166
9167			/* Preload the templates and model */
9168			Cargo.app.Rail.Templates = templates;
9169			Cargo.app.Rail.Model.LoadModels(models);
9170
9171			$("#rail_wrapper").addClass("api");
9172
9173			Cargo.Rail.Init();
9174			Cargo.View.Following.Init();
9175		}
9176
9177	});
9178
9179/**
9180 *	This is the following toolset that loads in the top left
9181 *	Depending on what your login status is and where the request
9182 *	is coming from, will determine which toolset will show up
9183 *	but that is up to PHP
9184 *
9185 *	This is done using the getJSON instead of a backbone model
9186 *	because of the need to use JSONP, which I don't believe backbone
9187 *	can do.
9188 *
9189 *	This handles all of the click events for the view as well
9190 *
9191 *
9192 */
9193	Cargo.o.view.Following = Backbone.View.extend({
9194		
9195		initialize: function() {
9196			this.Data = {
9197				mouse_down : false,
9198				tool_over : false,
9199				mouse_down : false,
9200				mouse_down : false
9201			};
9202
9203			var self = this;
9204
9205			// Don't do this if we are previewing or editing
9206			if(Cargo.API.Config.preview && !Cargo.Helper.IsAdminEdit()) {
9207				return false;
9208			}
9209
9210			$.getJSON(
9211				"//"+Cargo.Helper.GetBaseCargoDomain()+"/dispatch/cargosite/cargoToolset/"+Cargo.Helper.GetDomainOrUrl()+"?callback=?", 
9212				function(data) {
9213					if( data.error == "nocookie" || Cargo.InspectorLoading === true) {
9214						/* No cookie found, or we are inspecting */
9215						return;
9216					} else {
9217						if( data.jdata.url ) {
9218							if( Cargo.API.Config.is_domain ) {
9219								$.post(
9220									"/dispatch/cargosite/cargoToolsetLocal", 
9221									{ token : data.jdata.token, url : data.jdata.url, muid : data.jdata.muid, cv : data.jdata.cv },
9222									function(dataObj) {
9223										if($("#follow_popup").length == 0) {
9224											self.$el.append(dataObj.jdata.html);
9225										} else {
9226											$("#follow_popup").replaceWith(dataObj.jdata.html);
9227										}
9228
9229										// Init
9230										self.InitToolset();
9231										self.Init();
9232									},
9233									"json");	
9234							} 
9235						} else {
9236							// Init
9237							self.InitToolset();
9238							self.Init();
9239						}
9240
9241						if( data.html ) {
9242							self.$el.prepend( data.html );
9243
9244							self.InitToolset();
9245							self.Init();
9246						}
9247
9248					}
9249				}
9250			);
9251		},
9252
9253		/*
9254		 *	After the toolset is loaded, do this
9255		 */
9256		InitToolset : function() {
9257			/* Fetch the Rail data */
9258			Cargo.Model.Rail.fetch({reset: true});
9259
9260			var self = this;
9261
9262			/* Toolset click */
9263			$("#toolset_admin").mousedown(function(e) {
9264				self.Data.mouse_down = true;
9265				if(e.button == 2){
9266					self.ShowContextMenu();
9267				} else{
9268					setTimeout("Cargo.View.Following.ToolsetToggle('off')", 500);
9269				}
9270				
9271			}).mouseup(function() {
9272				self.Data.mouse_down = false;
9273				
9274			}).mouseover(function() { 
9275				self.Data.tool_over = true;
9276				
9277			}).mouseout(function() {
9278				self.Data.tool_over = false;
9279			
9280			}).click(function() { 
9281				if ($("#toolset_admin a").attr("href").length > 0 && self.Data.tool_over) {
9282					document.location.href = $("#toolset_admin a").attr("href");
9283					self.HideContextMenu();
9284				}
9285				return false;
9286			});
9287			
9288			$("#toolset_admin").bind("contextmenu", function(e) {
9289				return false;
9290			});
9291			
9292			$(document).click(function() { 
9293				if(!self.Data.mouse_down && !self.Data.tool_over){
9294					self.HideContextMenu();
9295				}
9296		
9297			}).mouseup(function(e) { 
9298				if(e.button == 2){
9299					self.Data.mouse_down = false;
9300				}
9301				setTimeout("Cargo.View.Following.ToolsetToggle('on')", 100);
9302			});
9303			
9304			$("#toolset_menu a").bind("click mouseup mouseover", function(e) {
9305				if(e.type == "mouseover") {
9306					self.ToolsetToggle("on");
9307				} else {
9308					// window.location.href = $(this).attr("href");
9309					self.HideContextMenu();
9310				}
9311			});
9312			
9313			$("#toolset_admin a, #toolset_menu a:first").click(function(e) {
9314				if(e.ctrlKey) { 
9315					self.ShowContextMenu();
9316					return false;
9317				} else if(typeof require == "function") {
9318					Cargo.Event.trigger("add_history", "admin");
9319					return false;
9320				}
9321			});
9322			
9323			Cargo.Event.on("project_load_complete", function( pid ) { 
9324				self.HideContextMenu();
9325			});
9326			
9327			$("#toolset_menu a:first").addClass("toolset_first");
9328			$("#toolset_menu a:last").addClass("toolset_last");
9329
9330			Cargo.app.Rail.Events.trigger("toolset:loaded");
9331			
9332		},
9333
9334		/*
9335		 *	Hide the content menu
9336		 */
9337		HideContextMenu : function() {
9338			$("#toolset_admin").removeClass("toolset_active");
9339			$("#toolset_menu").hide();
9340
9341			Cargo.View.Following.ToolsetToggle("on");
9342		},
9343
9344		/*
9345		 *	Show the context menu
9346		 */
9347		ShowContextMenu : function(which) {
9348			var toolset_top = $("#toolset").css("top");
9349			var toolset_right = $("#toolset").css("right");	
9350			$("#toolset_menu").css({"top" : toolset_top , "right" : toolset_right});
9351			$("#toolset_admin").addClass("toolset_active");
9352			$("#toolset_menu").show();
9353			$("#css_inspector_trigger").on("click", function() {
9354				loadInspector();
9355				return false;
9356			});
9357		},
9358
9359		/*
9360		 *	Toggle the toolset menu
9361		 */
9362		ToolsetToggle : function(which) {
9363			if (which == "on") {
9364				if ($("#toolset_admin a").length > 0 && $("#toolset_admin a").attr("rel") != "undefined") {
9365					$("#toolset_admin a").attr("href",$("#toolset_admin a").attr("rel")).removeAttr("rel");
9366				}
9367			} else if (which == "off") {
9368				if ($("#toolset_admin a").length > 0 && $("#toolset_admin a").attr("href") != "undefined" && tool_over && mouse_down) {
9369					$("#toolset_admin a").attr("rel",$("#toolset_admin a").attr("href")).removeAttr("href");
9370					mouse_down = false;
9371					Cargo.View.Following.ShowContextMenu();
9372				}
9373			}
9374		},
9375
9376		/**
9377		 *	bind all follow/unfollow links	
9378		 */
9379		Init : function() {
9380
9381			$("a[rel='follow']").off('click').on('click', function() {
9382				Cargo.View.Following.ShowFollowWindow($(this));
9383				return false;
9384			});
9385
9386			$(".follow_popup_ok").off('click').on('click', function() {
9387				Cargo.View.Following.DoFollow($(this).attr('data-my_uid'), $(this).attr('data-my_cv'), $(this).attr('data-their_uid'), $(this).attr('data-type'));
9388				return false;
9389			});
9390
9391			$(".follow_popup_cancel").off('click').on('click', function() {
9392				Cargo.View.Following.Hide();
9393				return false;
9394			});
9395
9396		},
9397
9398		Show : function() {
9399			$("#follow_popup").show();
9400			Cargo.Core.KeyboardShortcut.Add("Return", 13, function() { 
9401				$("a.follow_popup_ok").click(); 
9402			});
9403
9404		},
9405
9406		Hide : function() {
9407
9408			// Hide the element
9409			$("#follow_popup").hide();
9410
9411			// Remove the keys
9412			Cargo.Core.KeyboardShortcut.Remove("Return");
9413
9414			if(typeof Cargo.Home == "object") {
9415				Cargo.Home.Personal.AddEnterShortcut();
9416			}
9417
9418		},
9419
9420
9421		ShowFollowWindow : function(el) {
9422			Cargo.View.Following.Show();
9423
9424			$("#follow_popup_note").html(ucFirst($(el).attr("data-type"))+" "+$(el).attr('data-displayurl')+"?");
9425			$(".follow_popup_ok").attr({
9426				'data-their_uid': $(el).attr("data-their_uid"),
9427				'data-my_uid' 	: $(el).attr("data-my_uid"),
9428				'data-my_cv' 	: $(el).attr("data-my_cv"),
9429				'data-type'		: $(el).attr("data-type")
9430			});
9431			Cargo.View.Following.Data.el = el;
9432
9433		},
9434
9435		DoFollow : function(my_uid, my_cv, their_uid, type) {
9436			$("#following_loadspin").show();
9437			$.post(
9438				"/dispatch/user/doFollow", 
9439				{ my_uid:my_uid, my_cv:my_cv, their_uid:their_uid, type:type }, 
9440				Cargo.View.Following.FollowResult,
9441				"json"
9442			);
9443			
9444		},
9445
9446		FollowResult : function(dataObj) {
9447			$("#following_loadspin").hide();
9448
9449			if(typeof Cargo.Home == "object") {
9450				Cargo.Home.Personal.hideProjectAfterUnfollow(Cargo.View.Following.Data.el);
9451
9452			} else if(Cargo.View.Following.Data.el.attr("id")) {
9453				$("#follow_links").html(dataObj.html);
9454				$("#rail_follow").html(dataObj.html);
9455
9456				// update "template"
9457				if(Cargo.app.Rail.view.following) {
9458					Cargo.app.Rail.Templates.following_tpl = Cargo.app.Rail.view.following.el.innerHTML;
9459				}
9460			
9461			} else {
9462				Cargo.View.Following.Data.el.replaceWith(dataObj.jdata.tag_html);
9463			}
9464
9465			Cargo.View.Following.Hide();
9466		}
9467
9468
9469
9470	});
9471
9472/**
9473 *	search
9474 */
9475Cargo.o.view.Search = Backbone.View.extend({
9476		events: {
9477			'keypress input[type=text]' : 'submitOnEnter',
9478			'focus input[type=text]' : 'focus',
9479			'blur input[type=text]' : 'blur'
9480		},
9481
9482		initialize: function() {
9483			var rel = $("input[type=text]", this.$el).attr("rel");
9484			if(rel !== undefined){
9485				if($("input[type=text]", this.$el).val() == "" && rel.length > 0) {
9486					$("input[type=text]", this.$el).val( rel );
9487				}
9488			}
9489
9490			// If this is the first load of search, load everything
9491			if(this.$el.hasClass('search') || $('input#results_search_term', this.$el).length > 0) {
9492				Cargo.Collection.Search = new Cargo.o.collection.search();
9493				Cargo.Model.DisplayOptions.set('pagination_count', 9999);
9494				var _this = this;
9495				Cargo.Collection.Search.fetch({reset: true, complete: function(data) { 
9496					_this.render();
9497				}});
9498			}
9499		},
9500
9501		render : function( ) {
9502			var template = Cargo.Template.Get('Search');
9503			var data_model = Cargo.API.GetDataPackage('search', Cargo.Collection.Search.toJSON());
9504			var markup = Cargo.Core.Handlebars.Render( template, data_model );
9505
9506			this.$el.empty();
9507			this.$el.html( markup );
9508
9509			$('input#results_search_term', this.$el).val(Cargo.API.Config.search_term);
9510		},
9511
9512		focus : function(e) {
9513			var t = e.currentTarget;
9514			if($(t).val() == $(t).attr("rel")) {
9515				$(t).val("");
9516			}
9517		},
9518
9519		blur : function(e) {
9520			var t = e.currentTarget;
9521			if($(t).val() == "") {
9522				$(t).val( $(t).attr("rel") );
9523			}
9524		},
9525
9526		submitOnEnter: function(e) {
9527			if (e.keyCode != 13) return;
9528			var t = e.currentTarget;
9529			Cargo.Helper.GoToUrl('search/' + $(t).val());
9530		},
9531
9532
9533});
9534
9535/**
9536 *	This is a view for sets. It is identical to the default view
9537 */
9538	Cargo.o.view.Sets = Backbone.View.extend({
9539			
9540			initialize: function(options) {
9541				this.options = options;
9542
9543				if( typeof this.collection == "object" ) {
9544					/* Set an event to wait for the model to change */
9545					this.listenTo(this.collection, "reset", this.render);
9546					this.listenTo(this.collection, "add", this.add);
9547
9548					this.collection.fetch({ reset : true });
9549				}
9550			},
9551
9552			render : function( ) {
9553				
9554				/* Make a reference to self */
9555				var self 		= this;
9556
9557				/* Get the template for this view (Prevents first load render) */
9558				var template 	= Cargo.Template.Get("Sets");
9559
9560				/*
9561				 *	If a template exists, render it
9562				 */
9563				if( template ) {
9564
9565					var dataset = (this.options.datasetname) ? this.options.datasetname.toLowerCase() : "sets";
9566					
9567					/* Create a package of data for the render */
9568					var data_model = Cargo.API.GetDataPackage( dataset, this.collection.toJSON() );
9569
9570					/* Render the template and get the markup */
9571					var markup = Cargo.Core.Handlebars.Render( template, data_model );
9572
9573					/* Empty the container */
9574					this.$el.empty();
9575					this.$el.append( markup );
9576
9577				} // end template
9578
9579
9580			},
9581
9582			/**
9583			 *	The add event is for adding a single item into the collection
9584			 *	this is intended for autopagination
9585			 */
9586			add : function( model ) {
9587				/* Get the template for this view */
9588				var template 	= Cargo.Template.Get(this.options.template);
9589				var self = this;
9590				var in_thumb = ($("[href='"+model.get('url')+"']", self.$el).length > 0);
9591				var in_data  = ($("[data-id='"+model.get('id')+"']", self.$el).length > 0);
9592
9593				/* Verify that this model is not already onscreen */
9594				if( template && !in_thumb && !in_data ) {
9595					/* Create a package of data for the render */
9596					var data_model = Cargo.API.GetDataPackage( this.options.datasetname.toLowerCase(), model.toJSON() );
9597
9598					/* Render the template and get the markup */
9599					var markup = Cargo.Core.Handlebars.Render( template, data_model );
9600
9601					/* Prepend this to the autopagination handle */
9602					this.$el.append( markup );
9603				}
9604			}
9605	});
9606
9607/**
9608 * 
9609 */
9610	Cargo.o.view.Header = Backbone.View.extend({
9611			
9612			initialize: function(options) {
9613				this.options = options;
9614
9615				this.listenTo(this.collection, "change", this.render);
9616			},
9617
9618			render : function( ) {
9619				/* Make a reference to self */
9620				var self 		= this;
9621
9622				/* Get the template for this view (Prevents first load render) */
9623				var template 	= Cargo.Template.Get("Header");
9624
9625				/*
9626				 *	If a template exists, render it
9627				 */
9628				if( template ) {
9629					
9630					/* Create a package of data for the render */
9631					var data_model = Cargo.API.GetDataPackage();
9632
9633					/* Render the template and get the markup */
9634					var markup = Cargo.Core.Handlebars.Render( template, data_model );
9635
9636					/* Empty the container */
9637					this.$el.empty();
9638					this.$el.append( markup );
9639
9640				} // end template
9641
9642
9643			},
9644
9645	});
9646
9647/***************************************************************************
9648 *
9649 *	Initialize the application
9650 *	Find template stubs and load them
9651 *	Start the router
9652 *
9653 **************************************************************************/
9654
9655/*
9656 *	Start this application 
9657 *	Initialize all objects
9658 *	Start the router
9659 */
9660
9661$(function() {
9662	
9663	/*
9664	 *	Default data storage
9665	 */
9666	Cargo.Data 		= { }; 
9667
9668	/*
9669	 *	Init all the backbone things
9670	 */
9671	Cargo.API 	 	= new Cargo.o.api();
9672	Cargo.Helper 	= new Cargo.o.helpers();
9673	Cargo.Template 	= new Cargo.o.templates();
9674	Cargo.Router 	= new Cargo.o.router();
9675	Cargo.Analytics = new Cargo.o.analytics();
9676
9677	/*
9678	 *	Create a base class for all the models
9679	 */
9680	Cargo.Model.BaseProject		= Cargo.o.model.project;	
9681	Cargo.Model.Project			= new Cargo.Model.BaseProject();	
9682	Cargo.Model.DisplayOptions 	= new Cargo.o.model.display_options();
9683	Cargo.Model.Site 			= new Cargo.o.model.site();
9684	Cargo.Model.Rail 			= new Cargo.o.model.rail();
9685	Cargo.Model.Asset 			= new Cargo.o.model.asset();
9686	Cargo.Model.Images 			= new Cargo.o.model.images();
9687	Cargo.Model.Default			= new Cargo.o.model.undefined_default();
9688
9689	/*
9690	 *	Set the first collection of projects and navigation
9691	 */
9692	Cargo.Collection.Projects = new Cargo.o.collection.projects({
9693		model 	: Cargo.Model.BaseProject
9694	});
9695
9696	Cargo.Collection.Pages = new Cargo.o.collection.pages({
9697		model 	: Cargo.Model.BaseProject
9698	});
9699
9700	Cargo.Collection.Navigation	= new Cargo.o.collection.navigation({
9701		model 	: Cargo.Model.BaseProject
9702	});
9703
9704	Cargo.Collection.Sets = new Cargo.o.collection.sets({
9705		model 	: Cargo.Model.BaseProject
9706	});
9707
9708	Cargo.Collection.Assets	= new Cargo.o.collection.assets({
9709		model 	: Cargo.Model.Asset
9710	});
9711
9712	Cargo.Collection.Images = new Cargo.o.collection.images({
9713		model 	: Cargo.Model.Images
9714	});
9715
9716	Cargo.Collection.Default = new Cargo.o.collection.undefined_default({
9717		model 	: Cargo.Model.Default
9718	});
9719
9720	/*
9721	 *	Bind the reset event on the project collection
9722	 */
9723	Cargo.Collection.Projects.on('reset', function(collection) {
9724		Cargo.Event.trigger("project_collection_reset");
9725	});
9726
9727	/*
9728	 *	Fetch the default data first (pre-populate the page)
9729	 */
9730	
9731	Cargo.Model.DisplayOptions.once('change', function(){
9732
9733		Cargo.Event.trigger('display_options_available', Cargo.Model.DisplayOptions);
9734
9735	});
9736
9737	Cargo.Model.DisplayOptions.fetch({reset: true});
9738	Cargo.Model.Project.fetch({reset: true});
9739	Cargo.Collection.Assets.fetch();
9740	Cargo.Collection.Pages.fetch();
9741	Cargo.Model.Site.fetch();
9742
9743	// Check to see if there is a wrong URL case and redirect
9744	Cargo.Helper.TestUrlCaseAndRedirect();
9745
9746	/*
9747	 *	Make sure the main view has been init
9748	 */
9749	if( typeof(Cargo.View.Main) == "undefined" ) {
9750		Cargo.View.Main = new Cargo.o.view.Main({ 
9751			el    		: "body",
9752			collection 	: Cargo.Model.Site 
9753		});
9754	}
9755
9756	/*
9757	 *	Finds all of the view stubs in the DOM
9758	 */
9759	Cargo.Template.FindAndAddViewStubs();
9760	
9761	/*
9762	 *	Init all found views
9763	 */
9764	Cargo.Template.LoadFoundViews();
9765
9766	/*
9767	 *	Fetch the data
9768	 *	But delay project data if the site was just created
9769	 */
9770	if(Cargo.Helper.GetRouteName() != "search") {
9771		Cargo.Collection.Projects.fetch({
9772			complete: function() { 
9773				Cargo.Event.trigger("project_collection_reset"); 
9774				Cargo.Event.trigger("first_project_collection_reset"); 
9775			}
9776		});
9777	}
9778
9779	/*
9780	 * Fetch the navigation data and trigger a reset event
9781	 */
9782	Cargo.Collection.Navigation.fetch({
9783		complete: function() { 
9784			Cargo.Event.trigger("first_navigation_collection_reset"); 
9785		}
9786	});
9787
9788	/*
9789	 *	Start the router, if we should
9790	 */
9791	if( Cargo.Helper.IsFeedDesign() && Cargo.Helper.isOldIE() ) {
9792		// Do not start history
9793	} else {
9794		// Start backbone history
9795		Backbone.history.start({
9796			pushState: true,
9797			root: Cargo.Helper.GetHomeUrl(true)
9798		});
9799
9800		// Trigger set loaded event
9801		if(Cargo.API.Config.set) {
9802			Cargo.Event.trigger("set_loaded", Cargo.API.Config.set);	
9803		}
9804	}
9805	
9806	/*
9807	 *	Init the default keyboard shortcuts
9808	 */
9809	Cargo.Core.KeyboardShortcut.InitDefaults();
9810
9811	/* 
9812	 * Dig through the index and find any stubs to add views 
9813	 *	This will load the templates into memory
9814	 */
9815	Cargo.Template.LoadAll();
9816
9817	/*
9818	 *	Init all found views (again)
9819	 *	This time, it is taking into account the design
9820	 */
9821	Cargo.Template.LoadFoundViews();
9822
9823
9824	/* 
9825	 * Check and init freshbox 
9826	 *	Must be here because it requires text.js
9827	 */
9828	Cargo.Core.CheckAndInitFreshbox();
9829
9830	/* 
9831	 * Sniff for inspector
9832	 */
9833	if(location.search.replace("?","") == "inspector") {
9834		Cargo.Core.LoadInspector();
9835	}
9836
9837	/*
9838	 *	Activate the hi-res images if needed
9839	 */
9840	Cargo.Core.HiRes.Loader.init();
9841
9842	/* Common events that are fired multiple times */
9843	Cargo.Event.Common();
9844
9845	/* Look for a search form in a project or on the page */
9846	Cargo.Core.FindAndLoadSearch(true);
9847
9848	/* Look for a meta tag with the EU cookie notification */
9849	Cargo.Core.EU_cookie_notification();
9850	
9851
9852});
9853
9854/***************************************************************************
9855 *
9856 *	The Rail app is the controller of the right rail housing
9857 *	following, tracemarks and other things
9858 *
9859 *	This is meant to be agnostic to Cargo 1.0 or 2.0
9860 *	Uses Backbone and Handlebars
9861 *
9862 *	Assumes the Cargo namespace exists
9863 *	Sets the Cargo.app namepsace if one does not exist
9864 *
9865 **************************************************************************/
9866Cargo.app = window.Cargo.app || { };
9867Cargo.app.Rail = { };
9868
9869/* Create the default objects */
9870Cargo.app.Rail.Model 		= { };	// All models base
9871Cargo.app.Rail.Collection	= { };	// All collections base
9872Cargo.app.Rail.View  		= { };	// All views base
9873Cargo.app.Rail.view  		= { };	// instanciated views
9874Cargo.app.Rail.Events  		= { };	// All events base
9875Cargo.app.Rail.Templates	= { };	// All templates (these are loaded on init)
9876
9877
9878/***************************************************************************
9879 *
9880 *	Models
9881 *
9882 **************************************************************************/
9883
9884/**
9885 *	This is the init and loads all models that are passed
9886 *	Expects an array of models as name => data
9887 *	name = "masterview_model"
9888 *	Create the model name for this application like "MasterviewModel"
9889 */
9890Cargo.app.Rail.Model.LoadModels = function( models ) {
9891	
9892	_( models ).each(function( value, key ) {
9893		var model_name = ucFirst(key.split("_")[0]) + ucFirst(key.split("_")[1]) + "";
9894		
9895		/* Create a new Backbone model based on the data coming in */		
9896		Cargo.app.Rail.Model[model_name] = new Backbone.Model( value );
9897
9898	});
9899};
9900
9901
9902/***************************************************************************
9903 *
9904 *	Events
9905 *
9906 **************************************************************************/
9907
9908/* Init the events */
9909Cargo.app.Rail.Events = _.extend({}, Backbone.Events);
9910
9911
9912/**
9913 *	An event for showing the following
9914 *	This is triggered when the following icon is clicked
9915 */
9916Cargo.app.Rail.Events.on("show_rail_following", function() { 
9917	
9918	/* Make sure to destroy old versions */
9919	Cargo.app.Rail.Events.trigger("following:destroy");
9920	
9921	/* Init a new version of this view */
9922	Cargo.app.Rail.view.following = new Cargo.app.Rail.View.Following({ 
9923		el : "#rail_following",
9924		collection: Cargo.app.Rail.Model.FollowingModel 
9925	});
9926
9927});
9928
9929
9930/* 
9931+ * Event for image drag start 
9932+ * Init the tracemarks view
9933+ */
9934Cargo.app.Rail.Events.on("image_drag_start", function() { 
9935  
9936  /* Make sure to destroy old versions */
9937  Cargo.app.Rail.Events.trigger("selections:destroy");
9938
9939  /* Init a new version of this view */
9940  new Cargo.app.Rail.View.Selections({ 
9941    el : "#rail_selections",
9942    collection: Cargo.app.Rail.Model.SelectionsModel 
9943  });
9944
9945});
9946
9947
9948/***************************************************************************
9949 *
9950 *	Views
9951 *
9952 **************************************************************************/
9953
9954/**************************
9955 *	Master
9956 *************************/
9957Cargo.app.Rail.View.Master = Backbone.View.extend({
9958
9959	initialize : function() {
9960
9961		var self = this;
9962
9963		// Set an event to listen for showing the rail
9964		Cargo.app.Rail.Events.bind("rail:show", this.Show, this);
9965
9966		// Set an event to listen for hiding the rail
9967		Cargo.app.Rail.Events.bind("rail:hide", this.Hide, this);
9968
9969		// IE check
9970		if ( $.browser.msie ) {
9971			$("#rail_wrapper").addClass("rail_ie");
9972		}
9973
9974        new Cargo.app.Rail.View.Screen({ 
9975            el : "#rail_screen"
9976        });
9977
9978        if(Cargo.hasOwnProperty('Core')){
9979        	Cargo.Core.ReplaceLoadingAnims.init();
9980    	} else {
9981    		Cargo.ReplaceLoadingAnims();
9982    	}
9983
9984	},
9985
9986	events : {
9987		
9988	},
9989
9990	render : function() {
9991
9992	},
9993
9994	/**
9995	 * Hide the rail and reset the data
9996	 */
9997	Show : function() {
9998
9999		if ( ! $.browser.msie ) {
10000
10001			_.delay(function() {
10002				$("body, html").addClass("rail_active");
10003		        $("#rail_wrapper").addClass("active");
10004		    }, 1);
10005
10006		} else {
10007
10008			$("body, html").addClass("rail_active");
10009		    $("#rail_wrapper").addClass("active");
10010
10011		}
10012	},
10013	
10014	Hide : function() {
10015		$("body, html").removeClass("rail_active");
10016        $("#rail_wrapper").attr("class", "api");
10017
10018
10019		// IE check
10020		if ( $.browser.msie ) {
10021			$("#rail_wrapper").addClass("rail_ie");
10022		}
10023
10024		// Hide the follow popup
10025		if( typeof Cargo.Follow != "undefined" ) {
10026			Cargo.Follow.Hide();	
10027		}
10028		
10029
10030        // Trigger the rail hide event
10031		_.delay(function() { Cargo.app.Rail.Events.trigger("following:hide") }, 300);
10032	}
10033
10034});
10035
10036/**************************
10037 *	Screen
10038 *************************/
10039Cargo.app.Rail.View.Screen = Backbone.View.extend({
10040
10041	initialize : function() {
10042
10043	},
10044
10045	events : {
10046		"click"     : "clicked",
10047		"mouseover" : "hovered"
10048	},
10049
10050	render : function() {
10051
10052	},
10053
10054	clicked : function() {
10055		Cargo.app.Rail.Events.trigger("rail:hide");
10056	},
10057
10058	hovered : function() {
10059		if ( !Cargo.Rail.Data.dragging && 
10060			 !Cargo.Rail.Data.is_saving &&
10061			 $("#rail_selections").is(":visible") 
10062		) {
10063		 	Cargo.app.Rail.Events.trigger("rail:hide");
10064		}
10065	}
10066
10067});
10068
10069
10070/**************************
10071 *	Following
10072 *************************/
10073Cargo.app.Rail.View.Following = Backbone.View.extend({
10074
10075	initialize : function() {
10076		// Set the template
10077		this.template = Cargo.app.Rail.Templates.following_tpl;
10078
10079		// Set an event to wait for the model to change
10080		if(this.collection) {
10081			this.collection.bind("reset", this.render, this);
10082
10083			// Show the rail
10084			Cargo.app.Rail.Events.trigger("rail:show");
10085
10086			// Create an event for the destroy method
10087			Cargo.app.Rail.Events.bind("following:destroy", this.destroy, this);
10088
10089			// Set an event to listen for hiding the rail
10090			Cargo.app.Rail.Events.bind("following:hide", this.Hide, this);
10091
10092			this.render();
10093		}
10094	},
10095
10096
10097	events: {
10098		"click #rail_follow a" : "DoFollow"
10099	},
10100
10101	render : function() {
10102
10103		/* Empty the container */
10104		this.$el.empty();
10105
10106		var markup = Handlebars.compile( this.template )( this.collection );
10107		this.$el.append( markup ).show();
10108
10109		// Active state
10110		$("#rail_wrapper").addClass("following_active");
10111
10112		// Close rail when clicking name
10113		$(".rail_name").click(function() {
10114			Cargo.app.Rail.Events.trigger("rail:hide");
10115			return false;
10116		});
10117
10118		var self = this;
10119		this.columns();
10120
10121		$(window).resize(function() { self.columns() });
10122
10123	},
10124
10125	columns : function() {
10126
10127        var width = $(this.$el).width() - 40;
10128        var ratio = Math.floor((width / 120));
10129        var percent = 100 / ratio;
10130
10131        if ( width < 181 ) {
10132        	percent = 100;
10133        }
10134        
10135        // $(".rail_module", this.$el).width(percent + "%");
10136
10137    },
10138
10139	/* Hide this */
10140	Hide : function() {
10141		this.$el.hide();
10142	},
10143
10144	/**
10145	 *  Destroy this view and unbind it's events
10146	 */
10147	destroy : function() {
10148		this.undelegateEvents();
10149   },
10150
10151   /*
10152	 *	This does the actual follow / unfollow action
10153	 */
10154	DoFollow : function( e ) {
10155		e.preventDefault();
10156
10157		var target 		= e.currentTarget;
10158		var ftype		= $(target).attr("data-ftype");
10159		var ucase_which = ucFirst(ftype);
10160
10161		if(typeof Cargo.Follow == "object") {
10162			Cargo.Follow.ShowFollowWindow($(target));
10163			
10164		} else if(typeof Cargo.View.Following == "object") {
10165			Cargo.View.Following.ShowFollowWindow($(target));
10166		}
10167
10168	}
10169
10170});
10171
10172
10173
10174/**************************
10175 *	Selections
10176 *************************/
10177Cargo.app.Rail.View.Selections = Backbone.View.extend({
10178
10179	last_id_drop : 0,
10180
10181	initialize : function() {
10182		// Set the template
10183		this.template = Cargo.app.Rail.Templates.selections_tpl;
10184
10185		// Set an event to wait for the model to change
10186		this.collection.bind("reset", this.render, this);
10187
10188		// Show the rail
10189		Cargo.app.Rail.Events.trigger("rail:show");
10190
10191		// Create an event for the destroy method
10192		Cargo.app.Rail.Events.bind("selections:destroy", this.destroy, this);
10193
10194		// Set an event to listen for hiding the rail
10195		Cargo.app.Rail.Events.bind("rail:hide", this.Hide, this);
10196
10197		this.render();
10198	},
10199
10200
10201	events: {
10202		"mouseover" : "hovered",
10203		"mouseout" : "hoverout"
10204	},
10205
10206	hovered : function() {
10207		Cargo.Rail.Data.is_on_screen = false;
10208	},
10209
10210	hoverout : function() {
10211		Cargo.Rail.Data.is_on_screen = true;
10212	},
10213
10214	render : function() {
10215		/* Empty the container */
10216		this.$el.empty();
10217
10218		var markup = Handlebars.compile( this.template )( this.collection );
10219		this.$el.append( markup ).show();
10220
10221		this.SetDropZone();
10222
10223		$("#rail_wrapper").addClass("selection_active");
10224	},
10225
10226	SetDropZone : function() {
10227	    var self = this;
10228
10229	    $("#rail_screen").droppable(
10230            '*',
10231            // Drag enter
10232            function(e) { },
10233            // Drag leave
10234            function() { },
10235            // Drop!
10236            function(e) {
10237            	Cargo.app.Rail.Events.trigger("rail:hide");
10238            }
10239		);
10240
10241		$("#selections_login").droppable(
10242            '*',
10243            // Drag enter
10244            function(e) { },
10245            // Drag leave
10246            function() { },
10247            // Drop!
10248            function(e) {
10249               Cargo.Rail.Data.dragging = false;
10250            }
10251		);
10252
10253	    $("#rail_droplet").droppable(
10254            // Accepts
10255            '*',
10256            
10257            // Drag enter
10258            function(e) {
10259              $(this).addClass("active");
10260              Cargo.Rail.Data.is_on_screen = false;
10261            },
10262            
10263            // Drag leave
10264            function() {
10265                $(this).removeClass("active");
10266            },
10267            
10268            // Drop!
10269            function(e) {
10270                self.HandleImageDrop();
10271            }
10272
10273        ); // end drop
10274
10275	  },
10276
10277	  HandleImageDrop : function() {
10278	  	Cargo.Rail.Data.is_saving = true;
10279
10280	  	var id = this.collection.get("mid");
10281	  	// don't allow for the image in the rail to be dropped again
10282	  	if(this.last_id_drop == id) {
10283	  		return;
10284	  	}
10285
10286	  	this.last_id_drop = id;
10287
10288		$("#rail_save").addClass("active");
10289		$(".rail_add").addClass("hidden");
10290
10291		$("#rail_preview").addClass("selection_dropped");
10292		$("#rail_preview").attr("data-id", id);
10293
10294		/*
10295		 * Rail Preview
10296		 */
10297		$("#rail_preview img:not(.rail_preview_spinner)").attr("src", this.collection.get("src"));
10298		$("#rail_preview .rail_preview_spinner").css("top", ( $("#rail_preview img:not(.rail_preview_spinner)").height() / 2 ) + 40 + "px");
10299
10300		if( typeof Cargo.ReplaceLoadingAnims == "function") {
10301			Cargo.ReplaceLoadingAnims();
10302		} else {
10303			Cargo.Core.ReplaceLoadingAnims.init();
10304		}
10305		
10306		// Save the image
10307		var cuid = "";
10308
10309		if(typeof Cargo.Helper == "undefined"){
10310			cuid = Cargo.Config.GetCookieUid();
10311		} else {
10312			cuid = Cargo.Helper.GetUrl();
10313		}
10314
10315		$.post("/dispatch/selections/selection", { mid : this.collection.get("mid"), cuid : cuid }, function(server_data) {
10316			if(server_data.error) {
10317				$(".rail_preview_spinner").hide();
10318				alert(server_data.message);
10319				Cargo.app.Rail.Events.trigger("rail:hide");
10320
10321			} else {
10322
10323				// Remove the fake spinner
10324				_.delay(function() { 
10325					$("#rail_preview[data-id=" + id + "]").addClass("selection_sent");
10326				}, 600);
10327
10328				_.delay(function() {
10329					if(Cargo.Rail.Data.is_on_screen) {
10330						Cargo.app.Rail.Events.trigger("rail:hide");
10331					}
10332					Cargo.Rail.Data.is_saving = false; 
10333				}, 1000);
10334
10335			}
10336		});
10337
10338		// Tell the app the dragging has stopped, but give it a beat
10339		_.delay(function() { Cargo.Rail.Data.dragging = false; }, 500);
10340
10341	  },
10342
10343	
10344
10345	/* Hide this */
10346	Hide : function() {
10347		this.last_id_drop = 0;
10348		this.$el.hide();
10349	},
10350
10351	/**
10352	 *  Destroy this view and unbind it's events
10353	 */
10354	destroy : function() {
10355		this.undelegateEvents();
10356   }
10357 
10358
10359});
10360
10361
10362
10363
10364
10365
10366/*
10367 *  Start this backbone application 
10368 *  Set the jquery dragging
10369 *  Hijack the following icon click
10370 */
10371Cargo.Rail = {
10372
10373    Data : {
10374        "dragging"      : false,
10375        "is_saving"     : false,
10376        "is_on_screen"  : false
10377    },
10378
10379    Init : function() {
10380
10381        /*
10382         *  Set the backbone view for the master
10383         */
10384        new Cargo.app.Rail.View.Master({ 
10385            el : "#rail_wrapper"
10386        });
10387
10388        /* 
10389         *  Following icon
10390         */
10391        $("#toolset_follow_pair a, #toolset_follow a").click(function(e) {
10392            e.preventDefault();
10393            Cargo.app.Rail.Events.trigger("show_rail_following");
10394            return false;
10395        });
10396
10397        /*
10398         *  Dragging and dropping
10399         */
10400        var setDrag = function() {
10401            // Disallow on mobile
10402            var is_mobile = (typeof Cargo.Helper == "object") ? Cargo.Helper.isMobile() : Cargo.Config.isMobile();
10403            if(is_mobile) {
10404                return false;
10405            }
10406            
10407            // Firefox/IE anchor img slideshow
10408            if ( $.browser.mozilla || $.browser.msie ) {
10409                $(".project_content a:has(img[data-mid]), #container a:has(img[data-mid])").dragstart(function(e) {
10410                    Cargo.Rail.Drag($("img:visible", this));
10411                });
10412            }
10413
10414            // Configure the drag for all other images
10415            $("img[data-mid]").dragstart(function(e) {
10416                // console.log("Dragging starting");
10417                Cargo.Rail.Drag($(this));
10418
10419            }).dragend(function(e) {
10420
10421                // Delay unless in webkit
10422                if ( $.browser.mozilla || $.browser.msie ) {
10423                    setTimeout(function() { Cargo.Rail.Data.dragging = false }, 100);
10424                } else {
10425                    Cargo.Rail.Data.dragging = false
10426                }
10427
10428            });
10429        }
10430
10431        /**
10432         * Bind to events
10433         */
10434
10435        $(document).bind("projectLoadComplete", function(e, pid) {
10436            Cargo.app.Rail.Events.trigger("rail:hide");
10437            setDrag();
10438        });
10439
10440        $(document).bind("projectLoadComplete", function(e, pid) {
10441            setDrag();
10442        });
10443
10444        $(document).bind("projectIndex", function(e, pid) {
10445            Cargo.app.Rail.Events.trigger("rail:hide");
10446        });
10447
10448        $(document).bind("paginationComplete", function(e, data) {
10449            setDrag();
10450        });
10451
10452        setDrag();
10453    },
10454
10455    Drag : function(element) {
10456
10457        Cargo.Rail.Data.dragging = true;
10458
10459        Cargo.app.Rail.Events.trigger("image_drag_start");
10460
10461        // Set the value of the dragged element in the model
10462        Cargo.app.Rail.Model.SelectionsModel.set({
10463             src : $(element).attr('src'),
10464             mid : $(element).attr('data-mid')
10465        });
10466
10467    }
10468    
10469};
10470
10471/*! cargoEditor 05-05-2015 */
10472!function(){var a=function(a,b,c){return a.prototype.hasOwnProperty(name)!==!1?!1:void(a.prototype[b]=c)};a(Node,"saveable",!1),a(Node,"setSaveable",function(a){return this.saveable=a,this}),a(Node,"isSaveable",function(){return this.saveable}),Node.prototype.persistCloneNode=function(){var a=function(b,c,d){for(var e=0;e<b.childNodes.length;e++)d(b.childNodes[e],c.childNodes[e]),b.childNodes[e].childNodes.length>0&&a(b.childNodes[e],c.childNodes[e],d)},b=Node.prototype.cloneNode.apply(this,arguments);return this.isSaveable()===!0&&b.setSaveable(!0),this.childNodes.length>0&&a(this,b,function(a,b){a.isSaveable()===!0&&b.setSaveable(!0)}),b},this.core={markAsEditable:function(){}}}(window.CargoEditor=window.CargoEditor||{});
10473

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.