├── .gitignore ├── .npmignore ├── LICENSE.mit ├── README.md ├── package.json └── src ├── form-errors.js ├── form.js ├── helpers.js ├── http.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | dist/ 3 | node_modules/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | src/ 3 | -------------------------------------------------------------------------------- /LICENSE.mit: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cody Mercer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-laravel-forms 2 | Form helpers for [Laravel](https://laravel.com) backed [Vue.js](https://vuejs.org) projects. 3 | 4 | **Disclaimer: This plugin is still in a BETA state** 5 | 6 | ## Installation 7 | **Install package via NPM** 8 | ``` 9 | npm install vue-laravel-forms 10 | ``` 11 | 12 | ## Setup 13 | **Install plugin within project** 14 | ```javascript 15 | import Vue from 'vue' 16 | import { FormHelpers } from 'vue-laravel-forms' 17 | 18 | Vue.use(FormHelpers); 19 | ``` 20 | 21 | or 22 | 23 | ```javascript 24 | window.Vue = require('vue'); 25 | require('vue-laravel-forms'); 26 | ``` 27 | 28 | Alternatively, you may import the various components of this plugin separately. 29 | ```javascript 30 | import { Form, FormErrors, Http } from 'vue-laravel-forms' 31 | 32 | window.AppForm = Form; 33 | window.AppFormErrors = FormErrors; 34 | 35 | _.extend(App, new Http()) // Vue.http config not needed 36 | _.extend(App, new Http(Vue.http)) // Vue.http config needed 37 | ``` 38 | 39 | ## Usage 40 | ### Creating a Form 41 | _Components installed via Vue_ 42 | ```javascript 43 | Vue.component('user-registration-form', { 44 | forms: { 45 | userRegistrationForm: { 46 | name: '', 47 | email: '', 48 | password: '', 49 | password_confirmation: '' 50 | } 51 | } 52 | } 53 | ``` 54 | 55 | _Components installed separately_ 56 | ```javascript 57 | Vue.component('user-registration-form', { 58 | data() { 59 | return { 60 | userRegistrationForm: new AppForm({ 61 | name: '', 62 | email: '', 63 | password: '', 64 | password_confirmation: '', 65 | }); 66 | } 67 | } 68 | }); 69 | ``` 70 | 71 | ### Submitting a Form 72 | _Via a POST request (Components installed via Vue)_ 73 | ```javascript 74 | Vue.component('user-registration-form', { 75 | 76 | // Create your form using one of the techniques described above. 77 | 78 | methods: { 79 | registerUser() { 80 | this.$forms.post('api/users', this.userRegistrationForm) 81 | .then(response => console.log(response.data)) 82 | .catch(errors => console.log(errors)); 83 | } 84 | } 85 | ``` 86 | 87 | _Via a POST request (Components installed separately)_ 88 | ```javascript 89 | Vue.component('user-registration-form', { 90 | 91 | // Create your form using one of the techniques described above. 92 | 93 | methods: { 94 | registerUser() { 95 | App.postForm('api/users', this.userRegistrationForm) 96 | .then(response => console.log(response.data)) 97 | .catch(errors => console.log(errors)); 98 | } 99 | } 100 | ``` 101 | 102 | ##### Available methods for submitting a form 103 | _Components installed via Vue_ 104 | * `vm.$form.delete(uri, form)` 105 | * `vm.$form.post(uri, form)` 106 | * `vm.$form.put(uri, form)` 107 | * `vm.$form.submit(method, uri, form)` 108 | 109 | _Components installed Separately_ 110 | * `App.deleteForm(uri, form)` 111 | * `App.postForm(uri, form)` 112 | * `App.putForm(uri, form)` 113 | * `App.sendForm(method, uri, form)` 114 | 115 | 116 | ### Template Helpers 117 | ##### Check a field for errors 118 | ```javascript 119 | Vue.component('user-registration-form', { 120 | 121 | methods: { 122 | checkFieldForError(field) { 123 | return this.userRegistrationForm.errors.has(field); 124 | } 125 | } 126 | 127 | }); 128 | ``` 129 | 130 | ##### Use the `fieldClass` helper method 131 | 132 | ```javascript 133 | formInstance.fieldClass(field, defaultClass, errorClass) 134 | ``` 135 | 136 | ```vue 137 |
138 | // Truncated for brevity 139 |
140 | ``` 141 | 142 | Alternatively, pass callbacks for `defaultClass` and `errorClass`. 143 | ```vue 144 |
145 | // Truncated for brevity 146 |
147 | ``` 148 | ```javascript 149 | Vue.component('user-registration-form', { 150 | 151 | methods: { 152 | getFieldClass(field) { 153 | return `form-group ${field}`; 154 | }, 155 | 156 | getFieldErrorClass(field) { 157 | return `has-error ${field}-error`; 158 | } 159 | } 160 | 161 | }); 162 | ``` 163 | 164 | ##### Get the error message for a field 165 | ```vue 166 |

167 | {{ userRegistrationForm.errors.get('email') }} 168 |

169 | ``` 170 | 171 | ## Contributing 172 | If you have any questions, feedback or would like to make improvements, **please** open an issue or pull request. 173 | 174 | ## Credits 175 | * [Taylor Otwell](https://github.com/taylorotwell) - This project is heavily based on the form helpers that were 176 | included in the alpha release of [laravel/spark](https://spark.laravel.com). 177 | 178 | ## License 179 | [MIT](https://opensource.org/licenses/MIT) 180 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-laravel-forms", 3 | "version": "1.0.10-beta", 4 | "description": "Form helpers for Laravel backed Vue.js projects.", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "compile": "babel --presets=es2015 -d dist src", 8 | "prepublish": "npm run compile" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/cklmercer/vue-laravel-forms.git" 13 | }, 14 | "keywords": [ 15 | "vue", 16 | "laravel", 17 | "forms" 18 | ], 19 | "author": "Cody Mercer ", 20 | "maintainer": "Matt Trask ", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "babel-cli": "^6.11.4", 24 | "babel-preset-es2015": "^6.13.2", 25 | "lodash": "^4.15.0", 26 | "vue": "^1.0.26", 27 | "vue-resource": "^0.9.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/form-errors.js: -------------------------------------------------------------------------------- 1 | import { flatten, has, isEmpty, toArray } from 'lodash' 2 | 3 | class FormErrors 4 | { 5 | /* 6 | * Create a new FormErrors instance. 7 | */ 8 | constructor() { 9 | this.errors = {}; 10 | } 11 | 12 | /* 13 | * Get all of the raw errors for the collection. 14 | */ 15 | all() { 16 | return this.errors; 17 | } 18 | 19 | /* 20 | * Determine if the collection has any errors. 21 | */ 22 | hasErrors() { 23 | return ! isEmpty(this.errors); 24 | } 25 | 26 | /* 27 | * Get all of the errors for the collection in a flat array. 28 | */ 29 | flatten() { 30 | return flatten(toArray(this.errors)); 31 | } 32 | 33 | /* 34 | * Forget all of the errors currently in the collection. 35 | */ 36 | forget() { 37 | this.errors = {}; 38 | } 39 | 40 | /* 41 | * Get the first error for the given field. 42 | */ 43 | get(field) { 44 | if (this.has(field)) { 45 | return this.errors[field][0]; 46 | } 47 | } 48 | 49 | /* 50 | * Determine if the collection has any errors for the given field. 51 | */ 52 | has(field) { 53 | return has(this.errors, field); 54 | } 55 | 56 | /* 57 | * Set the raw errors for the collection. 58 | */ 59 | set(errors) { 60 | if (typeof errors === 'object') { 61 | this.errors = errors; 62 | } else { 63 | this.errors = { 64 | form: ['Something went wrong. Please try again or contact customer support.'] 65 | } 66 | } 67 | } 68 | } 69 | 70 | export default FormErrors; -------------------------------------------------------------------------------- /src/form.js: -------------------------------------------------------------------------------- 1 | import FormErrors from './form-errors'; 2 | import { assignIn, forIn, keys, pick } from 'lodash'; 3 | 4 | class Form 5 | { 6 | /* 7 | * Create a new Form instance. 8 | */ 9 | constructor(fields) { 10 | this.busy = false; 11 | this.errors = new FormErrors(); 12 | this.initialFields = fields; 13 | this.successful = false; 14 | assignIn(this, fields); 15 | } 16 | 17 | /* 18 | * Get the form's fields. 19 | */ 20 | get fields() { 21 | let fields = pick(this, keys(this.initialFields)); 22 | 23 | // Here we unset null fields. 24 | forIn(fields, function (value, key) { 25 | if (value == null) { 26 | delete fields[key]; 27 | } 28 | }) 29 | 30 | return fields; 31 | } 32 | 33 | /* 34 | * Get the html/css class for the given field. 35 | */ 36 | fieldClass(field, defaultClass = '', errorClass = '') { 37 | let defaultClassString = typeof defaultClass == 'function' ? defaultClass(field) : defaultClass; 38 | let errorClassString = typeof errorClass == 'function' ? errorClass(field) : errorClass; 39 | 40 | return this.errors.has(field) ? `${defaultClassString} ${errorClassString}` : defaultClassString; 41 | } 42 | 43 | /* 44 | * Finish processing the form. 45 | */ 46 | finishProcessing() { 47 | this.busy = false; 48 | this.successful = true; 49 | } 50 | 51 | /* 52 | * Completely reset the form. 53 | */ 54 | reset() { 55 | this.resetFields(); 56 | this.resetStatus(); 57 | } 58 | 59 | /* 60 | * Reset the fields to their initial state.. 61 | */ 62 | resetFields() { 63 | keys(this.initialFields).forEach((key) => this[key] = this.initialFields[key]); 64 | } 65 | 66 | /* 67 | * Reset the errors and other state for the form. 68 | */ 69 | resetStatus() { 70 | this.busy = false; 71 | this.errors.forget(); 72 | this.successful = false; 73 | } 74 | 75 | /* 76 | * Set the errors on the form. 77 | */ 78 | setErrors(errors) { 79 | this.busy = false; 80 | this.errors.set(errors); 81 | } 82 | 83 | /* 84 | * Start processing the form. 85 | */ 86 | startProcessing() { 87 | this.errors.forget(); 88 | this.busy = true; 89 | this.successful = false; 90 | } 91 | 92 | } 93 | 94 | export default Form; -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | function getFileExtension(file) { 2 | return file.name.substr(file.name.lastIndexOf('.') + 1); 3 | } 4 | 5 | function isFile(field) { 6 | return Boolean(field) && Boolean(field.size); 7 | } 8 | 9 | export { getFileExtension, isFile } -------------------------------------------------------------------------------- /src/http.js: -------------------------------------------------------------------------------- 1 | import { forIn } from 'lodash'; 2 | import { getFileExtension, isFile } from './helpers'; 3 | 4 | export default function (Http = null) { 5 | 6 | if ( ! Http) { 7 | var Vue = require('vue'); 8 | var VueResource = require('vue-resource'); 9 | Vue.use(VueResource) 10 | } 11 | 12 | return { 13 | /* 14 | * Helper method for submitting a form as a DELETE request. 15 | */ 16 | deleteForm(uri, form) { 17 | return this.sendForm('delete', uri, form); 18 | }, 19 | 20 | /* 21 | * Helper method for submitting a form as a POST request. 22 | */ 23 | postForm(uri, form) { 24 | let formData = new FormData; 25 | 26 | forIn(form.fields, (value, key) => { 27 | if (isFile(value)) { 28 | let ext = getFileExtension(value); 29 | formData.append(key, value, `${key}.${ext}`); 30 | } else { 31 | formData.append(key, value) 32 | } 33 | }); 34 | 35 | return this.sendForm('post', uri, form, formData); 36 | }, 37 | 38 | 39 | /* 40 | * Helper method for submitting a form as a PUT request. 41 | */ 42 | putForm(uri, form) { 43 | return this.sendForm('put', uri, form); 44 | }, 45 | 46 | /* 47 | * Helper method for submitting a form. 48 | */ 49 | sendForm(method, uri, form, formData = null) { 50 | let submitter = Http ? Http : Vue.http; 51 | 52 | let data = formData ? formData : JSON.parse(JSON.stringify(form.fields)); 53 | 54 | return new Promise((resolve, reject) => { 55 | form.startProcessing(); 56 | 57 | submitter[method](uri, data) 58 | .then(response => { 59 | form.finishProcessing(); 60 | resolve(response.data); 61 | }) 62 | .catch(errors => { 63 | form.setErrors(errors.data); 64 | form.busy = false; 65 | reject(errors.data); 66 | }); 67 | }); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Http from './http'; 2 | import Form from './form'; 3 | import FormErrors from './form-errors'; 4 | import { has } from 'lodash'; 5 | 6 | function FormHelpers (Vue) { 7 | 8 | let http = Vue.hasOwnProperty('http') ? Vue.http : null; 9 | let formHelper = new Http(http); 10 | 11 | Object.defineProperty(Vue.prototype, '$forms', { 12 | get() { 13 | return { 14 | /* 15 | * Create a new Form instance. 16 | */ 17 | create(fields) { 18 | return new Form(fields); 19 | }, 20 | 21 | /* 22 | * Create a new FormErrors instance. 23 | */ 24 | errors() { 25 | return new FormErrors(); 26 | }, 27 | 28 | /* 29 | * Submit the given Form to the given URI via a DELETE request. 30 | */ 31 | delete(uri, form) { 32 | return formHelper.deleteForm(uri, form); 33 | }, 34 | 35 | /* 36 | * Submit the given Form to the given URI via a POST request. 37 | */ 38 | post(uri, form) { 39 | return formHelper.postForm(uri, form); 40 | }, 41 | 42 | /* 43 | * Submit the given Form to the given URI via a PUT request 44 | */ 45 | put(uri, form) { 46 | return formHelper.putForm(uri, form); 47 | }, 48 | 49 | /* 50 | * Submit the given Form to the given URI using the given HTTP method. 51 | */ 52 | submit(method, uri, form, formData = null) { 53 | return formHelper.sendForm(method, uri, form, formData); 54 | } 55 | } 56 | } 57 | }); 58 | 59 | Vue.mixin({ 60 | 61 | /* 62 | * The 'beforeCreate' life-cycle hook for Vue 2.0. 63 | */ 64 | beforeCreate() { 65 | registerForms(this); 66 | }, 67 | 68 | /* 69 | * The 'init' life-cycle hook for Vue 1.0. 70 | */ 71 | init() { 72 | registerForms(this); 73 | } 74 | 75 | }); 76 | } 77 | 78 | /* 79 | * Register the forms in the forms option. 80 | */ 81 | function registerForms(vm) { 82 | let forms = vm.$options.forms; 83 | 84 | if (typeof forms == 'object') { 85 | let dataIsFunction = typeof vm.$options.data == 'function'; 86 | let data = dataIsFunction ? vm.$options.data() : vm.$options.data; 87 | 88 | if (typeof data == 'undefined') { 89 | data = {}; 90 | } 91 | 92 | for (var form in forms) { 93 | if (has(data, form)) { 94 | throw new Error(`The form, ${form}, has a name which is colliding with another form or data property!`) 95 | } 96 | data[form] = new Form(forms[form]); 97 | } 98 | 99 | vm.$options.data = function () { return data }; 100 | } 101 | } 102 | 103 | if (typeof window !== 'undefined' && window.Vue) { 104 | window.Vue.use(FormHelpers); 105 | } 106 | 107 | export { FormHelpers, Http, Form, FormErrors } 108 | --------------------------------------------------------------------------------