UNPKG

tiger

Version:

A full port of Spine.js MVC framework to Titanium Mobile, with enhancements

400 lines (322 loc) 9.85 kB
# Tiger 0.2.1 by Charles Phillips <charles@doublerebel.com> # A library enhancing Titanium apps with Spine's MVC architecture # Uses CoffeeScript's inheritance, and adds jQuery-like chainability # Add tiger.db for persistent storage # MIT Licensed, Copyright 2011 - 2013 Double Rebel Spine = @Spine or require './spine' # Utilities extend = (target, sources...) -> target[key] = val for key, val of source for source in sources target makeArray = (args) -> Array::slice.call(args, 0) # Tiger Modules class Module extends Spine.Class @include extend: (sources...) -> extend @, sources... Env = {} logLevels = ['info', 'warn', 'error', 'debug', 'trace'] Log = extend {}, Spine.Log, logLevel: false stackTraceLimit: 10 log: (args...) -> return unless Tiger.Log.trace level = args[0] in logLevels and args.shift() level = @logLevel or level or 'info' prefix = @logPrefix and @logPrefix + ' ' or '' for obj in args if typeof obj is 'string' then Ti.API.log level, prefix + obj else Ti.API.log level, prefix + "#{key}: #{val}" for key, val of obj @ stackTrace: (err = new Error) -> Error.stackTraceLimit = @stackTraceLimit Error.prepareStackTrace = (err, stack) -> stack Error.captureStackTrace err, arguments.callee for frame in err.stack Log.debug "(trace) #{frame.getFileName()}:#{frame.getLineNumber()} - #{frame.getFunctionName()}" for level in logLevels do (level) -> Log[level] = (args...) -> args.unshift(level) Log.log.apply(@, args) class Ajax extends Module @include Log logPrefix: '(Ajax)' defaults: method: 'GET' url: null data: false #contentType: 'application/x-www-form-urlencoded; charset=UTF-8' contentType: 'application/json' enableKeepAlive: false # validatesSecureCertificate # withCredentials timeout: 10000 # Ti API Options async: true autoEncodeUrl: true # Callbacks success: -> error: -> beforeSend: null complete: -> onreadystatechanged: null @encode: (string) -> Ti.Network.encodeURIComponent string @params: (data) -> return '' unless data? params = ("#{@encode(key)}=#{@encode(val)}" for key, val of data) params.join '&' @get: (o) -> o.method = 'GET' new @ o @post: (o) -> o.method = 'POST' new @ o @download: (options) -> file = conf.file options.onload = (xhr) -> return unless xhr.responseData? if xhr.responseData.type is 1 f = Ti.Filesystem.getFile xhr.responseData.nativePath file.deleteFile() if file.exists() f.move file.nativePath else file.write xhr.responseData options.success file, xhr.statusText, xhr new @ options constructor: (options = {}) -> options = Tiger.extend {}, @defaults, options options.method = options.method.toUpperCase() @debug "#{options.method} #{options.url} ..." xhr = Ti.Network.createHTTPClient autoEncodeUrl: options.autoEncodeUrl async: options.async timeout: options.timeout xhr.onerror = -> unless xhr.statusText if xhr.readyState is xhr.OPENED then error = 'No response from server' else error = 'Unknown error' options.error xhr, xhr.statusText, error options.complete xhr, xhr.statusText xhr.onload = -> try response = xhr.responseXML catch e response = xhr.responseText unless response options.success response, xhr.statusText, xhr options.complete xhr, xhr.statusText _debug = @proxy @debug xhr.onreadystatechanged = options.onreadystatechanged or -> switch @readyState when @OPENED then _debug 'readyState: opened...' when @HEADERS_RECEIVED then _debug 'readyState: headers received...' when @LOADING then _debug 'readyState: loading...' when @DONE then _debug 'readyState: done.' xhr.onsendstream = options.onsendstream or (e) => @debug('Upload progress: ' + e.progress) if options.method is 'GET' and options.data if options.url.indexOf('?') isnt -1 then options.url += '&' else options.url += '?' options.url += @constructor.params options.data if Ti.Network.networkType is Ti.Network.NETWORK_NONE @debug "No network available. Cannot open connection to #{options.url}" return xhr xhr.open options.method, options.url xhr.file = options.file if options.file if options.headers xhr.setRequestHeader name, header for name, header of options.headers options.beforeSend xhr, options if options.beforeSend if options.data and options.method is 'POST' or options.method is 'PUT' @debug "Sending #{options.data} ..." xhr.setRequestHeader 'Content-Type', options.contentType xhr.send options.data else xhr.send() xhr class Controller extends Module @include Spine.Events @include Log eventSplitter: /^(\w+)\s*(.*)$/ constructor: (@options = {}) -> @[key] = val for key, val of @options @_map = {} @_events = {} @elements or= @constructor.elements @refreshElements() if @elements @events or= @constructor.events @delegateEvents() if @events @map or= @constructor.map @bindSynced() if @map refreshElements: -> return unless @view for el in @elements @[el] = @view[el] @ mapSelector: (selector) -> return el if el = @_map[selector] if '.' in selector selectors = selector.split '.' sel = selectors.shift() el = @[sel] or @[sel] = @view[sel] el = el[s] for s in selectors else el = @[selector] or @[selector] = @view[selector] @_map[selector] = el delegateEvents: -> for key, methodName of @events @_events[key] = @proxy @[methodName] match = key.match @eventSplitter eventName = match[1] selector = match[2] @debug "Binding #{selector} #{eventName}..." if selector is '' then @view.tiBind eventName, @_events[key] else el = @mapSelector selector el.tiBind eventName, @_events[key] @ release: (key) => unless key @trigger 'release' @view.remove() @unbind() else match = key.match @eventSplitter eventName = match[1] selector = match[2] el = if selector then (@mapSelector selector) else @view el.tiUnbind eventName, @_events[key] bindSynced: -> for field, selector of @map @debug "Binding #{field} to #{selector}..." do (field, self = @) -> el = self.mapSelector selector el.change((e) -> self.store[field] = e.value) if el @ loadSynced: -> for field, selector of @map if '.' in selector selectors = selector.split '.' prop = "#{selectors.pop()}" selector = selectors.join() else prop = 'value' el = @_map[selector] or @mapSelector selector value = @store[field] value or= '' if prop is 'value' props = {} props[prop] = value el.set props @ delay: (timeout = 0, func) -> setTimeout @proxy(func), timeout # Tiger View Element Event Wrapper eventList = [ 'return' 'click' 'dblclick' 'longpress' 'swipe' 'touchstart' 'touchmove' 'touchcancel' 'touchend' 'singletap' 'twofingertap' 'pinch' 'change' # 'blur' # 'focus' 'open' 'close' 'postlayout' # 'show', # 'hide', ] eventWraps = {} for event in eventList do (event) -> eventWraps[event] = (fn) -> if not fn then @element.fireEvent(event) else @tiBind(event, fn) @ for event in ['blur', 'focus'] do (event) -> eventWraps[event] = (fn) -> if not fn then @element[event]() else @tiBind(event, fn) @ capitalize = (string) -> string.charAt(0).toUpperCase() + string.slice(1) class Element extends Module @include eventWraps constructor: (props = {}) -> props = Tiger.extend {}, @defaults or {}, props @element = Ti.UI['create' + @elementName](props) add: (el) -> @element.add(el.element or el) @ set: (props) -> for key, val of props cKey = capitalize(key) if 'set' + cKey of @ @['set' + cKey](val) else @element[key] = val @ get: (prop) -> cProp = capitalize(prop) return @[prop] or @['get' + cProp] and @['get' + cProp]() or @element[prop] hide: -> @element.hide() @element.visible = false @ show: -> @element.show() @element.visible = true @ tiBind: (event, fn) -> @element.addEventListener(event, fn) @ tiUnbind: (event, fn) -> @element.removeEventListener(event, fn) @ tiOne: (event, fn) -> @tiBind event, -> @removeEventListener(event, arguments.callee) fn.apply(@, arguments) tiTrigger: -> @element.fireEvent.apply(@element, arguments) @ remove: (el) -> @element.remove(el.element or el) @ step: -> if @animations.length delete @animation @animation = @animations.shift() @element.animate @animation @ animate: (options, callback) -> callbackAndStep = => callback() if callback @step() animation = Ti.UI.createAnimation(options) animation.addEventListener 'complete', callbackAndStep # if @animations then @animations.push animation # else # @animations = [animation] # @step() @animations = [animation] @step() # Globals Tiger = @Tiger = {} module?.exports = Tiger Tiger.version = '0.2.1' Tiger.extend = extend Tiger.makeArray = makeArray Tiger.isArray = Spine.isArray Tiger.Class = Module Tiger.Ajax = Ajax Tiger.Controller = Controller Tiger.Element = Element Tiger.Env = Env Tiger.Events = Spine.Events Tiger.Log = Log Tiger.Model = Spine.Model