diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..561a6af
Binary files /dev/null and b/.DS_Store differ
diff --git a/README.md b/README.md
index 3ff291d..7c0c7de 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,152 @@
# vue-laravel-form
Vue Js Forms with laravel validation
-
+
+
+
+This package provides a Form class that can validation our forms with laravel backend validation logic. The class is meant to be used with a Laravel back end.
+
+
+## Install
+
+You can install the package via yarn (or npm):
+```bash
+$ npm install @imritesh/form
+```
+```bash
+$ yarn add @imritesh/form
+```
+By default, this package expects `axios` to be installed
+
+```bash
+$ yarn add axios
+```
+
+## Usage
+```html
+
+
+
+
+
+```
+```js
+
+import Form from 'form-backend-validation';
+
+// Instantiate a form class with some values
+const form = new Form({
+ field1: 'value 1',
+ field2: 'value 2',
+ person: {
+ first_name: 'John',
+ last_name: 'Doe',
+ },
+});
+
+// A form can also be initiated with an array
+const form = new Form(['field1', 'field2']);
+
+// Submit the form, you can also use `.put`, `.patch` and `.delete`
+form.post(anUrl)
+ .then(response => ...)
+ .catch(response => ...);
+
+// Returns true if request is being executed
+form.processing;
+
+// If there were any validation errors, you easily access them
+
+// Example error response (json)
+{
+ "errors": {
+ "field1": ['Value is required'],
+ "field2": ['Value is required']
+ }
+}
+
+// Returns an object in which the keys are the field names
+// and the values array with error message sent by the server
+form.errors.all();
+
+// Returns true if there were any error
+form.errors.any();
+
+// Returns true if there is an error for the given field name or object
+form.errors.has(key);
+
+// Returns the first error for the given field name
+form.errors.first(key);
+
+// Returns an array with errors for the given field name
+form.errors.get(key);
+
+// Shortcut for getting the errors for the given field name
+form.getError(key);
+
+// Clear all errors
+form.errors.clear();
+
+// Clear the error of the given field name or all errors on the given object
+form.errors.clear(key);
+
+// Returns an object containing fields based on the given array of field names
+form.only(keys);
+
+// Reset the values of the form to those passed to the constructor
+form.reset();
+
+// Set the values which should be used when calling reset()
+form.setInitialValues();
+
+// Populate a form after its instantiation, the populated fields won't override the initial fields
+// Fields not present at instantiation will not be populated
+const form = new Form({
+ field1: '',
+ field2: '',
+});
+
+form.populate({
+ field1: 'foo',
+ field2: 'bar',
+});
+
+```
+
+### Options
+
+The `Form` class accepts a second `options` parameter.
+
+```js
+const form = new Form({
+ field1: 'value 1',
+ field2: 'value 2',
+}, {
+ resetOnSuccess: false,
+});
+```
+
+You can also pass options via a `withOptions` method (this example uses the `create` factory method.
+
+```
+const form = Form.create()
+ .withOptions({ resetOnSuccess: false })
+ .withData({
+ field1: 'value 1',
+ field2: 'value 2',
+ });
+```
+
+#### `resetOnSuccess: bool`
+
+Default: `true`. Set to `false` if you don't want the form to reset to its original values after a succesful submit.
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..1e02007
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@imritesh/form",
+ "version": "2.0.0",
+ "description": "An easy way to validate forms using laravel backend logic",
+ "author": "Ritesh Singh",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/riteshsingh1/vue-laravel-form.git"
+ },
+ "main": "src/index.js",
+ "keywords": [
+ "vuejs-form",
+ "vue-form",
+ "laravel-form",
+ "validation",
+ "server"
+ ]
+}
diff --git a/src/Errors.js b/src/Errors.js
new file mode 100755
index 0000000..f0a9f5b
--- /dev/null
+++ b/src/Errors.js
@@ -0,0 +1,83 @@
+class Errors {
+ /**
+ * Create a new Errors instance.
+ */
+ constructor(errors = {}) {
+ this.record(errors);
+ }
+
+ /**
+ * Get all the errors.
+ *
+ * @return {object}
+ */
+ all() {
+ return this.errors;
+ }
+
+ /**
+ * Determine if any errors exists for the given field or object.
+ *
+ * @param {string} field
+ */
+ has(field) {
+ let hasError = this.errors.hasOwnProperty(field);
+
+ if (!hasError) {
+ const errors = Object.keys(this.errors).filter(
+ e => e.startsWith(`${field}.`) || e.startsWith(`${field}[`)
+ );
+
+ hasError = errors.length > 0;
+ }
+
+ return hasError;
+ }
+
+ first(field) {
+ return this.get(field)[0];
+ }
+
+ get(field) {
+ return this.errors[field] || [];
+ }
+
+ /**
+ * Determine if we have any errors.
+ */
+ any() {
+ return Object.keys(this.errors).length > 0;
+ }
+
+ /**
+ * Record the new errors.
+ *
+ * @param {object} errors
+ */
+ record(errors = {}) {
+ this.errors = errors;
+ }
+
+ /**
+ * Clear a specific field, object or all error fields.
+ *
+ * @param {string|null} field
+ */
+ clear(field) {
+ if (!field) {
+ this.errors = {};
+
+ return;
+ }
+
+ let errors = Object.assign({}, this.errors);
+
+ Object.keys(errors)
+ .filter(e => e === field || e.startsWith(`${field}.`) || e.startsWith(`${field}[`))
+ .forEach(e => delete errors[e]);
+
+ this.errors = errors;
+ }
+}
+
+export default Errors;
diff --git a/src/Form.js b/src/Form.js
new file mode 100755
index 0000000..ab104ae
--- /dev/null
+++ b/src/Form.js
@@ -0,0 +1,325 @@
+import Errors from './Errors';
+import { guardAgainstReservedFieldName, isArray, isFile, merge, objectToFormData } from './util';
+
+class Form {
+ /**
+ * Create a new Form instance.
+ *
+ * @param {object} data
+ * @param {object} options
+ */
+ constructor(data = {}, options = {}) {
+ this.processing = false;
+ this.successful = false;
+
+ this.withData(data)
+ .withOptions(options)
+ .withErrors({});
+ }
+
+ withData(data) {
+ if (isArray(data)) {
+ data = data.reduce((carry, element) => {
+ carry[element] = '';
+ return carry;
+ }, {});
+ }
+
+ this.setInitialValues(data);
+
+ this.errors = new Errors();
+ this.processing = false;
+ this.successful = false;
+
+ for (const field in data) {
+ guardAgainstReservedFieldName(field);
+
+ this[field] = data[field];
+ }
+
+ return this;
+ }
+
+ withErrors(errors) {
+ this.errors = new Errors(errors);
+
+ return this;
+ }
+
+ withOptions(options) {
+ this.__options = {
+ resetOnSuccess: true,
+ };
+
+ if (options.hasOwnProperty('resetOnSuccess')) {
+ this.__options.resetOnSuccess = options.resetOnSuccess;
+ }
+
+ if (options.hasOwnProperty('onSuccess')) {
+ this.onSuccess = options.onSuccess;
+ }
+
+ if (options.hasOwnProperty('onFail')) {
+ this.onFail = options.onFail;
+ }
+
+ const windowAxios = typeof window === 'undefined' ? false : window.axios
+
+ this.__http = options.http || windowAxios || require('axios');
+
+ if (!this.__http) {
+ throw new Error(
+ 'No http library provided. Either pass an http option, or install axios.'
+ );
+ }
+
+ return this;
+ }
+
+ /**
+ * Fetch all relevant data for the form.
+ */
+ data() {
+ const data = {};
+
+ for (const property in this.initial) {
+ data[property] = this[property];
+ }
+
+ return data;
+ }
+
+ /**
+ * Fetch specific data for the form.
+ *
+ * @param {array} fields
+ * @return {object}
+ */
+ only(fields) {
+ return fields.reduce((filtered, field) => {
+ filtered[field] = this[field];
+ return filtered;
+ }, {});
+ }
+
+ /**
+ * Reset the form fields.
+ */
+ reset() {
+ merge(this, this.initial);
+
+ this.errors.clear();
+ }
+
+ setInitialValues(values) {
+ this.initial = {};
+
+ merge(this.initial, values);
+ }
+
+ populate(data) {
+ Object.keys(data).forEach(field => {
+ guardAgainstReservedFieldName(field);
+
+ if (this.hasOwnProperty(field)) {
+ merge(this, { [field]: data[field] });
+ }
+ });
+
+ return this;
+ }
+
+ /**
+ * Clear the form fields.
+ */
+ clear() {
+ for (const field in this.initial) {
+ this[field] = '';
+ }
+
+ this.errors.clear();
+ }
+
+ /**
+ * Send a POST request to the given URL.
+ *
+ * @param {string} url
+ */
+ post(url) {
+ return this.submit('post', url);
+ }
+
+ /**
+ * Send a PUT request to the given URL.
+ *
+ * @param {string} url
+ */
+ put(url) {
+ return this.submit('put', url);
+ }
+
+ /**
+ * Send a PATCH request to the given URL.
+ *
+ * @param {string} url
+ */
+ patch(url) {
+ return this.submit('patch', url);
+ }
+
+ /**
+ * Send a DELETE request to the given URL.
+ *
+ * @param {string} url
+ */
+ delete(url) {
+ return this.submit('delete', url);
+ }
+
+ /**
+ * Submit the form.
+ *
+ * @param {string} requestType
+ * @param {string} url
+ */
+ submit(requestType, url) {
+ this.__validateRequestType(requestType);
+ this.errors.clear();
+ this.processing = true;
+ this.successful = false;
+
+ return new Promise((resolve, reject) => {
+ this.__http[requestType](
+ url,
+ this.hasFiles() ? objectToFormData(this.data()) : this.data()
+ )
+ .then(response => {
+ this.processing = false;
+ this.onSuccess(response.data);
+
+ resolve(response.data);
+ })
+ .catch(error => {
+ this.processing = false;
+ this.onFail(error);
+
+ reject(error);
+ });
+ });
+ }
+
+ /**
+ * @returns {boolean}
+ */
+ hasFiles() {
+ for (const property in this.initial) {
+ if (this.hasFilesDeep(this[property])) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ /**
+ * @param {Object|Array} object
+ * @returns {boolean}
+ */
+ hasFilesDeep(object) {
+ if (object === null) {
+ return false;
+ }
+
+ if (typeof object === 'object') {
+ for (const key in object) {
+ if (object.hasOwnProperty(key)) {
+ if (isFile(object[key])) {
+ return true;
+ }
+ }
+ }
+ }
+
+ if (Array.isArray(object)) {
+ for (const key in object) {
+ if (object.hasOwnProperty(key)) {
+ return this.hasFilesDeep(object[key]);
+ }
+ }
+ }
+
+ return isFile(object);
+ }
+
+ /**
+ * Handle a successful form submission.
+ *
+ * @param {object} data
+ */
+ onSuccess(data) {
+ this.successful = true;
+
+ if (this.__options.resetOnSuccess) {
+ this.reset();
+ }
+ }
+
+ /**
+ * Handle a failed form submission.
+ *
+ * @param {object} data
+ */
+ onFail(error) {
+ this.successful = false;
+
+ if (error.response && error.response.data.errors) {
+ this.errors.record(error.response.data.errors);
+ }
+ }
+
+ /**
+ * Get the error message(s) for the given field.
+ *
+ * @param field
+ */
+ hasError(field) {
+ return this.errors.has(field);
+ }
+
+ /**
+ * Get the first error message for the given field.
+ *
+ * @param {string} field
+ * @return {string}
+ */
+ getError(field) {
+ return this.errors.first(field);
+ }
+
+ /**
+ * Get the error messages for the given field.
+ *
+ * @param {string} field
+ * @return {array}
+ */
+ getErrors(field) {
+ return this.errors.get(field);
+ }
+
+ __validateRequestType(requestType) {
+ const requestTypes = ['get', 'delete', 'head', 'post', 'put', 'patch'];
+
+ if (requestTypes.indexOf(requestType) === -1) {
+ throw new Error(
+ `\`${requestType}\` is not a valid request type, ` +
+ `must be one of: \`${requestTypes.join('`, `')}\`.`
+ );
+ }
+ }
+
+ static create(data = {}) {
+ return new Form().withData(data);
+ }
+}
+
+export default Form;
diff --git a/src/index.js b/src/index.js
new file mode 100755
index 0000000..804de2d
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,3 @@
+export { default } from './Form';
+export { default as Form } from './Form';
+export { default as Errors } from './Errors';
diff --git a/src/util/fieldNameValidation.js b/src/util/fieldNameValidation.js
new file mode 100755
index 0000000..9c5e26b
--- /dev/null
+++ b/src/util/fieldNameValidation.js
@@ -0,0 +1,35 @@
+export const reservedFieldNames = [
+ '__http',
+ '__options',
+ '__validateRequestType',
+ 'clear',
+ 'data',
+ 'delete',
+ 'errors',
+ 'getError',
+ 'getErrors',
+ 'hasError',
+ 'initial',
+ 'onFail',
+ 'only',
+ 'onSuccess',
+ 'patch',
+ 'populate',
+ 'post',
+ 'processing',
+ 'successful',
+ 'put',
+ 'reset',
+ 'submit',
+ 'withData',
+ 'withErrors',
+ 'withOptions',
+];
+
+export function guardAgainstReservedFieldName(fieldName) {
+ if (reservedFieldNames.indexOf(fieldName) !== -1) {
+ throw new Error(
+ `Field name ${fieldName} isn't allowed to be used in a Form or Errors instance.`
+ );
+ }
+}
diff --git a/src/util/formData.js b/src/util/formData.js
new file mode 100755
index 0000000..a253b75
--- /dev/null
+++ b/src/util/formData.js
@@ -0,0 +1,33 @@
+export function objectToFormData(object, formData = new FormData(), parent = null) {
+ if (object === null || object === 'undefined' || object.length === 0) {
+ return formData.append(parent, object);
+ }
+
+ for (const property in object) {
+ if (object.hasOwnProperty(property)) {
+ appendToFormData(formData, getKey(parent, property), object[property]);
+ }
+ }
+
+ return formData;
+}
+
+function getKey(parent, property) {
+ return parent ? parent + '[' + property + ']' : property;
+}
+
+function appendToFormData(formData, key, value) {
+ if (value instanceof Date) {
+ return formData.append(key, value.toISOString());
+ }
+
+ if (value instanceof File) {
+ return formData.append(key, value, value.name);
+ }
+
+ if (typeof value !== 'object') {
+ return formData.append(key, value);
+ }
+
+ objectToFormData(value, formData, key);
+}
diff --git a/src/util/index.js b/src/util/index.js
new file mode 100755
index 0000000..2288e88
--- /dev/null
+++ b/src/util/index.js
@@ -0,0 +1,3 @@
+export * from './objects';
+export * from './formData';
+export * from './fieldNameValidation';
diff --git a/src/util/objects.js b/src/util/objects.js
new file mode 100755
index 0000000..1783d80
--- /dev/null
+++ b/src/util/objects.js
@@ -0,0 +1,49 @@
+export function isArray(object) {
+ return Object.prototype.toString.call(object) === '[object Array]';
+}
+
+export function isFile(object) {
+ return object instanceof File || object instanceof FileList;
+}
+
+export function merge(a, b) {
+ for (const key in b) {
+ a[key] = cloneDeep(b[key]);
+ }
+}
+
+export function cloneDeep(object) {
+ if (object === null) {
+ return null;
+ }
+
+ if (isFile(object)) {
+ return object;
+ }
+
+ if (Array.isArray(object)) {
+ const clone = [];
+
+ for (const key in object) {
+ if (object.hasOwnProperty(key)) {
+ clone[key] = cloneDeep(object[key]);
+ }
+ }
+
+ return clone;
+ }
+
+ if (typeof object === 'object') {
+ const clone = {};
+
+ for (const key in object) {
+ if (object.hasOwnProperty(key)) {
+ clone[key] = cloneDeep(object[key]);
+ }
+ }
+
+ return clone;
+ }
+
+ return object;
+}