├── .gitignore ├── test ├── spec │ └── support │ │ └── jasmine.json ├── webpack.config.js ├── vue-mock.js └── index.js ├── src ├── index.js ├── errors.js └── form.js ├── webpack.config.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /test/spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false 11 | } 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Form from './form.js' 2 | 3 | function install (Vue) { 4 | Vue.prototype.$form = function (data) { 5 | return new Form(Vue, data); 6 | } 7 | 8 | Vue.form = function (data) { 9 | return new Form(Vue, data); 10 | } 11 | } 12 | 13 | export default install -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: __dirname + '/src/index.js', 3 | output: { 4 | path: __dirname + '/dist/', 5 | filename: 'vue-form.js', 6 | }, 7 | module: { 8 | loaders: [ 9 | { 10 | test: /.js/, 11 | exclude: /node_modules/, 12 | loader: 'babel', 13 | query: {presets: ['es2015']} 14 | } 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /test/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: __dirname + '/index.js', 3 | output: { 4 | path: __dirname + '/spec/', 5 | filename: 'spec.js', 6 | }, 7 | module: { 8 | loaders: [ 9 | { 10 | test: /.js/, 11 | exclude: /node_modules/, 12 | loader: 'babel', 13 | query: {presets: ['es2015']} 14 | } 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-form-state", 3 | "version": "0.2.1", 4 | "description": "A VueJs plugin to simplify ajax form handling.", 5 | "main": "dist/vue-form.js", 6 | "scripts": { 7 | "test": "webpack --config test/webpack.config.js", 8 | "build": "webpack" 9 | }, 10 | "keywords": [ 11 | "vue", 12 | "vuejs", 13 | "form", 14 | "vue-form" 15 | ], 16 | "author": "Caleb Porzio", 17 | "license": "MIT", 18 | "dependencies": { 19 | "lodash": "^4.14.1" 20 | }, 21 | "devDependencies": { 22 | "babel": "^6.5.2", 23 | "babel-core": "^6.11.4", 24 | "babel-loader": "^6.2.4", 25 | "babel-preset-es2015": "^6.9.0", 26 | "webpack": "^1.13.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/vue-mock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | http: { 3 | get: function (url) { 4 | return new Promise((resolve, reject) => { 5 | if (url == 'resolve') { 6 | resolve({ data: 7 | { 8 | name: 'John', 9 | game: 'Jacks' 10 | } 11 | }) 12 | } else { 13 | reject({ data: 14 | { 15 | field: ["Some Error"] 16 | } 17 | }) 18 | } 19 | }) 20 | }, 21 | post(url, data) { 22 | return new Promise((resolve, reject) => { 23 | if (url == 'resolve') { 24 | resolve({ data: 25 | data || {} 26 | }) 27 | } else { 28 | reject({ data: 29 | { 30 | field: ["Some Error"] 31 | } 32 | }) 33 | } 34 | }) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | export default function Errors () { 4 | this.errors = {}; 5 | 6 | this.hasErrors = function () { 7 | return ! _.isEmpty(this.errors); 8 | }; 9 | 10 | this.has = function (field) { 11 | return _.indexOf(_.keys(this.errors), field) > -1; 12 | }; 13 | 14 | this.all = function () { 15 | return this.errors; 16 | }; 17 | 18 | this.flatten = function () { 19 | return _.flatten(_.toArray(this.errors)); 20 | }; 21 | 22 | this.get = function (field) { 23 | if (this.has(field)) { 24 | return this.errors[field][0]; 25 | } 26 | }; 27 | 28 | this.set = function (errors) { 29 | if (typeof errors === 'object') { 30 | this.errors = errors; 31 | } else { 32 | this.errors = {'form': ['Whoops, looks like something went wrong.']}; 33 | } 34 | }; 35 | 36 | this.forget = function () { 37 | this.errors = {}; 38 | }; 39 | }; -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from './vue-mock' 3 | 4 | import Form from '../src/form.js' 5 | 6 | describe("Form", () => { 7 | 8 | let form = new Form(Vue, { name: 'Jack', girlfriend: 'jill' }) 9 | 10 | it("puts data into it's data property when instantiated", () => { 11 | expect(form.data.name).toBe('Jack') 12 | expect(form.data.girlfriend).toBe('jill') 13 | }) 14 | 15 | describe("The init method", () => { 16 | 17 | it("overwrites the data object on completion", (done) => { 18 | form.init('resolve') 19 | .then((response) => { 20 | expect(form.data.name).toBe('John') 21 | done() 22 | }).catch(() => done()) 23 | }) 24 | 25 | it("doesnt add uninitialized data properties", (done) => { 26 | form.init('resolve') 27 | .then(response => { 28 | expect(form.data.game).toBe(undefined) 29 | done() 30 | }) 31 | }) 32 | }) 33 | 34 | describe("Form.errors", () => { 35 | 36 | it("has errors when an ajax request fails", (done) => { 37 | form.post('reject') 38 | .catch(errors => { 39 | expect(form.errors.hasErrors()).toBe(true) 40 | expect(form.errors.flatten()[0]).toBe("Some Error") 41 | expect(form.errors.has("field")).toBe(true) 42 | expect(form.errors.get("field")).toBe("Some Error") 43 | expect(form.errors.all().field.length).toBe(1) 44 | 45 | form.errors.forget() 46 | 47 | expect(form.errors.hasErrors()).toBe(false) 48 | 49 | done() 50 | }) 51 | }) 52 | }) 53 | 54 | }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue-Form 2 | --- 3 | A plugin for Vue.js that takes the pain out ajax-heavy web apps. 4 | (Heavily inspired from SparkForm in the [Laravel Spark](https://spark.laravel.com/) source code.) 5 | 6 | ## Installation 7 | 8 | ### NPM 9 | ``` 10 | $ npm install vue-form-state 11 | ``` 12 | 13 | ### Setup 14 | ```javascript 15 | var Vue = require('vue'); 16 | var VueResource = require('vue-resource'); 17 | var VueFormState = require('vue-form-state'); 18 | 19 | Vue.use(VueResource); 20 | Vue.use(VueFormState); 21 | ``` 22 | 23 | ### Usage 24 | 25 | This should demonstrate the basic usage of VueFormState in a Vue.js component. 26 | ```javascript 27 | vm = new Vue({ 28 | data: { 29 | form: new Vue.form({ 30 | foo: 'foo', 31 | bar: 'bar' 32 | }) 33 | }, 34 | 35 | ready: function () { 36 | // Initialize the data property via get request 37 | // with data from the server. 38 | this.form.init('some/url'); 39 | }, 40 | 41 | methods: { 42 | send: function () { 43 | // Send the data property to the server 44 | // update the state, and return a promise. 45 | 46 | // Post 47 | this.form.post('some/url'); 48 | 49 | // Put 50 | this.form.put('some/url/1'); 51 | 52 | // Delete 53 | this.form.delete('some/url/1'); 54 | } 55 | } 56 | }) 57 | ``` 58 | 59 | #### State Helpers 60 | These are the methods available on your form object. Feel free to use them in your templates for more convenient operations. 61 | ```javascript 62 | form.ready; // Set to true after the init method returns successfully. 63 | 64 | form.busy; // Set to true during POST / PUT / DELETE requests. 65 | 66 | form.successful; // Set to true after a 200 response from the server. 67 | 68 | form.errors.hasErrors(); // Returns true if the form recieved errors from the server. 69 | 70 | form.errors.has('someError'); // Get a specific error. 71 | 72 | form.errors.all(); // Get an object of all the errors. 73 | 74 | form.errors.flatten(); // Get a flattened array of all the errors. 75 | 76 | form.errors.get('someError'); // Get a specific error message / array of messages 77 | ``` 78 | 79 | As always, the source code is the best form of documentation. Take a look at [this file](https://github.com/calebporzio/vue-form-state/blob/master/src/form.js) for all available methods. 80 | -------------------------------------------------------------------------------- /src/form.js: -------------------------------------------------------------------------------- 1 | import Errors from './errors.js' 2 | 3 | function extend (object1, object2) { 4 | for(var key in object2) 5 | if(object1.hasOwnProperty(key)) 6 | object1[key] = object2[key]; 7 | return object1; 8 | } 9 | 10 | export default function Form (Vue, data) { 11 | var form = this; 12 | 13 | this.data = data || {}; 14 | 15 | this.errors = new Errors(); 16 | 17 | this.ready = false; 18 | this.busy = false; 19 | this.successful = false; 20 | 21 | this.startProcessing = function () { 22 | form.errors.forget(); 23 | form.busy = true; 24 | form.successful = false; 25 | }; 26 | 27 | this.finishProcessing = function () { 28 | form.busy = false; 29 | form.successful = true; 30 | }; 31 | 32 | this.resetStatus = function () { 33 | form.errors.forget(); 34 | form.busy = false; 35 | form.successful = false; 36 | }; 37 | 38 | this.setErrors = function (errors) { 39 | form.busy = false; 40 | form.errors.set(errors); 41 | }; 42 | 43 | this.init = function (uri) { 44 | return new Promise((resolve, reject) => { 45 | Vue.http.get(uri, form.clean(form.data)) 46 | .then(response => { 47 | extend(form.data, response.data); 48 | form.ready = true; 49 | resolve(response); 50 | }) 51 | .catch(errors => { 52 | form.errors.set(errors.data); 53 | reject(errors); 54 | }); 55 | }); 56 | }; 57 | 58 | this.post = function (uri) { 59 | return this.sendForm('post', uri); 60 | }; 61 | 62 | this.get = function (uri) { 63 | return this.sendForm('get', uri); 64 | }; 65 | 66 | this.put = function (uri) { 67 | return this.sendForm('put', uri); 68 | }; 69 | 70 | this.delete = function (uri) { 71 | return this.sendForm('delete', uri); 72 | }; 73 | 74 | this.sendForm = function (method, uri) { 75 | return new Promise((resolve, reject) => { 76 | form.startProcessing(); 77 | 78 | Vue.http[method](uri, form.clean(form.data)) 79 | .then(response => { 80 | extend(form.data, response.data); 81 | 82 | form.finishProcessing(); 83 | 84 | resolve(response); 85 | }) 86 | .catch(errors => { 87 | form.errors.set(errors.data); 88 | form.busy = false; 89 | 90 | reject(errors); 91 | }); 92 | }); 93 | }; 94 | 95 | this.clean = function (data) { 96 | return JSON.parse(JSON.stringify(data)); 97 | }; 98 | } --------------------------------------------------------------------------------