Skip to content

Instantly share code, notes, and snippets.

@flockonus
Created April 30, 2012 05:19
Show Gist options
  • Save flockonus/2555725 to your computer and use it in GitHub Desktop.
Save flockonus/2555725 to your computer and use it in GitHub Desktop.
Code Sample #1 - js modules

This gist aims to describe the way I usually code front-end when there are pages that require specific functionality to run.

JS modules keep code organized and neat, they group significant classes together. Also, a important factor I consider when to apply this pattern is how extensive is the website, that is, doesn't make a lot of sense breaking down code into several files if there are only a few pages with no forecast of expansion.

All classes are wrapped in a anonymous function closure, that is to enforce that functions and variables created in that module/class remain inside the scope and don't pollute the global scope.

I'd like to highlight here 3 files that I use in http://bgr.herokuapp.com

  • main.js which is initialized for every page. It enables the login popover, and also hold some utility
  • serp_view.js is only initialized on the search results page. Activate results filtering
  • game_view.js is only initialized on the game view page. Enable hide/show reviews in the page

This method holds very little overhead, while it assume the existence of dependencies and previous bits.

The benefit of this approach is that the code can be minimized in just one file, loaded in every page, and will only be executed on specific pages by a simply calling the module/class method onLoad ex:

j(document).ready(function(){
  GW.SERPView.onLoad()
})

*note, while I do use the class declaration method with the respective prototypes it makes more sense to be used when a class is supposed to be initialized multiple times, such as:

function Sprite(name,url){
	this.name = name;
	this.url = url
	this.preload()
}

Sprite.prototype.preload = function() {
	console.log('fetching', this.name);
	this.obj = new Image(this.url);
	this.obj.src = this.src;
};

