Step 1 in converting to a Backbone-based website
This commit is contained in:
@@ -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
|
||||
@@ -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()
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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})"
|
||||
@@ -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 ###############################################################################
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
@@ -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> </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"];}
|
||||
Reference in New Issue
Block a user