diff --git a/LICENSE b/LICENSE index 29a7e3b..42fcd9e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 10Quality +Copyright (c) 2017 10Quality Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d873de8..55df581 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ [![GitHub version](https://badge.fury.io/gh/10quality%2Fvue-form.svg)](https://badge.fury.io/gh/10quality%2Fvue-form) [![Bower version](https://badge.fury.io/bo/10q-vue-form.svg)](https://badge.fury.io/bo/10q-vue-form) -Form handler component for [Vue v1](http://vuejs.org/). +Form handler component for [Vue](http://vuejs.org/) v2. + +**NOTE** Use [v1.0 branch](https://github.com/10quality/vue-form/tree/v1.0) to use this with Vue v1. View [DEMO](http://codepen.io/amostajo/pen/vKdPbj). @@ -44,7 +46,7 @@ Add the component in your vue view. ```html - +
- +
``` *NOTE:* `inline-template` must be present. @@ -90,7 +92,7 @@ Data sent by form should be binded to the `request` data model. In other words, As reference, a basic contact form sample: ```html - +
@@ -115,7 +117,7 @@ As reference, a basic contact form sample: - +
``` ### Response @@ -134,7 +136,7 @@ If the following data is found, the form will *auto-process* the response (json) This response can be displayed in the template like: ```html - +
@@ -146,7 +148,7 @@ This response can be displayed in the template like: - +
``` Computed properties to use in template: @@ -160,7 +162,7 @@ Property | Data Type | Description Another example using Bootstrap: ```html - +
@@ -173,7 +175,7 @@ Another example using Bootstrap: - +
``` #### Redirection @@ -193,7 +195,7 @@ If the following data is found, the form will redirect the current window to the Form comes with a child component called `results`. This component will facilitate the handling of data returned by request (thought for searches). ```html - +
@@ -204,7 +206,7 @@ Form comes with a child component called `results`. This component will facilita /> @@ -219,7 +221,7 @@ Form comes with a child component called `results`. This component will facilita - +
``` In the example above, `results` child component is handling search results returned by the response (assuming `response` contains only results) and it is computing them into a property called `records`. @@ -241,11 +243,11 @@ Prop | Data Type | Default | Description Another example: ```html - +
- +
{{record | json}} @@ -255,7 +257,7 @@ Another example: - +
``` ### Input handling @@ -283,14 +285,14 @@ Response: In template: ```html - +
- +
``` -In the example above, the response returned a list of errors per input. `input-handler` will process the response and if errors are found, it will add an error class to the input wrapper and will list the erros under the input using a `
    ` HTML tag. +In the example above, the response returned a list of errors per input. `input-handler` will process the response and if errors are found (response must be passed as `v-model`), it will add an error class to the input wrapper and will list the erros under the input using a `
      ` HTML tag. #### Props @@ -339,14 +341,14 @@ Form comes with a set of validation rules that can be applied to input values pr In the following example, the input will validate that name is not empty (is a required field) and that it has at least 8 characters: ```html - +
      @@ -359,7 +361,7 @@ In the following example, the input will validate that name is not empty (is a r - +
      ``` List of available rules to use: @@ -383,34 +385,25 @@ Rule | Params | Sample Events dispatched by form: -Event | Data sent | Description ----------------- | ---------------------------- | ------------------- -`vform_success` | | Dispatched once response is returned and assigned to model `response`. -`vform_error` | `e` Error response returned. | Dispatched on request error. (Error is thrown to console too). -`vform_complete` | | Dispatched after request completed. (Success or error) -`vform_invalid` | `errors` List of errors. | Dispatched and broadcasted when a validation ocurred. +Event | Data sent | Description +---------- | ---------------------------- | ------------------- +`success` | | Emitted once response is returned and assigned to model `response`. +`error` | `e` Error response returned. | Emitted on request error. (Error is thrown to console too). +`complete` | | Emitted after request completed. (Success or error) +`invalid` | `errors` List of errors. | Emitted and broadcasted when a validation ocurred. Usage example: -```javacript -var app = new Vue({ - el: '#app', // Assuming binding is app - events: { - 'vform_success': function() { - // TODO MY CODE - }, - 'vform_error': function(e) { - // TODO MY CODE - }, - 'vform_complete': function() { - // TODO MY CODE - }, - 'vform_invalid': function(errors) { - // TODO MY CODE - }, - }, -}); +```html +
      + +
      ``` ## License -Copyright (c) 2016 [10Quality](http://www.10quality.com/). Under MIT License. \ No newline at end of file +Copyright (c) 2017 [10Quality](http://www.10quality.com/). Under MIT License. \ No newline at end of file diff --git a/bower.json b/bower.json index 2e01761..dab84b7 100644 --- a/bower.json +++ b/bower.json @@ -24,7 +24,7 @@ }, "private": false, "dependencies": { - "vue": "^1.0.24", - "vue-resource": "^0.9.3" + "vue": "^2.3.4", + "vue-resource": "^1.3.4" } } diff --git a/dist/vue.form.js b/dist/vue.form.js index bc778fc..b8f48b7 100644 --- a/dist/vue.form.js +++ b/dist/vue.form.js @@ -6,7 +6,7 @@ * @author Alejandro Mostajo * @copyright 10Quality * @license MIT - * @version 1.0.9 + * @version 2.0.0 */ Vue.component('vform', Vue.extend({ props: @@ -120,9 +120,10 @@ Vue.component('vform', Vue.extend({ /** * Form loop key (in case it its used inside a v-for). * @since 1.0.5 + * @since 2.0.0 Refactored due to be a reserved name. * @var string */ - key: + index: { type: [String, Number], default: undefined, @@ -199,22 +200,22 @@ Vue.component('vform', Vue.extend({ * @since 1.0.0 * @since 1.0.1 Options generated based on method. * @since 1.0.2 Validations added. + * @since 2.0.0 Use $emit and remove $set. */ submit: function() { // Input validations var isValid = true; - this.$set('response', {}); + this.response = {}; for (var i in this.$children) { if (typeof(this.$children[i].validate) === 'function') isValid = this.$children[i].validate() && isValid; } if (isValid) { - this.$set('isLoading', true); + this.isLoading = true; this.$http(this.getOptions()).then(this.onSubmit, this.onError); } else { - this.$dispatch('vform_invalid', this.response.errors); - this.$broadcast('vform_invalid', this.response.errors); + this.$emit('invalid', this.response.errors); } }, /** @@ -224,25 +225,21 @@ Vue.component('vform', Vue.extend({ * @since 1.0.3 Added event broadcast. * @since 1.0.4 Response errors triggers invalid event. * @since 1.0.9 Forces response conversion to jsob or blob. + * @since 2.0.0 Use $emit and remove $set. * * @param object response Response */ onSubmit: function(response) { - this.$set( - 'response', - this.responseJson - ? response.json() - : (this.responseBlob - ? response.blob() - : response.data - ) - ); - this.$dispatch('vform_success'); - this.$broadcast('vform_success'); + this.response = this.responseJson + ? response.json() + : (this.responseBlob + ? response.blob() + : response.data + ); + this.$emit('success', response); if (this.response.errors !== undefined && Object.keys(this.response.errors).length > 0) { - this.$dispatch('vform_invalid', this.response.errors); - this.$broadcast('vform_invalid', this.response.errors); + this.$emit('invalid', this.response.errors); } if (response.data.redirect !== undefined) return window.location = response.data.redirect; @@ -253,26 +250,25 @@ Vue.component('vform', Vue.extend({ * @since 1.0.0 * @since 1.0.1 Added event dispatch. * @since 1.0.3 Added event broadcast. + * @since 2.0.0 Use $emit and remove $set. */ onComplete: function() { - this.$set('isLoading', false); - this.$dispatch('vform_complete'); - this.$broadcast('vform_complete'); + this.isLoading = false; + this.$emit('complete'); }, /** * Handles submission error. * @since 1.0.0 * @since 1.0.1 Added event dispatch. * @since 1.0.3 Added event broadcast. + * @since 2.0.0 Use $emit instead. * * @param object e Error */ onError: function(e) { - console.log(e); - this.$dispatch('vform_error', e); - this.$broadcast('vform_error', e); + this.$emit('error', e); this.onComplete(); }, /** @@ -325,15 +321,16 @@ Vue.component('vform', Vue.extend({ * Input Handler. * Handles input errors. * Vue sub component. - * @since 1.0.2 + * @since 1.0.0 + * @since 2.0.0 Refactored to Vue2. */ - 'input-handler': Vue.extend({ - template: '
      • {{error}}
      ', + 'input-handler': { + template: '
      ', props: { /** * Name of the error key to listen to. - * @since 1.0 + * @since 1.0.0 * @var string */ listen: @@ -343,17 +340,18 @@ Vue.component('vform', Vue.extend({ }, /** * CSS class to apply to wrapper. - * @since 1.0 + * @since 1.0.0 + * @since 2.0.0 Refactored to cssClass * @var string */ - class: + cssClass: { type: String, default: '', }, /** * CSS class to apply to wrapper when errors are available. - * @since 1.0 + * @since 1.0.0 * @var string */ classError: @@ -363,10 +361,11 @@ Vue.component('vform', Vue.extend({ }, /** * Input errors to listen to. - * @since 1.0 + * @since 1.0.0 + * @since 2.0.0 Refactored to "value" in order to use v-model directive. * @var object */ - response: + value: { type: [Object, Array], default: function() { @@ -394,10 +393,10 @@ Vue.component('vform', Vue.extend({ inputErrors: function() { var errors = []; - if (this.response.errors !== undefined - && this.response.errors[this.listen] !== undefined + if (this.value.errors !== undefined + && this.value.errors[this.listen] !== undefined ) { - errors = this.response.errors[this.listen]; + errors = this.value.errors[this.listen]; } return errors; }, @@ -575,17 +574,18 @@ Vue.component('vform', Vue.extend({ /** * Adds error to response. * @since 1.0.2 + * @since 2.0.0 Remove $set. * * @param object options */ addError: function(options) { if (this.$parent.response === undefined) - this.$parent.$set('response', {}); + this.$parent.response = {}; if (this.$parent.response.errors === undefined) - this.$parent.$set('response.errors', {}); + this.$parent.response.errors = {}; if (this.$parent.response.errors[this.listen] === undefined) - this.$parent.$set('response.errors.'+this.listen, []); + this.$parent.response.errors[this.listen] = []; var message = this.$parent.errors[options[0]]; if (options.length > 1) message = message.replace(/\%1\%/, options[1]); @@ -594,22 +594,24 @@ Vue.component('vform', Vue.extend({ this.$parent.response.errors[this.listen].push(message); }, }, - }), + }, /** * Results. * Handles response results. * Vue sub component. * @since 1.0.0 + * @since 2.0.0 Refactored to Vue2. */ - 'results': Vue.extend({ + 'results': { props: { /** * Results model. * @since 1.0.0 + * @since 2.0.0 Refactored to "value" to use v-model directive. * @var mixed */ - model: + value: { type: [Array, Object, String], default: function() @@ -667,18 +669,19 @@ Vue.component('vform', Vue.extend({ /** * Returns computed records. * @since 1.0.0 + * @since 2.0.0 Remove $set. * * @return array */ records: function() { - if (!this.$parent.hasMessage && Array.isArray(this.model)) { + if (!this.$parent.hasMessage && Array.isArray(this.value)) { if (this.clearOnFetch) { - this.$set('buffer', this.model); + this.buffer = this.value; } else { - for (var i in this.model) { - if (this.model[i] !== undefined && this.model[i] !== null) - this.buffer.push(this.model[i]); + for (var i in this.value) { + if (this.value[i] !== undefined && this.value[i] !== null) + this.buffer.push(this.value[i]); } } } @@ -700,6 +703,6 @@ Vue.component('vform', Vue.extend({ if (this.fetchOnready) this.$parent.submit(); }, - }), + }, }, })); \ No newline at end of file diff --git a/dist/vue.form.min.js b/dist/vue.form.min.js index f06a577..43e041e 100644 --- a/dist/vue.form.min.js +++ b/dist/vue.form.min.js @@ -1 +1 @@ -"use strict";Vue.component("vform",Vue.extend({props:{action:{type:String,"default":""},method:{type:String,"default":"POST"},headers:{type:Object,"default":function(){}},timeout:{type:[String,Number],"default":void 0},credentials:{type:[String,Boolean],"default":void 0},emulateHttp:{type:[String,Boolean],"default":void 0},emulateJson:{type:[String,Boolean],"default":void 0},errors:{type:Object,"default":function(){return{required:"Required field.",number:"Value must be numeric.",email:"Email value is invalid.",min:"Value must have at least %1% character(s).",min_number:"Value must be at least %1%.",max:"Value must have no more than %1% character(s).",max_number:"Value must be no more than %1%.",between:"Value must have between %1% to %2% characters.",between_number:"Value must be between %1% to %2%.",equals:"Value must be equal to %1%.",required_if:"Required field.",url:"Url value is invalid."}}},id:{type:[String,Number],"default":void 0},key:{type:[String,Number],"default":void 0},responseJson:{type:[String,Boolean],"default":!1},responseBlob:{type:[String,Boolean],"default":!1}},data:function(){return{request:{},isLoading:!1,response:{}}},computed:{hasMessage:function(){return void 0!=this.response.message},hasError:function(){return void 0!=this.response.error&&this.response.error}},methods:{submit:function(){var a=!0;this.$set("response",{});for(var b in this.$children)"function"==typeof this.$children[b].validate&&(a=this.$children[b].validate()&&a);a?(this.$set("isLoading",!0),this.$http(this.getOptions()).then(this.onSubmit,this.onError)):(this.$dispatch("vform_invalid",this.response.errors),this.$broadcast("vform_invalid",this.response.errors))},onSubmit:function(a){return this.$set("response",this.responseJson?a.json():this.responseBlob?a.blob():a.data),this.$dispatch("vform_success"),this.$broadcast("vform_success"),void 0!==this.response.errors&&Object.keys(this.response.errors).length>0&&(this.$dispatch("vform_invalid",this.response.errors),this.$broadcast("vform_invalid",this.response.errors)),void 0!==a.data.redirect?window.location=a.data.redirect:void this.onComplete()},onComplete:function(){this.$set("isLoading",!1),this.$dispatch("vform_complete"),this.$broadcast("vform_complete")},onError:function(a){console.log(a),this.$dispatch("vform_error",a),this.$broadcast("vform_error",a),this.onComplete()},getOptions:function(){var a={url:this.action,method:this.method};switch(void 0!==this.headers&&(a.headers=this.headers),void 0!==this.timeout&&(a.timeout=this.timeout),void 0!==this.credentials&&(a.credentials="boolean"==typeof this.credentials?this.credentials:"true"===this.credentials),void 0!==this.emulateHttp&&(a.emulateHTTP="boolean"==typeof this.emulateHttp?this.emulateHttp:"true"===this.emulateHttp),void 0!==this.emulateJson&&(a.emulateJSON="boolean"==typeof this.emulateJson?this.emulateJson:"true"===this.emulateJson),this.method){case"post":case"POST":case"put":case"PUT":case"patch":case"PATCH":a.body=this.request;break;default:a.params=this.request}return a}},components:{"input-handler":Vue.extend({template:'
      • {{error}}
      ',props:{listen:{type:String,"default":""},"class":{type:String,"default":""},classError:{type:String,"default":void 0},response:{type:[Object,Array],"default":function(){return{}}},validations:{type:String,"default":""}},computed:{inputErrors:function(){var a=[];return void 0!==this.response.errors&&void 0!==this.response.errors[this.listen]&&(a=this.response.errors[this.listen]),a},hasErrors:function(){return this.inputErrors.length>0},errorCss:function(){var a={};return void 0!==this.classError&&(a[this.classError]=this.hasErrors),a}},methods:{validate:function(){var a=!0,b=this.validations.split("|");for(var c in b){var d=b[c].split(":");switch(d[0]){case"required":void 0!==this.$parent.request[this.listen]&&0!==this.$parent.request[this.listen].length||(this.addError(d),a=!1);break;case"number":void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&isNaN(this.$parent.request[this.listen])&&(this.addError(d),a=!1);break;case"min":if(d.length<2)throw"Minimum value is not defined in validation rules";void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&this.$parent.request[this.listen].length0&&this.$parent.request[this.listen].length>parseInt(d[1])&&(this.addError(d),a=!1);break;case"max_number":if(d.length<2)throw"Minimum value is not defined in validation rules";void 0!==this.$parent.request[this.listen]&&parseInt(this.$parent.request[this.listen])>parseInt(d[1])&&(this.addError(d),a=!1);break;case"between":if(d.length<3)throw"One or all between values are not defined in validation rules";void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&(this.$parent.request[this.listen].lengthparseInt(d[2]))&&(this.addError(d),a=!1);break;case"between_number":if(d.length<3)throw"One or all between values are not defined in validation rules";void 0!==this.$parent.request[this.listen]&&(parseInt(this.$parent.request[this.listen])parseInt(d[2]))&&(this.addError(d),a=!1);break;case"email":var e=/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&!e.test(this.$parent.request[this.listen])&&(this.addError(d),a=!1);break;case"equals":if(d.length<2)throw"Comparison field is not defined in validation rules";void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen]!==this.$parent.request[d[1]]&&(this.addError(d),a=!1);break;case"required_if":if(d.length<2)throw"Comparison field is not defined in validation rules";void 0!==this.$parent.request[d[1]]&&this.$parent.request[d[1]].length>0&&(void 0===this.$parent.request[this.listen]||0===this.$parent.request[this.listen].length)&&(this.addError(d),a=!1);break;case"url":var e=/(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/;void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&!e.test(this.$parent.request[this.listen])&&(this.addError(d),a=!1)}}return a},addError:function(a){void 0===this.$parent.response&&this.$parent.$set("response",{}),void 0===this.$parent.response.errors&&this.$parent.$set("response.errors",{}),void 0===this.$parent.response.errors[this.listen]&&this.$parent.$set("response.errors."+this.listen,[]);var b=this.$parent.errors[a[0]];a.length>1&&(b=b.replace(/\%1\%/,a[1])),a.length>2&&(b=b.replace(/\%2\%/,a[2])),this.$parent.response.errors[this.listen].push(b)}}}),results:Vue.extend({props:{model:{type:[Array,Object,String],"default":function(){return[]}},request:{type:Object,"default":function(){return{}}},fetchOnready:{type:[String,Boolean],"default":!1},clearOnFetch:{type:[String,Boolean],"default":!0}},data:function(){return{buffer:[]}},computed:{records:function(){if(!this.$parent.hasMessage&&Array.isArray(this.model))if(this.clearOnFetch)this.$set("buffer",this.model);else for(var a in this.model)void 0!==this.model[a]&&null!==this.model[a]&&this.buffer.push(this.model[a]);return this.buffer},hasRecords:function(){return this.records.length>0}},ready:function(){this.fetchOnready&&this.$parent.submit()}})}})); \ No newline at end of file +"use strict";Vue.component("vform",Vue.extend({props:{action:{type:String,"default":""},method:{type:String,"default":"POST"},headers:{type:Object,"default":function(){}},timeout:{type:[String,Number],"default":void 0},credentials:{type:[String,Boolean],"default":void 0},emulateHttp:{type:[String,Boolean],"default":void 0},emulateJson:{type:[String,Boolean],"default":void 0},errors:{type:Object,"default":function(){return{required:"Required field.",number:"Value must be numeric.",email:"Email value is invalid.",min:"Value must have at least %1% character(s).",min_number:"Value must be at least %1%.",max:"Value must have no more than %1% character(s).",max_number:"Value must be no more than %1%.",between:"Value must have between %1% to %2% characters.",between_number:"Value must be between %1% to %2%.",equals:"Value must be equal to %1%.",required_if:"Required field.",url:"Url value is invalid."}}},id:{type:[String,Number],"default":void 0},index:{type:[String,Number],"default":void 0},responseJson:{type:[String,Boolean],"default":!1},responseBlob:{type:[String,Boolean],"default":!1}},data:function(){return{request:{},isLoading:!1,response:{}}},computed:{hasMessage:function(){return void 0!=this.response.message},hasError:function(){return void 0!=this.response.error&&this.response.error}},methods:{submit:function(){var a=!0;this.response={};for(var b in this.$children)"function"==typeof this.$children[b].validate&&(a=this.$children[b].validate()&&a);a?(this.isLoading=!0,this.$http(this.getOptions()).then(this.onSubmit,this.onError)):this.$emit("invalid",this.response.errors)},onSubmit:function(a){return this.response=this.responseJson?a.json():this.responseBlob?a.blob():a.data,this.$emit("success",a),void 0!==this.response.errors&&Object.keys(this.response.errors).length>0&&this.$emit("invalid",this.response.errors),void 0!==a.data.redirect?window.location=a.data.redirect:void this.onComplete()},onComplete:function(){this.isLoading=!1,this.$emit("complete")},onError:function(a){this.$emit("error",a),this.onComplete()},getOptions:function(){var a={url:this.action,method:this.method};switch(void 0!==this.headers&&(a.headers=this.headers),void 0!==this.timeout&&(a.timeout=this.timeout),void 0!==this.credentials&&(a.credentials="boolean"==typeof this.credentials?this.credentials:"true"===this.credentials),void 0!==this.emulateHttp&&(a.emulateHTTP="boolean"==typeof this.emulateHttp?this.emulateHttp:"true"===this.emulateHttp),void 0!==this.emulateJson&&(a.emulateJSON="boolean"==typeof this.emulateJson?this.emulateJson:"true"===this.emulateJson),this.method){case"post":case"POST":case"put":case"PUT":case"patch":case"PATCH":a.body=this.request;break;default:a.params=this.request}return a}},components:{"input-handler":{template:'
      ',props:{listen:{type:String,"default":""},cssClass:{type:String,"default":""},classError:{type:String,"default":void 0},value:{type:[Object,Array],"default":function(){return{}}},validations:{type:String,"default":""}},computed:{inputErrors:function(){var a=[];return void 0!==this.value.errors&&void 0!==this.value.errors[this.listen]&&(a=this.value.errors[this.listen]),a},hasErrors:function(){return this.inputErrors.length>0},errorCss:function(){var a={};return void 0!==this.classError&&(a[this.classError]=this.hasErrors),a}},methods:{validate:function(){var a=!0,b=this.validations.split("|");for(var c in b){var d=b[c].split(":");switch(d[0]){case"required":void 0!==this.$parent.request[this.listen]&&0!==this.$parent.request[this.listen].length||(this.addError(d),a=!1);break;case"number":void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&isNaN(this.$parent.request[this.listen])&&(this.addError(d),a=!1);break;case"min":if(d.length<2)throw"Minimum value is not defined in validation rules";void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&this.$parent.request[this.listen].length0&&this.$parent.request[this.listen].length>parseInt(d[1])&&(this.addError(d),a=!1);break;case"max_number":if(d.length<2)throw"Minimum value is not defined in validation rules";void 0!==this.$parent.request[this.listen]&&parseInt(this.$parent.request[this.listen])>parseInt(d[1])&&(this.addError(d),a=!1);break;case"between":if(d.length<3)throw"One or all between values are not defined in validation rules";void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&(this.$parent.request[this.listen].lengthparseInt(d[2]))&&(this.addError(d),a=!1);break;case"between_number":if(d.length<3)throw"One or all between values are not defined in validation rules";void 0!==this.$parent.request[this.listen]&&(parseInt(this.$parent.request[this.listen])parseInt(d[2]))&&(this.addError(d),a=!1);break;case"email":var e=/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&!e.test(this.$parent.request[this.listen])&&(this.addError(d),a=!1);break;case"equals":if(d.length<2)throw"Comparison field is not defined in validation rules";void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen]!==this.$parent.request[d[1]]&&(this.addError(d),a=!1);break;case"required_if":if(d.length<2)throw"Comparison field is not defined in validation rules";void 0!==this.$parent.request[d[1]]&&this.$parent.request[d[1]].length>0&&(void 0===this.$parent.request[this.listen]||0===this.$parent.request[this.listen].length)&&(this.addError(d),a=!1);break;case"url":var e=/(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/;void 0!==this.$parent.request[this.listen]&&this.$parent.request[this.listen].length>0&&!e.test(this.$parent.request[this.listen])&&(this.addError(d),a=!1)}}return a},addError:function(a){void 0===this.$parent.response&&(this.$parent.response={}),void 0===this.$parent.response.errors&&(this.$parent.response.errors={}),void 0===this.$parent.response.errors[this.listen]&&(this.$parent.response.errors[this.listen]=[]);var b=this.$parent.errors[a[0]];a.length>1&&(b=b.replace(/\%1\%/,a[1])),a.length>2&&(b=b.replace(/\%2\%/,a[2])),this.$parent.response.errors[this.listen].push(b)}}},results:{props:{value:{type:[Array,Object,String],"default":function(){return[]}},request:{type:Object,"default":function(){return{}}},fetchOnready:{type:[String,Boolean],"default":!1},clearOnFetch:{type:[String,Boolean],"default":!0}},data:function(){return{buffer:[]}},computed:{records:function(){if(!this.$parent.hasMessage&&Array.isArray(this.value))if(this.clearOnFetch)this.buffer=this.value;else for(var a in this.value)void 0!==this.value[a]&&null!==this.value[a]&&this.buffer.push(this.value[a]);return this.buffer},hasRecords:function(){return this.records.length>0}},ready:function(){this.fetchOnready&&this.$parent.submit()}}}})); \ No newline at end of file diff --git a/package.json b/package.json index 38e9028..502744d 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "vue-form", - "version": "1.0.9", + "version": "2.0.0", "description": "Form component for Vue JS.", - "main": "dist/vue.social-share.js", + "main": "dist/vue.form.min.js", "repository": { "type": "git", "url": "https://github.com/10quality/vue-form.git" @@ -25,5 +25,9 @@ "grunt": "^0.4.5", "grunt-contrib-copy": "^0.8.2", "grunt-contrib-uglify": "^0.11.0" + }, + "dependencies": { + "vue": "^2.3.4", + "vue-resource": "^1.3.4" } } diff --git a/src/vue.form.js b/src/vue.form.js index bc778fc..b8f48b7 100644 --- a/src/vue.form.js +++ b/src/vue.form.js @@ -6,7 +6,7 @@ * @author Alejandro Mostajo * @copyright 10Quality * @license MIT - * @version 1.0.9 + * @version 2.0.0 */ Vue.component('vform', Vue.extend({ props: @@ -120,9 +120,10 @@ Vue.component('vform', Vue.extend({ /** * Form loop key (in case it its used inside a v-for). * @since 1.0.5 + * @since 2.0.0 Refactored due to be a reserved name. * @var string */ - key: + index: { type: [String, Number], default: undefined, @@ -199,22 +200,22 @@ Vue.component('vform', Vue.extend({ * @since 1.0.0 * @since 1.0.1 Options generated based on method. * @since 1.0.2 Validations added. + * @since 2.0.0 Use $emit and remove $set. */ submit: function() { // Input validations var isValid = true; - this.$set('response', {}); + this.response = {}; for (var i in this.$children) { if (typeof(this.$children[i].validate) === 'function') isValid = this.$children[i].validate() && isValid; } if (isValid) { - this.$set('isLoading', true); + this.isLoading = true; this.$http(this.getOptions()).then(this.onSubmit, this.onError); } else { - this.$dispatch('vform_invalid', this.response.errors); - this.$broadcast('vform_invalid', this.response.errors); + this.$emit('invalid', this.response.errors); } }, /** @@ -224,25 +225,21 @@ Vue.component('vform', Vue.extend({ * @since 1.0.3 Added event broadcast. * @since 1.0.4 Response errors triggers invalid event. * @since 1.0.9 Forces response conversion to jsob or blob. + * @since 2.0.0 Use $emit and remove $set. * * @param object response Response */ onSubmit: function(response) { - this.$set( - 'response', - this.responseJson - ? response.json() - : (this.responseBlob - ? response.blob() - : response.data - ) - ); - this.$dispatch('vform_success'); - this.$broadcast('vform_success'); + this.response = this.responseJson + ? response.json() + : (this.responseBlob + ? response.blob() + : response.data + ); + this.$emit('success', response); if (this.response.errors !== undefined && Object.keys(this.response.errors).length > 0) { - this.$dispatch('vform_invalid', this.response.errors); - this.$broadcast('vform_invalid', this.response.errors); + this.$emit('invalid', this.response.errors); } if (response.data.redirect !== undefined) return window.location = response.data.redirect; @@ -253,26 +250,25 @@ Vue.component('vform', Vue.extend({ * @since 1.0.0 * @since 1.0.1 Added event dispatch. * @since 1.0.3 Added event broadcast. + * @since 2.0.0 Use $emit and remove $set. */ onComplete: function() { - this.$set('isLoading', false); - this.$dispatch('vform_complete'); - this.$broadcast('vform_complete'); + this.isLoading = false; + this.$emit('complete'); }, /** * Handles submission error. * @since 1.0.0 * @since 1.0.1 Added event dispatch. * @since 1.0.3 Added event broadcast. + * @since 2.0.0 Use $emit instead. * * @param object e Error */ onError: function(e) { - console.log(e); - this.$dispatch('vform_error', e); - this.$broadcast('vform_error', e); + this.$emit('error', e); this.onComplete(); }, /** @@ -325,15 +321,16 @@ Vue.component('vform', Vue.extend({ * Input Handler. * Handles input errors. * Vue sub component. - * @since 1.0.2 + * @since 1.0.0 + * @since 2.0.0 Refactored to Vue2. */ - 'input-handler': Vue.extend({ - template: '
      • {{error}}
      ', + 'input-handler': { + template: '
      ', props: { /** * Name of the error key to listen to. - * @since 1.0 + * @since 1.0.0 * @var string */ listen: @@ -343,17 +340,18 @@ Vue.component('vform', Vue.extend({ }, /** * CSS class to apply to wrapper. - * @since 1.0 + * @since 1.0.0 + * @since 2.0.0 Refactored to cssClass * @var string */ - class: + cssClass: { type: String, default: '', }, /** * CSS class to apply to wrapper when errors are available. - * @since 1.0 + * @since 1.0.0 * @var string */ classError: @@ -363,10 +361,11 @@ Vue.component('vform', Vue.extend({ }, /** * Input errors to listen to. - * @since 1.0 + * @since 1.0.0 + * @since 2.0.0 Refactored to "value" in order to use v-model directive. * @var object */ - response: + value: { type: [Object, Array], default: function() { @@ -394,10 +393,10 @@ Vue.component('vform', Vue.extend({ inputErrors: function() { var errors = []; - if (this.response.errors !== undefined - && this.response.errors[this.listen] !== undefined + if (this.value.errors !== undefined + && this.value.errors[this.listen] !== undefined ) { - errors = this.response.errors[this.listen]; + errors = this.value.errors[this.listen]; } return errors; }, @@ -575,17 +574,18 @@ Vue.component('vform', Vue.extend({ /** * Adds error to response. * @since 1.0.2 + * @since 2.0.0 Remove $set. * * @param object options */ addError: function(options) { if (this.$parent.response === undefined) - this.$parent.$set('response', {}); + this.$parent.response = {}; if (this.$parent.response.errors === undefined) - this.$parent.$set('response.errors', {}); + this.$parent.response.errors = {}; if (this.$parent.response.errors[this.listen] === undefined) - this.$parent.$set('response.errors.'+this.listen, []); + this.$parent.response.errors[this.listen] = []; var message = this.$parent.errors[options[0]]; if (options.length > 1) message = message.replace(/\%1\%/, options[1]); @@ -594,22 +594,24 @@ Vue.component('vform', Vue.extend({ this.$parent.response.errors[this.listen].push(message); }, }, - }), + }, /** * Results. * Handles response results. * Vue sub component. * @since 1.0.0 + * @since 2.0.0 Refactored to Vue2. */ - 'results': Vue.extend({ + 'results': { props: { /** * Results model. * @since 1.0.0 + * @since 2.0.0 Refactored to "value" to use v-model directive. * @var mixed */ - model: + value: { type: [Array, Object, String], default: function() @@ -667,18 +669,19 @@ Vue.component('vform', Vue.extend({ /** * Returns computed records. * @since 1.0.0 + * @since 2.0.0 Remove $set. * * @return array */ records: function() { - if (!this.$parent.hasMessage && Array.isArray(this.model)) { + if (!this.$parent.hasMessage && Array.isArray(this.value)) { if (this.clearOnFetch) { - this.$set('buffer', this.model); + this.buffer = this.value; } else { - for (var i in this.model) { - if (this.model[i] !== undefined && this.model[i] !== null) - this.buffer.push(this.model[i]); + for (var i in this.value) { + if (this.value[i] !== undefined && this.value[i] !== null) + this.buffer.push(this.value[i]); } } } @@ -700,6 +703,6 @@ Vue.component('vform', Vue.extend({ if (this.fetchOnready) this.$parent.submit(); }, - }), + }, }, })); \ No newline at end of file diff --git a/tests/test1.html b/tests/test1.html index 4e9812d..0b8a48f 100644 --- a/tests/test1.html +++ b/tests/test1.html @@ -1,60 +1,65 @@ - + +
      - -

      Country search sample

      - Provided by: http://restcountries.eu/ -

      -

      Alpha code 2
      - - - - - Clear results on submit? -

      + +
      +

      Country search sample

      + Provided by: http://restcountries.eu/ +

      +

      Alpha code 2
      + + + -
      - - Loading... -
      + Clear results on submit? +

      - -
      -
      -
      - {{country.name}} - Capital: {{country.capital}} | {{country.alpha2Code}} | {{country.region}} +
      + + Loading...
      -
      - - + +
      +
      +
      + {{country.name}} + Capital: {{country.capital}} | {{country.alpha2Code}} | {{country.region}} +
      +
      + No country found. +
      +
      +
      +
      + - - +
      + + - +
      + +