Skip to content

Commit 1c8a4d6

Browse files
authored
Merge pull request #60 from Lemoncode/issue#54-jquery-sample
Implement a sample using jQuery + ES6.
2 parents 766d190 + b5dd995 commit 1c8a4d6

13 files changed

Lines changed: 534 additions & 15 deletions

File tree

lib/lcformvalidation.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface FieldValidationFunction {
3535

3636
export interface FieldValidationConstraint {
3737
validator: FieldValidationFunction;
38-
eventFilters?: ValidationEventsFilter;
38+
eventsFilter?: ValidationEventsFilter;
3939
customParams?: any;
4040
}
4141

lib/src/rules/email.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
import { FieldValidationResult, FieldValidationFunction } from '../entities';
2+
import { isValidPattern } from './pattern';
23

34
// RegExp from http://emailregex.com
45
const EMAIL_PATTERN = /^(([^<>()\[\]\\.,;:\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,}))$/;
56

67
export const email: FieldValidationFunction = (value: string) => {
78
const validationResult = new FieldValidationResult();
8-
const isValid = isValidEmail(value);
9+
const isValid = isValidPattern(value, EMAIL_PATTERN);
910

1011
validationResult.succeeded = isValid;
1112
validationResult.type = 'EMAIL';
1213
validationResult.errorMessage = isValid ? '' : 'Please enter a valid email address.';
1314
return validationResult;
1415
};
15-
16-
function isValidEmail(value): boolean {
17-
return isEmptyValue(value) ?
18-
true :
19-
EMAIL_PATTERN.test(value);
20-
}
21-
22-
function isEmptyValue(value): boolean {
23-
return value === null ||
24-
value === undefined ||
25-
value === '';
26-
}

lib/src/rules/pattern.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const BAD_PARAMETER = 'FieldValidationError: pattern option for pattern validati
88

99
export function pattern(value: string, vm, customParams: PatternParams): FieldValidationResult {
1010
const pattern = parsePattern(customParams);
11-
const isValid = pattern.test(value);
11+
const isValid = isValidPattern(value, pattern);
1212
const validationResult = new FieldValidationResult();
1313

1414
validationResult.succeeded = isValid;
@@ -31,3 +31,15 @@ function getRegExp(pattern): RegExp {
3131
pattern :
3232
new RegExp(pattern);
3333
}
34+
35+
function isEmptyValue(value) {
36+
return value === null ||
37+
value === undefined ||
38+
value === '';
39+
}
40+
41+
export function isValidPattern(value, pattern: RegExp): boolean {
42+
return isEmptyValue(value) ?
43+
true :
44+
pattern.test(value);
45+
}

lib/src/rules/spec/pattern.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,50 @@ describe('[pattern] validation rule tests =>', () => {
9292
});
9393
});
9494