Sprite.prototype.draw = function( ctx ) {
	console.log('drawing',this.name, 'to', ctx)
	// TODO
};
(function() {
if (typeof GW === 'undefined') {
GW = {}
};
GW.GameView = {}
// actually tranfer all to some hiden div!
function hideReviews(){
j('#dreviewswrap').children().hide().appendTo('#lololand')
}
function orderReviewsByCriterion( by ){
var all = j('#lololand').children(),
size = all.size()
destiny = j('#dreviewswrap')
// matter of date
if( (/date\-/).test( by ) ){
if( by === 'date-asc'){
for (var i=0; i < size; i++) {
all.filter("[iposition="+i+"]").appendTo( destiny ).slideDown("fast")
}
} else {
for (var i=0; i < size; i++) {
all.filter("[iposition="+(size-i)+"]").appendTo( destiny ).slideDown("fast")
}
}
} else { // matter of presence
var selected = j()
switch( by ){
case('hate'): selected = all.has('.sprites.sum0') ;break;
case('dislike'): selected = all.has('.sprites.sum1') ;break;
case('like'): selected = all.has('.sprites.sum2') ;break;
case('love'): selected = all.has('.sprites.sum3') ;break;
}
selected.each(function(i,e){
j(e).appendTo( destiny ).slideDown("fast")
})
}
setTimeout(function(){ j.scrollTo(j('#dreviews').get(0), 300) }, 100)
}
// sort reviews by attributes, read the <select>
GW.GameView.filterReview = function (){
// want them to immediatly hide
hideReviews()
// background processing
orderReviewsByCriterion( j('#rfilter').val() )
// reapear in correct order
}
GW.GameView.onLoad = function (){
// bind the click on detailed reviews to the content
j('#dreviews').prev().click(function(e){
if( j('#dreviews').is(':hidden') ){
j('#dreviews').show()
j.scrollTo(j('#dreviews').get(0), 300)
}
return false
})
j('#dofilter').click( GW.GameView.filterReview )
}
})()
// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
j=jQuery;
(function() {
if (typeof GW === 'undefined') {
GW = {}
};
GW.Main = {}
GW.Main.loginCallback = function( nick ){
j('#notice').text('Welcome!')
j('nav .popup').remove()
j('nav').prepend(nick)
j.scrollTo(0, 10)
}
GW.Main.providerRedirect = function( url ) {
window.location.href = url+'/to'
};
GW.Main.onLoad = function() {
// http://swip.codylindley.com/DOMWindowDemo.html
$('.popup').openDOMWindow({
eventType:'click',
windowSource:'iframe',
loader:1,
loaderImagePath:'/icons/ajax-loader.gif',
loaderHeight:16,
loaderWidth:17,
windowPadding: 5,
modal: false,
height: 500,
});
$('.popup').click( function() {
setTimeout(function (argument) { j.scrollTo(0, 5) }, 10)
setTimeout(function (argument) { j.scrollTo(0, 5) }, 50)
});
$('a.loginRequired').click(function(e){
if( j('.popup:first').click().size() ){
return false
} else {
return true
}
})
};
GW.URI = {}
GW.URI.decodeComponents = function() {
var qs = (window.location.href+'').split('?')[1].split("#")[0]//.replace(/\[/g,'').replace(/\]/g,'') // "name=&platforms=ps3&genres=action&genres=adv&modes=single"
, query = {}
, hasComma = /\,/
qs = decodeURIComponent( qs )
if( qs && qs.length ){
qs = qs.split('&') // array of pieces, ["name=", "platforms=x360"]
for (var i=0; i < qs.length; i++) {
var splitto = qs[i].split('=') // ['name', ''] / ['platforms', 'x360']
var val = splitto[1]// splitto[1] ? [splitto[1]] : '' //.split(',')
// if already has this key, then become an Array and concat
if( typeof query[splitto[0]] === 'string' ){
query[splitto[0]] = [ query[splitto[0]] ]
}
if( typeof query[splitto[0]] === 'object' ){
query[splitto[0]] = query[splitto[0]].concat( val )
}
if( !query[splitto[0]] ) query[splitto[0]] = val
};
}
//console.log('yielding query',query)
return query
};
})()
j(document).ready(GW.Main.onLoad)
(function() {
if (typeof GW !== 'object') {
alert('JS error, GW undefined!!')
};
GW.SERPView = {}
GW.SERPView.sort = function(e) {
//var sorting = j(e).attr('href').split(',')
var query = GW.URI.decodeComponents()
query['sort'] = j(this).attr('href').split('#')[1]
var target = window.location.href.split('?')[0] +'?'+ j.param( query ) +'#'+ window.location.href.split('#')[1]
//console.log('target', target)
window.location.href = target
return false
};
var filterCriteria = null
, h1Template = " Found ? games! "
function resetFilterCriteria(){
filterCriteria = {
platform: [],
genre: [],
tag: [],
}
}
resetFilterCriteria()
/*
* user just clicked a filter (toggle)
*/
GW.SERPView.filterClick = function(e) {
try{
var je = null
, target = ''
//var je = j(this)
// , target = je.attr('name')//je.data('target')
// , value = je.next().text()
//debugger
//if( je.hasClass('active') ){
// je.removeClass( 'active' )
// var i = filterCriteria[target].indexOf( value )
// filterCriteria[target].remove( i )
//} else {
// je.addClass( 'active' )
// filterCriteria[target].push( value )
//}
// do a complete recount of boxes, just to be sure!
resetFilterCriteria()
j('.filter:checked').each( function(i,el) {
je = j(this)
target = je.attr('name');
target = target.substr(0, target.length -1)
if( target === 'mode' ) target = 'tag'
filterCriteria[target].push( je.next().text() )
})
GW.SERPView.updateGameList()
} catch(err) {
console.log('filter error!', err, je)
}
GW.SERPView.updateURL()
return true
};
/*
* All games that do not present any criteria should be hidden!
*/
GW.SERPView.updateGameList = function( instant ){
var games = j('.gamel')
, hide = []
, je = null
, inter = null
if( typeof instant === 'undefined' ) instant = false
games.each(function(i,el){
je = j(el)
// every element should not be hid, unless it fail to match some criteria
hide[i] = false
// platforms
if( filterCriteria.platform.length > 0 ){
inter = _.intersection( je.find('.gplatforms strong').text().split(' '), filterCriteria.platform )
//console.log( 'gplatforms', inter.length, filterCriteria.platform.length )
if( inter.length != filterCriteria.platform.length ){
hide[i] = true
return true // go next
}
}
// genres
if( filterCriteria.genre.length > 0 ){
inter = _.intersection( je.find('.ggenres').text().split(' '), filterCriteria.genre )
//console.log( 'ggenres', inter.length, filterCriteria.genre.length )
if( inter.length != filterCriteria.genre.length ){
hide[i] = true
return true // go next
}
}
// tags
if( filterCriteria.tag.length > 0 ){
inter = _.intersection( je.find('.gtags').text().split(' '), filterCriteria.tag )
//console.log( 'gtags', inter.length, filterCriteria.tag.length )
if( inter.length != filterCriteria.tag.length ){
hide[i] = true
return true // go next
}
}
})
var num = 0//_.reduce(hide, function(memo, val){ return (!val ? memo+1 : memo ) }, 0);
for (var i=0; i < hide.length; i++) {
if(!hide[i]) num += 1
};
j('.content h1:first').text( h1Template.replace('?', num+'' ) )
games.each(function(i,el){
je = j(this);
if( hide[i] ){
if(je.is(':visible')){
if( instant ) je.hide()
else je.hide('slow')//slideUp()
}
} else {
if(je.is(':hidden')){
if( instant ) je.show()
else je.show('slow')//slideDown()
}
}
})
}
/*
* After user toogle 1 filter URI should be updated
*/
GW.SERPView.updateURL = function() {
var url = window.location.href
var anchor = [
_.map(filterCriteria.platform, function(s) {return s.replace(/ /g,'_')}).join('-'),
_.map(filterCriteria.genre, function(s) {return s.replace(/ /g,'_')}).join('-'),
_.map(filterCriteria.tag, function(s) {return s.replace(/ /g,'_')}).join('-'),
]
anchor = anchor.join('!')
window.location.href = url.split('#')[0] +'#'+ encodeURIComponent(anchor)
};
/*
* does filtering once the page load
*/
GW.SERPView.applyURLFilters = function() {
var url = window.location.href
// URL broken down params
var original = {}
_.each( _.flatten( _.compact(GW.URI.decodeComponents()) ), function(s){
original[s] = true
})
//console.log(original)
j('.filter').each(function(i,el){
//console.log(this.value)
if( original[this.value] ) this.checked = true
})
// filters applyed
var anchor = url.split('#')[1]
if( anchor ){
anchor = decodeURIComponent( anchor.replace(/\_/g,' ') )
anchor = anchor.split('!')
} else {
anchor = []
}
if( anchor.length == 3 ){
filterCriteria = {
platform: _.compact(anchor[0].split('-')),
genre: _.compact(anchor[1].split('-')),
tag: _.compact(anchor[2].split('-')),
}
console.log(filterCriteria)
GW.SERPView.updateGameList( true )
var allCriteria = filterCriteria.platform.concat( filterCriteria.genre, filterCriteria.tag )
_.each( allCriteria, function(str) {
j('#SERPfilters li:contains('+str+')').children('input').attr('checked', true)
//j('.filter:contains("'+ str +'")').addClass('active')
})
}
};
GW.SERPView.onLoad = function (){
// bind the click on detailed reviews to the content
j('.resultsFilters a').click( GW.SERPView.sort )
GW.SERPView.applyURLFilters()
j('.filter').click( GW.SERPView.filterClick )
j('#dofilter').click( GW.GameView.filterReview )
}
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment