Created
January 29, 2013 10:02
-
-
Save dtuite/4663167 to your computer and use it in GitHub Desktop.
Someone asked for the JS code from http://domainmasher.com so here it is.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Service for checking the availability of a given word | |
Splitter.module('Checkers', function(Checkers, Splitter) { | |
var LocalChecker = { | |
comKeyFor: function(compound) { | |
return 'avail/.com/' + compound; | |
}, | |
check: function(compound) { | |
return $.jStorage.get(this.comKeyFor(compound)); | |
}, | |
store: function(compound, availability) { | |
var key = ''; | |
// Don't want to store error states. | |
if (availability === 'available' || availability === 'unavailable') { | |
key = this.comKeyFor(compound); | |
// Expire the value in 10 mins. | |
return $.jStorage.set(key, availability, { TTL: 600000 }) | |
}; | |
return false; | |
} | |
}; | |
// Class which can check the availability of a word. | |
var Checker = { | |
check: function(compound, options) { | |
options || (options = {}); | |
// console.log("Checking availability of", compound); | |
// No-op if the word is blank. | |
if ($.trim(compound).length === 0) { return; }; | |
var localAvailability = LocalChecker.check(compound); | |
if (localAvailability) { | |
// console.log("Local availability of", compound + ".com", localAvailability); | |
options.success(localAvailability); | |
} else { | |
$.ajax({ | |
url: '/availabilities/' + encodeURIComponent(compound), | |
dataType: 'json', | |
success: function(json) { | |
var availability = json.status; | |
// console.log("Remote availability of", compound + ".com", availability); | |
LocalChecker.store(compound, availability); | |
options.success(availability); | |
}, | |
error: options.error | |
}); | |
} | |
} | |
}; | |
Checkers.check = function(word, success) { | |
Checker.check(word, success); | |
}; | |
// Create a debounced version of the checking method. | |
Checkers.debouncedCheck = _.debounce(Checkers.check, 300); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The widgets for selecting words. This comprises the main part of the UI. | |
Splitter.module('Slot', function(Slot, Splitter, Backbone, M) { | |
var Word = Backbone.Model.extend({ | |
defaults: { text: '', active: false }, | |
activate: function(options) { | |
options || (options = {}); | |
this.collection.activateWord(this, options); | |
}, | |
deactivate: function(options) { | |
options || (options = {}); | |
return this.set({ active: false }, options); | |
}, | |
}); | |
var SynList = Backbone.Collection.extend({ model: Word }); | |
var WordList = Backbone.Collection.extend({ | |
model: Word, | |
url: '/words', | |
activeWord: function() { | |
var word = this.where({ active: true })[0]; | |
if (!word) { | |
word = this.add({ active: true, text: '' }); | |
}; | |
return word; | |
}, | |
activeIndex: function() { | |
return this.indexOf(this.activeWord()); | |
}, | |
preActives: function() { | |
var number = this.length - (this.activeIndex() + 1); | |
return this.last(number); | |
}, | |
postActives: function() { | |
return this.first(this.activeIndex()); | |
}, | |
resetActive: function(text) { | |
var newActiveWord; | |
text = $.trim(text); | |
if (text !== this.lastFetchedText) { | |
newActiveWord = new this.model(); | |
// Don't do anything if the text is invalid. | |
if (newActiveWord.set({ text: text, active: true })) { | |
this.reset(newActiveWord, { silent: true }); | |
this.fetchSynonyms(); | |
} | |
} | |
}, | |
fetchSynonyms: function() { | |
var text = this.activeWord().get('text'); | |
// Don't actually send a request if the text is blank. | |
if (text.length === 0) { | |
this.lastFetchedText = text; | |
this.reset(); | |
return; | |
} | |
$.ajax({ | |
url: '/synonyms/' + encodeURIComponent(text), | |
dataType: 'json', | |
success: _.bind(function(json) { | |
var wordAttrs; | |
// console.log("Fetched synonyms of", text, json.synonyms); | |
wordAttrs = _.map(json.synonyms, function(syn) { | |
return { text: syn, active: false }; | |
}) | |
// The problem with using add is that it causes the | |
// syn lists to re-render loads of times. THus, we must add | |
// silently before triggering the event once. | |
this.add(wordAttrs, { silent: true }); | |
}, this), | |
error: _.bind(function(json) { | |
this.trigger('error', text, json.error); | |
}, this), | |
// This needs to happen on both success and error. | |
complete: _.bind(function(json) { | |
this.lastFetchedText = text; | |
// console.log("Last fetched text set to", this.lastFetchedText); | |
// To clear the synonyms. | |
this.trigger('reset'); | |
}, this) | |
}); | |
}, | |
deactivateAll: function() { | |
this.each(function(word) { word.deactivate({ silent: true }) }); | |
}, | |
activateWord: function(word, options) { | |
options || (options = {}); | |
if (word) { | |
this.deactivateAll(); | |
return word.set({ active: true }, options); | |
}; | |
}, | |
promoteActive: function() { this.changeActiveBy(1); }, | |
demoteActive: function() { this.changeActiveBy(-1); }, | |
changeActiveBy: function(places) { | |
var currentActiveIndex = this.activeIndex(), | |
wordToActivate = this.at(currentActiveIndex + places); | |
// console.log("Changing active by", places, currentActiveIndex, wordToActivate); | |
this.activateWord(wordToActivate); | |
} | |
}); | |
var SlotView = M.Layout.extend({ | |
template: '#slot-layout-template', | |
className: 'slot span4', | |
regions: { | |
postActiveRegion: '.post-active-region', | |
preActiveRegion: '.pre-active-region', | |
activeWordRegion: '.active-word-region' | |
}, | |
events: { | |
'click a.demote' : 'demoteClicked', | |
'click a.promote' : 'promoteClicked' | |
}, | |
initialize: function() { | |
var modelId = this.model.id; | |
// Grab the focus when the slot tells us to. | |
this.bindTo(this.model, 'focus', this.takeFocus); | |
// Attach the sub views. | |
this.activeWordView = new ActiveWordView({ | |
collection: this.model.get('words'), | |
}); | |
this.activeWordView.on('show', function() { | |
// Add an arbitrary integer to the tabindex to leave | |
// room for in-between tab indexes later. | |
// NOTE: I can't move this into the sub view bacause I don't | |
// have access to the slot id there. | |
this.setTabIndex(modelId + 11); | |
}); | |
}, | |
renderInactives: function() { | |
var preActives = new SynonymListView({ | |
collection: this.model.get('preActives') | |
}); | |
this.preActiveRegion.show(preActives); | |
var postActives = new SynonymListView({ | |
collection: this.model.get('postActives') | |
}); | |
this.postActiveRegion.show(postActives); | |
}, | |
renderActive: function() { | |
this.activeWordRegion.show(this.activeWordView); | |
}, | |
onRender: function() { | |
// Add the slot id to the root element. | |
this.$el.attr({ id: 'slot_' + this.model.id }); | |
this.renderActive(); | |
this.renderInactives(); | |
}, | |
takeFocus: function() { | |
this.activeWordView.takeFocus(); | |
}, | |
demoteClicked: function(e) { | |
e.preventDefault(); | |
this.model.demoteActive(); | |
}, | |
promoteClicked: function(e) { | |
e.preventDefault(); | |
this.model.promoteActive(); | |
} | |
}); | |
var Slot = Backbone.Model.extend({ | |
defaults: function() { | |
return { | |
words: new WordList(), | |
preActives: new SynList(), | |
postActives: new SynList() | |
}; | |
}, | |
initialize: function() { | |
this.activeWord().bind('fetch:synonyms', this.fetchSynonyms, this); | |
this.get('words').bind('reset add change:active', this.resetSynLists, this); | |
this.get('words').bind('reset change:active', function(word) { | |
// console.log("Acive component", this.id, this.activeText()); | |
Splitter.vent.trigger('change:activeWord', this.id, this.activeText()); | |
}, this); | |
}, | |
resetSynLists: function() { | |
this.get('preActives').reset(this.get('words').preActives()); | |
this.get('postActives').reset(this.get('words').postActives()); | |
}, | |
activeText: function() { return this.activeWord().get('text'); }, | |
activeWord: function() { return this.get('words').activeWord(); }, | |
promoteActive: function() { this.get('words').promoteActive(); }, | |
demoteActive: function() { this.get('words').demoteActive(); }, | |
}); | |
var SynonymView = M.ItemView.extend({ | |
template: '#synonym-template', | |
tagName: 'li', | |
events: { | |
'click a' : 'linkClicked' | |
}, | |
serializeData: function() { | |
return { | |
text: this.model.get('text') | |
}; | |
}, | |
linkClicked: function(e) { | |
e.preventDefault(); | |
this.model.activate(); | |
} | |
}); | |
var SynonymListView = M.CollectionView.extend({ | |
itemView: SynonymView, | |
tagName: 'ul', | |
}); | |
var ActiveWordView = M.ItemView.extend({ | |
template: '#active-word-template', | |
tagName: 'form', | |
events: { | |
'keyup input' : 'keyPressed', | |
'submit' : 'formSubmitted' | |
}, | |
// Override the initial events method to prevent the | |
// view from re-rendering whenever the collection resets. | |
initialEvents: function() {}, | |
keyPressed: function(e) { | |
switch(e.which) { | |
case 40: | |
this.downPressed(e); | |
break; | |
case 38: | |
this.upPressed(e); | |
break; | |
case 37: | |
this.leftPressed(e); | |
break; | |
case 39: | |
this.rightPressed(e); | |
break; | |
default: | |
this.typingFinished(e); | |
return; | |
} | |
}, | |
ui: { input: 'input' }, | |
cursorPosition: function() { | |
return this.$el.find(this.ui.input).cursorPosition(); | |
}, | |
getText: function() { | |
return this.$el.find(this.ui.input).val(); | |
}, | |
setTabIndex: function(index) { | |
this.$el.find(this.ui.input).attr({ tabIndex: index }); | |
}, | |
leftPressed: function(e) { | |
if (this.cursorPosition() === 0) { | |
this.removeErrors(); | |
Splitter.slotsList.focusLeft(); | |
} | |
}, | |
rightPressed: function(e) { | |
if (this.cursorPosition() === this.getText().length) { | |
this.removeErrors(); | |
Splitter.slotsList.focusRight(); | |
} | |
}, | |
takeFocus: function() { this.$el.find(this.ui.input).select(); }, | |
downPressed: function() { | |
this.collection.demoteActive(); | |
this.takeFocus(); | |
}, | |
upPressed: function(e) { | |
this.collection.promoteActive(); | |
this.takeFocus(); | |
}, | |
initialize: function() { | |
this.typingFinished = _.debounce(this.typingFinished, 500); | |
// Re-render when the user changes the active word. | |
this.bindTo(this.collection, 'change', this.render); | |
// Display invalid when the user enters an invalid word. | |
this.bindTo(this.collection, 'error', this.displayErrors); | |
}, | |
serializeData: function() { | |
return { | |
text: this.collection.activeWord().get('text') | |
}; | |
}, | |
displayErrors: function() { | |
this.$el.find(this.ui.input) | |
.closest('.control-group').addClass('error'); | |
}, | |
removeErrors: function() { | |
this.$el.find(this.ui.input) | |
.closest('.control-group').removeClass('error'); | |
}, | |
typingFinished: function(e) { | |
this.removeErrors(); | |
this.collection.resetActive(this.getText()); | |
}, | |
formSubmitted: function(e) { | |
e.preventDefault(); | |
this.typingFinished(); | |
} | |
}); | |
var SlotsList = Backbone.Collection.extend({ | |
url: '/words', | |
model: Slot, | |
nextSlotId: function() { return this.models.length; }, | |
focusLeft: function() { | |
// HACK: Hard-coded slot id. | |
var leftSlot = this.at(0); | |
if (leftSlot) { leftSlot.trigger('focus'); }; | |
}, | |
focusRight: function() { | |
// HACK: Hard-coded slot id. | |
var rightSlot = this.at(1); | |
if (rightSlot) { rightSlot.trigger('focus'); }; | |
}, | |
// Add a slot to the collection. | |
addSlot: function(component) { | |
var newModel = new this.model({ id: this.nextSlotId() }); | |
this.add(newModel); | |
if (component) { newModel.get('words').resetActive(component); } | |
} | |
}); | |
var SlotsListView = M.CollectionView.extend({ | |
itemView: SlotView, | |
}); | |
Splitter.addInitializer(function(options) { | |
var wordAttrs = options.word || {}, | |
slotsListView; | |
this.slotsList = new SlotsList(); | |
_.each(wordAttrs.components, _.bind(function(component) { | |
this.slotsList.addSlot(component); | |
}, this)); | |
slotsListView = new SlotsListView({ collection: this.slotsList }); | |
Splitter.slotsRegion.show(slotsListView); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is the top level namespace for the application and some default settings. | |
window.Splitter = new Backbone.Marionette.Application(); | |
Splitter.addRegions({ | |
slotsRegion: '#slots-region', | |
wordRegion: '#word-region' | |
}); | |
// Override Underscore templating style (because of erb). | |
_.templateSettings = { | |
interpolate: /\{\{\=(.+?)\}\}/g, | |
evaluate: /\{\{(.+?)\}\}/g | |
}; | |
$(function(){ | |
$("body").tooltip({ | |
selector: '[rel=tooltip]', | |
placement: 'bottom' | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A model and view for showing the availability of the compound word chosen by the user. | |
Splitter.module('Word', function(Word, Splitter) { | |
var WordModel = Backbone.Model.extend({ | |
defaults: { | |
components: [], | |
comAvailability: '' | |
}, | |
compound: function() { | |
return this.get('components').join('').replace(/ /g, ''); | |
}, | |
updateAvailability: function() { | |
// No-op if the same word as last time. | |
if (this.lastCheckedCompound === this.compound()) { return; } | |
// Set the com availability to waiting because we are | |
// in the process of fetching new com availability. | |
this.set({ comAvailability: 'waiting' }); | |
Splitter.Checkers.debouncedCheck(this.compound(), { | |
success: _.bind(function(availabilty) { | |
this.lastCheckedCompound = this.compound(); | |
this.set({ comAvailability: availabilty }); | |
}, this), | |
error: _.bind(function(xhr) { | |
// console.log("Availabilty checking error", this, arguments); | |
this.lastCheckedCompound = this.compound(); | |
this.set({ comAvailability: 'invalid' }); | |
}, this) | |
}); | |
}, | |
isBlank: function() { return this.compound().length === 0; }, | |
isComAvailable: function() { | |
return this.get('comAvailability') === 'available'; | |
}, | |
isComUnavailable: function() { | |
return this.get('comAvailability') === 'unavailable'; | |
}, | |
isComUnknown: function() { | |
return this.get('comAvailability') === 'unknown'; | |
}, | |
changeComponent: function(index, newWord) { | |
var newComponents = _.clone(this.get('components')); | |
newComponents[index] = newWord; | |
// console.log("Setting new components", newComponents, index, newWord); | |
this.set({ components: newComponents }); | |
this.updateAvailability(); | |
} | |
}) | |
var WordView = Backbone.Marionette.ItemView.extend({ | |
template: '#word-template', | |
className: 'word', | |
initialize: function() { | |
this.bindTo(this.model, 'change', this.render, this); | |
}, | |
serializeData: function() { | |
return { | |
compound: this.model.compound(), | |
comAvailability: this.model.get('comAvailability'), | |
notBlank: !this.model.isBlank(), | |
isComAvailable: this.model.isComAvailable(), | |
isComUnavailable: this.model.isComUnavailable(), | |
isComUnknown: this.model.isComUnknown(), | |
host: this.model.compound() + '.com', | |
// Can't figure out how to make templateHelpers work. | |
visitLink: function() { | |
var localDetails = {}, | |
templateName = '#visit-link-template', | |
mkup; | |
if (this.isComUnavailable) { | |
localDetails.href = "http://" + this.host; | |
mkup = $(templateName).html(); | |
return _.template(mkup, _.extend(this, localDetails)); | |
} | |
return ''; | |
}, | |
purcahseLink: function() { | |
var localDetails = {}, | |
templateName = '#purchase-link-template', | |
mkup; | |
if (this.isComAvailable) { | |
localDetails.href = | |
Splitter.DomainRegistrar.affiliateUrlFor(this.host); | |
localDetails.registrar = Splitter.domainRegistrar; | |
mkup = $(templateName).html(); | |
return _.template(mkup, _.extend(this, localDetails)); | |
} | |
return ''; | |
}, | |
unknownLink: function() { | |
if (this.isComUnknown) { | |
var templateName = '#unknown-link-template'; | |
return _.template($(templateName).html(), this); | |
} | |
return ''; | |
} | |
}; | |
} | |
}); | |
Splitter.addInitializer(function(options) { | |
var wordAttrs = options.word || {}, | |
wordView; | |
this.word = new WordModel(wordAttrs); | |
wordView = new WordView({ model: this.word }); | |
this.vent.on('change:activeWord', function(id, word) { | |
Splitter.word.changeComponent(id, word); | |
}); | |
this.wordRegion.show(wordView); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment