|
|
|
|
@@ -0,0 +1,857 @@
|
|
|
|
|
---
|
|
|
|
|
id: 67279fe50237291f80eed8b8
|
|
|
|
|
title: Build a Customer Complaint Form
|
|
|
|
|
challengeType: 25
|
|
|
|
|
dashedName: build-a-customer-complaint-form
|
|
|
|
|
demoType: onClick
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# --description--
|
|
|
|
|
|
|
|
|
|
For this lab, you have been provided with all the HTML and CSS. You will use JavaScript to validate the complaint form.
|
|
|
|
|
|
|
|
|
|
**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
|
|
|
|
|
|
|
|
|
|
**User Stories:**
|
|
|
|
|
|
|
|
|
|
1. When the form is submitted, you should ensure that:
|
|
|
|
|
- `#full-name` is not empty.
|
|
|
|
|
- `#email` is a valid email address format.
|
|
|
|
|
- `#order-no` is a sequence of ten numbers starting with `2024`.
|
|
|
|
|
- `#product-code` follows the pattern `XX##-X###-XX#`, where `X` represents either a lowercase letter or an uppercase letter and `#` represents a number.
|
|
|
|
|
- `#quantity` is a positive integer.
|
|
|
|
|
- at least one checkbox from `#complaints-group` is checked.
|
|
|
|
|
- `#complaint-description` contains at least twenty characters if the `Other` checkbox is checked.
|
|
|
|
|
- a radio button from `#solutions-group` is selected.
|
|
|
|
|
- `#solution-description` contains at least twenty characters if the `Other` radio button is selected.
|
|
|
|
|
1. You should have a function named `validateForm` that returns an object containing the following keys: `full-name`, `email`, `order-no`, `product-code`, `quantity`, `complaints-group`, `complaint-description`, `solutions-group`, and `solution-description`. The value of each key should be `true` if the corresponding form field is correctly filled and `false` otherwise.
|
|
|
|
|
1. You should have a function named `isValid` that takes the object returned by `validateForm` as argument and returns `true` if every form field is correctly filled and `false` otherwise.
|
|
|
|
|
1. If a change event is triggered on a form field and it has a valid value, you should set its border color to `green`. In case of checkbox and radio button groups, you should set the border color of the parent `fieldset`.
|
|
|
|
|
1. If a change event is triggered on a form field and it has an invalid value, you should set its border color to `red`. In case of checkbox and radio button groups, you should set the border color of the parent `fieldset`.
|
|
|
|
|
1. When you try to submit the form you should call `isValid` to validate the form.
|
|
|
|
|
1. When you try to submit the form, if the form has any invalid field, each invalid field should be highlighted by setting the border color of each invalid input, textarea or fieldset (in case of checkbox and radio button groups) to `red`.
|
|
|
|
|
|
|
|
|
|
# --hints--
|
|
|
|
|
|
|
|
|
|
You should have a function named `validateForm`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
assert.isFunction(validateForm);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`validateForm` should return an object.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
assert.isObject(validateForm());
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`validateForm()["full-name"]` should be `false` when `#full-name` is empty, and `true` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("full-name");
|
|
|
|
|
field.value = "";
|
|
|
|
|
assert.isFalse(validateForm()["full-name"]);
|
|
|
|
|
field.value = "testing";
|
|
|
|
|
assert.isTrue(validateForm()["full-name"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When a `change` event is triggered on `#full-name`, you should set its border color to `green` if it contains a valid value, and `red` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("full-name");
|
|
|
|
|
field.value = "testing"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "green");
|
|
|
|
|
field.value = ""
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "red");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`validateForm()["email"]` should be `true` when `#email` contains a valid email address, and `false` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("email");
|
|
|
|
|
field.value = "example@domain.com"
|
|
|
|
|
assert.isTrue(validateForm()["email"]);
|
|
|
|
|
field.value = "testing"
|
|
|
|
|
assert.isFalse(validateForm()["email"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When a `change` event is triggered on `#email`, you should set its border color to `green` if it contains a valid email address, and `red` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("email");
|
|
|
|
|
field.value = "example@domain.com"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "green");
|
|
|
|
|
field.value = ""
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "red");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`validateForm()["order-no"]` should be `true` when `#order-no` contains a valid value, and `false` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("order-no");
|
|
|
|
|
field.value = "2024001122"
|
|
|
|
|
assert.isTrue(validateForm()["order-no"]);
|
|
|
|
|
field.value = "testing"
|
|
|
|
|
assert.isFalse(validateForm()["order-no"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When a `change` event is triggered on `#order-no`, you should set its border color to `green` if it contains a valid value, and `red` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("order-no");
|
|
|
|
|
field.value = "2024001122"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "green");
|
|
|
|
|
field.value = "20240011"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "red");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`validateForm()["product-code"]` should be `true` when `#product-code` contains a valid value, and `false` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("product-code");
|
|
|
|
|
field.value = "Xa22-X123-Xb4";
|
|
|
|
|
assert.isTrue(validateForm()["product-code"]);
|
|
|
|
|
field.value = "testing"
|
|
|
|
|
assert.isFalse(validateForm()["product-code"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When a `change` event is triggered on `#product-code`, you should set its border color to `green` if it contains a valid value, and `red` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("product-code");
|
|
|
|
|
field.value = "Xa22-X123-Xb4"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "green");
|
|
|
|
|
field.value = "Xa22-123-Xb4"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "red");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`validateForm()["quantity"]` should be `true` when `#quantity` contains a valid value, and `false` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("quantity");
|
|
|
|
|
field.value = "5";
|
|
|
|
|
assert.isTrue(validateForm()["quantity"]);
|
|
|
|
|
field.value = "0";
|
|
|
|
|
assert.isFalse(validateForm()["quantity"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When a `change` event is triggered on `#quantity`, you should set its border color to `green` if it contains a valid value, and `red` otherwise.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("quantity");
|
|
|
|
|
field.value = "2"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "green");
|
|
|
|
|
field.value = "0"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "red");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When at least one checkbox from `#complaints-group` is checked, `validateForm()["complaints-group"]` should be `true`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("damaged-product").checked = true
|
|
|
|
|
assert.isTrue(validateForm()["complaints-group"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When none of the checkboxes from `#complaints-group` is checked, `validateForm()["complaints-group"]` should be `false`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("damaged-product").checked = false;
|
|
|
|
|
document.getElementById("nonconforming-product").checked = false;
|
|
|
|
|
document.getElementById("delayed-dispatch").checked = false;
|
|
|
|
|
document.getElementById("other-complaint").checked = false;
|
|
|
|
|
assert.isFalse(validateForm()["complaints-group"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Once one checkbox from `#complaints-group` is checked, you should set `#complaints-group`'s border color to `green`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("damaged-product").checked = true;
|
|
|
|
|
document.getElementById("nonconforming-product").checked = false;
|
|
|
|
|
document.getElementById("delayed-dispatch").checked = false;
|
|
|
|
|
document.getElementById("other-complaint").checked = false;
|
|
|
|
|
const fieldset = document.getElementById("complaints-group");
|
|
|
|
|
fieldset.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(fieldset.style.borderColor, "green");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When all of the checkboxes from `#complaints-group` are changed to the unchecked state, you should set `#complaints-group`'s border color to `red`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("damaged-product").checked = false;
|
|
|
|
|
document.getElementById("nonconforming-product").checked = false;
|
|
|
|
|
document.getElementById("delayed-dispatch").checked = false;
|
|
|
|
|
document.getElementById("other-complaint").checked = false;
|
|
|
|
|
const fieldset = document.getElementById("complaints-group");
|
|
|
|
|
fieldset.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(fieldset.style.borderColor, "red");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When `#other-complaint` is checked and `#complaint-description` contains at least twenty characters, `validateForm()["complaint-description"]` should be `true`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("other-complaint").checked = true
|
|
|
|
|
document.getElementById("complaint-description").value = "A sentence with at least twenty characters";
|
|
|
|
|
assert.isTrue(validateForm()["complaint-description"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When `#other-complaint` is checked and `#complaint-description` contains less than twenty characters, `validateForm()["complaint-description"]` should be `false`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("other-complaint").checked = true
|
|
|
|
|
document.getElementById("complaint-description").value = "Less than 20 chars";
|
|
|
|
|
assert.isFalse(validateForm()["complaint-description"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Once the value of `#complaint-description` is changed to a valid value, you should set its border color to `green`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("complaint-description");
|
|
|
|
|
field.value = "A sentence with at least twenty characters"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "green");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Once the value of `#complaint-description` is changed to an invalid value, you should set its border color to `red`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("complaint-description");
|
|
|
|
|
field.value = "Not enough chars"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "red");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When a radio button from `#solutions-group` is checked, `validateForm()["solutions-group"]` should be `true`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("refund").checked = true
|
|
|
|
|
assert.isTrue(validateForm()["solutions-group"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When none of the radio buttons from `#solutions-group` is checked, `validateForm()["solutions-group"]` should be `false`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("refund").checked = false;
|
|
|
|
|
document.getElementById("exchange").checked = false;
|
|
|
|
|
document.getElementById("other-solution").checked = false;
|
|
|
|
|
assert.isFalse(validateForm()["solutions-group"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Once a radio button from `#solutions-group` is checked, you should set `#solutions-group`'s border color to `green`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("refund").checked = true;
|
|
|
|
|
document.getElementById("exchange").checked = false;
|
|
|
|
|
document.getElementById("other-solution").checked = false;
|
|
|
|
|
const fieldset = document.getElementById("solutions-group");
|
|
|
|
|
fieldset.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(fieldset.style.borderColor, "green");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When all of the checkboxes from `#complaints-group` are changed to the unchecked state, you should set `#complaints-group`'s border color to `red`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("refund").checked = false;
|
|
|
|
|
document.getElementById("exchange").checked = false;
|
|
|
|
|
document.getElementById("other-solution").checked = false;
|
|
|
|
|
const fieldset = document.getElementById("solutions-group");
|
|
|
|
|
fieldset.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(fieldset.style.borderColor, "red");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When `#other-solution` is checked and `#solution-description` contains at least twenty characters, `validateForm()["solution-description"]` should be `true`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("other-solution").checked = true
|
|
|
|
|
document.getElementById("solution-description").value = "A sentence with at least twenty characters";
|
|
|
|
|
assert.isTrue(validateForm()["solution-description"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When `#other-solution` is checked and `#solution-description` contains less than twenty characters, `validateForm()["solution-description"]` should be `false`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("other-solution").checked = true
|
|
|
|
|
document.getElementById("solution-description").value = "Less than 20 chars";
|
|
|
|
|
assert.isFalse(validateForm()["solution-description"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Once the value of `#solution-description` is changed to a valid value, you should set its border color to `green`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("solution-description");
|
|
|
|
|
field.value = "A sentence with at least twenty characters"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "green");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Once the value of `#solution-description` is changed to an invalid value, you should set its border color to `red`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const field = document.getElementById("solution-description");
|
|
|
|
|
field.value = "Not enough chars"
|
|
|
|
|
field.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
|
|
assert.equal(field.style.borderColor, "red");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
You should have a function named `isValid`.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
assert.isFunction(isValid);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Your `isValid` function should take the `validateForm()` as its argument and return `true` when all the form fields have been filled correctly.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
document.getElementById("full-name").value = "testing"
|
|
|
|
|
document.getElementById("email").value = "example@domain.com"
|
|
|
|
|
document.getElementById("order-no").value = "2024001122"
|
|
|
|
|
document.getElementById("product-code").value = "Xa22-X123-Xb4"
|
|
|
|
|
document.getElementById("quantity").value = "5"
|
|
|
|
|
document.getElementById("damaged-product").checked = true
|
|
|
|
|
document.getElementById("other-complaint").checked = false
|
|
|
|
|
document.getElementById("refund").checked = true
|
|
|
|
|
document.getElementById("other-solution").checked = false
|
|
|
|
|
assert.isTrue(isValid(validateForm()));
|
|
|
|
|
|
|
|
|
|
document.getElementById("other-complaint").checked = true
|
|
|
|
|
document.getElementById("complaint-description").value = "A sentence with at least twenty characters";
|
|
|
|
|
document.getElementById("other-solution").checked = true
|
|
|
|
|
document.getElementById("solution-description").value = "A sentence with at least twenty characters";
|
|
|
|
|
assert.isTrue(isValid(validateForm()));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Your `isValid` function should take the `validateForm()` as its argument and return `false` when not all the form fields have been filled correctly.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const name = document.getElementById("full-name");
|
|
|
|
|
const email = document.getElementById("email");
|
|
|
|
|
const orderNo = document.getElementById("order-no");
|
|
|
|
|
const prCode = document.getElementById("product-code");
|
|
|
|
|
const quantity = document.getElementById("quantity");
|
|
|
|
|
const damaged = document.getElementById("damaged-product");
|
|
|
|
|
const nonConforming = document.getElementById("nonconforming-product");
|
|
|
|
|
const delayed = document.getElementById("delayed-dispatch");
|
|
|
|
|
const otherC = document.getElementById("other-complaint");
|
|
|
|
|
const complaintDescr = document.getElementById("complaint-description");
|
|
|
|
|
const refund = document.getElementById("refund");
|
|
|
|
|
const exchange = document.getElementById("exchange");
|
|
|
|
|
const otherS = document.getElementById("other-solution");
|
|
|
|
|
const solutionDescr = document.getElementById("solution-description");
|
|
|
|
|
|
|
|
|
|
// invalid full-name
|
|
|
|
|
name.value = "";
|
|
|
|
|
email.value = "example@domain.com";
|
|
|
|
|
orderNo.value = "2024001122";
|
|
|
|
|
prCode.value = "Xa22-X123-Xb4";
|
|
|
|
|
quantity.value = "5";
|
|
|
|
|
damaged.checked = true;
|
|
|
|
|
refund.checked = true;
|
|
|
|
|
assert.isFalse(isValid(validateForm()));
|
|
|
|
|
|
|
|
|
|
// invalid email
|
|
|
|
|
name.value = "testing";
|
|
|
|
|
email.value = "";
|
|
|
|
|
assert.isFalse(isValid(validateForm()));
|
|
|
|
|
|
|
|
|
|
// invalid order-no
|
|
|
|
|
email.value = "example@domain.com";
|
|
|
|
|
orderNo.value = "202400";
|
|
|
|
|
assert.isFalse(isValid(validateForm()));
|
|
|
|
|
|
|
|
|
|
// invalid product-code
|
|
|
|
|
orderNo.value = "2024001122";
|
|
|
|
|
prCode.value = "1a22-Xa23-Xb4";
|
|
|
|
|
assert.isFalse(isValid(validateForm()));
|
|
|
|
|
|
|
|
|
|
// invalid quantity
|
|
|
|
|
prCode.value = "Xa22-X123-Xb4";
|
|
|
|
|
quantity.value = 0;
|
|
|
|
|
assert.isFalse(isValid(validateForm()));
|
|
|
|
|
|
|
|
|
|
// invalid complaints-group
|
|
|
|
|
quantity.value = 5;
|
|
|
|
|
damaged.checked = false;
|
|
|
|
|
nonConforming.checked = false;
|
|
|
|
|
delayed.checked = false;
|
|
|
|
|
otherC.checked = false;
|
|
|
|
|
assert.isFalse(isValid(validateForm()));
|
|
|
|
|
|
|
|
|
|
// invalid complaint-description
|
|
|
|
|
otherC.checked = true;
|
|
|
|
|
complaintDescr.value = "not enough chars";
|
|
|
|
|
assert.isFalse(isValid(validateForm()));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// invalid solutions-group
|
|
|
|
|
complaintDescr.value = "A sentence with at least twenty characters";
|
|
|
|
|
refund.checked = false;
|
|
|
|
|
exchange.checked = false;
|
|
|
|
|
otherS.checked = false;
|
|
|
|
|
assert.isFalse(isValid(validateForm()));
|
|
|
|
|
|
|
|
|
|
// invalid solutions-description
|
|
|
|
|
otherS.checked = true;
|
|
|
|
|
solutionDescr.value = "not enough chars";
|
|
|
|
|
assert.isFalse(isValid(validateForm()));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
You should call `isValid` when you try to submit the form.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
let flag = false;
|
|
|
|
|
const temp = isValid;
|
|
|
|
|
try {
|
|
|
|
|
isValid = (obj) => {flag = true};
|
|
|
|
|
document.getElementById("form").dispatchEvent(new Event("submit"));
|
|
|
|
|
assert.isTrue(flag);
|
|
|
|
|
} finally {
|
|
|
|
|
isValid = temp;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# --seed--
|
|
|
|
|
|
|
|
|
|
## --seed-contents--
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
|
|
|
|
|
<head>
|
|
|
|
|
<title>Customer Complaint Form</title>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<link rel="stylesheet" href="styles.css">
|
|
|
|
|
</head>
|
|
|
|
|
|
|
|
|
|
<body>
|
|
|
|
|
<h1>Complaint Form</h1>
|
|
|
|
|
<form id="form">
|
|
|
|
|
<fieldset id="personal-info">
|
|
|
|
|
<div>
|
|
|
|
|
<label for="full-name">Full Name:</label>
|
|
|
|
|
<input type="text" id="full-name" name="full-name" placeholder="John Doe">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label for="email">Email Address:</label>
|
|
|
|
|
<input type="email" id="email" name="email" placeholder="example@domain.com">
|
|
|
|
|
</div>
|
|
|
|
|
</fieldset>
|
|
|
|
|
<hr>
|
|
|
|
|
<fieldset id="product-info">
|
|
|
|
|
<div>
|
|
|
|
|
<label for="order-no">Order No:</label>
|
|
|
|
|
<input type="text" id="order-no" name="order-no" placeholder="2024######">
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="product-code">Product Code:</label>
|
|
|
|
|
<input type="text" id="product-code" name="product-code" placeholder="XX##-X###-XX#">
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="quantity">Quantity:</label>
|
|
|
|
|
<input type="number" id="quantity" name="quantity" min="1">
|
|
|
|
|
</div>
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
<fieldset id="complaints-group">
|
|
|
|
|
<legend>Complaint Reason:</legend>
|
|
|
|
|
<div>
|
|
|
|
|
<input type="checkbox" id="damaged-product" name="complaint" value="damaged-product">
|
|
|
|
|
<label for="damaged-product">Damaged Product</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<input type="checkbox" id="nonconforming-product" name="complaint" value="nonconforming-product">
|
|
|
|
|
<label for="nonconforming-product">Nonconforming Product</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<input type="checkbox" id="delayed-dispatch" name="complaint" value="delayed-dispatch">
|
|
|
|
|
<label for="delayed-dispatch">Delayed Dispatch</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<input type="checkbox" id="other-complaint" name="complaint" value="other">
|
|
|
|
|
<label for="other-complaint">Other</label>
|
|
|
|
|
</div>
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
<div id="complaint-description-container">
|
|
|
|
|
<legend>Description of Complaint Reason</legend>
|
|
|
|
|
<textarea placeholder="Describe the reason of your complaint in at least 20 characters"
|
|
|
|
|
name="complaint-textarea" id="complaint-description"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<fieldset id="solutions-group">
|
|
|
|
|
<legend>Desired Solution</legend>
|
|
|
|
|
<input type="radio" name="solutions" id="refund" value="refund">
|
|
|
|
|
<label for="refund">Refund</label>
|
|
|
|
|
|
|
|
|
|
<input type="radio" name="solutions" id="exchange" value="exchange">
|
|
|
|
|
<label for="exchange">Exchange</label>
|
|
|
|
|
|
|
|
|
|
<input type="radio" name="solutions" id="other-solution" value="other">
|
|
|
|
|
<label for="other-solution">Other</label>
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
<div id="solution-description-container">
|
|
|
|
|
<legend>Description of Desired Solution</legend>
|
|
|
|
|
<textarea placeholder="Describe the desired solution to your issue in at least 20 characters"
|
|
|
|
|
name="solution-textarea" id="solution-description"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="btn-container">
|
|
|
|
|
<button type="submit" id="submit-btn">Submit</button>
|
|
|
|
|
<span id="message-box"></span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<script src="script.js"></script>
|
|
|
|
|
</body>
|
|
|
|
|
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```css
|
|
|
|
|
* {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h1 {
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#form {
|
|
|
|
|
max-width: 70%;
|
|
|
|
|
margin: auto;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
input {
|
|
|
|
|
border-color: rgb(118, 118, 118);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#personal-info input, #product-info input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fieldset {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
border-color: rgb(118, 118, 118);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
textarea {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border-color: rgb(118, 118, 118);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#btn-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#submit-btn, #clear-btn {
|
|
|
|
|
margin: 10px 15px 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## --solutions--
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
|
|
|
|
|
<head>
|
|
|
|
|
<title>Customer Complaint Form</title>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<link rel="stylesheet" href="styles.css">
|
|
|
|
|
</head>
|
|
|
|
|
|
|
|
|
|
<body>
|
|
|
|
|
<h1>Complaint Form</h1>
|
|
|
|
|
<form id="form">
|
|
|
|
|
<fieldset id="personal-info">
|
|
|
|
|
<div>
|
|
|
|
|
<label for="full-name">Full Name:</label>
|
|
|
|
|
<input type="text" id="full-name" name="full-name" placeholder="John Doe">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label for="email">Email Address:</label>
|
|
|
|
|
<input type="email" id="email" name="email" placeholder="example@domain.com">
|
|
|
|
|
</div>
|
|
|
|
|
</fieldset>
|
|
|
|
|
<hr>
|
|
|
|
|
<fieldset id="product-info">
|
|
|
|
|
<div>
|
|
|
|
|
<label for="order-no">Order No:</label>
|
|
|
|
|
<input type="text" id="order-no" name="order-no" placeholder="2024######">
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="product-code">Product Code:</label>
|
|
|
|
|
<input type="text" id="product-code" name="product-code" placeholder="XX##-X###-XX#">
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="quantity">Quantity:</label>
|
|
|
|
|
<input type="number" id="quantity" name="quantity" min="1">
|
|
|
|
|
</div>
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
<fieldset id="complaints-group">
|
|
|
|
|
<legend>Complaint Reason:</legend>
|
|
|
|
|
<div>
|
|
|
|
|
<input type="checkbox" id="damaged-product" name="complaint" value="damaged-product">
|
|
|
|
|
<label for="damaged-product">Damaged Product</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<input type="checkbox" id="nonconforming-product" name="complaint" value="nonconforming-product">
|
|
|
|
|
<label for="nonconforming-product">Nonconforming Product</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<input type="checkbox" id="delayed-dispatch" name="complaint" value="delayed-dispatch">
|
|
|
|
|
<label for="delayed-dispatch">Delayed Dispatch</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<input type="checkbox" id="other-complaint" name="complaint" value="other">
|
|
|
|
|
<label for="other-complaint">Other</label>
|
|
|
|
|
</div>
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
<div id="complaint-description-container">
|
|
|
|
|
<legend>Description of Complaint Reason</legend>
|
|
|
|
|
<textarea placeholder="Describe the reason of your complaint in at least 20 characters"
|
|
|
|
|
name="complaint-textarea" id="complaint-description"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<fieldset id="solutions-group">
|
|
|
|
|
<legend>Desired Solution</legend>
|
|
|
|
|
<input type="radio" name="solutions" id="refund" value="refund">
|
|
|
|
|
<label for="refund">Refund</label>
|
|
|
|
|
|
|
|
|
|
<input type="radio" name="solutions" id="exchange" value="exchange">
|
|
|
|
|
<label for="exchange">Exchange</label>
|
|
|
|
|
|
|
|
|
|
<input type="radio" name="solutions" id="other-solution" value="other">
|
|
|
|
|
<label for="other-solution">Other</label>
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
|
|
|
|
<div id="solution-description-container">
|
|
|
|
|
<legend>Description of Desired Solution</legend>
|
|
|
|
|
<textarea placeholder="Describe the desired solution to your issue in at least 20 characters"
|
|
|
|
|
name="solution-textarea" id="solution-description"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="btn-container">
|
|
|
|
|
<button type="submit" id="submit-btn">Submit</button>
|
|
|
|
|
<span id="message-box"></span>
|
|
|
|
|
<button type="button" id="clear-btn">Clear</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<script src="script.js"></script>
|
|
|
|
|
</body>
|
|
|
|
|
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```css
|
|
|
|
|
* {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h1 {
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#form {
|
|
|
|
|
max-width: 70%;
|
|
|
|
|
margin: auto;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
input {
|
|
|
|
|
border-color: rgb(118, 118, 118);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#personal-info input, #product-info input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fieldset {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
border-color: rgb(118, 118, 118);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
textarea {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border-color: rgb(118, 118, 118);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#btn-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#submit-btn, #clear-btn {
|
|
|
|
|
margin: 10px 15px 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const form = document.getElementById("form");
|
|
|
|
|
const fullName = document.getElementById("full-name");
|
|
|
|
|
const emailAddress = document.getElementById("email");
|
|
|
|
|
const orderNo = document.getElementById("order-no");
|
|
|
|
|
const productCode = document.getElementById("product-code");
|
|
|
|
|
const quantity = document.getElementById("quantity");
|
|
|
|
|
const complaints = document.querySelectorAll('input[name="complaint"]');
|
|
|
|
|
const complaintDescription = document.getElementById("complaint-description");
|
|
|
|
|
const complaintTextAreaDiv = document.getElementById("complaint-description-container");
|
|
|
|
|
const solutions = document.querySelectorAll('input[name="solutions"]');
|
|
|
|
|
const solutionDescription = document.getElementById("solution-description");
|
|
|
|
|
const solutionTextAreaDiv = document.getElementById("solution-description-container");
|
|
|
|
|
const submitBtn = document.getElementById("submit-btn");
|
|
|
|
|
const messageBox = document.getElementById("message-box");
|
|
|
|
|
const clearBtn = document.getElementById("clear-btn");
|
|
|
|
|
|
|
|
|
|
const hideTextareas = () => {
|
|
|
|
|
complaintTextAreaDiv.style.display = "none";
|
|
|
|
|
solutionTextAreaDiv.style.display = "none";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hideTextareas();
|
|
|
|
|
|
|
|
|
|
const formFields = {
|
|
|
|
|
"full-name": fullName,
|
|
|
|
|
"email": emailAddress,
|
|
|
|
|
"order-no": orderNo,
|
|
|
|
|
"product-code": productCode,
|
|
|
|
|
"quantity": quantity,
|
|
|
|
|
"complaints-group": Array.from(complaints),
|
|
|
|
|
"complaint-description": complaintDescription,
|
|
|
|
|
"solutions-group": Array.from(solutions),
|
|
|
|
|
"solution-description": solutionDescription
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const clearForm = () => {
|
|
|
|
|
Object.entries(formFields).forEach(entry => {
|
|
|
|
|
const [key, val] = entry;
|
|
|
|
|
if (Array.isArray(val)) {
|
|
|
|
|
val.forEach(i => i.checked = false);
|
|
|
|
|
} else {
|
|
|
|
|
val.value = "";
|
|
|
|
|
}
|
|
|
|
|
hideTextareas();
|
|
|
|
|
messageBox.innerText = "";
|
|
|
|
|
document.getElementById(key).style.borderColor = "rgb(118, 118, 118)";
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const validateForm = () => {
|
|
|
|
|
const complaintsArr = Array.from(complaints).map(i => i.checked);
|
|
|
|
|
const solutionsArr = Array.from(solutions).map(i => i.checked);
|
|
|
|
|
const validationObject = {
|
|
|
|
|
"full-name": Boolean(fullName.value.trim()),
|
|
|
|
|
"email": /^\S+@\S+\.\S+$/.test(emailAddress.value.trim()),
|
|
|
|
|
"order-no": /^2024\d{6}$/.test(orderNo.value.trim()),
|
|
|
|
|
"product-code": /[A-Z]{2}\d{2}-[A-Z]\d{3}-[A-Z]{2}\d/i.test(productCode.value.trim()),
|
|
|
|
|
"quantity": Number(quantity.value.trim()) > 0,
|
|
|
|
|
"complaints-group": complaintsArr.some(i => i),
|
|
|
|
|
"complaint-description": null,
|
|
|
|
|
"solutions-group": solutionsArr.some(i => i),
|
|
|
|
|
"solution-description": null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (complaintsArr[3]) {
|
|
|
|
|
complaintTextAreaDiv.style.display = "block";
|
|
|
|
|
const complaintsDescriptionVal = complaintDescription.value.trim();
|
|
|
|
|
if (complaintsDescriptionVal.length < 20) {
|
|
|
|
|
validationObject["complaint-description"] = false;
|
|
|
|
|
} else {
|
|
|
|
|
validationObject["complaint-description"] = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
complaintTextAreaDiv.style.display = "none";
|
|
|
|
|
}
|
|
|
|
|
if (solutionsArr[2]) {
|
|
|
|
|
solutionTextAreaDiv.style.display = "block";
|
|
|
|
|
const solutionDescriptionVal = solutionDescription.value.trim();
|
|
|
|
|
if (solutionDescriptionVal.length < 20) {
|
|
|
|
|
validationObject["solution-description"] = false;
|
|
|
|
|
} else {
|
|
|
|
|
validationObject["solution-description"] = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
solutionTextAreaDiv.style.display = "none";
|
|
|
|
|
}
|
|
|
|
|
if (validationObject["complaint-description"] === null) delete validationObject["complaint-description"];
|
|
|
|
|
if (validationObject["solution-description"] === null) delete validationObject["solution-description"];
|
|
|
|
|
|
|
|
|
|
return validationObject;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clearBtn.addEventListener("click", clearForm);
|
|
|
|
|
|
|
|
|
|
Object.keys(formFields).forEach(key => {
|
|
|
|
|
document.getElementById(key).addEventListener("change", (e) => {
|
|
|
|
|
const validationObject = validateForm();
|
|
|
|
|
if (Object.values(validationObject).every(val => val)) messageBox.innerText = "";
|
|
|
|
|
if (!validationObject[key]) {
|
|
|
|
|
document.getElementById(key).style.borderColor = "red";
|
|
|
|
|
} else {
|
|
|
|
|
document.getElementById(key).style.borderColor = "green";
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isValid = (validationObj) => Object.values(validationObj).every(i => i);
|
|
|
|
|
|
|
|
|
|
form.addEventListener("submit", (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const validationObject = validateForm();
|
|
|
|
|
if (!isValid(validationObject)) {
|
|
|
|
|
messageBox.innerText = "Please, fill out the required fields correctly before submitting.";
|
|
|
|
|
Object.keys(validationObject).forEach(key => {
|
|
|
|
|
if (!validationObject[key]) {
|
|
|
|
|
document.getElementById(key).style.borderColor = "red";
|
|
|
|
|
} else {
|
|
|
|
|
document.getElementById(key).style.borderColor = "green";
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
messageBox.innerText = "";
|
|
|
|
|
alert("Form successfully submitted.");
|
|
|
|
|
clearForm();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
```
|