Step 1 in converting to a Backbone-based website

This commit is contained in:
Andrew Miner
2014-12-22 16:36:04 -08:00
parent dc9fd0de07
commit 16832e02dd
95 changed files with 41866 additions and 17 deletions
+24
View File
@@ -0,0 +1,24 @@
###
# Crafting Guide - constants.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
exports.Duration = Duration = {}
Duration.snap = 100
Duration.fast = Duration.snap * 2
Duration.normal = Duration.fast * 2
Duration.slow = Duration.normal * 2
exports.Opacity = Opacity = {}
Opacity.hidden = 1e-6
Opacity.shown = 1
exports.Event = Event = {}
Event.book = {}
Event.book.load = {}
Event.book.load.started = 'book:load:started' # controller, url
Event.book.load.succeeded = 'book:load:succeeded' # controller, book
Event.book.load.failed = 'book:load:failed' # controller, error message
Event.book.load.finished = 'book:load:finished' # controller
@@ -0,0 +1,77 @@
###
# Crafting Guide - base_controller.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
views = require '../views'
########################################################################################################################
module.exports = class BaseController extends Backbone.View
constructor: (options={})->
@_rendered = false
@_parent = options.parent
@_children = []
@_loadTemplate options.templateName
super options
# Public Methods ###############################################################################
addChild: (Controller, atSelector, options={})->
options.el = @$(atSelector)[0]
options.parent = this
child = new Controller options
child.render()
@_children.push child
return child
refresh: ->
logger.verbose "#{this} refreshing"
# Event Methods ################################################################################
onWillRender: -> # do nothing
onDidRender: ->
@refresh()
# Backbone.View Overrides ######################################################################
render: (options={})->
return this unless not @_rendered or options.force
data = (@model?.toHash? and @model.toHash()) or @model or {}
if not @_template?
logger.error "Default render called for #{@constructor.name} without a template"
return this
logger.verbose "#{this} rendering with data: #{data}"
@onWillRender()
$oldEl = @$el
$newEl = Backbone.$(@_template(data))
if $oldEl
$oldEl.replaceWith $newEl
$newEl.addClass $oldEl.attr 'class'
@setElement $newEl
@_rendered = true
@onDidRender()
return this
# Object Overrides #############################################################################
toString: ->
return "#{@constructor.name}(#{@cid})"
# Private Methods ##############################################################################
_loadTemplate: (templateName)->
if templateName?
@_template = views[templateName]
@@ -0,0 +1,25 @@
###
# Crafting Guide - crafting_guide_controller.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseController = require './base_controller'
LandingPage = require '../models/landing_page'
RecipeCatalogController = require './recipe_catalog_controller'
########################################################################################################################
module.exports = class LandingPageController extends BaseController
constructor: (options={})->
options.model ?= new LandingPage
options.templateName = 'landing_page'
super options
# BaseController Overrides #####################################################################
onDidRender: ->
@recipeBooksController = @addChild RecipeCatalogController, '.view__recipe_catalog'
super
@@ -0,0 +1,16 @@
###
# Crafting Guide - recipe_catalog_controller.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseController = require './base_controller'
########################################################################################################################
module.exports = class RecipeCatalogController extends BaseController
constructor: (options={})->
options.templateName = 'recipe_catalog'
super options
+57
View File
@@ -0,0 +1,57 @@
###
# Crafting Guide - router.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
{Duration} = require './constants'
LandingPageController = require './controllers/landing_page_controller'
{Opacity} = require './constants'
########################################################################################################################
module.exports = class CraftingGuideRouter extends Backbone.Router
constructor: (options={})->
@_page = null
@_pageControllers = {}
super options
# Backbone.Router Overrides ####################################################################
routes:
'': 'landing'
# Route Methods ################################################################################
landing: ->
@_pageControllers.landing ?= new LandingPageController
@_setPage 'landing'
# Private Methods ##############################################################################
_setPage: (controllerName)->
controller = @_pageControllers[controllerName]
if not controller? then throw new Error "cannot find controller named: #{controllerName}"
return if @_page is controller
logger.info "changing to #{controllerName} page"
showDuration = Duration.normal
show = =>
@_page = controller
controller.render()
# controller.$el.css 'opacity', Opacity.hidden
$pageContent = $('.page')
$pageContent.empty()
$pageContent.append controller.$el
controller.$el.fadeIn showDuration
if @_mainController?
showDuration = Duration.fast
@_page.$el.fadeOut Duration.fast, show
else
show()
+87
View File
@@ -0,0 +1,87 @@
###
# Crafting Guide - logger.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
########################################################################################################################
module.exports = class Logger
@TRACE = {name:'TRACE ', value:0}
@DEBUG = {name:'DEBUG ', value:1}
@VERBOSE = {name:'VERBOSE', value:2}
@INFO = {name:'INFO ', value:3}
@WARNING = {name:'WARNING', value:4}
@ERROR = {name:'ERROR ', value:5}
@FATAL = {name:'FATAL ', value:6}
ALL_LEVELS = [@TRACE, @DEBUG, @VERBOSE, @INFO, @WARNING, @ERROR, @FATAL]
constructor: (options={})->
options.level ?= Logger.FATAL
@formatText = if options.format? then options.format else "<%= timestamp %> | <%= level %> | <%= message %>"
@level = @_parseLevel options
@_format = _.template @formatText
log: (level, message)->
return unless level.value >= @level.value
message = message() if _.isFunction message
entry = {timestamp:new Date(), level:level, message:message}
entry.level ?= @level
lines = @_formatEntry entry
if entry.level.value < Logger.WARNING.value
console.log(line) for line in lines
else
console.error(line) for line in lines
# Log Methods ##################################################################################
trace: (message)-> @log Logger.TRACE, message
debug: (message)-> @log Logger.DEBUG, message
verbose: (message)-> @log Logger.VERBOSE, message
info: (message)-> @log Logger.INFO, message
warning: (message)-> @log Logger.WARNING, message
error: (message)->
message = "#{message.stack}" if message.stack?
@log Logger.ERROR, message
fatal: (message)-> @log Logger.FATAL, message
# Private Methods ##############################################################################
_formatEntry: (entry, lines=[])->
message = entry.message.replace /\\n/g, '\n'
for line in message.split '\n'
result = []
result.push @_format
timestamp: "#{entry.timestamp}"
level: entry.level.name
message: line
lines.push result.join ''
return lines
_parseLevel: (options)->
return Logger.FATAL unless _(options).has 'level'
level = options.level
if not level?
candidates = []
else if _.isString level
candidates = (l for l in ALL_LEVELS when l.name.trim().toLowerCase() is level.trim().toLowerCase())
else if _.isNumber level
candidates = (l for l in ALL_LEVELS when l.value is level)
else if level?
candidates = (l for l in ALL_LEVELS when l is level)
throw new Error "invalid level: #{level}" unless candidates.length > 0
return candidates[0]
+20
View File
@@ -0,0 +1,20 @@
###
# Crafting Guide - main.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
views = require './views'
Logger = require './logger'
CraftingGuideRouter = require './crafting_guide_router'
if typeof(global) is 'undefined'
window.global = window
global.views = views
global.logger = new Logger level:Logger.TRACE
global.router = new CraftingGuideRouter
logger.info "CraftingGuide is ready"
Backbone.history.start pushState:true
+28
View File
@@ -0,0 +1,28 @@
###
# crafting_guide - base_model.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
########################################################################################################################
module.exports = class BaseModel extends Backbone.Model
constructor: (attributes={}, options={})->
super attributes, options
makeGetter = (name)-> return -> @get name
makeSetter = (name)-> return (value)-> @set name, value
for name in _.keys attributes
continue if name is 'id'
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
# Backbone.Model Overrides #####################################################################
sync: -> # do nothing
# Object Overrides #############################################################################
toString: ->
return "#{@constructor.name} (#{@cid})"
+19
View File
@@ -0,0 +1,19 @@
###
# Crafting Guide - item.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseModel = require './base_model'
########################################################################################################################
module.exports = class Item extends BaseModel
constructor: (attributes={}, options={})->
attributes.name ?= ''
attributes.quantity ?= 1
super attributes, options
# Public Methods ###############################################################################
+17
View File
@@ -0,0 +1,17 @@
###
# Crafting Guide - landing_page.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseModel = require './base_model'
RecipeCatalog = require './recipe_catalog'
########################################################################################################################
module.exports = class LandingPage extends BaseModel
constructor: (attributes={}, options={})->
attributes.catalog ?= new RecipeCatalog
super attributes, options
@@ -0,0 +1,81 @@
###
# Crafting Guide - v1.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
Item = require '../item'
Recipe = require '../recipe'
RecipeBook = require '../recipe_book'
########################################################################################################################
module.exports = class V1
constructor: ->
@_errorLocation = 'the header information'
parse: (data)->
return @_parseRecipeBook data
# Private Methods ##############################################################################
_parseRecipeBook: (data)->
if not data? then throw new Error 'recipe book data is missing'
if not data.version? then throw new Error 'version is required'
if not data.mod_name? then throw new Error 'mod_name is required'
if not data.mod_version? then throw new Error 'mod_version is required'
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
book = new RecipeBook version:data.version, modName:data.mod_name, modVersion:data.mod_version
book.description = data.description or ''
for index in [0...data.recipes.length]
@_errorLocation = "recipe #{index + 1}"
recipeData = data.recipes[index]
book.recipes.push @_parseRecipe recipeData
return book
_parseRecipe: (data, options={})->
if not data? then throw new Error "recipe data is missing for #{@_errorLocation}"
if not data.output? then throw new Error "#{@_errorLocation} is missing output"
if not data.input? then throw new Error "#{@_errorLocation} is missing input"
output = @_parseItemList data.output, field:'output', canBeEmpty:false
@_errorLocation = "recipe for output[0].name"
data.tools ?= []
input = @_parseItemList data.input, field:'input', canBeEmpty:false
tools = @_parseItemList data.tools, field:'tools', canBeEmpty:true
return new Recipe input:input, output:output, tools:tools
_parseItemList: (data, options={})->
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
if not _.isArray(data) then data = [data]
if data.length is 0 and not options.canBeEmpty
throw new Error "#{options.field} for #{@_errorLocation} cannot be empty"
result = []
for index in [0...data.length]
itemData = data[index]
result.push @_parseItem itemData, field:options.field, index:index
return result
_parseItem: (data, options={})->
errorBase = "#{options.field} element #{options.index} for #{@_errorLocation}"
if not data? then throw new Error "#{errorBase} is missing"
if _.isString(data) then data = [1, data]
if not _.isArray(data) then throw new Error "#{errorBase} must be an array"
if data.length is 1 then data.unshift 1
if data.length isnt 2 then throw new Error "#{errorBase} must have at least one element"
if not _.isNumber(data[0]) then throw new Error "#{errorBase} must start with a number"
return new Item quantity:data[0], name:data[1]
+17
View File
@@ -0,0 +1,17 @@
###
# Crafting Guide - recipe.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseModel = require './base_model'
########################################################################################################################
module.exports = class Recipe extends BaseModel
constructor: (attributes={}, options={})->
super attributes, options
Object.defineProperty this, 'name', get:-> @output[0].name
+38
View File
@@ -0,0 +1,38 @@
###
# Crafting Guide - recipe_book.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseModel = require './base_model'
########################################################################################################################
module.exports = class RecipeBook extends BaseModel
constructor: (attributes={}, options={})->
if _.isEmpty(attributes.modName) then throw new Error 'modName cannot be empty'
if _.isEmpty(attributes.modVersion) then throw new Error 'modVersion cannot be empty'
attributes.description ?= ''
attributes.recipes ?= []
super attributes, options
# Public Methods ###############################################################################
getRecipes: (name)->
result = []
for recipe in @recipes
if recipe.name is name
result.push recipe
return result
# Object Overrides #############################################################################
toString: ->
return "RecipeBook (#{@cid}) {
modName:#{@modName},
modVersion:#{@modVersion},
recipes:#{@recipes.length} items}"
@@ -0,0 +1,26 @@
###
# Crafting Guide - recipe_book_parser.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
V1 = require './parser_versions/v1'
########################################################################################################################
module.exports = class RecipeBookParser
constructor: ->
@_parsers =
'1': new V1
parse: (data)->
if not data? then throw new Error 'recipe book data is missing'
if not data.version? then throw new Error 'version is required'
parser = @_parsers["#{data.version}"]
if not parser?
throw new Error "cannot parse version #{data.version} recipe books"
return parser.parse data
+54
View File
@@ -0,0 +1,54 @@
###
# Crafting Guide - recipe_catalog.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseModel = require './base_model'
{Event} = require '../constants'
RecipeBookParser = require './recipe_book_parser'
########################################################################################################################
module.exports = class RecipeCatalog extends BaseModel
constructor: (attributes={}, options={})->
attributes.books ?= []
super attributes, options
@_parser = new RecipeBookParser
# Public Methods ###############################################################################
loadBook: (url)->
@trigger Event.book.load.started, this, url
$.ajax
url: url
dataType: 'json'
success: (data, status, xhr)=> @onBookLoaded(data, status, xhr)
error: (xhr, status, error)=> @onBookLoadFailed(error, status, xhr)
# Event Methods ################################################################################
onBookLoaded: (data, status, xhr)->
try
@books.push @_parser.parse data
_(@books).sortBy 'name'
logger.info "loaded recipe book: #{book}"
@trigger Event.book.load.succeeded, this, book
@trigger Event.book.load.finished, this
catch e
@onBookLoadFailed error, status, xhr
onBookLoadFailed: (error, status, xhr)->
logger.error "failed to load recipe book: #{error}"
@trigger Event.book.load.failed, this, error.message
@trigger Event.book.load.finished, this
# Object Overrides #############################################################################
toString: ->
return "RecipeCatalog (#{@cid}) {books:#{@books.length} items}"
+32
View File
@@ -0,0 +1,32 @@
var jade = jade || require('jade').runtime;
this["JST"] = this["JST"] || {};
this["JST"]["landing_page"] = function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var jade_indent = [];
buf.push("\n<div class=\"view__landing_page\">\n <div class=\"view__recipe_catalog\"></div>\n <div class=\"view__crafter\"></div>\n</div>");;return buf.join("");
};
this["JST"]["recipe_catalog"] = function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var jade_indent = [];
buf.push("\n<div class=\"view__recipe_catalog\">\n <h2><img src=\"/images/bookshelf.png\"/>\n <p>Recipe Catalog</p>\n </h2>\n <table class=\"books\">\n <tr>\n <td>&nbsp;</td>\n <td>\n <input placeholder=\"enter a URL to load another recipe book...\" class=\"recipe_book_url\"/>\n <button class=\"recipe_book_load_button\">Load</button>\n </td>\n </tr>\n </table>\n <div class=\"load_error\">\n <p></p>\n </div>\n</div>");;return buf.join("");
};
this["JST"]["test"] = function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var jade_indent = [];
buf.push("\n<p>Hello world!</p>");;return buf.join("");
};
if (typeof exports === 'object' && exports) {module.exports = this["JST"];}