diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f9bd2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +*.js +*.d.ts +build.json diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..a8b13b5 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +src/ +.gitignore +build.json +tsconfig.json +yarn.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..14edd7e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,69 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Added +### Changed +### Fixed +### Removed + +## [0.2.0] - 2019-10-24 + +### Added +- .gitignore +- .npmignore +- CHANGELOG.md +- CONTRIBUTING.md +- LICENSE.md +- README.md +- too much things + +### Changed +- everything + +### Fixed +- everything + +## [0.1.0] - 2019-08-27 +### Added +- FormManager + - assign + - setupInputs + - getInit + - verify + - submit + - getJSON + - fillFromJSON + - fillFromURI + - clear +- Interfaces + - InputArrayInterface + - FMAssignInterface +- FMInput + - setValue + - getValue + - getDefault + - getName + - verify +- modules + - FMRepeatInput + - loopInputs + - setValue + - getValue + - Assignement + - FMDateInput + - setValue + - getValue + - getDefault + - Assignement + - FMDatalistInput + - setValue + - getValue + - Assignement + +[0.2.0]: https://git.delta-wings.net/dzeio/FormManager/src/tag/0.2.0 +[0.1.0]: https://git.delta-wings.net/dzeio/FormManager/src/tag/0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..296b1c3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,95 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish to make via issue, +email, or any other method with the owners of this repository before making a change. + +Please note we have a code of conduct, please follow it in all your interactions with the project. + +## Pull Request Process + +1. Ensure any install or build dependencies are removed before the end of the layer when doing a + build. +2. Update the [README.md][readme] with details of changes to the interface, this includes new environment + variables, exposed ports, useful file locations and container parameters. +3. Increase the version numbers in any examples files and the [README.md][readme] to the new version that this + Pull Request would represent. The versioning scheme we use is [SemVer][semver]. +4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you + do not have permission to do that, you may request the second reviewer to merge it for you. + +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [contact@delta-wings.net][email]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[readme]: ./README.md +[semver]: http://semver.org/ +[email]: mailto:contact@delta-wings.net +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/FMInput.ts b/FMInput.ts deleted file mode 100644 index 8cbb2b0..0000000 --- a/FMInput.ts +++ /dev/null @@ -1,76 +0,0 @@ -import FormManager from "./FormManager" - -export default class FMInput { - - element: HTMLInputElement - form: FormManager - - constructor(element: HTMLElement, form: FormManager) { - this.element = element as HTMLInputElement - this.form = form - this.setToDefault() - } - - /** - * Set the element Value - * - * @param {*} value - * @memberof FMInput - */ - setValue(value: any) { - this.element.value = value - this.element.setAttribute("value", value) - } - - /** - * Get the element value - * - * @returns {*} the value - * @memberof FMInput - */ - getValue(): any { - return this.formatValue(this.element.value) - } - - formatValue(value: any): any { - if (!isNaN(Number(value))) return Number(value) - return value - - } - - getDefault(args: string): any { - if (args.startsWith("run:")) { - args = args.split("run:")[1] - return eval(args) - } - return args - } - - setToDefault() { - if (this.element.hasAttribute("data-default")) { - return this.setValue(this.getDefault(this.element.dataset.default)) - } - return this.setValue("") - } - - getName(): string { - return this.element.getAttribute("name") == undefined ? this.element.dataset.name : this.element.getAttribute("name") - } - - /** - * Verify if the element is correct - * - * @returns {boolean} - * @memberof FMInput - */ - verify(): boolean { - let val: string = this.getValue() - if(val == "" && this.element.hasAttribute("required")) { - return false - } - if(this.element.dataset.regex !== undefined) { - return new RegExp(this.element.dataset.regex, 'g').test(val as string) - } - return true - } -} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f0360cd --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Delta Wings + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 68c2378..277dedd 100644 --- a/README.md +++ b/README.md @@ -1,137 +1,109 @@ -# FormManager +# Form Manager A powerfull Manager for all your forms +## Table of Content -## Table of Content +- [Form Manager](#form-manager) + - [Table of Content](#table-of-content) + - [Installation](#installation) + - [usage](#usage) + - [Typescript](#typescript) + - [Modules & Attributes](#modules--attributes) + - [Modules](#modules) + - [Attributes](#attributes) + - [Issues](#issues) + - [Changelog](#changelog) -- [FormManager](#formmanager) - - [Base](#base) - - [modules](#modules) - - [datalist changes](#datalist-changes) - - [date changes](#date-changes) - - [Repeat Element](#repeat-element) - - [TODO LIST](#todo-list) +## Installation -## Base +nothing difficult -to have the basic system just import `FormManager` +```bash +yarn add @dzeio/form-manager +or +npm install @dzeio/form-manager +``` + +## usage + +### Typescript ```ts import FormManager from '@dzeio/form-manager'; -const fm = new FormManager(docuement.getElementById("formId")) -``` +const fm = new FormManager(docuement.getElementById("form")); -from now on you can get datas by using `fm.getJSON()` fill by using `fm.fillFromJSON()` or `fm.fillFromURI()` verify datas with `fm.verify()` +// add modules +import { FMRepeatAssignment } from '@dzeio/FormManager/modules/FMRepeatInput' -## modules +fm.assign(FMRepeatAssignment) -Actually there is 3 modules included in the system _non of them are loaded_ -each modules add new functionality to the form (see later) +// or -to load a module: -```ts -//the three modules availables -import { FMRepeatInputAssignment } from '@dzeio/form-manager/modules/FMRepeatInput' -import { FMDatalistAssignement } from '@dzeio/form-manager/modules/FMDatalistInput' -import { FMDateInputAssignement } from '@dzeio/form-manager/modules/FMDateInput' - -// add the modules to the Form -fm.assign(FMRepeatInputAssignment) -fm.assign(FMDateInputAssignement) -fm.assign(FMDatalistAssignement) - -// reload the manager to use the new modules -fm.setupInputs() - -``` - -you can customise how the module assign himself to an element by doing this: -```ts -/* - input: typeof FMInput - classes?: string[] | string - attributes?: string[] | string - type?: string - tagName?: string -*/ +import FMDateInput from '../FormManagerGit/modules/FMDateInput' fm.assign({ - input: FMRepeatInput, - classes: "custom-class", - attributes: "data-custom", - type: "number", //for input - tagName: "div" -}) + input: FMDateInput, + type: "date", + tagName: "input" +}); + +// After adding modules run to reffect modules to inputs +fm.setupInputs(); + +// verify form validity: +fm.verify(); //return true if valid else return false +// if it returns false you can use the variable under to see th FMInput that isnt valid +fm.lastErroredInput + +// submit your data to an endpoint +fm.submit("/api/idk", (ev) => {/* onloaded callback*/}, /* verify datas beforehand default:true*/ true) + +// get the json of your form +fm.getJSON() + +// fill form from URI (datas MUST be in JSON (see getJSON for examples)) +fm.fillFromURI("uri") + +// same as before but you give the json from ts +fm.fillFromJSON(json) + +// change if you only see the form or edit them +fm.setMode(FMMode.ViewMode or FMMode.EditMode) + +// same thing as before but just for one field +fm.setModeForInput(FMMode.ViewMode or FMMode.EditMode, "inputName") + +// Reset the form to it's defaults values +fm.clear() + ``` -### datalist changes +## Modules & Attributes -the values for the datalist will not be the `value` attribute anymore but a `data-value` so the end user will see wht you wnt and not the value you want to send +### Modules -even if you set `data-strict` the result value will only be set if it's from one of the option -if not set the value will stay set by what the user wrote -***ATTENTION if multiple `option` has the same `value` attribute the final value will be the `data-value` of the first `value`*** +| Module name | Description | +| :---------: | :---------: | +| [Datalist](https://git.delta-wings.net/dzeio/FormManager/wiki/modules.datalist) | Manage the datalist better than ever ! | +| [Date](https://git.delta-wings.net/dzeio/FormManager/wiki/modules.date) | Manage the date element | +| [File](https://git.delta-wings.net/dzeio/FormManager/wiki/modules.file) | Manage single file uploads | +| [Repeat](https://git.delta-wings.net/dzeio/FormManager/wiki/modules.repeat) | Make your fields repeatable ! | +| [Select](https://git.delta-wings.net/dzeio/FormManager/wiki/modules.select) | Fix your Select | -ex: -```html - - - - - - - -``` +### Attributes -### date changes +| Attribute name | Description | +| :------------: | :---------: | +| [data-autoset](https://git.delta-wings.net/dzeio/FormManager/wiki/attribute.data-autoset) | Update your value in _near_ realtime | +| [data-default](https://git.delta-wings.net/dzeio/FormManager/wiki/attribute.data-default) | a better value than `value` | +| [data-ignore](https://git.delta-wings.net/dzeio/FormManager/wiki/attribute.data-ignore) | i don't see this | +| [data-regex](https://git.delta-wings.net/dzeio/FormManager/wiki/attribute.data-regex) | regex your value | -`FMDateInput` change the result type from a string to a `Date` object -and if `data-default` is set the current date will be set +## Issues -ex: -```html - -``` +Complete listing [here](https://git.delta-wings.net/dzeio/FormManager/issues) -### Repeat Element +## Changelog -the Repeat element allow to add/delete multiple time the sames input(s) - -***NOTE***_: actually the filling don't work but the rest work just fine_ - -Organisation -```html -
-
- - - -
- -
-
- - - -
- -
-
-``` - -## TODO LIST - -more Listing [here](https://git.delta-wings.net/dzeio/FormManager/issues) - -- [ ] add `data-autoset` to autofill the input with data from another one _with `readonly` and `disabled`_ -- [ ] allow filling of `FMRepeatInput` -- [ ] add `data-regex` for verification -- [ ] add `data-ignore` mainly for `data-autoset` +[here](./CHANGELOG.md) diff --git a/modules/FMDatalistInput.ts b/modules/FMDatalistInput.ts deleted file mode 100644 index ac8b243..0000000 --- a/modules/FMDatalistInput.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { FMAssignInterface } from '../Interfaces'; -import FormManager from "../FormManager" -import FMInput from "../FMInput" - -/** - * the upgraded datalist element - * @priority 2 - * @class FMDatalistInput - * @extends {FMInput} - */ -export default class FMDatalistInput extends FMInput { - - datalist: HTMLDataListElement - isStrict: boolean - - constructor(element: HTMLInputElement, form: FormManager) { - super(element, form) - this.isStrict = this.element.hasAttribute("data-strict") - let id = this.element.getAttribute("list") - let tmpDatalist = document.getElementById(id) - this.datalist = tmpDatalist !== undefined ? tmpDatalist as HTMLDataListElement : undefined - } - - setValue(value: string) { - if (value == "") { - this.element.value = "" - return - } - if (this.datalist) { - if ((value as any).id != undefined) { - value = (value as any).id - } - let option: HTMLOptionElement = this.datalist.querySelector(`[data-value="${value}"]`) - if (option != undefined) { - this.element.value = option.value - return - } - if (option == undefined && !this.isStrict) { - this.element.value = value - return - } - if (option || !this.isStrict) { - this.element.value = value - return - } - } - } - - getValue(): string { - if (this.datalist) { - let option: HTMLOptionElement = this.datalist.querySelector(`[value="${this.element.value}"]`) - if (option) return this.formatValue(option.dataset.value) - } - return this.isStrict ? undefined : this.formatValue(this.element.value) - } -} - -export const FMDatalistAssignement: FMAssignInterface = { - input: FMDatalistInput, - attributes: "list", - tagName: "input" -} diff --git a/package.json b/package.json index dc2f405..3e48b7a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "@dzeio/form-manager", - "version": "0.1.1", - "main": "./FormManager.ts", + "version": "0.2.0", + "main": "./dist/FormManager.js", + "types": "./dist/FormManager.d.ts", "description": "A powerfull Form Manager", "repository": { "type": "git", @@ -17,5 +18,11 @@ "url": "https://dze.io" }, "license": "MIT", - "private": false + "private": false, + "devDependencies": { + "typescript": "^3.6.4" + }, + "scripts": { + "build": "tsc" + } } diff --git a/src/FMInput.ts b/src/FMInput.ts new file mode 100644 index 0000000..a64ba61 --- /dev/null +++ b/src/FMInput.ts @@ -0,0 +1,103 @@ +import FormManager from "./FormManager" + +export default class FMInput { + + element: HTMLInputElement + form: FormManager + required: boolean + + constructor(element: Element, form: FormManager) { + this.element = element as HTMLInputElement + this.form = form + this.required = element.hasAttribute("required") + + // Set element value to it's default one + this.setToDefault() + } + + /** + * Set the element Value + * + * @param {*} value + * @memberof FMInput + */ + setValue(value: any) { + this.element.value = value + } + + /** + * Get the element value + * + * @returns {*} the value + * @memberof FMInput + */ + getValue(): any { + return this.formatValue(this.element.value) + } + + + /** + * Format the value + * ex: if the value is "1" it will return the value as a number 1 + * + * @protected + * @param {*} value + * @returns {*} + * @memberof FMInput + */ + protected formatValue(value: any): any { + // if the value is a number return it as a number obj + if (!isNaN(Number(value))) return Number(value) + return value + + } + + getDefault(args?: string): any { + // if arg is set and startsWith run: run the function in it + if (args && args.startsWith("run:")) { + args = args.split("run:")[1] + return eval(args) + } + return args + } + + setToDefault() { + if (this.element.hasAttribute("data-default")) { + return this.setValue(this.getDefault(this.element.dataset.default)) + } + return this.setValue(this.getDefault("")) + } + + /** + * Retrieve the input name + * + * @returns {string} + * @memberof FMInput + */ + getName(): string { + // while we search for inputs containing [name] we search for the input real name in [name] or [data-name] + // (Allow other inputs to play with inputs) + let attr = this.element.getAttribute("name") || this.element.dataset.name; + if (attr) return attr + throw Error("Error: could not get input name!") + } + + /** + * Verify if the element is correct + * + * @returns {boolean} + * @memberof FMInput + */ + verify(): boolean { + let val: any = this.getValue() + // if element is required and value is undefined retur false + if (this.required && (val === undefined || val === null || val === "")) return false + + // check regex + const regex = this.element.dataset.regex + if(regex) { + return new RegExp(regex, 'g').test(val + "") + } + return true + } +} diff --git a/FormManager.ts b/src/FormManager.ts similarity index 90% rename from FormManager.ts rename to src/FormManager.ts index f298aae..d2c24a0 100644 --- a/FormManager.ts +++ b/src/FormManager.ts @@ -133,19 +133,16 @@ export default class FormManager { * @type {FMInput} * @memberof FormManager */ - public lastErroredInput: FMInput + public lastErroredInput: FMInput|undefined - - private _form: HTMLFormElement /** * The Form Element of the FM * - * @private * @type {HTMLFormElement} * @memberof FormManager */ - public set form(v : HTMLFormElement) {this._form = v} - public get form(): HTMLFormElement {return this._form} + public form: HTMLFormElement + /** * Creates an instance of FormManager. @@ -169,9 +166,9 @@ export default class FormManager { this.setupInputs() setInterval(() => { - this.form.querySelectorAll("[data-autoset]").forEach((el: HTMLInputElement) => { + (this.form.querySelectorAll("[data-autoset]") as NodeListOf).forEach((el: HTMLInputElement) => { let autosetStr = el.dataset.autoset - if (autosetStr.startsWith("run:")) { + if (autosetStr && autosetStr.startsWith("run:")) { let tmp = autosetStr.split("run:")[1] el.value = eval(tmp) } @@ -195,9 +192,10 @@ export default class FormManager { * @memberof FormManager */ public setupInputs() { - this.form.querySelectorAll("[name]:not([data-name])").forEach((element: HTMLElement) => { + this.inputs = {} + this.form.querySelectorAll("[name]:not([data-name])").forEach((element: Element) => { let el = this.getInit(element) - this.inputs[el.getName()] = el + if (el) this.inputs[el.getName()] = el }); } @@ -208,10 +206,10 @@ export default class FormManager { * @returns {FMInput} * @memberof FormManager */ - public getInit(element: HTMLElement): FMInput { + public getInit(element: Element): FMInput|undefined { inputsLoop: for (const input of this.FMInputs) { if (input.classes != undefined) { - let tmpList: string[] + let tmpList: string[] = [] if (typeof input.classes == "object") tmpList = input.classes if (typeof input.classes === "string") tmpList = [input.classes] for (const classe of tmpList) { @@ -219,7 +217,7 @@ export default class FormManager { } } if (input.attributes != undefined) { - let tmpList: string[] + let tmpList: string[] = [] if (typeof input.attributes == "object") tmpList = input.attributes if (typeof input.attributes === "string") tmpList = [input.attributes] for (const classe of tmpList) { @@ -334,6 +332,7 @@ export default class FormManager { } public setMode(mode: FMMode) { + console.log(mode) if (mode == FMMode.ViewMode) { for (const name in this.inputs) { if (this.inputs.hasOwnProperty(name)) { @@ -353,13 +352,28 @@ export default class FormManager { } } + public setModeForInput(mode: FMMode, inputName: string) { + console.log(mode) + if (mode == FMMode.ViewMode) { + if (this.inputs[inputName]) { + this.inputs[inputName].element.setAttribute("disabled", "") + } + return + } + if (mode == FMMode.EditMode) { + if (this.inputs[inputName]) { + this.inputs[inputName].element.removeAttribute("disabled") + } + } + } + /** * Clear the fields in the form * * @memberof FormManager */ public clear() { - this.form.querySelectorAll("[name]").forEach((el: HTMLInputElement) => { + (this.form.querySelectorAll("[name]") as NodeListOf).forEach((el: HTMLInputElement) => { for (const name in this.inputs) { if (this.inputs.hasOwnProperty(name)) { const input = this.inputs[name]; @@ -377,7 +391,7 @@ export enum FMMode { /** * TODO: FMFileInput - * have a data-endpoint with an URI + * have a data-type with an typeId linked to an URI * on file set -> show button to upload * on file change -> show button "delete and upload" * on upload -> upload and create hidden field with the result ID @@ -392,7 +406,9 @@ export enum FMMode { * retrieve pic: /enpoint?get=pic-id * return {uri:"/static/pic-name.jpg"} if it exist * return {error:true,msg:"picture don't exist"} is pic dont exist - * upload pic: /enpoint?upload + * upload pic: /enpoint?upload&type=x + * with type is a type id (to set a different location in the system) + * _default to type 1_ * return {uploaded:true,id:2} * delete pic: /endpoint?del=pic-id * return {deleted=true} if deleted diff --git a/Interfaces.ts b/src/Interfaces.ts similarity index 100% rename from Interfaces.ts rename to src/Interfaces.ts diff --git a/src/modules/FMDatalistInput.ts b/src/modules/FMDatalistInput.ts new file mode 100644 index 0000000..65baa07 --- /dev/null +++ b/src/modules/FMDatalistInput.ts @@ -0,0 +1,71 @@ +import { FMAssignInterface } from '../Interfaces'; +import FormManager from "../FormManager" +import FMInput from "../FMInput" + +/** + * the upgraded datalist element + * @class FMDatalistInput + * @extends {FMInput} + */ +export default class FMDatalistInput extends FMInput { + + datalist: HTMLDataListElement + isStrict: boolean + + constructor(element: HTMLInputElement, form: FormManager) { + super(element, form) + + // check if input is strict on inputs + this.isStrict = this.element.hasAttribute("data-strict") + + // get datalist id + let id = this.element.getAttribute("list") + if (!id) throw Error(`Error: your input "${this.getName()}" MUST have a list attribute`); + + // get datalist + this.datalist = document.getElementById(id) as HTMLDataListElement + if (!this.datalist) throw Error(`Error: Datalist not found for ${this.getName()} input`) + } + + setValue(value: string) { + // if value is "" set value to "" + if (value == "") { + this.element.value = "" + return + } + // value is an object containing an id then set value to the id + if ((value as any).id != undefined) { + value = (value as any).id + } + + // get the option element containing the value + let option = this.datalist.querySelector(`[data-value="${value}"]`) + + // if it was set set the element value to the option value + if (option != undefined) { + this.element.value = (option as HTMLOptionElement).value + return + } + + // if datalist is not strict set it to the value inputted + if (!this.isStrict) { + this.element.value = value + return + } + } + + getValue(): string { + // if element value == option value return option data-value + let option = this.datalist.querySelector(`[value="${this.element.value}"]`) + if (option) return this.formatValue((option as HTMLOptionElement).dataset.value) + + // if strict return undefined else return element value + return this.isStrict ? undefined : this.formatValue(this.element.value) + } +} + +export const FMDatalistAssignement: FMAssignInterface = { + input: FMDatalistInput, + attributes: "list", + tagName: "input" +} diff --git a/modules/FMDateInput.ts b/src/modules/FMDateInput.ts similarity index 50% rename from modules/FMDateInput.ts rename to src/modules/FMDateInput.ts index be2313a..ffb62dd 100644 --- a/modules/FMDateInput.ts +++ b/src/modules/FMDateInput.ts @@ -1,4 +1,4 @@ -import { FMAssignInterface } from './../Interfaces'; +import { FMAssignInterface } from '../Interfaces'; import FMInput from "../FMInput" /** @@ -9,22 +9,26 @@ import FMInput from "../FMInput" export default class FMDateInput extends FMInput { setValue(value: Date|string) { + // if value is a string set value to the date of the string if (typeof(value) == "string") { value = new Date(value) } this.element.valueAsDate = value } - getValue(): Date { - return this.element.valueAsDate + getValue(): Date|undefined { + // if get date and if null return undefined else return value + let date = this.element.valueAsDate + return date == null ? undefined : date } getDefault(args: string): Date { + // if data-default is present return the current date return new Date } } -export const FMDateInputAssignement: FMAssignInterface = { +export const FMDateAssignement: FMAssignInterface = { input: FMDateInput, type: "date", tagName: "input" diff --git a/src/modules/FMFileInput.ts b/src/modules/FMFileInput.ts new file mode 100644 index 0000000..8879f73 --- /dev/null +++ b/src/modules/FMFileInput.ts @@ -0,0 +1,118 @@ +import { FMAssignInterface } from '../Interfaces'; +import FMInput from "../FMInput" +import FormManager from '../FormManager'; + +/** + * + * @class FMFileInput + * @extends {FMInput} + */ +export default class FMFileInput extends FMInput { + + isUploaded = false + + type = 1 + + + + button: HTMLButtonElement + + constructor(element: HTMLInputElement, form: FormManager) { + super(element, form) + + this.type = this.element.dataset.uploadType ? parseInt(this.element.dataset.uploadType): 1 + + element.addEventListener("change", () => { + console.log("pouet") + let files = element.files + if (files && element.parentElement && files.length > 0) { + const name = element.parentElement.querySelector(".file-name") + if (name) name.textContent = files[0].name + } + }) + + if (this.element.hasAttribute("data-button") && element.parentElement && element.dataset.button) { + let btn = element.parentElement.querySelector(element.dataset.button) + this.button = btn ? btn as HTMLButtonElement : undefined + // this.button = element.parentElement.querySelector(element.dataset.button) + } + + if (this.button) { + this.button.addEventListener("click", () => { + if (!this.element.disabled) { + console.log("pouet!") + this.upload() + } + }) + } + + + } + + upload() { + // if (this.form.getJSON()["id"] == 0 || this.form.getJSON()["id"] == undefined) { + // NotificationManager.getNotificationManager().add("Merci de sauvegarder l'offre au moins une fois !") + // } + let files = this.element.files + if (files && files.length > 0) { + const file = files[0] + const ajax = new XMLHttpRequest + let form = new FormData + form.append(this.getName(), file, file.name) + ajax.open("POST", `/api/file?upload&type=${this.type}`) + ajax.addEventListener("load", (ev) => { + console.log(ev) + }) + ajax.addEventListener("progress", (ev) => { + console.log(ev) + }) + ajax.addEventListener("loadstart", () => { + if (this.button) this.button.classList.add("is-loading") + if (!this.element.hasAttribute("disabled")) { + this.element.setAttribute("disabled", "") + this.element.setAttribute("data-uploading", "") + } + }) + ajax.addEventListener("loadend", () => { + if (this.button) this.button.classList.remove("is-loading") + if (this.element.hasAttribute("disabled") && this.element.hasAttribute("data-uploading")) { + this.element.removeAttribute("disabled") + this.element.removeAttribute("data-uploading") + } + if (this.button) this.button.innerText = "Uploaded!" + this.element.dataset.id = JSON.parse(ajax.responseText).id + ajax.responseText + }) + ajax.send(form) + } + } + + setValue(value: string|number) { + if (value == "") { + this.element.dataset.id = value + "" + if (this.element.parentElement) { + const name = this.element.parentElement.querySelector(".file-name") + if (name) name.textContent = "" + } + } + + } + + getValue(): number { + return this.element.dataset.id ? parseInt(this.element.dataset.id):0 + } + + verify() { + if (this.element.hasAttribute("required")) { + return this.isUploaded + } + return true + } + +} + +export const FMFileAssignement: FMAssignInterface = { + input: FMFileInput, + type: "file", + tagName: "input" +} diff --git a/modules/FMRepeatInput.ts b/src/modules/FMRepeatInput.ts similarity index 81% rename from modules/FMRepeatInput.ts rename to src/modules/FMRepeatInput.ts index 2becce3..19d3b21 100644 --- a/modules/FMRepeatInput.ts +++ b/src/modules/FMRepeatInput.ts @@ -19,20 +19,24 @@ export default class FMRepeatInput extends FMInput { super(element, form) //fetch Template - this.template = element.querySelector(".fmr-template") + this.template = element.querySelector(".fmr-template") as HTMLElement + if (!this.template) throw Error(`Error: your repeat input "${this.getName()}" MUST have a child with the class .fmr-template`); + this.template.style.display = "none" //fetch add button - this.addBtn = element.querySelector(".fmr-add") + this.addBtn = element.querySelector(".fmr-add") as HTMLElement + if (!this.addBtn) throw Error(`Error: your repeat element "${this.getName()}" MUST have a child with the class .fmr-add`); + this.addBtn.addEventListener("click", () => { if (!this.addBtn.hasAttribute("disabled")) this.addLine() }) - //Observer to handle attributes changes + // Observer to handle attributes changes const observer = new MutationObserver((mutationList: any, observer: any) => { for (let mutation of mutationList) { if (mutation.type === 'attributes' && mutation.attributeName === "disabled") { - this.element.querySelectorAll(".fmr-add, .fmr-del").forEach((el: HTMLElement) => { + (this.element.querySelectorAll(".fmr-add, .fmr-del") as NodeListOf).forEach((el: HTMLElement) => { if (this.element.hasAttribute("disabled")) el.style.display = "none" else el.style.display = "" }) @@ -62,7 +66,7 @@ export default class FMRepeatInput extends FMInput { // loop through inputs ot init them let sub: FMInput[] = [] - node.querySelectorAll("[data-input]").forEach((el: HTMLElement) => { + node.querySelectorAll("[data-input]").forEach((el: Element) => { let input = this.form.getInit(el) if (this.element.hasAttribute("disabled")) { input.element.disabled = true @@ -81,8 +85,8 @@ export default class FMRepeatInput extends FMInput { // get the delete button let del = node.querySelector(".fmr-del") - del.addEventListener("click", () => { - if (!del.hasAttribute("disabled")) { + if (del) del.addEventListener("click", () => { + if (del && !del.hasAttribute("disabled")) { let id = this.element.querySelectorAll(".fmr-element").length-1 this.elements.splice(id) node.remove() @@ -138,7 +142,7 @@ export default class FMRepeatInput extends FMInput { } } -export const FMRepeatInputAssignment: FMAssignInterface = { +export const FMRepeatAssignment: FMAssignInterface = { input: FMRepeatInput, classes: "fm-repeat", tagName: "div" diff --git a/src/modules/FMSelectInput.ts b/src/modules/FMSelectInput.ts new file mode 100644 index 0000000..c61daf0 --- /dev/null +++ b/src/modules/FMSelectInput.ts @@ -0,0 +1,23 @@ +import { FMAssignInterface } from '../Interfaces'; +import FMInput from "../FMInput" + +/** + * + * @class FMDateInput + * @extends {FMInput} + */ +export default class FMSelectInput extends FMInput { + + getDefault(): any { + // check if element as a selected element and if true return it's value + let def = this.element.querySelector("option[selected]") + if (def) { + return (def as HTMLOptionElement).value + } + } +} + +export const FMSelectAssignement: FMAssignInterface = { + input: FMSelectInput, + tagName: "select" +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f9ea7fe --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,63 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { + /* Basic Options */ + "incremental": true, /* Enable incremental compilation */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + "tsBuildInfoFile": "./build.json", /* Specify file to store incremental compilation information */ + "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..c4a5d4a --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +typescript@^3.6.4: + version "3.6.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d" + integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==