UNPKG

@cypress/core-desktop-gui

Version:
254 lines (189 loc) 8.56 kB
@App.module "Routers", (Routers, App, Backbone, Marionette, $, _) -> ## match named params or splats and capture them routeParams = /(:([\w\d]+)|\*([\w\d]+))/g class Routers.Application ## default: add itself to application and register routes initialize: true ## default: don't belong to a module module: undefined ## default: update URL when actions are invoked updateUrl: true ## default: before / after functions prior to invoking actions before: -> after: -> ## maps the actions to the corresponding controller controllerMap: "list" : "List" "show" : "Show" "edit" : "Edit" "new" : "New" "destroy" : "Destroy" constructor: -> ## store the routes key and callback values @routes = @_createRoutes() @handlers = @_createHandlers() ## add to the application if initialize is true ## and we actually have some routes App.addRouter(@) if @initialize and @hasRoutes() ## think about renaming to 'into' or 'load' or 'enter' or 'action' to: (action, options = {}) -> ## add throw here if action doesn't exist? @handlers[action](options) hasRoutes: -> !_.isEmpty(@routes) _createRoutes: -> routes = @_getActions (action, key) => ## return empty object if action is undefined ## or it simply doesn't have a route definition return [] if _.isUndefined(action) or !_(action).has("route") ## return array of route, key [action.route, key] @_toObject(routes) _createHandlers: -> handlers = @_getActions (action, key) => fn = (options) => ## backup our resolve here since its sliced out of the options resolve = options.resolve ## instantiate our controller controller = new (@_getController(action, key))(options) ## attempt to resolve the deferred if its a callable function ## which is the default behavior ## return our controller instance as the resolution resolve?(controller) fn = _.wrap fn, (orig, args...) => ## we'll normalize all of the backbone arguments ## as well as our own function invocation to be ## a POJO options = @_normalizeArguments(args, action, key) df = $.Deferred() ## give our deferred a unique id so ## its more easily tracked df._id = _.uniqueId("deferred") ## set the options default ## to have our resolve object _.defaults options, resolve: df.resolve ## pass our deferred into the controller ## unless we're resolving it by default ## outside of the controller. ## this allows manually control over when ## our deferred is resolved and with what options.deferred = df if not options.resolve before = @_invokeBefore(options, action) return if before is false ## update the url here unless action.updateUrl is false ## or @updateUrl is false @_updateUrl(action, options) if @_shouldUpdateUrl(action, args) ## if before returns a promise, defer kicking off until its done resolved $.when(before).done => orig.call(@, options) return df [key, fn] @_toObject(handlers) _normalizeArguments: (args, action, key) -> ## normalizes the arguments the action is invoked ## with into an object ## if all the arguments are strings parse them into an object ## because we've received them from the backbone router args[0] = @_parseStringMatches(args, action, key) if @_argsArePresentAndStrings(args) ## reset to empty object if null or undefined args[0] ?= {} ## merge defaultParams into args defaultParams = if _.isFunction(action?.defaultParams) then action.defaultParams.call(@) else action?.defaultParams _.defaults args[0], (defaultParams or {}) ## return object return args[0] _parseStringMatches: (args, action, key) -> route = action?.route ## if this is a string and we dont have routes ## throw an error, we must be passed an object throw new Error("Routes must be defined on the action: #{key}") if not route matches = route.match(routeParams) ## just grab the current objects from args ## which represent our current query params queryParams = @_getObjsFromArgs(args) ## slices out any value in args which isnt a string strings = @_getStringsFromArgs(args) ## match named params or splats and capture them as an obj params = _.reduce strings, (memo, arg, i) -> memo[matches[i].slice(1)] = arg memo , {} ## now merge all of the objects together _.extend {}, queryParams, params _getObjsFromArgs: (args) -> ## grab all of the objects in our args args = _(args).filter _.isObject ## merge them all into 1 single object ## giving preference to elements later ## overwriting earlier collisions _.extend {}, args... _getStringsFromArgs: (args) -> _(args).filter _.isString _invokeBefore: (options, action) -> return if not before = @_shouldInvokeBefore(action) ## call the before callback as the instance's context before.call(@, options) _shouldInvokeBefore: (action) -> action?.before or @before _interpolateUrl: (action, options) -> options = _(options).reduce (memo, value, key) -> ## if its not an object or it is an array ## keep it, else discard it if not _.isObject(value) or _.isArray(value) memo[key] = value memo , {} Routes.create action.route, options _updateUrl: (action, options) -> route = @_interpolateUrl(action, options) ## navigate to this route if after we strip off the first forward slash ## these two routes don't match App.visit(route) if App.currentRoute() isnt route.replace(/^\//, "") _shouldUpdateUrl: (action, args) -> ## bail if our action isn't routable ## or if all the args are strings - which means we've just ## navigated to this route and was triggered through the router return false if not action?.route or @_argsArePresentAndStrings(args) ## also bail if our router has a splat in it ## and doesnt provide a routing cue ## so we know how to fill in the values in the splat or id ## assume the :id stuff is a property on the model ## return the actions updateUrl return action?.updateUrl if action and _(action).has("updateUrl") ## else return the value of updateUrl @updateUrl _getActions: (fn) -> _(@actions).map (action, key) => action = _.result(@actions, key) fn(action, key) _getController: (action, key) -> ## grab the controller definined on the action if its a constructor ## else if controller is a string use it based on the module ## or use the default controllerMap off of the module ## should attempt to also classify the key to find a controller switch when _.isFunction(action?.controller) then action.controller when _.isUndefined(@module) then throw new Error("Module must be defined on the resource in order to instantiate a controller") when _.isString(action?.controller) then @_getControllerConstructor(@module[action.controller]) else @_getControllerConstructor(@module[@controllerMap[key]], @controllerMap[key], @module.moduleName) _getControllerConstructor: (obj, key, module) -> ## try to find the Controller constructor on our object try if _.isFunction(obj) then obj else obj.Controller catch err throw new Error("The '#{key}' Controller was not found for for the module: '#{module}'") _argsArePresentAndStrings: (args) -> not _.isEmpty(args) and _.any args, _.isString _toObject: (array) -> ## takes the multi dimensional array ## removes any empty array values ## converts to an object _.chain(array).reject(_.isEmpty).object().value() ## add resource to list of initializers ## figure out its routes and handlers ## instantiate handlers App.addRouter = (resource) -> App.addInitializer -> new Marionette.AppRouter appRoutes: resource.routes controller: resource.handlers