parasails.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. /**
  2. * parasails.js
  3. * (lightweight structures for apps with more than one page)
  4. *
  5. * v0.5.0
  6. *
  7. * Copyright 2017, Mike McNeil (@mikermcneil)
  8. * MIT License
  9. * https://www.npmjs.com/package/parasails
  10. * https://sailsjs.com/support
  11. *
  12. * > Parasails is a tiny (but opinionated) and pipeline-agnostic wrapper
  13. * > around Vue.js, jQuery, and Lodash.
  14. */
  15. (function(global, factory){
  16. var Vue;
  17. var _;
  18. var VueRouter;
  19. var $;
  20. //˙°˚°·.
  21. //‡CJS ˚°˚°·˛
  22. if (typeof exports === 'object' && typeof module !== 'undefined') {
  23. var _require = require;// eslint-disable-line no-undef
  24. var _module = module;// eslint-disable-line no-undef
  25. // required deps:
  26. Vue = _require('vue');
  27. _ = _require('lodash');
  28. // optional deps:
  29. try { VueRouter = _require('vue-router'); } catch (e) { if (e.code === 'MODULE_NOT_FOUND') {/* ok */} else { throw e; } }
  30. try { $ = _require('jquery'); } catch (e) { if (e.code === 'MODULE_NOT_FOUND') {/* ok */} else { throw e; } }
  31. // export:
  32. _module.exports = factory(Vue, _, VueRouter, $);
  33. }
  34. //˙°˚°·
  35. //‡AMD ˚¸
  36. else if(typeof define === 'function' && define.amd) {// eslint-disable-line no-undef
  37. // Register as an anonymous module.
  38. define([], function () {// eslint-disable-line no-undef
  39. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  40. // FUTURE: maybe use optional dep. loading here instead?
  41. // e.g. `function('vue', 'lodash', 'vue-router', 'jquery')`
  42. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  43. // required deps:
  44. if (!global.Vue) { throw new Error('`Vue` global does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the Vue.js library is getting brought in before `parasails`.)'); }
  45. Vue = global.Vue;
  46. if (!global._) { throw new Error('`_` global does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the Lodash library is getting brought in before `parasails`.)'); }
  47. _ = global._;
  48. // optional deps:
  49. VueRouter = global.VueRouter || undefined;
  50. $ = global.$ || global.jQuery || undefined;
  51. // So... there's not really a huge point to supporting AMD here--
  52. // except that if you're using it in your project, it makes this
  53. // module fit nicely with the others you're using. And if you
  54. // really hate globals, I guess there's that.
  55. // ¯\_(ツ)_/¯
  56. return factory(Vue, _, VueRouter, $);
  57. });//ƒ
  58. }
  59. //˙°˚˙°·
  60. //‡NUDE ˚°·˛
  61. else {
  62. // required deps:
  63. if (!global.Vue) { throw new Error('`Vue` global does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the Vue.js library is getting brought in before `parasails`.)'); }
  64. Vue = global.Vue;
  65. if (!global._) { throw new Error('`_` global does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the Lodash library is getting brought in before `parasails`.)'); }
  66. _ = global._;
  67. // optional deps:
  68. VueRouter = global.VueRouter || undefined;
  69. $ = global.$ || global.jQuery || undefined;
  70. // export:
  71. if (global.parasails) { throw new Error('Conflicting global (`parasails`) already exists!'); }
  72. global.parasails = factory(Vue, _, VueRouter, $);
  73. }
  74. })(this, function (Vue, _, VueRouter, $){
  75. // ██████╗ ██████╗ ██╗██╗ ██╗ █████╗ ████████╗███████╗
  76. // ██╔══██╗██╔══██╗██║██║ ██║██╔══██╗╚══██╔══╝██╔════╝
  77. // ██████╔╝██████╔╝██║██║ ██║███████║ ██║ █████╗
  78. // ██╔═══╝ ██╔══██╗██║╚██╗ ██╔╝██╔══██║ ██║ ██╔══╝
  79. // ██║ ██║ ██║██║ ╚████╔╝ ██║ ██║ ██║ ███████╗
  80. // ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝
  81. //
  82. // ███████╗████████╗ █████╗ ████████╗███████╗
  83. // ██╔════╝╚══██╔══╝██╔══██╗╚══██╔══╝██╔════╝
  84. // ███████╗ ██║ ███████║ ██║ █████╗
  85. // ╚════██║ ██║ ██╔══██║ ██║ ██╔══╝
  86. // ███████║ ██║ ██║ ██║ ██║ ███████╗
  87. // ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝
  88. //
  89. /**
  90. * Module state
  91. */
  92. // Keep track of whether or not a page script has already been loaded in the DOM.
  93. var didAlreadyLoadPageScript;
  94. // The variable we'll be exporting.
  95. var parasails;
  96. // ██████╗ ██████╗ ██╗██╗ ██╗ █████╗ ████████╗███████╗
  97. // ██╔══██╗██╔══██╗██║██║ ██║██╔══██╗╚══██╔══╝██╔════╝
  98. // ██████╔╝██████╔╝██║██║ ██║███████║ ██║ █████╗
  99. // ██╔═══╝ ██╔══██╗██║╚██╗ ██╔╝██╔══██║ ██║ ██╔══╝
  100. // ██║ ██║ ██║██║ ╚████╔╝ ██║ ██║ ██║ ███████╗
  101. // ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝
  102. //
  103. // ██╗ ██╗████████╗██╗██╗ ███████╗
  104. // ██║ ██║╚══██╔══╝██║██║ ██╔════╝
  105. // ██║ ██║ ██║ ██║██║ ███████╗
  106. // ██║ ██║ ██║ ██║██║ ╚════██║
  107. // ╚██████╔╝ ██║ ██║███████╗███████║
  108. // ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝
  109. //
  110. /**
  111. * Module utilities (private)
  112. */
  113. function _ensureGlobalCache(){
  114. parasails._cache = parasails._cache || {};
  115. }
  116. function _exportOnGlobalCache(moduleName, moduleDefinition){
  117. _ensureGlobalCache();
  118. if (parasails._cache[moduleName]) { throw new Error('Something else (e.g. a utility or constant) has already been registered under that name (`'+moduleName+'`)'); }
  119. parasails._cache[moduleName] = moduleDefinition;
  120. }
  121. function _exposeJQueryPoweredMethods(def, currentModuleEntityNoun){
  122. if (!currentModuleEntityNoun) { throw new Error('Consistency violation: Bad internal usage. '); }
  123. if (def.methods && def.methods.$get) { throw new Error('This '+currentModuleEntityNoun+' contains `methods` with a `$get` key, but you\'re not allowed to override that'); }
  124. if (def.methods && def.methods.$find) { throw new Error('This '+currentModuleEntityNoun+' contains `methods` with a `$find` key, but you\'re not allowed to override that'); }
  125. if (def.methods && def.methods.$focus) { throw new Error('This '+currentModuleEntityNoun+' contains `methods` with a `$focus` key, but you\'re not allowed to override that'); }
  126. def.methods = def.methods || {};
  127. if ($) {
  128. def.methods.$get = function (){ return $(this.$el); };
  129. def.methods.$find = function (subSelector){ return $(this.$el).find(subSelector); };
  130. def.methods.$focus = function (subSelector){
  131. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  132. // FUTURE: If the current Vue thing hasn't mounted yet, throw an error.
  133. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  134. var $fieldToAutoFocus = $(this.$el).find(subSelector);
  135. if ($fieldToAutoFocus.length === 0) { throw new Error('Could not autofocus-- no such element exists within this '+currentModuleEntityNoun+'.'); }
  136. if ($fieldToAutoFocus.length > 1) { throw new Error('Could not autofocus `'+subSelector+'`-- too many elements matched!'); }
  137. $fieldToAutoFocus.focus();
  138. };
  139. }
  140. else {
  141. def.methods.$get = function (){ throw new Error('Cannot use .$get() method because, at the time when this '+currentModuleEntityNoun+' was registered, jQuery (`$`) did not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure jQuery is getting brought in before `parasails`.)'); };
  142. def.methods.$find = function (){ throw new Error('Cannot use .$find() method because, at the time when this '+currentModuleEntityNoun+' was registered, jQuery (`$`) did not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure jQuery is getting brought in before `parasails`.)'); };
  143. def.methods.$focus = function (){ throw new Error('Cannot use .$focus() method because, at the time when this '+currentModuleEntityNoun+' was registered, jQuery (`$`) did not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure jQuery is getting brought in before `parasails`.)'); };
  144. }
  145. }
  146. function _wrapMethodsAndVerifyNoArrowFunctions(def){
  147. def.methods = def.methods || {};
  148. _.each(_.keys(def.methods), function (methodName) {
  149. if (!_.isFunction(def.methods[methodName])) {
  150. throw new Error('Unexpected definition for Vue method `'+methodName+'`. Expecting a function, but got "'+def.methods[methodName]+'"');
  151. }
  152. var isArrowFunction;
  153. try {
  154. var asString = def.methods[methodName].toString();
  155. isArrowFunction = asString.match(/^\s*\(\s*/) || asString.match(/^\s*async\s*\(\s*/);
  156. } catch (err) {
  157. console.warn('Consistency violation: Encountered unexpected error when attempting to verify that Vue method `'+methodName+'` is not an arrow function. (What browser is this?!) Anyway, error details:', err);
  158. }
  159. if (isArrowFunction) {
  160. throw new Error('Unexpected definition for Vue method `'+methodName+'`. Vue methods cannot be specified as arrow functions, because then you wouldn\'t have access to `this` (i.e. the Vue vm instance). Please use a function like `function(){…}` instead.');
  161. }
  162. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  163. // FUTURE:
  164. // Inject a wrapper function in order to provide more advanced / cleaner error handling.
  165. // (especially for AsyncFunctions)
  166. // ```
  167. // var _originalMethod = def.methods[methodName];
  168. // def.methods[methodName] = function(){
  169. //
  170. // var rawResult;
  171. // var originalCtx = this;
  172. // (function(proceed){
  173. // if (_originalMethod.constructor.name === 'AsyncFunction') {
  174. // rawResult = _originalMethod.apply(originalCtx, arguments);
  175. // // The result of an AsyncFunction is always a promise:
  176. // rawResult.catch(function(err) {
  177. // proceed(err);
  178. // });//_∏_
  179. // rawResult.then(function(actualResult){
  180. // return proceed(undefined, actualResult);
  181. // });
  182. // }
  183. // else {
  184. // try {
  185. // rawResult = _originalMethod.apply(originalCtx, arguments);
  186. // } catch (err) { return proceed(err); }
  187. // return proceed(undefined, rawResult);
  188. // }
  189. // })(function(err, actualResult){//eslint-disable-line no-unused-vars
  190. // if (err) {
  191. // // FUTURE: perform more advanced error handling here
  192. // throw err;
  193. // }
  194. //
  195. // // Otherwise do nothing.
  196. //
  197. // });//_∏_ (†)
  198. //
  199. // // For compatibility, return the raw result.
  200. // return rawResult;
  201. //
  202. // };//ƒ
  203. // ```
  204. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  205. });//∞
  206. }
  207. // ███████╗██╗ ██╗██████╗ ██████╗ ██████╗ ████████╗███████╗
  208. // ██╔════╝╚██╗██╔╝██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝██╔════╝
  209. // █████╗ ╚███╔╝ ██████╔╝██║ ██║██████╔╝ ██║ ███████╗
  210. // ██╔══╝ ██╔██╗ ██╔═══╝ ██║ ██║██╔══██╗ ██║ ╚════██║
  211. // ███████╗██╔╝ ██╗██║ ╚██████╔╝██║ ██║ ██║ ███████║
  212. // ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝
  213. //
  214. /**
  215. * Module exports
  216. */
  217. parasails = {};
  218. /**
  219. * registerUtility()
  220. *
  221. * Build a callable utility function, then attach it to the global namespace
  222. * so that it can be accessed later via `.require()`.
  223. *
  224. * @param {String} utilityName
  225. * @param {Function} def
  226. */
  227. parasails.registerUtility = function(utilityName, def){
  228. // Usage
  229. if (!utilityName) { throw new Error('1st argument (utility name) is required'); }
  230. if (!def) { throw new Error('2nd argument (utility function definition) is required'); }
  231. if (!_.isFunction(def)) { throw new Error('2nd argument (utility function definition) should be a function'); }
  232. // Build callable utility
  233. // > FUTURE: also support machine defs?
  234. var callableUtility = def;
  235. callableUtility.name = utilityName;
  236. // Attach to global cache
  237. _exportOnGlobalCache(utilityName, callableUtility);
  238. };
  239. /**
  240. * registerConstant()
  241. *
  242. * Attach a constant to the global namespace so that it can be accessed
  243. * later via `.require()`.
  244. *
  245. * @param {String} constantName
  246. * @param {Ref} value
  247. */
  248. parasails.registerConstant = function(constantName, value){
  249. // Usage
  250. if (!constantName) { throw new Error('1st argument (constant name) is required'); }
  251. if (value === undefined) { throw new Error('2nd argument (the constant value) is required'); }
  252. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  253. // FUTURE: deep-freeze constant, if supported
  254. // (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
  255. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  256. // Attach to global cache
  257. _exportOnGlobalCache(constantName, value);
  258. };
  259. /**
  260. * registerComponent()
  261. *
  262. * Define a Vue component.
  263. *
  264. * @param {String} componentName
  265. * @param {Dictionary} def
  266. *
  267. * @returns {Ref} [new vue component for this page]
  268. */
  269. parasails.registerComponent = function(componentName, def){
  270. // Expose extra methods on component def, if jQuery is available.
  271. _exposeJQueryPoweredMethods(def, 'component');
  272. // Make sure none of the specified Vue methods are defined with any naughty arrow functions.
  273. _wrapMethodsAndVerifyNoArrowFunctions(def);
  274. Vue.component(componentName, def);
  275. };
  276. /**
  277. * require()
  278. *
  279. * Require a utility function or constant from the global namespace.
  280. *
  281. * @param {String} moduleName
  282. * @returns {Ref} [e.g. the callable utility function, or the value of the constant]
  283. * @throws {Error} if no such module has been registered
  284. */
  285. parasails.require = function(moduleName) {
  286. // Usage
  287. if (!moduleName) { throw new Error('1st argument (module name -- i.e. the name of a utility or constant) is required'); }
  288. // Fetch from global cache
  289. _ensureGlobalCache();
  290. if (parasails._cache[moduleName] === undefined) {
  291. var err = new Error('No utility or constant is registered under that name (`'+moduleName+'`)');
  292. err.name = 'RequireError';
  293. err.code = 'MODULE_NOT_FOUND';
  294. throw err;
  295. }
  296. return parasails._cache[moduleName];
  297. };
  298. /**
  299. * registerPage()
  300. *
  301. * Define a page script, if applicable for the current contents of the DOM.
  302. *
  303. * @param {String} pageName
  304. * @param {Dictionary} def
  305. *
  306. * @returns {Ref} [new vue app thing for this page]
  307. */
  308. parasails.registerPage = function(pageName, def){
  309. // Usage
  310. if (!pageName) { throw new Error('1st argument (page name) is required'); }
  311. if (!def) { throw new Error('2nd argument (page script definition) is required'); }
  312. // Only actually build+load this page script if it is relevant for the current contents of the DOM.
  313. if (!document.getElementById(pageName)) { return; }//eslint-disable-line no-undef
  314. // Spinlock
  315. if (didAlreadyLoadPageScript) { throw new Error('Cannot load page script (`'+pageName+') because a page script has already been loaded on this page.'); }
  316. didAlreadyLoadPageScript = true;
  317. // Automatically set `el`
  318. if (def.el) { throw new Error('Page script definition contains `el`, but you\'re not allowed to override that'); }
  319. def.el = '#'+pageName;
  320. // Expose extra methods, if jQuery is available.
  321. _exposeJQueryPoweredMethods(def, 'page script');
  322. // Make sure none of the specified Vue methods are defined with any naughty arrow functions.
  323. _wrapMethodsAndVerifyNoArrowFunctions(def);
  324. // Automatically attach `pageName` to `data`, for convenience.
  325. if (def.data && def.data.pageName) { throw new Error('Page script definition contains `data` with a `pageName` key, but you\'re not allowed to override that'); }
  326. def.data = _.extend({
  327. pageName: pageName
  328. }, def.data||{});
  329. // Attach `goto` method, for convenience.
  330. if (def.methods && def.methods.goto) { throw new Error('Page script definition contains `methods` with a `goto` key-- but you\'re not allowed to override that'); }
  331. def.methods = def.methods || {};
  332. if (VueRouter) {
  333. def.methods.goto = function (rootRelativeUrlOrOpts){
  334. return this.$router.push(rootRelativeUrlOrOpts);
  335. };
  336. }
  337. else {
  338. def.methods.goto = function (){ throw new Error('Cannot use .goto() method because, at the time when this page script was registered, VueRouter did not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure VueRouter is getting brought in before `parasails`.)'); };
  339. }
  340. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  341. // FUTURE: Make sure we didn't type "beforeMounted" or "beforeDestroyed" because those aren't real things
  342. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  343. // If virtualPages was specified, check usage and then...
  344. if (def.virtualPages && def.router) { throw new Error('Cannot specify both `virtualPages` AND an actual Vue `router`! Use one or the other.'); }
  345. if (def.router && !VueRouter) { throw new Error('Cannot use `router`, because that depends on the Vue Router. But `VueRouter` does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the VueRouter plugin is getting brought in before `parasails`.)'); }
  346. if (!def.virtualPages && def.html5HistoryMode !== undefined) { throw new Error('Cannot specify `html5HistoryMode` without also specifying `virtualPages`!'); }
  347. if (!def.virtualPages && def.beforeEach !== undefined) { throw new Error('Cannot specify `beforeEach` without also specifying `virtualPages`!'); }
  348. if ((def.beforeNavigate || def.afterNavigate) && def.virtualPages !== true) { throw new Error('Cannot specify `beforeNavigate` or `afterNavigate` unless you set `virtualPages: true`!'); }
  349. if (def.virtualPages) {
  350. if (!VueRouter) { throw new Error('Cannot use `virtualPages`, because it depends on the Vue Router. But `VueRouter` does not exist on the page yet. (If you\'re using Sails, please check dependency loading order in pipeline.js and make sure the VueRouter plugin is getting brought in before `parasails`.)'); }
  351. // Now we'll replace `virtualPages` in our def with the thing that VueRouter actually expects:
  352. // If `virtualPages: true` was specified, then use reasonable defaults:
  353. //
  354. // > Note: This assumes that, somewhere within the parent page's template, there is:
  355. // > ```
  356. // > <router-view></router-view>
  357. // > ```
  358. if (def.virtualPages === true) {
  359. if (def.beforeEach !== undefined) { throw new Error('Cannot specify `virtualPages: true` AND `beforeEach` at the same time!'); }
  360. if (!def.virtualPagesRegExp && def.html5HistoryMode === 'history') { throw new Error('If `html5HistoryMode: \'history\'` is specified, then virtualPagesRegExp must also be specified!'); }
  361. if (def.virtualPagesRegExp && !_.isRegExp(def.virtualPagesRegExp)) { throw new Error('Invalid `virtualPagesRegExp`: If specified, this must be a regular expression -- e.g. `/^\/manage\/access\/?([^\/]+)?/`'); }
  362. // Check for <router-view> element
  363. // (to provide a better error msg if it was omitted)
  364. var customBeforeMountLC;
  365. if (def.beforeMount) {
  366. customBeforeMountLC = def.beforeMount;
  367. }//fi
  368. def.beforeMount = function(){
  369. // Inject additional code to check for <router-view> element:
  370. // console.log('this.$find(\'router-view\').length', this.$find('router-view').length);
  371. if (this.$find('router-view').length === 0) {
  372. throw new Error(
  373. 'Cannot mount this page with `virtualPages: true` because no '+
  374. '<router-view> element exists in this page\'s HTML.\n'+
  375. 'Please be sure the HTML includes:\n'+
  376. '\n'+
  377. '```\n'+
  378. '<router-view></router-view>\n'+
  379. '```\n'
  380. );
  381. }//•
  382. // Then call the original, custom "beforeMount" function, if there was one.
  383. if (customBeforeMountLC) {
  384. customBeforeMountLC.apply(this, []);
  385. }
  386. };//ƒ
  387. if (def.methods._navigate) {
  388. throw new Error('Could not use `virtualPages: true`, because a conflicting `_navigate` method is defined. Please remove it, or do something else.');
  389. }
  390. // Set up local variables to refer to things in `def`, since it will be changing below.
  391. var pathMatchingRegExp;
  392. if (def.html5HistoryMode === 'history') {
  393. pathMatchingRegExp = def.virtualPagesRegExp;
  394. } else {
  395. pathMatchingRegExp = /.*/;
  396. }
  397. var beforeNavigate = def.beforeNavigate;
  398. var afterNavigate = def.afterNavigate;
  399. // Now modify the definition's methods and remove all relevant top-level props understood
  400. // by parasails (but not by Vue.js) to avoid creating any weird additional dependence on
  401. // parasails features beyond the expected usage.
  402. def.methods = _.extend(def.methods||{}, {
  403. _navigate: function(virtualPageSlug){
  404. if (beforeNavigate) {
  405. var doCancelNavigate = beforeNavigate.apply(this, [ virtualPageSlug ]);
  406. if (doCancelNavigate === false) {
  407. return;
  408. }//•
  409. }
  410. this.virtualPageSlug = virtualPageSlug;
  411. // console.log('navigate! Got:', arguments);
  412. // console.log('Navigated. (Set `this.virtualPageSlug=\''+virtualPageSlug+'\'`)');
  413. if (afterNavigate) {
  414. afterNavigate.apply(this, [ virtualPageSlug ]);
  415. }
  416. }
  417. });
  418. def = _.extend({
  419. router: new VueRouter({
  420. mode: def.html5HistoryMode || 'hash',
  421. routes: [
  422. {
  423. path: '*',
  424. component: (function(){
  425. var vueComponentDef = {
  426. render: function(){},
  427. beforeRouteUpdate: function (to,from,next){
  428. // this.$emit('navigate', to.path); <<old way
  429. var path = to.path;
  430. var matches = path.match(pathMatchingRegExp);
  431. if (!matches) { throw new Error('Could not match current URL path (`'+path+'`) as a virtual page. Please check the `virtualPagesRegExp` -- e.g. `/^\/foo\/bar\/?([^\/]+)?/`'); }
  432. // console.log('this.$parent', this.$parent);
  433. this.$parent._navigate(matches[1]||'');
  434. // this.$emit('navigate', {
  435. // rawPath: path,
  436. // virtualPageSlug: matches[1]||''
  437. // });
  438. return next();
  439. },
  440. mounted: function(){
  441. // this.$emit('navigate', this.$route.path); <<old way
  442. var path = this.$route.path;
  443. var matches = path.match(pathMatchingRegExp);
  444. if (!matches) { throw new Error('Could not match current URL path (`'+path+'`) as a virtual page. Please check the `virtualPagesRegExp` -- e.g. `/^\/foo\/bar\/?([^\/]+)?/`'); }
  445. this.$parent._navigate(matches[1]||'');
  446. // this.$emit('navigate', {
  447. // rawPath: path,
  448. // virtualPageSlug: matches[1]||''
  449. // });
  450. }
  451. };
  452. // Expose extra methods on virtual page script, if jQuery is available.
  453. _exposeJQueryPoweredMethods(vueComponentDef, 'virtual page');
  454. // Make sure none of the specified Vue methods are defined with any naughty arrow functions.
  455. _wrapMethodsAndVerifyNoArrowFunctions(vueComponentDef);
  456. return vueComponentDef;
  457. })()
  458. }
  459. ],
  460. })
  461. }, _.omit(def, ['virtualPages', 'virtualPagesRegExp', 'html5HistoryMode', 'beforeNavigate', 'afterNavigate']));
  462. }
  463. // Otherwise, if a dictionary of `virtualPages` was specified, use those client-side
  464. // routes to configure VueRouter.
  465. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  466. // FUTURE: Re-evaluate this. This usage will probably change!
  467. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  468. else if (_.isObject(def.virtualPages) && !_.isArray(def.virtualPages) && !_.isFunction(def.virtualPages)) {
  469. if (def.virtualPagesRegExp) { throw new Error('Cannot use `virtualPagesRegExp` with current `virtualPages` setting. To use the regexp, you must use `virtualPages: true`.'); }
  470. def = _.extend(
  471. {
  472. // Pass in `router`
  473. router: (function(){
  474. var newRouter = new VueRouter({
  475. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  476. // FUTURE: Consider binding popstate handler in order to intercept
  477. // back/fwd button navigation / typing in the URL bar that would send
  478. // the user to another URL under the same domain. This would provide
  479. // a slightly better user experience for certain cases.
  480. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  481. mode: def.html5HistoryMode || 'hash',
  482. routes: _.reduce(def.virtualPages, function(memo, vueComponentDef, urlPattern) {
  483. // Expose extra methods on virtual page script, if jQuery is available.
  484. _exposeJQueryPoweredMethods(vueComponentDef, 'virtual page');
  485. // Make sure none of the specified Vue methods are defined with any naughty arrow functions.
  486. _wrapMethodsAndVerifyNoArrowFunctions(vueComponentDef);
  487. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  488. // FUTURE: If urlPattern contains a url pattern variable (e.g. `:id`)
  489. // or wildcard "splat" (e.g. `*`), then log a warning reminding whoever
  490. // did it to be careful because of this:
  491. // https://router.vuejs.org/en/essentials/dynamic-matching.html#reacting-to-params-changes
  492. //
  493. // In other words, going between `/foo/3` and `/foo/4` doesn't work as expected.
  494. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  495. memo.push({
  496. path: urlPattern,
  497. component: vueComponentDef
  498. });
  499. return memo;
  500. }, [])
  501. });
  502. if (def.beforeEach) {
  503. newRouter.beforeEach(def.beforeEach);
  504. }//fi
  505. return newRouter;
  506. })(),
  507. },
  508. _.omit(def, ['virtualPages', 'html5HistoryMode', 'beforeEach'])
  509. );
  510. }
  511. else {
  512. throw new Error('Cannot use `virtualPages` because the specified value doesn\'t match any recognized meaning. Please specify either `true` (for the default handling) or a dictionary of client-side routing rules.');
  513. }
  514. }//fi
  515. // Construct Vue instance for this page script.
  516. var vm = new Vue(def);
  517. return vm;
  518. };//ƒ
  519. return parasails;
  520. });//…)