Skip to content

Instantly share code, notes, and snippets.

@mikermcneil
Created September 13, 2017 23:47
Show Gist options
  • Save mikermcneil/2d3ae647038e0b8c38aa43a04b07ae14 to your computer and use it in GitHub Desktop.
Save mikermcneil/2d3ae647038e0b8c38aa43a04b07ae14 to your computer and use it in GitHub Desktop.
A little wrapper to make it easier for you to work with Vue.js files in a hybrid web application. (Designed for use with Sails.js.)
/**
* globalizer.js
* v0.2.1
*
* Copyright 2017, Mike McNeil (@mikermcneil)
* MIT License
*/
(function(global, factory){
var Vue;
var _;
var VueRouter;
var $;
//˙°˚°·.
//‡CJS ˚°˚°·˛
if (typeof exports === 'object' && typeof module !== 'undefined') {
var _require = require;// eslint-disable-line no-undef
var _module = module;// eslint-disable-line no-undef
// required deps:
Vue = _require('vue');
_ = _require('lodash');
// optional deps:
try { VueRouter = _require('vue-router'); } catch (e) { if (e.code === 'MODULE_NOT_FOUND') {/* ok */} else { throw e; } }
try { $ = _require('jquery'); } catch (e) { if (e.code === 'MODULE_NOT_FOUND') {/* ok */} else { throw e; } }
// export:
_module.exports = factory(Vue, _, VueRouter, $);
}
//˙°˚°·
//‡AMD ˚¸
else if(typeof define === 'function' && define.amd) {// eslint-disable-line no-undef
// Register as an anonymous module.
define([], function () {// eslint-disable-line no-undef
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// FUTURE: maybe use optional dep. loading here instead?
// e.g. `function('vue', 'lodash', 'vue-router', 'jquery')`
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// required deps:
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 `globalizer`.)'); }
Vue = global.Vue;
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 `globalizer`.)'); }
_ = global._;
// optional deps:
VueRouter = global.VueRouter || undefined;
$ = global.$ || global.jQuery || undefined;
// So... there's not really a huge point to supporting AMD here--
// except that if you're using it in your project, it makes this
// module fit nicely with the others you're using. And if you
// really hate globals, I guess there's that.
// ¯\_(ツ)_/¯
return factory(Vue, _, VueRouter, $);
});//ƒ
}
//˙°˚˙°·
//‡NUDE ˚°·˛
else {
// required deps:
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 `globalizer`.)'); }
Vue = global.Vue;
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 `globalizer`.)'); }
_ = global._;
// optional deps:
VueRouter = global.VueRouter || undefined;
$ = global.$ || global.jQuery || undefined;
// export:
if (global.globalizer) { throw new Error('Conflicting global (`globalizer`) already exists!'); }
global.globalizer = factory(Vue, _, VueRouter, $);
}
})(this, function (Vue, _, VueRouter, $){
/**
* Module state
*/
// Keep track of whether or not a page script has already been loaded in the DOM.
var didAlreadyLoadPageScript;
// The variable we'll be exporting.
var globalizer;
/**
* Module utilities (private)
*/
function _ensureGlobalCache(){
globalizer._cache = globalizer._cache || {};
};
function _exportOnGlobalCache(moduleName, moduleDefinition){
_ensureGlobalCache();
if (globalizer._cache[moduleName]) { throw new Error('Something else (e.g. a helper or constant) has already been registered under that name (`'+moduleName+'`'); }
globalizer._cache[moduleName] = moduleDefinition;
};
function _exposeJQueryPoweredMethods(def){
if (def.methods && def.methods.$get) { throw new Error('Page script definition contains `methods` with a `$get` key, but you\'re not allowed to override that'); }
if (def.methods && def.methods.$find) { throw new Error('Page script definition contains `methods` with a `$find` key, but you\'re not allowed to override that'); }
def.methods = def.methods || {};
if ($) {
def.methods.$get = function (){ return $(this.$el); };
def.methods.$find = function (subSelector){ return $(this.$el).find(subSelector); };
}
else {
def.methods.$get = function (){ throw new Error('Cannot use .$get() method because, at the time when this page script 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 `globalizer`.)'); };
def.methods.$find = function (){ throw new Error('Cannot use .$find() method because, at the time when this page script 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 `globalizer`.)'); };
}
};
/**
* Module exports
*/
globalizer = {};
/**
* registerHelper()
*
* Build a callable helper function from a helper script definition,
* then attach it to the global namespace so that it can be accessed later
* via `.require()`.
*
* @param {String} helperName
* @param {Function} def
*/
globalizer.registerHelper = function(helperName, def){
// Usage
if (!helperName) { throw new Error('1st argument (helper name) is required'); }
if (!def) { throw new Error('2nd argument (helper definition) is required'); }
if (!_.isFunction(def)) { throw new Error('2nd argument (helper definition) should be a function'); }
// Build callable helper
// > FUTURE: support machine defs
var callableHelper = def;
callableHelper.name = helperName;
// Attach to global cache
_exportOnGlobalCache(helperName, callableHelper);
};
/**
* registerConstant()
*
* Attach a constant to the global namespace so that it can be accessed
* later via `.require()`.
*
* @param {String} constantName
* @param {Ref} value
*/
globalizer.registerConstant = function(constantName, value){
// Usage
if (!constantName) { throw new Error('1st argument (constant name) is required'); }
if (_.isUndefined(value)) { throw new Error('2nd argument (the constant value) is required'); }
// > FUTURE: freeze constant, if supported
// Attach to global cache
_exportOnGlobalCache(constantName, value);
};
/**
* registerPage()
*
* Define a page script, if applicable for the current contents of the DOM.
*
* @param {String} pageName
* @param {Dictionary} def
*
* @returns {Ref} [new vue app thing for this page]
*/
globalizer.registerPage = function(pageName, def){
// Usage
if (!pageName) { throw new Error('1st argument (page name) is required'); }
if (!def) { throw new Error('2nd argument (page script definition) is required'); }
// Only actually build+load this page script if it is relevant for the current contents of the DOM.
if (!document.getElementById(pageName)) { return; }
// Spinlock
if (didAlreadyLoadPageScript) { throw new Error('Cannot load page script (`'+pageName+') because a page script has already been loaded on this page.'); }
didAlreadyLoadPageScript = true;
// Automatically set `el`
if (def.el) { throw new Error('Page script definition contains `el`, but you\'re not allowed to override that'); }
def.el = '#'+pageName;
// Expose extra methods, if jQuery is available.
_exposeJQueryPoweredMethods(def);
// Automatically attach `pageName` to `data`, for convenience.
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'); }
def.data = _.extend({
pageName: pageName
}, def.data||{});
// Attach `goto` method, for convenience.
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'); }
def.methods = def.methods || {};
if (VueRouter) {
def.methods.goto = function (rootRelativeUrlOrOpts){
return this.$router.push(rootRelativeUrlOrOpts);
};
}
else {
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 `globalizer`.)'); };
}
// If virtualPages was specified, check usage and then...
if (def.virtualPages && def.router) { throw new Error('Cannot specify both `virtualPages` AND an actual Vue `router`! Use one or the other.'); }
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 `globalizer`.)'); }
if (!def.virtualPages && def.html5HistoryMode !== undefined) { throw new Error('Cannot specify `html5HistoryMode` without also specifying `virtualPages`!'); }
if (!def.virtualPages && def.beforeEach !== undefined) { throw new Error('Cannot specify `beforeEach` without also specifying `virtualPages`!'); }
if (def.virtualPages) {
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 `globalizer`.)'); }
// Change `virtualPages` in our def so it's the thing that VueRouter expects.
def = _.extend(
{
// Pass in `router`
router: (function(){
var newRouter = new VueRouter({
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// FUTURE: Consider binding popstate handler in order to intercept
// back/fwd button navigation / typing in the URL bar that would send
// the user to another URL under the same domain. This would provide
// a slightly better user experience for certain cases.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
mode: def.html5HistoryMode || 'hash',
routes: _.reduce(def.virtualPages, function(memo, vueComponentDef, urlPattern) {
// Expose extra methods on virtual page script, if jQuery is available.
_exposeJQueryPoweredMethods(vueComponentDef);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// FUTURE: If urlPattern contains a url pattern variable (e.g. `:id`)
// or wildcard "splat" (e.g. `*`), then log a warning reminding whoever
// did it to be careful because of this:
// https://router.vuejs.org/en/essentials/dynamic-matching.html#reacting-to-params-changes
//
// In other words, going between `/foo/3` and `/foo/4` doesn't work as expected.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
memo.push({
path: urlPattern,
component: vueComponentDef
});
return memo;
}, [])
});
if (def.beforeEach) {
newRouter.beforeEach(def.beforeEach);
}//fi
return newRouter;
})(),
},
_.omit(def, ['virtualPages'])
);
}//fi
// Construct Vue instance for this page script.
var vm = new Vue(def);
return vm;
};//ƒ
/**
* registerComponent()
*
* Define a Vue component.
*
* @param {String} componentName
* @param {Dictionary} def
*
* @returns {Ref} [new vue component for this page]
*/
globalizer.registerComponent = function(componentName, def){
// Expose extra methods on component def, if jQuery is available.
_exposeJQueryPoweredMethods(def);
Vue.component(componentName, def);
};
/**
* require()
*
* Require a helper function, constant, virtual page def, or component def from the global namespace.
*
* @param {String} moduleName
* @returns {Ref} [e.g. the callable helper function, the value of the constant, or the definition]
* @throws {Error} if no such module has been registered
*/
globalizer.require = function(moduleName) {
// Usage
if (!moduleName) { throw new Error('1st argument (module name -- i.e. the name of a helper or constant) is required'); }
// Fetch from global cache
_ensureGlobalCache();
if (_.isUndefined(globalizer._cache[moduleName])) {
var err = new Error('No module (helper, constant, virtual page def, or component def) is registered under that name (`'+moduleName+'`');
err.name = 'RequireError';
err.code = 'MODULE_NOT_FOUND';
throw err;
}
return globalizer._cache[moduleName];
};
return globalizer;
});//…)
@mikermcneil
Copy link
Author

