Initial commit of the version 2 rewrite
This commit is contained in:
parent
d3a1aea436
commit
27bd2bda26
18
.babelrc
18
.babelrc
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
"stage-2",
|
|
||||||
["env", {
|
|
||||||
"browsers": "ie >= 11"
|
|
||||||
}]
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
[
|
|
||||||
"transform-runtime", {
|
|
||||||
"helpers": false,
|
|
||||||
"polyfill": false,
|
|
||||||
"regenerator": true,
|
|
||||||
"moduleName": "babel-runtime"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
1
.cache/04/3a4f461d9fc91ccb6102017dbce1c9.json
Normal file
1
.cache/04/3a4f461d9fc91ccb6102017dbce1c9.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"id":"FormulateInput.js","dependencies":[{"name":"/Users/justinschroeder/Projects/vue-formulate/package.json","includedInParent":true,"mtime":1570029289731}],"generated":{"js":"var template;\ntemplate += '<div class=\"formulate-input\"></div>';\nmodule.exports = {\n template: template\n};"},"sourceMaps":{"js":{"mappings":[{"generated":{"line":1,"column":0},"source":"FormulateInput.js","original":{"line":1,"column":0}},{"name":"template","generated":{"line":1,"column":4},"source":"FormulateInput.js","original":{"line":1,"column":4}},{"generated":{"line":1,"column":12},"source":"FormulateInput.js","original":{"line":1,"column":0}},{"name":"template","generated":{"line":2,"column":0},"source":"FormulateInput.js","original":{"line":2,"column":0}},{"generated":{"line":2,"column":8},"source":"FormulateInput.js","original":{"line":2,"column":8}},{"generated":{"line":2,"column":12},"source":"FormulateInput.js","original":{"line":2,"column":12}},{"generated":{"line":2,"column":49},"source":"FormulateInput.js","original":{"line":2,"column":0}},{"name":"module","generated":{"line":3,"column":0},"source":"FormulateInput.js","original":{"line":4,"column":0}},{"generated":{"line":3,"column":6},"source":"FormulateInput.js","original":{"line":4,"column":6}},{"name":"exports","generated":{"line":3,"column":7},"source":"FormulateInput.js","original":{"line":4,"column":7}},{"generated":{"line":3,"column":14},"source":"FormulateInput.js","original":{"line":4,"column":0}},{"generated":{"line":3,"column":17},"source":"FormulateInput.js","original":{"line":4,"column":17}},{"name":"template","generated":{"line":4,"column":0},"source":"FormulateInput.js","original":{"line":5,"column":2}},{"name":"template","generated":{"line":4,"column":2},"source":"FormulateInput.js","original":{"line":5,"column":2}},{"generated":{"line":4,"column":10},"source":"FormulateInput.js","original":{"line":5,"column":10}},{"name":"template","generated":{"line":4,"column":12},"source":"FormulateInput.js","original":{"line":5,"column":12}},{"generated":{"line":5,"column":0},"source":"FormulateInput.js","original":{"line":4,"column":17}},{"generated":{"line":5,"column":1},"source":"FormulateInput.js","original":{"line":4,"column":0}}],"sources":{"FormulateInput.js":"var template\ntemplate += '<div class=\"formulate-input\"></div>'\n\nmodule.exports = {\n template: template\n}\n"},"lineCount":null}},"error":null,"hash":"22dfb709434432cfadc29ec640064600","cacheData":{"env":{}}}
|
1
.cache/05/caa36fd98cf97e55fa7bfd3719dbdd.json
Normal file
1
.cache/05/caa36fd98cf97e55fa7bfd3719dbdd.json
Normal file
File diff suppressed because one or more lines are too long
1
.cache/12/735df070acd1ce86c5aed445df4bf1.json
Normal file
1
.cache/12/735df070acd1ce86c5aed445df4bf1.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"id":"../node_modules/isobject/index.js","dependencies":[{"name":"/Users/justinschroeder/Projects/vue-formulate/package.json","includedInParent":true,"mtime":1570029289731},{"name":"/Users/justinschroeder/Projects/vue-formulate/node_modules/isobject/package.json","includedInParent":true,"mtime":1570025459931}],"generated":{"js":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = isObject;\n\n/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\nfunction isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n}\n\n;"},"sourceMaps":{"js":{"mappings":[{"generated":{"line":8,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":1,"column":0}},{"generated":{"line":14,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":15}},{"name":"isObject","generated":{"line":14,"column":9},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":24}},{"generated":{"line":14,"column":17},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":15}},{"name":"val","generated":{"line":14,"column":18},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":33}},{"generated":{"line":14,"column":21},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":15}},{"generated":{"line":14,"column":23},"source":"../node_modules/isobject/index.js","original":{"line":8,"column":38}},{"generated":{"line":15,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":2}},{"name":"val","generated":{"line":15,"column":9},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":9}},{"generated":{"line":15,"column":12},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":12}},{"generated":{"line":15,"column":16},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":16}},{"generated":{"line":15,"column":20},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":9}},{"generated":{"line":15,"column":24},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":24}},{"name":"val","generated":{"line":15,"column":31},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":31}},{"generated":{"line":15,"column":34},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":24}},{"generated":{"line":15,"column":39},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":39}},{"generated":{"line":15,"column":47},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":9}},{"name":"Array","generated":{"line":15,"column":51},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":51}},{"generated":{"line":15,"column":56},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":56}},{"name":"isArray","generated":{"line":15,"column":57},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":57}},{"generated":{"line":15,"column":64},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":51}},{"name":"val","generated":{"line":15,"column":65},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":65}},{"generated":{"line":15,"column":68},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":51}},{"generated":{"line":15,"column":74},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":74}},{"generated":{"line":15,"column":79},"source":"../node_modules/isobject/index.js","original":{"line":9,"column":2}},{"generated":{"line":16,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1}},{"generated":{"line":18,"column":0},"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1}}],"sources":{"../node_modules/isobject/index.js":"/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\nexport default function isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n};\n"},"lineCount":null}},"error":null,"hash":"6f384709c2688dd9587aa95ad5f2aadc","cacheData":{"env":{}}}
|
1
.cache/1e/78c811898ee87048ab5f0f61513488.json
Normal file
1
.cache/1e/78c811898ee87048ab5f0f61513488.json
Normal file
File diff suppressed because one or more lines are too long
1
.cache/68/46c7a8bab070f5c3cc5044dd457250.json
Normal file
1
.cache/68/46c7a8bab070f5c3cc5044dd457250.json
Normal file
File diff suppressed because one or more lines are too long
1
.cache/79/174177a894587743e66f3567d8a6a4.json
Normal file
1
.cache/79/174177a894587743e66f3567d8a6a4.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"id":"2368","dependencies":[{"name":"/Users/justinschroeder/Projects/vue-formulate/package.json","includedInParent":true,"mtime":1570038445966},{"name":"/Users/justinschroeder/Projects/vue-formulate/node_modules/isobject/package.json","includedInParent":true,"mtime":1570025459931}],"generated":{"js":"\"use strict\";function e(e){return null!=e&&\"object\"==typeof e&&!1===Array.isArray(e)}Object.defineProperty(exports,\"__esModule\",{value:!0}),exports.default=e;"},"sourceMaps":{"js":{"mappings":[{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":0}},{"source":"../node_modules/isobject/index.js","original":{"line":8,"column":15},"generated":{"line":1,"column":13}},{"source":"../node_modules/isobject/index.js","name":"isObject","original":{"line":8,"column":24},"generated":{"line":1,"column":22}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":8,"column":33},"generated":{"line":1,"column":24}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":9,"column":9},"generated":{"line":1,"column":27}},{"source":"../node_modules/isobject/index.js","original":{"line":9,"column":16},"generated":{"line":1,"column":34}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":9,"column":9},"generated":{"line":1,"column":40}},{"source":"../node_modules/isobject/index.js","original":{"line":9,"column":39},"generated":{"line":1,"column":43}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":9,"column":31},"generated":{"line":1,"column":60}},{"source":"../node_modules/isobject/index.js","original":{"line":9,"column":74},"generated":{"line":1,"column":64}},{"source":"../node_modules/isobject/index.js","name":"Array","original":{"line":9,"column":51},"generated":{"line":1,"column":68}},{"source":"../node_modules/isobject/index.js","name":"isArray","original":{"line":9,"column":57},"generated":{"line":1,"column":74}},{"source":"../node_modules/isobject/index.js","name":"val","original":{"line":9,"column":65},"generated":{"line":1,"column":82}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":85}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":92}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":107}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":115}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":128}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":129}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":136}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":140}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":148}},{"source":"../node_modules/isobject/index.js","original":{"line":10,"column":1},"generated":{"line":1,"column":156}}],"sources":{"../node_modules/isobject/index.js":"/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\nexport default function isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n};\n"},"lineCount":null}},"error":null,"hash":"f6e2f7389bba4628636f1d49740fe136","cacheData":{"env":{}}}
|
1
.cache/dc/b972228de3c56ec1eaff4844cbdf48.json
Normal file
1
.cache/dc/b972228de3c56ec1eaff4844cbdf48.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"id":"vLuo","dependencies":[],"generated":{"js":"\n(function(){var t=exports.default||module.exports;\"function\"==typeof t&&(t=t.options),Object.assign(t,{render:function(){var t=this.$createElement;return(this._self._c||t)(\"div\",{staticClass:\"formulate-input\"},[this._v(\" Formulate input!\\n\")])},staticRenderFns:[],_compiled:!0,_scopeId:null,functional:void 0});})();"},"sourceMaps":null,"error":null,"hash":"376c05db7b07c17660e31aef0b2213fa","cacheData":{}}
|
1
.cache/df/ed18e086c94069c456e7803ef4a863.json
Normal file
1
.cache/df/ed18e086c94069c456e7803ef4a863.json
Normal file
File diff suppressed because one or more lines are too long
1
.cache/e6/435a82638fb431be5676d3df0ad545.json
Normal file
1
.cache/e6/435a82638fb431be5676d3df0ad545.json
Normal file
File diff suppressed because one or more lines are too long
1
.cache/f1/bfa45b351fd450a7372dcfb2e94a6c.json
Normal file
1
.cache/f1/bfa45b351fd450a7372dcfb2e94a6c.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"id":"y3nB","dependencies":[{"name":"/Users/justinschroeder/Projects/vue-formulate/package.json","includedInParent":true,"mtime":1570038445966}],"generated":{"js":"var e;e+='<div class=\"formulate-input\">hi!</div>',module.exports={template:e};"},"sourceMaps":{"js":{"mappings":[{"source":"FormulateInput.js","original":{"line":1,"column":0},"generated":{"line":1,"column":0}},{"source":"FormulateInput.js","name":"template","original":{"line":1,"column":4},"generated":{"line":1,"column":4}},{"source":"FormulateInput.js","name":"template","original":{"line":2,"column":0},"generated":{"line":1,"column":6}},{"source":"FormulateInput.js","original":{"line":2,"column":12},"generated":{"line":1,"column":9}},{"source":"FormulateInput.js","name":"module","original":{"line":4,"column":0},"generated":{"line":1,"column":50}},{"source":"FormulateInput.js","name":"exports","original":{"line":4,"column":7},"generated":{"line":1,"column":57}},{"source":"FormulateInput.js","original":{"line":4,"column":17},"generated":{"line":1,"column":65}},{"source":"FormulateInput.js","name":"template","original":{"line":5,"column":2},"generated":{"line":1,"column":66}},{"source":"FormulateInput.js","name":"template","original":{"line":5,"column":12},"generated":{"line":1,"column":75}}],"sources":{"FormulateInput.js":"var template\ntemplate += '<div class=\"formulate-input\">hi!</div>'\n\nmodule.exports = {\n template: template\n}\n"},"lineCount":null}},"error":null,"hash":"fc879b8fcc134381138418e21efeb502","cacheData":{"env":{}}}
|
@ -1 +1,2 @@
|
|||||||
dist/*
|
dist/*
|
||||||
|
test/*
|
||||||
|
26
.eslintrc.js
26
.eslintrc.js
@ -1,22 +1,22 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true
|
|
||||||
},
|
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
parser: 'babel-eslint'
|
parser: 'babel-eslint',
|
||||||
|
sourceType: 'module'
|
||||||
},
|
},
|
||||||
|
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
|
||||||
extends: [
|
extends: [
|
||||||
'standard',
|
'standard',
|
||||||
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
|
'plugin:vue/recommended'
|
||||||
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
|
|
||||||
'plugin:vue/recommended',
|
|
||||||
],
|
|
||||||
// required to lint *.vue files
|
|
||||||
plugins: [
|
|
||||||
'vue'
|
|
||||||
],
|
],
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
},
|
||||||
// add your custom rules here
|
// add your custom rules here
|
||||||
rules: {}
|
'rules': {
|
||||||
|
// allow paren-less arrow functions
|
||||||
|
'arrow-parens': 0,
|
||||||
|
// allow debugger during development
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
227
README.md
227
README.md
@ -1,228 +1,5 @@
|
|||||||
# Vue Formulate
|
# Vue Formulate
|
||||||
---------------
|
---------------
|
||||||
[![Build Status](https://travis-ci.org/wearebraid/vue-formulate.svg?branch=master)](https://travis-ci.org/wearebraid/vue-formulate)
|
[![Build Status](https://travis-ci.com/wearebraid/vue-formulate-next.svg?token=4eHp5aiDcHwjrb1T8zpy&branch=version-2)](https://travis-ci.com/wearebraid/vue-formulate-next)
|
||||||
[![Current Version](https://img.shields.io/npm/v/vue-formulate.svg)](https://www.npmjs.com/package/vue-formulate)
|
|
||||||
[![License](https://img.shields.io/github/license/wearebraid/vue-formulate.svg)](https://github.com/wearebraid/vue-formulate/blob/master/LICENSE.txt)
|
|
||||||
|
|
||||||
### What is it?
|
Documentation development is taking place on the [vueformulate.com](https://github.com/wearebraid/vueformulate.com/tree/version-2) repository.
|
||||||
|
|
||||||
Vue Formulate is a [Vue](https://vuejs.org/) plugin that exposes an elegant
|
|
||||||
mechanism for building and validating forms with a centralized data store.
|
|
||||||
|
|
||||||
### Show and tell
|
|
||||||
|
|
||||||
You'll find an easy to use example, in [the example directory](https://github.com/wearebraid/vue-formulate/tree/master/example)
|
|
||||||
as well as a live demo available at: [demo.vueformulate.com](https://demo.vueformulate.com).
|
|
||||||
|
|
||||||
### Get Started
|
|
||||||
|
|
||||||
#### Download
|
|
||||||
First download the `vue-formulate` package from npm:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install vue-formulate
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to use ES6 features in your project
|
|
||||||
(and this readme assumes you do), then you'll also
|
|
||||||
need Babel:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
babel-preset-env
|
|
||||||
babel-preset-stage-2
|
|
||||||
```
|
|
||||||
|
|
||||||
Many Vue/Vuex projects require Babel’s `stage-2` preset.
|
|
||||||
Best practice is to include a `.babelrc` in the project
|
|
||||||
root:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
{
|
|
||||||
"presets": [
|
|
||||||
["env", { "modules": false }],
|
|
||||||
"stage-2"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Installation
|
|
||||||
|
|
||||||
Install `vue-formulate` like any other vue plugin:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import Vue from 'vue'
|
|
||||||
import formulate from 'vue-formulate'
|
|
||||||
|
|
||||||
Vue.use(formulate)
|
|
||||||
```
|
|
||||||
#### Vuex
|
|
||||||
`vue-formulate` needs to be linked to your vuex store. Vuex can be
|
|
||||||
configured as a single root store, or as namespaced modules and `vue-formualte`
|
|
||||||
can work with either setup.
|
|
||||||
|
|
||||||
**Vuex Module**
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {formulateModule} from 'vue-formulate'
|
|
||||||
|
|
||||||
export default formulateModule('namespace')
|
|
||||||
```
|
|
||||||
|
|
||||||
Using a namespaced vuex module is the recommended installation method. Just be
|
|
||||||
sure to replace `'namespace'` with the namespace of your vuex module.
|
|
||||||
|
|
||||||
Additionally, when using a vuex namespace, you _must_ also pass the namespace
|
|
||||||
in the Vue plugin installation call:
|
|
||||||
|
|
||||||
```js
|
|
||||||
Vue.use(formulate, {vuexModule: 'namespace'})
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can install `vue-formulate`'s store elements to your vuex
|
|
||||||
root store:
|
|
||||||
|
|
||||||
**Root Store**
|
|
||||||
|
|
||||||
```js
|
|
||||||
import Vue from 'vue'
|
|
||||||
import Vuex from 'vuex'
|
|
||||||
import {formulateState, formulateGetters, formulateMutations} from 'vue-formulate'
|
|
||||||
|
|
||||||
Vue.use(Vuex)
|
|
||||||
|
|
||||||
const state = () => ({
|
|
||||||
// your own state data can live next to vue-formulate's data
|
|
||||||
// Note: formulateState is a curried function.
|
|
||||||
your: 'data',
|
|
||||||
...formulateState()()
|
|
||||||
})
|
|
||||||
|
|
||||||
const getters = {
|
|
||||||
// Your own getters can live next to vue-formulate's getters
|
|
||||||
yourGetter (state) {
|
|
||||||
return state.your
|
|
||||||
},
|
|
||||||
...formulateGetters()
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
// Your own mutations can live next to vue-formulate's mutations
|
|
||||||
setYour (state, payload) {
|
|
||||||
state.your = payload
|
|
||||||
},
|
|
||||||
...formulateMutations()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new Vuex.Store({
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
mutations
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
`vue-formulate` automatically registers two components `formulate` and
|
|
||||||
`formulate-element`. These two elements are able to address most of your form
|
|
||||||
building needs. Here's a simple example:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<formulate name="registration">
|
|
||||||
<formulate-element
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
/>
|
|
||||||
...more formulate-elements
|
|
||||||
</formulate>
|
|
||||||
```
|
|
||||||
|
|
||||||
You can think of `<formulate>` elements a little bit like traditional
|
|
||||||
`<form>` tags. You _must_ wrap your `formulate-element` components
|
|
||||||
in a `<formulate>` component. The `formulate` component has a single
|
|
||||||
required prop `name` which creates the form’s key in the vuex store.
|
|
||||||
|
|
||||||
All `formulate-element` components nested inside a `<formulate>`
|
|
||||||
component will automatically commit mutations directly to the
|
|
||||||
store. The store becomes a live representation of all your form’s
|
|
||||||
values.
|
|
||||||
|
|
||||||
The `formulate-element` component is a powerful component which handles field
|
|
||||||
generation.
|
|
||||||
|
|
||||||
### Validation Rules
|
|
||||||
|
|
||||||
There are several built-in validation methods and you can easily add your own as well.
|
|
||||||
|
|
||||||
Rule | Arguments
|
|
||||||
----------|---------------
|
|
||||||
required | *none*
|
|
||||||
email | *none*
|
|
||||||
confirmed | confirmation field
|
|
||||||
number | *none*
|
|
||||||
|
|
||||||
You can add as many validation rules as you want to each `formulate-element`,
|
|
||||||
simply chain your rules with pipes `|'. Additional arguments can be passed to
|
|
||||||
validation rules by using parenthesis after the rule name:
|
|
||||||
|
|
||||||
```
|
|
||||||
validation="required|confirmed(confirmation_field)"
|
|
||||||
```
|
|
||||||
|
|
||||||
The field label used in built-in validation methods is the `validation-label`
|
|
||||||
attribute on your `formulate-element`. If no `validation-label` is found then
|
|
||||||
the `label` attribute is used, and if no `label` attribute is found it will
|
|
||||||
fall back to the field’s `name` attribute (which is required).
|
|
||||||
|
|
||||||
#### Custom Validation Rules
|
|
||||||
|
|
||||||
Validation rules are easy to write! They're just simple functions that are
|
|
||||||
always passed at least one argument, an object containing the `field` name,
|
|
||||||
`value` of the field, validation `label`, `error` function to generate an error
|
|
||||||
message, and an object containing all the `values` for the entire form.
|
|
||||||
|
|
||||||
Additionally, validation rules can pass an unlimited number of extra arguments.
|
|
||||||
These arguments are passed as the 2nd-nth arguments to the validation rule.
|
|
||||||
Their values are parsed from the optional parenthesis in the validation
|
|
||||||
attribute on the `formulate-element`.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<formulate-element
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
label="Password"
|
|
||||||
validation="confirmed(password_confirmation_field)"
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Validation rules should return an error message string if they failed, or
|
|
||||||
`false` if the input data is valid.
|
|
||||||
|
|
||||||
Adding your own validation rules is easy. Just pass an additional object
|
|
||||||
of rule functions in the plugin’s installation call:
|
|
||||||
|
|
||||||
```js
|
|
||||||
Vue.use(formulate, {
|
|
||||||
rules: {
|
|
||||||
isPizza ({field, value, error, values, label}) {
|
|
||||||
return value === 'pizza' ? false : `label is not pizza.`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Styling
|
|
||||||
|
|
||||||
Absolutely zero styles are included so feel free to write your own! The
|
|
||||||
`form-element` components have a wrapper `div` that receives the following
|
|
||||||
classes:
|
|
||||||
|
|
||||||
```
|
|
||||||
formulate-element
|
|
||||||
formulate-element--has-value
|
|
||||||
formulate-element--has-errors
|
|
||||||
```
|
|
||||||
|
|
||||||
### Full Documentation
|
|
||||||
|
|
||||||
There are many more options available, more documentation coming soon.
|
|
||||||
|
13
babel.config.js
Normal file
13
babel.config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
test: {
|
||||||
|
presets: [
|
||||||
|
[ '@babel/preset-env', {
|
||||||
|
targets: {
|
||||||
|
node: 'current'
|
||||||
|
}
|
||||||
|
} ]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
build/rollup.config.js
Normal file
24
build/rollup.config.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import commonjs from 'rollup-plugin-commonjs' // Convert CommonJS modules to ES6
|
||||||
|
import vue from 'rollup-plugin-vue' // Handle .vue SFC files
|
||||||
|
import buble from 'rollup-plugin-buble' // Transpile/polyfill with reasonable browser support
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/Formulate.js', // Path relative to package.json
|
||||||
|
output: {
|
||||||
|
name: 'Formulate',
|
||||||
|
exports: 'named',
|
||||||
|
globals: {
|
||||||
|
'is-plain-object': 'isPlainObject',
|
||||||
|
'nanoid': 'nanoid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
external: ['is-plain-object', 'nanoid'],
|
||||||
|
plugins: [
|
||||||
|
commonjs(),
|
||||||
|
vue({
|
||||||
|
css: true, // Dynamically inject css as a <style> tag
|
||||||
|
compileTemplate: true // Explicitly convert template to render function
|
||||||
|
}),
|
||||||
|
buble() // Transpile to ES5
|
||||||
|
]
|
||||||
|
}
|
1308
dist/formulate.esm.js
vendored
Normal file
1308
dist/formulate.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1315
dist/formulate.min.js
vendored
Normal file
1315
dist/formulate.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/formulate.min.js.map
vendored
Normal file
1
dist/formulate.min.js.map
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["../node_modules/isobject/index.js","../node_modules/is-plain-object/index.js","Formulate.js"],"names":["isObject","val","Array","isArray","isObjectObject","o","Object","prototype","toString","call","isPlainObject","ctor","prot","constructor","hasOwnProperty","require","FormulateInput","Formulate","defaults","components","install","Vue","options","componentName","$formulate","extend","component","base","extendWith","merged","key","prop","module","exports"],"mappings":";AASC,aAFc,SAASA,EAASC,GACxBA,OAAO,MAAPA,GAA8B,iBAARA,IAA2C,IAAvBC,MAAMC,QAAQF,GAChE,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,QAAA;;ACyBA,aAAA,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,QAAA,EA3BD,IAAA,EAAA,EAAA,QAAA,aA2BC,SAAA,EAAA,GAAA,OAAA,GAAA,EAAA,WAAA,EAAA,CAAA,QAAA,GAzBD,SAASG,EAAeC,GACf,OAAgB,KAAhB,EAASA,EAAAA,SAAAA,IAC2B,oBAAtCC,OAAOC,UAAUC,SAASC,KAAKJ,GAGvB,SAASK,EAAcL,GAChCM,IAAAA,EAAKC,EAELR,OAAsB,IAAtBA,EAAeC,KAIC,mBADpBM,EAAON,EAAEQ,gBAKoB,IAAzBT,EADJQ,EAAOD,EAAKJ,aAIiC,IAAzCK,EAAKE,eAAe;;;;;AC5B1B,IAAIJ,EAAgBK,QAAQ,mBACxBC,EAAiBD,QAAQ,wBAKzBE,EAAY,WACTC,KAAAA,SAAW,CACdC,WAAY,CAEVH,eAAgBA,KAQtBC,EAAUV,UAAUa,QAAU,SAAUC,EAAKC,GAGtC,IAAA,IAAIC,KAFTF,EAAId,UAAUiB,WAAa,KACtBF,KAAAA,QAAU,KAAKG,OAAO,KAAKP,SAAUI,GAAW,IAC3B,KAAKA,QAAQH,WACrCE,EAAIK,UAAUH,EAAe,KAAKD,QAAQH,WAAWI,KASzDN,EAAUV,UAAUkB,OAAS,SAAUE,EAAMC,GACvCC,IAAAA,EAAS,GACR,IAAA,IAAIC,KAAOH,EACVC,EAAWd,eAAegB,GAC5BD,EAAOC,GAAOpB,EAAckB,EAAWE,KAASpB,EAAciB,EAAKG,IAC/D,KAAKL,OAAOE,EAAKG,GAAMF,EAAWE,IAClCF,EAAWE,GAEfD,EAAOC,GAAOH,EAAKG,GAGlB,IAAA,IAAIC,KAAQH,EACVC,EAAOf,eAAeiB,KACzBF,EAAOE,GAAQH,EAAWG,IAGvBF,OAAAA,GAGTG,OAAOC,QAAU,IAAIhB","file":"formulate.min.js","sourceRoot":"../src","sourcesContent":["/*!\n * isobject <https://github.com/jonschlinkert/isobject>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\nexport default function isObject(val) {\n return val != null && typeof val === 'object' && Array.isArray(val) === false;\n};\n","/*!\n * is-plain-object <https://github.com/jonschlinkert/is-plain-object>\n *\n * Copyright (c) 2014-2017, Jon Schlinkert.\n * Released under the MIT License.\n */\n\nimport isObject from 'isobject';\n\nfunction isObjectObject(o) {\n return isObject(o) === true\n && Object.prototype.toString.call(o) === '[object Object]';\n}\n\nexport default function isPlainObject(o) {\n var ctor,prot;\n\n if (isObjectObject(o) === false) return false;\n\n // If has modified constructor\n ctor = o.constructor;\n if (typeof ctor !== 'function') return false;\n\n // If has modified prototype\n prot = ctor.prototype;\n if (isObjectObject(prot) === false) return false;\n\n // If constructor does not have an Object-specific method\n if (prot.hasOwnProperty('isPrototypeOf') === false) {\n return false;\n }\n\n // Most likely a plain Object\n return true;\n};\n","var isPlainObject = require('is-plain-object')\nvar FormulateInput = require('./FormulateInput.vue')\n\n/**\n * The base formulate libary.\n */\nvar Formulate = function () {\n this.defaults = {\n components: {\n // FormulateForm: FormulateForm,\n FormulateInput: FormulateInput\n }\n }\n}\n\n/**\n * Install vue formulate, and register it’s components.\n */\nFormulate.prototype.install = function (Vue, options) {\n Vue.prototype.$formulate = this\n this.options = this.extend(this.defaults, options || {})\n for (var componentName in this.options.components) {\n Vue.component(componentName, this.options.components[componentName])\n }\n}\n\n/**\n * Create a new object by copying properties of base and extendWith.\n * @param {Object} base\n * @param {Object} extendWith\n */\nFormulate.prototype.extend = function (base, extendWith) {\n var merged = {}\n for (var key in base) {\n if (extendWith.hasOwnProperty(key)) {\n merged[key] = isPlainObject(extendWith[key]) && isPlainObject(base[key])\n ? this.extend(base[key], extendWith[key])\n : extendWith[key]\n } else {\n merged[key] = base[key]\n }\n }\n for (var prop in extendWith) {\n if (!merged.hasOwnProperty(prop)) {\n merged[prop] = extendWith[prop]\n }\n }\n return merged\n}\n\nmodule.exports = new Formulate()\n"]}
|
1318
dist/formulate.umd.js
vendored
Normal file
1318
dist/formulate.umd.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
43
dist/index.js
vendored
43
dist/index.js
vendored
File diff suppressed because one or more lines are too long
140
dist/snow.css
vendored
Normal file
140
dist/snow.css
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
.formulate-input {
|
||||||
|
margin-bottom: 2em; }
|
||||||
|
.formulate-input .formulate-input-label {
|
||||||
|
display: block;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: .9em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: .1em; }
|
||||||
|
.formulate-input .formulate-input-element {
|
||||||
|
max-width: 20em;
|
||||||
|
margin-bottom: .1em; }
|
||||||
|
.formulate-input .formulate-input-help {
|
||||||
|
color: #6d6d6d;
|
||||||
|
font-size: .7em;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: .25em; }
|
||||||
|
.formulate-input .formulate-input-group-item {
|
||||||
|
margin-bottom: .5em; }
|
||||||
|
.formulate-input:last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
.formulate-input[data-classification='text'] input {
|
||||||
|
appearance: none;
|
||||||
|
border-radius: .3em;
|
||||||
|
border: 1px solid #cecece;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: .9em;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
padding: .75em;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 400; }
|
||||||
|
.formulate-input[data-classification='text'] input::placeholder {
|
||||||
|
color: #a8a8a8; }
|
||||||
|
.formulate-input[data-classification='text'] input:focus {
|
||||||
|
outline: 0;
|
||||||
|
border: 1px solid #41b883; }
|
||||||
|
.formulate-input[data-classification='textarea'] textarea {
|
||||||
|
appearance: none;
|
||||||
|
border-radius: .3em;
|
||||||
|
border: 1px solid #cecece;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: .9em;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
padding: .75em;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 400; }
|
||||||
|
.formulate-input[data-classification='textarea'] textarea::placeholder {
|
||||||
|
color: #a8a8a8; }
|
||||||
|
.formulate-input[data-classification='textarea'] textarea:focus {
|
||||||
|
outline: 0;
|
||||||
|
border: 1px solid #41b883; }
|
||||||
|
.formulate-input[data-classification='select'] .formulate-input-element {
|
||||||
|
position: relative; }
|
||||||
|
.formulate-input[data-classification='select'] .formulate-input-element::before {
|
||||||
|
content: '';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border: .3em solid transparent;
|
||||||
|
border-top-color: #cecece;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -.1em;
|
||||||
|
right: 1em;
|
||||||
|
position: absolute; }
|
||||||
|
.formulate-input[data-classification='select'] select {
|
||||||
|
appearance: none;
|
||||||
|
border-radius: .3em;
|
||||||
|
border: 1px solid #cecece;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: .9em;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
padding: .75em;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 400;
|
||||||
|
padding-right: 2em; }
|
||||||
|
.formulate-input[data-classification='select'] select::placeholder {
|
||||||
|
color: #a8a8a8; }
|
||||||
|
.formulate-input[data-classification='select'] select:focus {
|
||||||
|
outline: 0;
|
||||||
|
border: 1px solid #41b883; }
|
||||||
|
.formulate-input[data-classification='select'] select[data-placeholder-selected] {
|
||||||
|
color: #a8a8a8; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element {
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element input {
|
||||||
|
position: absolute;
|
||||||
|
left: -999px; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element-decorator {
|
||||||
|
display: block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border-radius: .25em;
|
||||||
|
border: 1px solid #cecece;
|
||||||
|
position: relative; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element-decorator::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: right;
|
||||||
|
width: calc(100% - .125em);
|
||||||
|
height: calc(100% - .125em);
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: .0625em;
|
||||||
|
left: .0625em; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element[data-type="radio"] .formulate-input-element-decorator {
|
||||||
|
border-radius: 1em; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element[data-type="radio"] .formulate-input-element-decorator::before {
|
||||||
|
border-radius: 1em;
|
||||||
|
width: calc(100% - .5em);
|
||||||
|
height: calc(100% - .5em);
|
||||||
|
top: .25em;
|
||||||
|
left: .25em; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element input[type="checkbox"]:checked ~ .formulate-input-element-decorator {
|
||||||
|
border-color: #41b883; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element input[type="checkbox"]:checked ~ .formulate-input-element-decorator::before {
|
||||||
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="%2341b883"><path d="M8.76,56.2c-6.38-6.34,3.26-16,9.64-9.69L38,65.88,80.56,23.29c6.38-6.38,16.07,3.32,9.69,9.69L42.84,80.37a6.83,6.83,0,0,1-9.65,0Z"/></svg>'); }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element input[type="radio"]:checked ~ .formulate-input-element-decorator {
|
||||||
|
border-color: #41b883; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element input[type="radio"]:checked ~ .formulate-input-element-decorator::before {
|
||||||
|
background-color: #41b883; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-element input:focus ~ .formulate-input-element-decorator {
|
||||||
|
border-color: #41b883; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-label--after {
|
||||||
|
margin-left: .5em; }
|
||||||
|
.formulate-input[data-classification='box'] .formulate-input-label--before {
|
||||||
|
margin-right: .5em; }
|
||||||
|
.formulate-input[data-classification="group"] > .formulate-input-wrapper > .formulate-input-label {
|
||||||
|
margin-bottom: .5em; }
|
2
dist/snow.min.css
vendored
Normal file
2
dist/snow.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12739
package-lock.json
generated
12739
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
88
package.json
88
package.json
@ -1,13 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-formulate",
|
"name": "vue-formulate",
|
||||||
"version": "0.13.0",
|
"version": "2.0.0-alpha.0",
|
||||||
"description": "The easiest way to build forms in Vue with validation and vuex support.",
|
"description": "The easiest way to build forms in Vue.",
|
||||||
"main": "dist/index.js",
|
"main": "dist/formulate.umd.js",
|
||||||
|
"module": "dist/formulate.esm.js",
|
||||||
|
"unpkg": "dist/formulate.min.js",
|
||||||
|
"browser": {
|
||||||
|
"./sfc": "src/Formulate.js"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack --hide-modules --watch",
|
"build": "npm run build:umd & npm run build:es & npm run build:unpkg & npm run build:css",
|
||||||
"build": "NODE_ENV=production && webpack -p --hide-modules",
|
"build:css": "node-sass themes/snow/snow.scss dist/snow.css && postcss --use autoprefixer -b '> 2%' < dist/snow.css | postcss --use cssnano > dist/snow.min.css",
|
||||||
"test": "ava",
|
"build:umd": "rollup --config build/rollup.config.js --format umd --file dist/formulate.umd.js",
|
||||||
"watch": "ava --watch"
|
"build:es": "rollup --config build/rollup.config.js --format es --file dist/formulate.esm.js",
|
||||||
|
"build:unpkg": "rollup --config build/rollup.config.js --format iife --file dist/formulate.min.js",
|
||||||
|
"test": "NODE_ENV=test jest --config test/jest.conf.js",
|
||||||
|
"test:watch": "NODE_ENV=test jest --config test/jest.conf.js --watch"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -26,40 +34,42 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/wearebraid/vue-formulate/issues"
|
"url": "https://github.com/wearebraid/vue-formulate/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/wearebraid/vue-formulate#readme",
|
"homepage": "https://www.vueformulate.com",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ava": "^0.25.0",
|
"@babel/core": "^7.6.2",
|
||||||
"babel-core": "^6.26.0",
|
"@babel/plugin-transform-modules-commonjs": "^7.6.0",
|
||||||
"babel-eslint": "^8.2.1",
|
"@babel/preset-env": "^7.6.2",
|
||||||
"babel-loader": "^7.1.2",
|
"@vue/component-compiler-utils": "^3.0.0",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"@vue/test-utils": "^1.0.0-beta.29",
|
||||||
"babel-polyfill": "^6.26.0",
|
"autoprefixer": "^9.6.1",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"babel-preset-stage-2": "^6.24.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"eslint": "^4.16.0",
|
"babel-jest": "^24.9.0",
|
||||||
"eslint-config-standard": "^11.0.0-beta.0",
|
"cssnano": "^4.1.10",
|
||||||
"eslint-plugin-import": "^2.8.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-plugin-node": "^5.2.1",
|
"eslint-config-standard": "^12.0.0",
|
||||||
"eslint-plugin-promise": "^3.6.0",
|
"eslint-plugin-import": "^2.16.0",
|
||||||
"eslint-plugin-standard": "^3.0.1",
|
"eslint-plugin-node": "^8.0.1",
|
||||||
"eslint-plugin-vue": "^4.2.0",
|
"eslint-plugin-promise": "^4.1.1",
|
||||||
"vue-loader": "^13.7.0",
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"vue-template-compiler": "^2.5.13",
|
"eslint-plugin-vue": "^5.2.3",
|
||||||
"webpack": "^3.10.0"
|
"jest": "^24.9.0",
|
||||||
},
|
"jest-vue-preprocessor": "^1.5.0",
|
||||||
"ava": {
|
"node-sass": "^4.12.0",
|
||||||
"require": [
|
"postcss": "^7.0.18",
|
||||||
"babel-register",
|
"postcss-cli": "^6.1.3",
|
||||||
"babel-polyfill"
|
"rollup": "^1.22.0",
|
||||||
]
|
"rollup-plugin-buble": "^0.19.8",
|
||||||
},
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
"babel": {
|
"rollup-plugin-vue": "^5.0.1",
|
||||||
"presets": [
|
"vue": "^2.6.10",
|
||||||
"env"
|
"vue-jest": "^3.0.5",
|
||||||
]
|
"vue-template-compiler": "^2.6.10",
|
||||||
|
"vue-template-es2015-compiler": "^1.9.1",
|
||||||
|
"watch": "^1.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clone-deep": "^3.0.1",
|
"is-plain-object": "^3.0.0",
|
||||||
"shortid": "^2.2.8"
|
"nanoid": "^2.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
src/FormulateForm.vue
Normal file
5
src/FormulateForm.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<form>
|
||||||
|
<slot />
|
||||||
|
</form>
|
||||||
|
</template>
|
105
src/FormulateInput.vue
Normal file
105
src/FormulateInput.vue
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="formulate-input"
|
||||||
|
:data-classification="classification"
|
||||||
|
:data-type="type"
|
||||||
|
>
|
||||||
|
<div class="formulate-input-wrapper">
|
||||||
|
<slot
|
||||||
|
v-if="context.label && context.labelPosition === 'before'"
|
||||||
|
name="label"
|
||||||
|
v-bind="context"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="formulate-input-label formulate-input-label--before"
|
||||||
|
:for="id"
|
||||||
|
v-text="context.label"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
<slot v-bind="context">
|
||||||
|
<component
|
||||||
|
:is="context.component"
|
||||||
|
:context="context"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
<slot
|
||||||
|
v-if="context.label && context.labelPosition === 'after'"
|
||||||
|
name="label"
|
||||||
|
v-bind="context.label"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="formulate-input-label formulate-input-label--after"
|
||||||
|
:for="id"
|
||||||
|
v-text="context.label"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="help"
|
||||||
|
class="formulate-input-help"
|
||||||
|
v-text="help"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import context from './libs/context'
|
||||||
|
import nanoid from 'nanoid'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormulateInput',
|
||||||
|
inheritAttrs: false,
|
||||||
|
model: {
|
||||||
|
prop: 'formulateValue',
|
||||||
|
event: 'input'
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
formulateValue: {
|
||||||
|
type: [String, Number, Object, Boolean, Array],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Object, Boolean, Array],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: [Object, Array, Boolean],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
optionGroups: {
|
||||||
|
type: [Object, Boolean],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: [String, Boolean, Number],
|
||||||
|
default: () => nanoid(9)
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: [String, Boolean],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
labelPosition: {
|
||||||
|
type: [String, Boolean],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
help: {
|
||||||
|
type: [String, Boolean],
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
context,
|
||||||
|
classification () {
|
||||||
|
const classification = this.$formulate.classify(this.type)
|
||||||
|
return (classification === 'box' && this.options) ? 'group' : classification
|
||||||
|
},
|
||||||
|
component () {
|
||||||
|
return this.$formulate.component(this.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
40
src/FormulateInputGroup.vue
Normal file
40
src/FormulateInputGroup.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div class="formulate-input-group">
|
||||||
|
<component
|
||||||
|
:is="optionContext.component"
|
||||||
|
v-for="optionContext in optionsWithContext"
|
||||||
|
:key="optionContext.id"
|
||||||
|
v-model="context.model"
|
||||||
|
v-bind="optionContext"
|
||||||
|
class="formulate-input-group-item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FormulateInputGroup',
|
||||||
|
props: {
|
||||||
|
context: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
options () {
|
||||||
|
return this.context.options || []
|
||||||
|
},
|
||||||
|
optionsWithContext () {
|
||||||
|
const { options, labelPosition, attributes, ...context } = this.context
|
||||||
|
return this.options.map(option => this.groupItemContext(context, option))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
groupItemContext (...args) {
|
||||||
|
return Object.assign({}, ...args, {
|
||||||
|
component: 'FormulateInput'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
25
src/FormulateInputMixin.js
Normal file
25
src/FormulateInputMixin.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Default base for input components.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
context: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
type () {
|
||||||
|
return this.context.type
|
||||||
|
},
|
||||||
|
id () {
|
||||||
|
return this.context.id
|
||||||
|
},
|
||||||
|
attributes () {
|
||||||
|
return this.context.attributes || {}
|
||||||
|
},
|
||||||
|
hasValue () {
|
||||||
|
return !!this.context.model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
src/formulate.js
149
src/formulate.js
@ -1,115 +1,90 @@
|
|||||||
import FormulateGroup from './components/Formulate'
|
import library from './libs/library'
|
||||||
import FormulateElement from './components/FormulateElement'
|
import isPlainObject from 'is-plain-object'
|
||||||
import DefaultRules from './rules'
|
import FormulateInput from './FormulateInput.vue'
|
||||||
import DefaultErrors from './errors'
|
import FormulateForm from './FormulateForm.vue'
|
||||||
|
import FormulateInputGroup from './FormulateInputGroup.vue'
|
||||||
|
import FormulateInputBox from './inputs/FormulateInputBox.vue'
|
||||||
|
import FormulateInputText from './inputs/FormulateInputText.vue'
|
||||||
|
import FormulateInputSelect from './inputs/FormulateInputSelect.vue'
|
||||||
|
import FormulateInputTextArea from './inputs/FormulateInputTextArea.vue'
|
||||||
|
/**
|
||||||
|
* The base formulate library.
|
||||||
|
*/
|
||||||
class Formulate {
|
class Formulate {
|
||||||
/**
|
/**
|
||||||
* Initialize vue-formulate.
|
* Instantiate our base options.
|
||||||
*/
|
*/
|
||||||
constructor () {
|
constructor () {
|
||||||
this.defaultOptions = {
|
this.defaults = {
|
||||||
registerComponents: true,
|
components: {
|
||||||
tags: {
|
FormulateForm,
|
||||||
Formulate: 'formulate',
|
FormulateInput,
|
||||||
FormulateElement: 'formulate-element'
|
FormulateInputBox,
|
||||||
|
FormulateInputText,
|
||||||
|
FormulateInputGroup,
|
||||||
|
FormulateInputSelect,
|
||||||
|
FormulateInputTextArea
|
||||||
},
|
},
|
||||||
errors: {},
|
library
|
||||||
rules: {},
|
|
||||||
vuexModule: false
|
|
||||||
}
|
}
|
||||||
this.errors = DefaultErrors
|
|
||||||
this.rules = DefaultRules
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install vue-formulate as an instance of Vue.
|
* Install vue formulate, and register it’s components.
|
||||||
* @param {Vue} Vue
|
|
||||||
*/
|
*/
|
||||||
install (Vue, options = {}) {
|
install (Vue, options) {
|
||||||
Vue.prototype.$formulate = this
|
Vue.prototype.$formulate = this
|
||||||
options = Object.assign(this.defaultOptions, options)
|
this.options = this.extend(this.defaults, options || {})
|
||||||
if (options.registerComponents) {
|
for (var componentName in this.options.components) {
|
||||||
Vue.component(options.tags.Formulate, FormulateGroup)
|
Vue.component(componentName, this.options.components[componentName])
|
||||||
Vue.component(options.tags.FormulateElement, FormulateElement)
|
|
||||||
}
|
}
|
||||||
if (options.errors) {
|
|
||||||
this.errors = Object.assign(this.errors, options.errors)
|
|
||||||
}
|
|
||||||
if (options.rules) {
|
|
||||||
this.rules = Object.assign(this.rules, options.rules)
|
|
||||||
}
|
|
||||||
this.options = options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a string of rules parse them out to relevant pieces/parts
|
* Create a new object by copying properties of base and extendWith.
|
||||||
* @param {string} rulesString
|
* @param {Object} base
|
||||||
|
* @param {Object} extendWith
|
||||||
*/
|
*/
|
||||||
parseRules (rulesString) {
|
extend (base, extendWith) {
|
||||||
return rulesString.split('|')
|
var merged = {}
|
||||||
.map(rule => rule.trim())
|
for (var key in base) {
|
||||||
.map(rule => rule.match(/([a-zA-Z0-9]+)\((.*)?\)/) || [null, rule, ''])
|
if (extendWith.hasOwnProperty(key)) {
|
||||||
.map(([ruleString, rule, args]) => Object.assign({}, {rule}, args ? {
|
merged[key] = isPlainObject(extendWith[key]) && isPlainObject(base[key])
|
||||||
args: args.split(',').map(arg => arg.trim())
|
? this.extend(base[key], extendWith[key])
|
||||||
} : {args: []}))
|
: extendWith[key]
|
||||||
|
} else {
|
||||||
|
merged[key] = base[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var prop in extendWith) {
|
||||||
|
if (!merged.hasOwnProperty(prop)) {
|
||||||
|
merged[prop] = extendWith[prop]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the function that generates a validation error message for a given
|
* Determine what "class" of input this element is given the "type".
|
||||||
* validation rule.
|
* @param {string} type
|
||||||
* @param {string} rule
|
|
||||||
*/
|
*/
|
||||||
errorFactory (rule) {
|
classify (type) {
|
||||||
return this.errors[rule] ? this.errors[rule] : this.errors['default']
|
if (this.options.library.hasOwnProperty(type)) {
|
||||||
|
return this.options.library[type].classification
|
||||||
|
}
|
||||||
|
return 'unknown'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a particular field, value, validation rules, and form values
|
* Determine what type of component to render given the "type".
|
||||||
* perform asynchronous field validation.
|
* @param {string} type
|
||||||
* @param {Object} validatee
|
|
||||||
* @param {string} rulesString
|
|
||||||
* @param {Object} values
|
|
||||||
*/
|
*/
|
||||||
async validationErrors ({field, value, label}, rulesString, values) {
|
component (type) {
|
||||||
return rulesString ? Promise.all(
|
if (this.options.library.hasOwnProperty(type)) {
|
||||||
this.parseRules(rulesString)
|
return this.options.library[type].component
|
||||||
.map(({rule, args}) => {
|
|
||||||
if (typeof this.rules[rule] !== 'function') {
|
|
||||||
throw new Error(`Validation rule is invalid: ${rule}`)
|
|
||||||
}
|
}
|
||||||
return this.rules[rule]({field, value, label, error: this.errorFactory(rule), values}, ...args)
|
return false
|
||||||
})
|
|
||||||
).then(responses => responses.reduce((errors, error) => {
|
|
||||||
return error ? (Array.isArray(errors) ? errors.concat(error) : [error]) : errors
|
|
||||||
}, false)) : false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const formulate = new Formulate()
|
|
||||||
export default formulate
|
|
||||||
export * from './store'
|
|
||||||
|
|
||||||
/**
|
export default new Formulate()
|
||||||
* Mapper to allow bindings to the vuex store for custom fields.
|
|
||||||
* @param {Object} definitions
|
|
||||||
*/
|
|
||||||
export const mapModels = (definitions) => {
|
|
||||||
const models = {}
|
|
||||||
for (let mapTo in definitions) {
|
|
||||||
let [form, field] = definitions[mapTo].split('/')
|
|
||||||
models[mapTo] = {
|
|
||||||
set (value) {
|
|
||||||
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
|
|
||||||
this.$store.commit(`${m}setFieldValue`, {form, field, value})
|
|
||||||
},
|
|
||||||
get () {
|
|
||||||
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
|
|
||||||
if (this.$store.getters[`${m}formValues`][form]) {
|
|
||||||
return this.$store.getters[`${m}formValues`][form][field]
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return models
|
|
||||||
}
|
|
||||||
|
26
src/inputs/FormulateInputBox.vue
Normal file
26
src/inputs/FormulateInputBox.vue
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="formulate-input-element formulate-input-element--box"
|
||||||
|
:data-type="context.type"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="context.model"
|
||||||
|
:type="type"
|
||||||
|
:value="context.value"
|
||||||
|
v-bind="attributes"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="formulate-input-element-decorator"
|
||||||
|
:for="id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormulateInputMixin from '../FormulateInputMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormulateInputBox',
|
||||||
|
mixins: [FormulateInputMixin]
|
||||||
|
}
|
||||||
|
</script>
|
68
src/inputs/FormulateInputSelect.vue
Normal file
68
src/inputs/FormulateInputSelect.vue
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="formulate-input-element formulate-input-element--select"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="context.model"
|
||||||
|
v-bind="attributes"
|
||||||
|
:data-placeholder-selected="placeholderSelected"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-if="context.placeholder"
|
||||||
|
value=""
|
||||||
|
disabled
|
||||||
|
:selected="!hasValue"
|
||||||
|
>
|
||||||
|
{{ context.placeholder }}
|
||||||
|
</option>
|
||||||
|
<template
|
||||||
|
v-if="!optionGroups"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in options"
|
||||||
|
:key="option.id"
|
||||||
|
:value="option.value"
|
||||||
|
v-bind="option.attributes || {}"
|
||||||
|
v-text="option.label"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
<optgroup
|
||||||
|
v-for="(subOptions, label) in optionGroups"
|
||||||
|
:key="label"
|
||||||
|
:label="label"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in subOptions"
|
||||||
|
:key="option.id"
|
||||||
|
:value="option.value"
|
||||||
|
v-bind="option.attributes || {}"
|
||||||
|
v-text="option.label"
|
||||||
|
/>
|
||||||
|
</optgroup>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormulateInputMixin from '../FormulateInputMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormulateInputSelect',
|
||||||
|
mixins: [FormulateInputMixin],
|
||||||
|
computed: {
|
||||||
|
options () {
|
||||||
|
return this.context.options || {}
|
||||||
|
},
|
||||||
|
optionGroups () {
|
||||||
|
return this.context.optionGroups || false
|
||||||
|
},
|
||||||
|
placeholderSelected () {
|
||||||
|
return !!(!this.hasValue && this.context.attributes.placeholder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
20
src/inputs/FormulateInputText.vue
Normal file
20
src/inputs/FormulateInputText.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="formulate-input-element formulate-input-element--textarea"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="context.model"
|
||||||
|
:type="type"
|
||||||
|
v-bind="attributes"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormulateInputMixin from '../FormulateInputMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormulateInputText',
|
||||||
|
mixins: [FormulateInputMixin]
|
||||||
|
}
|
||||||
|
</script>
|
19
src/inputs/FormulateInputTextArea.vue
Normal file
19
src/inputs/FormulateInputTextArea.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="formulate-input-element formulate-input-element--textarea"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
v-model="context.model"
|
||||||
|
v-bind="attributes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormulateInputMixin from '../FormulateInputMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormulateInputTextArea',
|
||||||
|
mixins: [FormulateInputMixin]
|
||||||
|
}
|
||||||
|
</script>
|
118
src/libs/context.js
Normal file
118
src/libs/context.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import nanoid from 'nanoid'
|
||||||
|
import { reduce, map } from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a single instance of an input, export all of the context needed to fully
|
||||||
|
* render that element.
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
export default function context () {
|
||||||
|
return defineModel.call(this, Object.assign({
|
||||||
|
type: this.type,
|
||||||
|
value: this.value,
|
||||||
|
classification: this.classification,
|
||||||
|
component: this.component,
|
||||||
|
id: this.id,
|
||||||
|
label: this.label,
|
||||||
|
labelPosition: labelPosition.call(this),
|
||||||
|
attributes: attributeReducer.call(this, this.$attrs)
|
||||||
|
}, typeContext.call(this)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given (this.type), return an object to merge with the context
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
function typeContext () {
|
||||||
|
switch (this.classification) {
|
||||||
|
case 'select':
|
||||||
|
return {
|
||||||
|
options: createOptionList(this.options),
|
||||||
|
optionGroups: this.optionGroups ? map(this.optionGroups, (k, v) => createOptionList(v)) : false,
|
||||||
|
placeholder: this.$attrs.placeholder || false
|
||||||
|
}
|
||||||
|
case 'group':
|
||||||
|
if (this.options) {
|
||||||
|
return {
|
||||||
|
options: createOptionList(this.options),
|
||||||
|
component: 'FormulateInputGroup'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reducer for attributes that will be applied to each core input element.
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
function attributeReducer (attributes = {}) {
|
||||||
|
if (this.id) {
|
||||||
|
attributes.id = this.id
|
||||||
|
}
|
||||||
|
return attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the a best-guess location for the label (before or after).
|
||||||
|
* @return {string} before|after
|
||||||
|
*/
|
||||||
|
function labelPosition () {
|
||||||
|
if (this.labelPosition) {
|
||||||
|
return this.labelPosition
|
||||||
|
}
|
||||||
|
switch (this.classification) {
|
||||||
|
case 'box':
|
||||||
|
return 'after'
|
||||||
|
default:
|
||||||
|
return 'before'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an object or array of options, create an array of objects with label,
|
||||||
|
* value, and id.
|
||||||
|
* @param {array|object}
|
||||||
|
* @return {array}
|
||||||
|
*/
|
||||||
|
function createOptionList (options) {
|
||||||
|
if (!Array.isArray(options)) {
|
||||||
|
return reduce(options, (options, value, label) => options.concat({ value, label, id: nanoid(15) }), [])
|
||||||
|
} else if (Array.isArray(options) && !options.length) {
|
||||||
|
return [{ value: this.name, label: (this.label || this.name), id: nanoid(15) }]
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a getter/setter model factory
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
function defineModel (context) {
|
||||||
|
return Object.defineProperty(context, 'model', {
|
||||||
|
get: modelGetter.bind(this),
|
||||||
|
set: modelSetter.bind(this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value from a model.
|
||||||
|
**/
|
||||||
|
function modelGetter () {
|
||||||
|
if (this.type === 'checkbox' && !Array.isArray(this.formulateValue) && this.options) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (this.formulateValue === false) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return this.formulateValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value from a model.
|
||||||
|
**/
|
||||||
|
function modelSetter (value) {
|
||||||
|
this.$emit('input', value)
|
||||||
|
}
|
95
src/libs/library.js
Normal file
95
src/libs/library.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
export default {
|
||||||
|
// === SINGLE LINE TEXT STYLE INPUTS
|
||||||
|
text: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
hidden: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
month: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
range: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
tel: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
week: {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
'datetime-local': {
|
||||||
|
classification: 'text',
|
||||||
|
component: 'FormulateInputText'
|
||||||
|
},
|
||||||
|
|
||||||
|
// === MULTI LINE TEXT INPUTS
|
||||||
|
textarea: {
|
||||||
|
classification: 'textarea',
|
||||||
|
component: 'FormulateInputTextArea'
|
||||||
|
},
|
||||||
|
|
||||||
|
// === BOX STYLE INPUTS
|
||||||
|
checkbox: {
|
||||||
|
classification: 'box',
|
||||||
|
component: 'FormulateInputBox'
|
||||||
|
},
|
||||||
|
radio: {
|
||||||
|
classification: 'box',
|
||||||
|
component: 'FormulateInputBox'
|
||||||
|
},
|
||||||
|
|
||||||
|
// === BUTTON STYLE INPUTS
|
||||||
|
submit: {
|
||||||
|
classification: 'button',
|
||||||
|
component: 'FormulateInputButton'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
classification: 'button',
|
||||||
|
component: 'FormulateInputButton'
|
||||||
|
},
|
||||||
|
|
||||||
|
// === SELECT STYLE INPUTS
|
||||||
|
select: {
|
||||||
|
classification: 'select',
|
||||||
|
component: 'FormulateInputSelect'
|
||||||
|
}
|
||||||
|
}
|
41
src/libs/utils.js
Normal file
41
src/libs/utils.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Function to map over an object.
|
||||||
|
* @param {Object} obj An object to map over
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
export function map (original, callback) {
|
||||||
|
const obj = {}
|
||||||
|
for (let key in original) {
|
||||||
|
obj[key] = callback(key, original[key])
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to filter an object's properties
|
||||||
|
* @param {Object} original
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
export function filter (original, callback) {
|
||||||
|
let obj = {}
|
||||||
|
for (let key in original) {
|
||||||
|
if (callback(key, original[key])) {
|
||||||
|
obj[key] = original[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to reduce an object's properties
|
||||||
|
* @param {Object} original
|
||||||
|
* @param {Function} callback
|
||||||
|
* @param {*} accumulator
|
||||||
|
*/
|
||||||
|
export function reduce (original, callback, accumulator) {
|
||||||
|
for (let key in original) {
|
||||||
|
accumulator = callback(accumulator, key, original[key])
|
||||||
|
}
|
||||||
|
return accumulator
|
||||||
|
}
|
115
src~v1/formulate.js
Normal file
115
src~v1/formulate.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import FormulateGroup from './components/Formulate'
|
||||||
|
import FormulateElement from './components/FormulateElement'
|
||||||
|
import DefaultRules from './rules'
|
||||||
|
import DefaultErrors from './errors'
|
||||||
|
|
||||||
|
class Formulate {
|
||||||
|
/**
|
||||||
|
* Initialize vue-formulate.
|
||||||
|
*/
|
||||||
|
constructor () {
|
||||||
|
this.defaultOptions = {
|
||||||
|
registerComponents: true,
|
||||||
|
tags: {
|
||||||
|
Formulate: 'formulate',
|
||||||
|
FormulateElement: 'formulate-element'
|
||||||
|
},
|
||||||
|
errors: {},
|
||||||
|
rules: {},
|
||||||
|
vuexModule: false
|
||||||
|
}
|
||||||
|
this.errors = DefaultErrors
|
||||||
|
this.rules = DefaultRules
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install vue-formulate as an instance of Vue.
|
||||||
|
* @param {Vue} Vue
|
||||||
|
*/
|
||||||
|
install (Vue, options = {}) {
|
||||||
|
Vue.prototype.$formulate = this
|
||||||
|
options = Object.assign(this.defaultOptions, options)
|
||||||
|
if (options.registerComponents) {
|
||||||
|
Vue.component(options.tags.Formulate, FormulateGroup)
|
||||||
|
Vue.component(options.tags.FormulateElement, FormulateElement)
|
||||||
|
}
|
||||||
|
if (options.errors) {
|
||||||
|
this.errors = Object.assign(this.errors, options.errors)
|
||||||
|
}
|
||||||
|
if (options.rules) {
|
||||||
|
this.rules = Object.assign(this.rules, options.rules)
|
||||||
|
}
|
||||||
|
this.options = options
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string of rules parse them out to relevant pieces/parts
|
||||||
|
* @param {string} rulesString
|
||||||
|
*/
|
||||||
|
parseRules (rulesString) {
|
||||||
|
return rulesString.split('|')
|
||||||
|
.map(rule => rule.trim())
|
||||||
|
.map(rule => rule.match(/([a-zA-Z0-9]+)\((.*)?\)/) || [null, rule, ''])
|
||||||
|
.map(([ruleString, rule, args]) => Object.assign({}, {rule}, args ? {
|
||||||
|
args: args.split(',').map(arg => arg.trim())
|
||||||
|
} : {args: []}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the function that generates a validation error message for a given
|
||||||
|
* validation rule.
|
||||||
|
* @param {string} rule
|
||||||
|
*/
|
||||||
|
errorFactory (rule) {
|
||||||
|
return this.errors[rule] ? this.errors[rule] : this.errors['default']
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a particular field, value, validation rules, and form values
|
||||||
|
* perform asynchronous field validation.
|
||||||
|
* @param {Object} validatee
|
||||||
|
* @param {string} rulesString
|
||||||
|
* @param {Object} values
|
||||||
|
*/
|
||||||
|
async validationErrors ({field, value, label}, rulesString, values) {
|
||||||
|
return rulesString ? Promise.all(
|
||||||
|
this.parseRules(rulesString)
|
||||||
|
.map(({rule, args}) => {
|
||||||
|
if (typeof this.rules[rule] !== 'function') {
|
||||||
|
throw new Error(`Validation rule is invalid: ${rule}`)
|
||||||
|
}
|
||||||
|
return this.rules[rule]({field, value, label, error: this.errorFactory(rule), values}, ...args)
|
||||||
|
})
|
||||||
|
).then(responses => responses.reduce((errors, error) => {
|
||||||
|
return error ? (Array.isArray(errors) ? errors.concat(error) : [error]) : errors
|
||||||
|
}, false)) : false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const formulate = new Formulate()
|
||||||
|
export default formulate
|
||||||
|
export * from './store'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapper to allow bindings to the vuex store for custom fields.
|
||||||
|
* @param {Object} definitions
|
||||||
|
*/
|
||||||
|
export const mapModels = (definitions) => {
|
||||||
|
const models = {}
|
||||||
|
for (let mapTo in definitions) {
|
||||||
|
let [form, field] = definitions[mapTo].split('/')
|
||||||
|
models[mapTo] = {
|
||||||
|
set (value) {
|
||||||
|
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
|
||||||
|
this.$store.commit(`${m}setFieldValue`, {form, field, value})
|
||||||
|
},
|
||||||
|
get () {
|
||||||
|
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
|
||||||
|
if (this.$store.getters[`${m}formValues`][form]) {
|
||||||
|
return this.$store.getters[`${m}formValues`][form][field]
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return models
|
||||||
|
}
|
67
test/Formulate.test.js
Normal file
67
test/Formulate.test.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import Formulate from '../dist/formulate.esm.js'
|
||||||
|
|
||||||
|
test('can extend simple object', () => {
|
||||||
|
let a = {
|
||||||
|
optionA: true,
|
||||||
|
optionB: '1234'
|
||||||
|
}
|
||||||
|
let b = {
|
||||||
|
optionA: false
|
||||||
|
}
|
||||||
|
expect(Formulate.extend(a, b)).toEqual({
|
||||||
|
optionA: false,
|
||||||
|
optionB: '1234'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test('can extend recursively', () => {
|
||||||
|
let a = {
|
||||||
|
optionA: true,
|
||||||
|
optionC: {
|
||||||
|
first: '123',
|
||||||
|
third: {
|
||||||
|
a: 'b'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionB: '1234'
|
||||||
|
}
|
||||||
|
let b = {
|
||||||
|
optionB: '567',
|
||||||
|
optionC: {
|
||||||
|
first: '1234',
|
||||||
|
second: '789',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(Formulate.extend(a, b)).toEqual({
|
||||||
|
optionA: true,
|
||||||
|
optionC: {
|
||||||
|
first: '1234',
|
||||||
|
third: {
|
||||||
|
a: 'b'
|
||||||
|
},
|
||||||
|
second: '789'
|
||||||
|
},
|
||||||
|
optionB: '567'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('installs on vue instance', () => {
|
||||||
|
const components = [
|
||||||
|
'FormulateForm',
|
||||||
|
'FormulateInput',
|
||||||
|
'FormulateInputBox',
|
||||||
|
'FormulateInputText',
|
||||||
|
'FormulateInputGroup',
|
||||||
|
'FormulateInputSelect',
|
||||||
|
'FormulateInputTextArea'
|
||||||
|
]
|
||||||
|
const registry = []
|
||||||
|
function Vue () {}
|
||||||
|
Vue.component = function (name, instance) {
|
||||||
|
registry.push(name)
|
||||||
|
}
|
||||||
|
Formulate.install(Vue, { extended: true })
|
||||||
|
expect(Vue.prototype.$formulate).toBe(Formulate)
|
||||||
|
expect(registry).toEqual(components)
|
||||||
|
})
|
44
test/FormulateInputBox.test.js
Normal file
44
test/FormulateInputBox.test.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Formulate from '../dist/formulate.esm.js'
|
||||||
|
import FormulateInput from '../src/FormulateInput.vue'
|
||||||
|
import FormulateInputBox from '../src/inputs/FormulateInputBox.vue'
|
||||||
|
import FormulateInputGroup from '../src/FormulateInputGroup.vue'
|
||||||
|
|
||||||
|
Vue.use(Formulate)
|
||||||
|
|
||||||
|
test('type "checkbox" renders a box element', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox' } })
|
||||||
|
expect(wrapper.contains(FormulateInputBox)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "radio" renders a box element', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'radio' } })
|
||||||
|
expect(wrapper.contains(FormulateInputBox)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('box inputs properly process options object in context library', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
|
||||||
|
expect(Array.isArray(wrapper.vm.context.options)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "checkbox" with options renders a group', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
|
||||||
|
expect(wrapper.contains(FormulateInputGroup)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "radio" with options renders a group', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
|
||||||
|
expect(wrapper.contains(FormulateInputGroup)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test('labelPosition of type "checkbox" defaults to after', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox' } })
|
||||||
|
expect(wrapper.vm.context.labelPosition).toBe('after')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('labelPosition of type "checkbox" with options defaults to before', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'}}})
|
||||||
|
expect(wrapper.vm.context.labelPosition).toBe('before')
|
||||||
|
})
|
106
test/FormulateInputText.test.js
Normal file
106
test/FormulateInputText.test.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Formulate from '../dist/formulate.esm.js'
|
||||||
|
import FormulateInput from '../src/FormulateInput.vue'
|
||||||
|
import FormulateInputText from '../src/inputs/FormulateInputText.vue'
|
||||||
|
|
||||||
|
Vue.use(Formulate)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test each type of text element
|
||||||
|
*/
|
||||||
|
|
||||||
|
test('type "text" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "search" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'search' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "email" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'email' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "number" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'number' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "color" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'color' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "date" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'date' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "month" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'month' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "password" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'password' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "range" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'range' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "tel" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'tel' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "time" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'time' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "url" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'url' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type "week" renders a text input', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'week' } })
|
||||||
|
expect(wrapper.contains(FormulateInputText)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test functionality to text inputs
|
||||||
|
*/
|
||||||
|
|
||||||
|
test('text inputs automatically have id assigned', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
|
||||||
|
expect(wrapper.vm.context).toHaveProperty('id')
|
||||||
|
expect(wrapper.find(`input[id="${wrapper.vm.id}"]`).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('text inputs dont have labels', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
|
||||||
|
expect(wrapper.find('label').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('text inputs can have labels', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'text', label: 'Field label' } })
|
||||||
|
expect(wrapper.find(`label[for="${wrapper.vm.id}"]`).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('text inputs dont have help text', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
|
||||||
|
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('text inputs dont have help text', () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: { type: 'text', help: 'This is some help text' } })
|
||||||
|
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(true)
|
||||||
|
})
|
17
test/jest.conf.js
Normal file
17
test/jest.conf.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
rootDir: path.resolve(__dirname, '../'),
|
||||||
|
moduleFileExtensions: [
|
||||||
|
'js',
|
||||||
|
'json',
|
||||||
|
'vue'
|
||||||
|
],
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@/(.*)$': '<rootDir>/src/$1'
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
|
||||||
|
'.*\\.(vue)$': '<rootDir>/node_modules/jest-vue-preprocessor'
|
||||||
|
}
|
||||||
|
}
|
@ -1,55 +0,0 @@
|
|||||||
import test from 'ava'
|
|
||||||
import formulate from '../dist'
|
|
||||||
import VueMock from './mocks/VueMock'
|
|
||||||
|
|
||||||
test('checks plugin registration', async t => {
|
|
||||||
let components = []
|
|
||||||
VueMock.component = (name, object) => components.push({name, object})
|
|
||||||
formulate.install(VueMock)
|
|
||||||
t.truthy(VueMock.prototype.$formulate)
|
|
||||||
t.is(2, components.length)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('parses single rule', async t => {
|
|
||||||
let rules = formulate.parseRules('required')
|
|
||||||
t.deepEqual([{rule: 'required', args: []}], rules)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('parses multiple rules and trims', async t => {
|
|
||||||
let rules = formulate.parseRules('email |required')
|
|
||||||
t.deepEqual([{rule: 'email', args: []}, {rule: 'required', args: []}], rules)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('parses rule arguments', async t => {
|
|
||||||
let rules = formulate.parseRules('required(Name)|equals(confirm_password, Your Password)')
|
|
||||||
t.deepEqual([
|
|
||||||
{rule: 'required', args: ['Name']},
|
|
||||||
{rule: 'equals', args: ['confirm_password', 'Your Password']}
|
|
||||||
], rules)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('tests single validation error', async t => {
|
|
||||||
t.is(1, (await formulate.validationErrors({field: 'email', value: ''}, 'required')).length)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('tests multiple validation errors', async t => {
|
|
||||||
t.is(2, (await formulate.validationErrors({field: 'email', value: 'pastaparty'}, 'email|confirmed', {
|
|
||||||
email_confirmation: 'pizzaparty'
|
|
||||||
})).length)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('tests empty validation string', async t => {
|
|
||||||
t.is(false, await formulate.validationErrors({field: 'email', value: 'pastaparty'}, false))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can extend rules and errors', async t => {
|
|
||||||
formulate.install(VueMock, {
|
|
||||||
rules: {
|
|
||||||
isPizza: ({field, value, error}, label) => value === 'pizza' ? false : error({field, value})
|
|
||||||
},
|
|
||||||
errors: {
|
|
||||||
isPizza: ({field, value}) => `${field} is not a pizza`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.deepEqual(['pepperoni is not a pizza'], await formulate.validationErrors({field: 'pepperoni', value: 'meat'}, 'isPizza'))
|
|
||||||
})
|
|
@ -1,4 +0,0 @@
|
|||||||
class VueMock {
|
|
||||||
}
|
|
||||||
|
|
||||||
export default VueMock
|
|
@ -1,90 +0,0 @@
|
|||||||
import test from 'ava'
|
|
||||||
import f from '../dist'
|
|
||||||
|
|
||||||
const rules = f.rules
|
|
||||||
const error = ({field, value, label}) => {
|
|
||||||
return `${field}${label}`
|
|
||||||
}
|
|
||||||
|
|
||||||
test('test required rule failure', async t => {
|
|
||||||
let v = await rules.required({field: 'name', value: '', error, label: 'xyz'})
|
|
||||||
t.is('string', typeof v)
|
|
||||||
t.is('namexyz', v)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test number no failure on empty', async t => {
|
|
||||||
let v = await rules.number({field: 'name', value: '', error, label: 'xyz'})
|
|
||||||
t.is(false, v)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test number failure not number', async t => {
|
|
||||||
let v = await rules.number({field: 'name', value: 't', error, label: 'xyz'})
|
|
||||||
t.is('string', typeof v)
|
|
||||||
t.is('namexyz', v)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test number is typeof number', async t => {
|
|
||||||
let v = await rules.number({field: 'name', value: '3', error, label: 'xyz'})
|
|
||||||
t.is(false, v)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test required rule empty array failure', async t => {
|
|
||||||
let v = await rules.required({field: 'name', value: [], error, label: 'xyz'})
|
|
||||||
t.is('namexyz', v)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test required rule passes', async t => {
|
|
||||||
t.is(false, await rules.required({field: 'name', value: 'Justin'}))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test email rule with valid email', async t => {
|
|
||||||
t.is(false, await rules.email({field: 'email', value: 'valid@example.com'}))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test email rule with invalid email', async t => {
|
|
||||||
t.is('email123', await rules.email({field: 'email', value: 'invalid@example', error, label: '123'}))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test email with empty email', async t => {
|
|
||||||
t.is(false, await rules.email({field: 'email', value: '', error}))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test confirmed passes', async t => {
|
|
||||||
t.is(false, await rules.confirmed({
|
|
||||||
field: 'password',
|
|
||||||
value: 'password',
|
|
||||||
label: '123',
|
|
||||||
error,
|
|
||||||
values: {password_confirmation: 'password'}
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test confirmed passes custom field', async t => {
|
|
||||||
t.is(false, await rules.confirmed({
|
|
||||||
field: 'password',
|
|
||||||
value: 'password',
|
|
||||||
label: '123',
|
|
||||||
error,
|
|
||||||
values: {customfield: 'password'}
|
|
||||||
}, 'customfield'))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test confirmed fails', async t => {
|
|
||||||
t.is('password123', await rules.confirmed({
|
|
||||||
field: 'password',
|
|
||||||
value: 'password',
|
|
||||||
label: '123',
|
|
||||||
error,
|
|
||||||
values: {password_confirmation: 'pAssword'}
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('test empty confirmed passes', async t => {
|
|
||||||
t.is(false, await rules.confirmed({
|
|
||||||
field: 'password',
|
|
||||||
value: '',
|
|
||||||
label: '123',
|
|
||||||
error,
|
|
||||||
values: {password_confirmation: ''}
|
|
||||||
}))
|
|
||||||
})
|
|
@ -1,197 +0,0 @@
|
|||||||
import test from 'ava'
|
|
||||||
import {formulateState, formulateGetters, formulateMutations} from '../dist'
|
|
||||||
|
|
||||||
test('initial store state', async t => {
|
|
||||||
t.deepEqual({
|
|
||||||
values: {},
|
|
||||||
errors: {},
|
|
||||||
validationErrors: {},
|
|
||||||
meta: {}
|
|
||||||
}, formulateState()())
|
|
||||||
})
|
|
||||||
|
|
||||||
test('extended initial store state', async t => {
|
|
||||||
t.deepEqual({
|
|
||||||
values: {},
|
|
||||||
errors: {},
|
|
||||||
validationErrors: {},
|
|
||||||
meta: {},
|
|
||||||
additionalParam: 'test'
|
|
||||||
}, formulateState({
|
|
||||||
additionalParam: 'test'
|
|
||||||
})())
|
|
||||||
})
|
|
||||||
|
|
||||||
test('validationErrors getter', async t => {
|
|
||||||
let state = {
|
|
||||||
validationErrors: {
|
|
||||||
form: {
|
|
||||||
field: ['This is an error']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.is(formulateGetters().formValidationErrors(state), state.validationErrors)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('errors getter', async t => {
|
|
||||||
let state = {
|
|
||||||
errors: {
|
|
||||||
form: {
|
|
||||||
field: ['This is an error', 'second error']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.is(formulateGetters().formErrors(state), state.errors)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('values getter', async t => {
|
|
||||||
let state = {
|
|
||||||
values: {
|
|
||||||
form: {
|
|
||||||
name: 'Johan',
|
|
||||||
field: 'Guttenberg'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.is(formulateGetters().formValues(state), state.values)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('formMeta getter', async t => {
|
|
||||||
let state = {
|
|
||||||
meta: {
|
|
||||||
form: {
|
|
||||||
name: {name: 'name', label: 'Name'},
|
|
||||||
flail: {name: 'email', label: 'Email'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.deepEqual(formulateGetters().formMeta(state), {
|
|
||||||
form: [
|
|
||||||
{name: 'name', label: 'Name'},
|
|
||||||
{name: 'email', label: 'Email'}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('form has errors', async t => {
|
|
||||||
let state = {
|
|
||||||
errors: {
|
|
||||||
form: {
|
|
||||||
field: ['This is an error', 'second error']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.is(true, formulateGetters().hasErrors(state).form)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('form has empty errors', async t => {
|
|
||||||
let state = {
|
|
||||||
errors: {
|
|
||||||
form: {
|
|
||||||
field: [],
|
|
||||||
other: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.is(false, formulateGetters().hasErrors(state).form)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('form has no errors', async t => {
|
|
||||||
let state = {
|
|
||||||
errors: {
|
|
||||||
form: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.is(false, formulateGetters().hasErrors(state).form)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('adds a new field value', async t => {
|
|
||||||
let state = {values: {}}
|
|
||||||
formulateMutations().setFieldValue(state, {
|
|
||||||
form: 'form',
|
|
||||||
field: 'name',
|
|
||||||
value: 'test name'
|
|
||||||
})
|
|
||||||
t.deepEqual(state.values, {form: {name: 'test name'}})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('adds an existing field value', async t => {
|
|
||||||
let state = {values: {form: {name: 'old name'}}}
|
|
||||||
formulateMutations().setFieldValue(state, {
|
|
||||||
form: 'form',
|
|
||||||
field: 'name',
|
|
||||||
value: 'new name'
|
|
||||||
})
|
|
||||||
t.deepEqual(state.values, {form: {name: 'new name'}})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('adds an error to new field', async t => {
|
|
||||||
let state = {errors: {}}
|
|
||||||
formulateMutations().setFieldErrors(state, {
|
|
||||||
form: 'form',
|
|
||||||
field: 'name',
|
|
||||||
errors: ['i dislike this field']
|
|
||||||
})
|
|
||||||
t.deepEqual(state.errors, {form: {name: ['i dislike this field']}})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('adds an error to existing field', async t => {
|
|
||||||
let state = {errors: {form: {name: ['i like this field']}}}
|
|
||||||
formulateMutations().setFieldErrors(state, {
|
|
||||||
form: 'form',
|
|
||||||
field: 'name',
|
|
||||||
errors: ['i dislike this field']
|
|
||||||
})
|
|
||||||
t.deepEqual(state.errors, {form: {name: ['i dislike this field']}})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('adds a validationError to new field', async t => {
|
|
||||||
let state = {validationErrors: {}}
|
|
||||||
formulateMutations().setFieldValidationErrors(state, {
|
|
||||||
form: 'form',
|
|
||||||
field: 'name',
|
|
||||||
errors: ['i dislike this field']
|
|
||||||
})
|
|
||||||
t.deepEqual(state.validationErrors, {form: {name: ['i dislike this field']}})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('adds a validationError to existing field', async t => {
|
|
||||||
let state = {validationErrors: {form: {name: ['i like this field']}}}
|
|
||||||
formulateMutations().setFieldValidationErrors(state, {
|
|
||||||
form: 'form',
|
|
||||||
field: 'name',
|
|
||||||
errors: ['i dislike this field']
|
|
||||||
})
|
|
||||||
t.deepEqual(state.validationErrors, {form: {name: ['i dislike this field']}})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('reset a form', async t => {
|
|
||||||
let state = {values: {login: {username: 'testuser', password: 'secret'}}}
|
|
||||||
formulateMutations().resetForm(state, 'login')
|
|
||||||
t.deepEqual(state.values, {login: {username: undefined, password: undefined}})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('wont mutate undefined form on reset', async t => {
|
|
||||||
let state = {values: {login: {username: 'testuser', password: 'secret'}}}
|
|
||||||
formulateMutations().resetForm(state, 'register')
|
|
||||||
t.deepEqual(state.values, {login: {username: 'testuser', password: 'secret'}})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('removeField deletes values from store', async t => {
|
|
||||||
let state = {
|
|
||||||
values: {
|
|
||||||
register: {
|
|
||||||
username: 'testuser',
|
|
||||||
password: 'secret'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
formulateMutations().removeField(state, {form: 'register', field: 'password'})
|
|
||||||
t.deepEqual({
|
|
||||||
values: {
|
|
||||||
register: {
|
|
||||||
username: 'testuser'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, state)
|
|
||||||
})
|
|
183
themes/snow/_inputs.scss
Normal file
183
themes/snow/_inputs.scss
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
// Formulate inputs
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.formulate-input {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
|
||||||
|
.formulate-input-label {
|
||||||
|
display: block;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: .9em;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: .1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formulate-input-element {
|
||||||
|
max-width: 20em;
|
||||||
|
margin-bottom: .1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formulate-input-help {
|
||||||
|
color: $formulate-gray-ddd;
|
||||||
|
font-size: .7em;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: .25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formulate-input-group-item {
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Text inputs
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
&[data-classification='text'] {
|
||||||
|
input {
|
||||||
|
@include baseinput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Textarea inputs
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
&[data-classification='textarea'] {
|
||||||
|
textarea {
|
||||||
|
@include baseinput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Select lists
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
&[data-classification='select'] {
|
||||||
|
.formulate-input-element {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
@include down-arrow;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -.1em;
|
||||||
|
right: 1em;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
@include baseinput;
|
||||||
|
padding-right: 2em;
|
||||||
|
|
||||||
|
&[data-placeholder-selected] {
|
||||||
|
color: $formulate-gray-dd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Box inputs
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
&[data-classification='box'] {
|
||||||
|
.formulate-input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formulate-input-element {
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
input {
|
||||||
|
position: absolute;
|
||||||
|
left: -999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-decorator {
|
||||||
|
display: block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border-radius: .25em;
|
||||||
|
border: 1px solid $formulate-gray-d;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: right;
|
||||||
|
width: calc(100% - .125em);
|
||||||
|
height: calc(100% - .125em);
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: .0625em;
|
||||||
|
left: .0625em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&[data-type="radio"] {
|
||||||
|
.formulate-input-element-decorator {
|
||||||
|
border-radius: 1em;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border-radius: 1em;
|
||||||
|
width: calc(100% - .5em);
|
||||||
|
height: calc(100% - .5em);
|
||||||
|
top: .25em;
|
||||||
|
left: .25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked {
|
||||||
|
& ~ .formulate-input-element-decorator {
|
||||||
|
border-color: $formulate-green;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="%23#{str-slice("#{$formulate-green}", 2)}"><path d="M8.76,56.2c-6.38-6.34,3.26-16,9.64-9.69L38,65.88,80.56,23.29c6.38-6.38,16.07,3.32,9.69,9.69L42.84,80.37a6.83,6.83,0,0,1-9.65,0Z"/></svg>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"]:checked {
|
||||||
|
& ~ .formulate-input-element-decorator {
|
||||||
|
border-color: $formulate-green;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $formulate-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
& ~ .formulate-input-element-decorator {
|
||||||
|
border-color: $formulate-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.formulate-input-label--after {
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formulate-input-label--before {
|
||||||
|
margin-right: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-classification="group"] {
|
||||||
|
& > .formulate-input-wrapper {
|
||||||
|
& > .formulate-input-label {
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
themes/snow/_variables.scss
Normal file
56
themes/snow/_variables.scss
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Snow theme colors
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
$formulate-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
|
||||||
|
$formulate-gray: #efefef;
|
||||||
|
$formulate-gray-d: #cecece;
|
||||||
|
$formulate-gray-dd: #a8a8a8;
|
||||||
|
$formulate-gray-ddd: #6d6d6d;
|
||||||
|
$formulate-gray-dddd: #2e2e2e;
|
||||||
|
|
||||||
|
$formulate-dark: #000000;
|
||||||
|
|
||||||
|
$formulate-blue-l: #f3f4f4;
|
||||||
|
|
||||||
|
$formulate-green: #41b883;
|
||||||
|
|
||||||
|
$formulate-yellow-d: #6b5900;
|
||||||
|
$formulate-yellow: #e6c000;
|
||||||
|
$formulate-yellow-l: #fff8d2;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Mixins
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
@mixin baseinput {
|
||||||
|
appearance: none;
|
||||||
|
border-radius: .3em;
|
||||||
|
border: 1px solid $formulate-gray-d;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: .9em;
|
||||||
|
font-family: $formulate-font-stack;
|
||||||
|
padding: .75em;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: $formulate-gray-dd;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
border: 1px solid $formulate-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin down-arrow {
|
||||||
|
content: '';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border: .3em solid transparent;
|
||||||
|
border-top-color: $formulate-gray-d;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
2
themes/snow/snow.scss
Normal file
2
themes/snow/snow.scss
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
@import 'variables';
|
||||||
|
@import 'inputs';
|
@ -1,40 +0,0 @@
|
|||||||
const webpack = require('webpack')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: path.resolve(`${__dirname}/src/formulate.js`),
|
|
||||||
output: {
|
|
||||||
path: path.resolve(`${__dirname}/dist/`),
|
|
||||||
filename: 'index.js',
|
|
||||||
libraryTarget: 'umd',
|
|
||||||
library: 'vue-formulate',
|
|
||||||
umdNamedDefine: true
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.js', '.vue']
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
loaders: [
|
|
||||||
{
|
|
||||||
test: /\.vue$/,
|
|
||||||
loader: 'vue-loader',
|
|
||||||
options: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
include: [path.resolve(__dirname, 'src')]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.optimize.UglifyJsPlugin({
|
|
||||||
minimize: true,
|
|
||||||
sourceMap: false,
|
|
||||||
mangle: true,
|
|
||||||
compress: {
|
|
||||||
warnings: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user