95+
describe('Given an empty value', () => {
96+
it('should return true for null value', () => {
97+
// Arrange
98+
const value = null;
99+
const vm = undefined;
100+
const customParams: PatternParams = { pattern: 'test' };
101+
102+
// Act
103+
const validationResult = pattern(value, vm, customParams) as FieldValidationResult;
104+
105+
// Assert
106+
expect(validationResult.succeeded).to.be.true;
107+
expect(validationResult.type).to.be.equals('PATTERN');
108+
expect(validationResult.errorMessage).to.be.empty;
109+
});
110+
111+
it('should return true for undefined value', () => {
112+
// Arrange
113+
const value = undefined;
114+
const vm = undefined;
115+
const customParams: PatternParams = { pattern: 'test' };
116+
117+
// Act
118+
const validationResult = pattern(value, vm, customParams) as FieldValidationResult;
119+
120+
// Assert
121+
expect(validationResult.succeeded).to.be.true;
122+
expect(validationResult.type).to.be.equals('PATTERN');
123+
expect(validationResult.errorMessage).to.be.empty;
124+
});
125+
126+
it('should return true for an empty value', () => {
127+
// Arrange
128+
const value = '';
129+
const vm = undefined;
130+
const customParams: PatternParams = { pattern: 'test' };
131+
132+
// Act
133+
const validationResult = pattern(value, vm, customParams) as FieldValidationResult;
134+
135+
// Assert
136+
expect(validationResult.succeeded).to.be.true;
137+
expect(validationResult.type).to.be.equals('PATTERN');
138+
expect(validationResult.errorMessage).to.be.empty;
139+
});
140+
});
95141
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"presets": [
3+
[
4+
"env",
5+
{
6+
"modules": false
7+
}
8+
],
9+
"stage-3"
10+
]
11+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "jqueryboilerplate",
3+
"version": "1.0.0",
4+
"description": "",
5+
"scripts": {
6+
"start": "webpack-dev-server"
7+
},
8+
"author": "Braulio Diez",
9+
"license": "ISC",
10+
"dependencies": {
11+
"bootstrap": "^3.3.7",
12+
"core-js": "^2.4.1",
13+
"jquery": "^3.2.0",
14+
"lc-form-validation": "file:../../../lib/"
15+
},
16+
"devDependencies": {
17+
"babel-core": "^6.24.0",
18+
"babel-loader": "^6.4.1",
19+
"babel-preset-env": "^1.2.2",
20+
"babel-preset-stage-3": "^6.22.0",
21+
"css-loader": "^0.27.3",
22+
"extract-text-webpack-plugin": "^2.1.0",
23+
"file-loader": "^0.10.1",
24+
"html-webpack-plugin": "^2.28.0",
25+
"style-loader": "^0.14.1",
26+
"typescript": "^2.2.1",
27+
"url-loader": "^0.5.8",
28+
"webpack": "^2.2.1",
29+
"webpack-dev-server": "^2.4.2"
30+
}
31+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<title>LcFormValidation jQuery ES6 sample</title>
8+
</head>
9+
<body>
10+
<main class="container">
11+
<header class="top-buffer">
12+
<h2>Buy a mechanical keyboard</h2>
13+
</header>
14+
<section>
15+
<div class="col-md-6">
16+
<form id="formProducts" action="#">
17+
<div class="form-group">
18+
<label for="selBrands" class="control-label">Select a brand</label>
19+
<select id="selBrands" name="product" class="form-control">
20+
<option value>Select a brand</option>
21+
</select>
22+
<span class="help-block"></span>
23+
</div>
24+
<div class="form-group">
25+
<label for="selProducts" class="control-label">Select a product</label>
26+
<select id="selProducts" name="version" class="form-control" disabled>
27+
<option value>Select a product</option>
28+
</select>
29+
<span class="help-block"></span>
30+
</div>
31+
<div class="form-group">
32+
<label for="txtDiscount" class="control-label">Discount code (optional)</label>
33+
<input type="text" id="txtDiscount" name="discountCode" class="form-control" placeholder="XXXX-XXX-XXXX">
34+
<span class="help-block"></span>
35+
</div>
36+
<div class="form-group">
37+
<label for="txtNif" class="control-label">NIF/NIE</label>
38+
<input type="text" id="txtNif" name="nif" class="form-control">
39+
<span class="help-block"></span>
40+
</div>
41+
<div class="form-group">
42+
<button type="submit" class="btn btn-primary">Submit</button>
43+
</div>
44+
</form>
45+
</div>
46+
</section>
47+
</main>
48+
</body>
49+
</html>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { App } from './modules/app/app';
2+
const app = new App();
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import $ from 'jquery';
2+
import { productsService } from '../../services/productsService';
3+
import { productsFormValidation } from './validation/formProductValidationService';
4+
5+
let storedProducts = [];
6+
let $selBrands, $selProducts, $formProducts, $txtNif, $txtDiscount;
7+
8+
class App {
9+
constructor() {
10+
this.brands = [];
11+
12+
this.onBrandSelect = this.onBrandSelect.bind(this);
13+
this.onFieldChange = this.onFieldChange.bind(this);
14+
this.onSubmit = this.onSubmit.bind(this);
15+
16+
$(this.init.bind(this));
17+
}
18+
19+
init() {
20+
this.loadBrands();
21+
this.initializeSelectors();
22+
this.setEventHandlers();
23+
}
24+
25+
initializeSelectors() {
26+
$selBrands = $('#selBrands');
27+
$selProducts = $('#selProducts');
28+
$txtNif = $('#txtNif');
29+
$txtDiscount = $('#txtDiscount');
30+
$formProducts = $('#formProducts');
31+
}
32+
33+
loadBrands() {
34+
productsService.fetchBrands()
35+
.then(fetchedProducts => {
36+
this.brands = fetchedProducts;
37+
this.loadSelect($selBrands, this.brands);
38+
});
39+
}
40+
41+
loadSelect($select, items = []) {
42+
$select.children().not(':first').remove();
43+
const options = items
44+
.map(item => `<option value="${item.id}">${item.description}</option>`)
45+
.reduce((opts, option) => {
46+
return `${opts}${option}`;
47+
}, '');
48+
$select.append(options);
49+
}
50+
51+
setEventHandlers() {
52+
$selBrands.change(this.onBrandSelect);
53+
$txtNif.keyup(this.onFieldChange);
54+
$txtDiscount.keyup(this.onFieldChange);
55+
$txtDiscount.on('input', this.onDiscountType);
56+
$formProducts.submit(this.onSubmit);
57+
}
58+
59+
onBrandSelect(event) {
60+
this.onFieldChange(event);
61+
const product = Number(event.currentTarget.value) || null;
62+
const isValidProduct = (product !== null);
63+
if (!isValidProduct) {
64+
$selProducts.val('');
65+
}
66+
this.toggleAvailability($selProducts, isValidProduct);
67+
this.loadProductsByBrand(product);
68+
}
69+
70+
toggleAvailability($element, isEnabled) {
71+
if (isEnabled) {
72+
$element.removeAttr('disabled');
73+
} else {
74+
$element.attr('disabled', 'disabled');
75+
}
76+
}
77+
78+
loadProductsByBrand(brand) {
79+
if (brand !== null) {
80+
const products = this.getProductsByBrand(brand);
81+
this.loadSelect($selProducts, products);
82+
}
83+
}
84+
85+
getProductsByBrand(id) {
86+
const brand = this.brands.filter(brand => brand.id === id);
87+
return brand[0] && brand[0].products;
88+
}
89+
90+
onSubmit(event) {
91+
event.preventDefault();
92+
const $form = $(event.currentTarget);
93+
const vm = this.getModel($form);
94+
productsFormValidation
95+
.validateForm(vm)
96+
.then(validationResult => {
97+
validationResult.fieldErrors.forEach(this.handleFieldValidationResult($form));
98+
if (validationResult.succeeded) {
99+
console.log('Form is sent');
100+
}
101+
})
102+
.catch(error => {
103+
console.log('Error validating form', error);
104+
});
105+
}
106+
107+
getModel($form) {
108+
return $form
109+
.serializeArray()
110+
111+
// from { name: <prop>, value: <val> } to [<prop>, <val>] }
112+
.map(field => ({ [field.name]: field.value }))
113+
114+
// reduce all in an object
115+
.reduce((vm, field) => ({ ...vm, ...field }), {});
116+
}
117+
118+
handleFieldValidationResult($form) {
119+
return (fieldValidationResult) => {
120+
const field = $form.get(0)[fieldValidationResult.key];
121+
this.toggleErrorMessage(fieldValidationResult, $(field));
122+
};
123+
}
124+
125+
onFieldChange(event) {
126+
const $field = $(event.currentTarget);
127+
productsFormValidation
128+
.validateField(null, $field.attr('name'), $field.val())
129+
.then(validationResult => {
130+
this.toggleErrorMessage(validationResult, $field);
131+
})
132+
.catch(error => {
133+
console.log('Error validating field', error);
134+
});
135+
}
136+
137+
toggleErrorMessage(validationResult, $element) {
138+
const $parent = $element.closest('div.form-group');
139+
if (validationResult.succeeded) {
140+
$parent.removeClass('has-error').find('span.help-block').html('');
141+
} else {
142+
$parent.addClass('has-error').find('span.help-block').html(validationResult.errorMessage);
143+
}
144+
}
145+
146+
onDiscountType(event) {
147+
const $element = $(event.currentTarget);
148+
$element.val($element.val().toUpperCase());
149+
}
150+
}
151+
152+
export {
153+
App
154+
};

0 commit comments

Comments
 (0)