Example usage

First, put globalizer in assets/dependencies/globalizer.js, and make sure that stuff is getting loaded first in your pipeline.js file.

Next, make sure you also have Vue.js, lodash (and probably also VueRouter and jQuery, if you want the bonus stuff).

views/pages/login.ejs

<div id="login" v-cloak>
  <div class="container">
  …your stuff here…
  </div>
</div>

assets/styles/pages/login.less

make sure you're importing this in importer.less!

#login {
  //…  your styles here …
}

assets/js/pages/login.page.js

/**
 * Module dependencies
 */

// N/A


globalizer.registerPage('login', {

  //  ╔═╗╔╦╗╔═╗╔╦╗╔═╗
  //  ╚═╗ ║ ╠═╣ ║ ║╣
  //  ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
  data: {

    syncing: false,

    formData: {
      emailAddress: '',
      password: '',
      rememberMe: true,
    },

    validationErrors: {
      emailAddress: false,
      password: false
    },

    hasValidationErrors: false,

    hasServerError: false,

    invalidCredentials: false,

  },


  //  ╦  ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦  ╔═╗
  //  ║  ║╠╣ ║╣ ║  ╚╦╝║  ║  ║╣
  //  ╩═╝╩╚  ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
  beforeMount: function() {
    _.extend(this, SAILS_LOCALS);
  },

  mounted: function() {

    // Focus the first field
    $('#login').find('[name="email"]').focus();

  },


  //  ╔═╗╦  ╦╔═╗╔╗╔╔╦╗╔═╗
  //  ║╣ ╚╗╔╝║╣ ║║║ ║ ╚═╗
  //  ╚═╝ ╚╝ ╚═╝╝╚╝ ╩ ╚═╝
  methods: {

    submitLoginForm: function() {

      // Submit the login form.
      Cloud.login({
        emailAddress: this.formData.emailAddress,
        password: this.formData.password,
        rememberMe: this.formData.rememberMe
      })
      .exec((err)=>{
        this.hasServerError = false;
        this.invalidCredentials = false;
        this.syncing = false;
        if(err) {
          if(err.exit === 'notFound') {
            this.invalidCredentials = true;
            return;
          }

          this.hasServerError = true;
          return;
        }
        // Redirect to the welcome page.
        window.location = '/';
      });
    },

  }
});

@adisazhar123
Copy link

Mike, what is the different between data object (parasails) and data function (vue)?

p.s. I hope I asked it in the right forum.
Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment