Skip to content

Instantly share code, notes, and snippets.

@Parables
Last active June 5, 2021 20:56
Show Gist options
  • Save Parables/e17a96ba4b217178f9f918d3dbbea30c to your computer and use it in GitHub Desktop.
Save Parables/e17a96ba4b217178f9f918d3dbbea30c to your computer and use it in GitHub Desktop.
This is the test version of the source code used to make Dynamic Form Building using Classes
<script>
import FormBuilder from './FormBuilder.svelte'
import { Form } from './Form'
let form = new Form({id: "Test Form", sections: []})
let title ="New Section"
function addSection(){
form= form.addSection({title: title, rows:[]});
form.print()
}
function removeSection(e){
let {section, index} = e.detail
form = form.removeSection(section, index)
}
</script>
<div>
<label for="newSection"></label>
<input id="newSection" name="newSection" type="text" bind:value={title} />
<button on:click={addSection} >
Add Section
</button>
</div>
<FormBuilder bind:form on:sectionremoved={removeSection} />
<script>
import {createEventDispatcher } from 'svelte'
let dispatch = createEventDispatcher()
import { Form } from './Form'
export let form = new Form({id: "Test Form", sections: []})
</script>
<h1>{form.id}</h1>
{#each form.sections as s, i (i)}
<div>
<span style="margin-right: 20px;">{s.title}</span>
<button on:click={()=>{dispatch('sectionremoved',{section: s, index: i})}}>Remove Section </button>
</div>
{/each}
/**
* @param `arr` {Array<T>} The array of `el` elements
* @param {el}
* @returns {boolean}
*
* @description returns true if arr includes el otherwise false */
export function exists<T>(arr: T[], el: T): boolean {
return arr.includes(el);
}
/** returns an immuatable array of T[] with the el added or removed based on the mutation */
export function mutateList<T>(mutation: "ADD" | "REMOVE", arr: T[], el: T, index: number = arr.length): T[] {
if (mutation === "ADD")
return exists(arr, el) ? arr.slice() : [...arr.slice(0, index), el, ...arr.slice(index)];
else return [...arr.slice(0, index), ...arr.slice(index + 1)];
}
/** returns a new clone of an item...
* uses props to add different properties
* please add these props: id, name, value when cloning Fields
* and id, title when cloning rows
*/
export function clone<T, IT>(target: T, props?: Partial<IT>): T {
return props ? Object.assign(target, props) : target
}
export interface IForm {
id: string;
sections: ISection[];
classNames?: string;
store?: any;
validator?: any;
}
export default class Form implements IForm {
id: string = "";
sections: Section[] = [];
classNames?: string = "";
store?: any;
validator?: any;
constructor(props: IForm) {
Object.assign(this, props);
}
static from(props: IForm) {
return new Form(props);
}
addSection(section: ISection, insertAt?: number) {
this.sections = mutateList("ADD", this.sections, new Section(section), insertAt);
this.print()
return this;
}
removeSection(section: ISection, removeAt?: number) {
this.sections = mutateList("REMOVE", this.sections, new Section(section), removeAt);
this.print()
return this;
}
cloneSection(sectionAt: number, newProps?: Partial<ISection>, insertAt?: number) {
let newSection = clone(this.sections[sectionAt], newProps)
this.sections = mutateList("ADD", this.sections, newSection, insertAt);
this.print()
return this;
}
addRow(row: IRow, sectionAt: number, insertAt?: number) {
this.sections[sectionAt].addRow(row, insertAt)
this.print()
return this
}
removeRow(row: IRow, sectionAt: number, removeAt: number) {
this.sections[sectionAt].removeRow(row, removeAt)
this.print()
return this
}
cloneRow(sectionAt: number, rowAt: number, newProps: Partial<IRow>, insertAt?: number) {
let newRow = clone(this.sections[sectionAt].rows[rowAt], newProps)
this.addRow(newRow, sectionAt, insertAt)
this.print()
return this
}
addField(field: IField, sectionAt: number, rowAt: number, insertAt?: number) {
this.sections[sectionAt].rows[rowAt].addField(field, insertAt)
this.print()
return this
}
removeField(field: IField, sectionAt: number, rowAt: number, removeAt: number) {
this.sections[sectionAt].rows[rowAt].removeField(field, removeAt)
this.print()
return this
}
cloneField(sectionAt: number, rowAt: number, fieldAt: number, newProps: Partial<IField>, insertAt?: number) {
let newField = clone(this.sections[sectionAt].rows[rowAt].fields[fieldAt], newProps)
this.addField(newField, sectionAt, rowAt, insertAt)
this.print()
return this
}
print() {
let form = { ...this };
console.log(form);
return form;
}
}
export interface ISection {
title?: string;
classNames?: string;
rows: IRow[];
}
export class Section implements ISection {
title?: string = "";
classNames?: string = "";
rows: Row[] = [];
constructor(props: ISection) {
Object.assign(this, props);
}
addRow(row: IRow, insertAt?: number) {
this.rows = mutateList("ADD", this.rows, new Row(row), insertAt);
return this.rows;
}
removeRow(row: IRow, removeAt?: number) {
this.rows = mutateList("REMOVE", this.rows, new Row(row), removeAt);
return this.rows;
}
cloneRow(rowAt: number, newProps?: Partial<IRow>, insertAt?: number) {
let newRow = clone(this.rows[rowAt], newProps)
this.rows = mutateList("ADD", this.rows, newRow, insertAt);
return this.rows;
}
}
export interface IRow {
classNames?: string;
fields: IField[];
rowStyle?: "block" | "inline";
rowHeader?: string;
}
class Row {
classNames?: string = "";
fields: Field[] = [];
rowStyle?: "block" | "inline" = "inline";
rowHeader?: string = "";
constructor(props: IRow) {
Object.assign(this, props);
}
addField(field: IField, insertAt?: number) {
this.fields = mutateList("ADD", this.fields, new Field(field), insertAt);
return this.fields;
}
removeField(field: IField, removeAt?: number) {
this.fields = mutateList("REMOVE", this.fields, new Field(field), removeAt);
return this.fields;
}
cloneField(fieldAt: number, newProps?: Partial<IField>, insertAt?: number) {
let newField = clone(this.fields[fieldAt], newProps)
this.fields = mutateList("ADD", this.fields, newField, insertAt);
return this.fields;
}
}
export type FieldType =
| "text"
| "email"
| "password"
| "tel"
| "date"
| "time"
| "number"
| "typeahead"
| "radio"
| "checkbox"
| "checklist"
| "chip"
| "chipinput"
| "select"
| "range-time"
| "range-date"
| "range-number"
| "repeatField";
export interface IField {
id: string;
name: string;
value?: string;
label?: string;
type?: FieldType;
placeholder?: string;
colors?: any;
width?: string;
height?: string;
margin?: string;
min?: number;
max?: number;
step?: number;
items?: Item[] | string[];
passwordChar?: string;
multiSelect?: boolean;
readonly?: boolean;
variant?: "outlined" | "material" | "default";
validate?: string[];
startIcon?: boolean;
endIcon?: boolean;
validators?: any[];
}
export interface Item {
id: string;
name: string;
value: string;
label?: string;
}
export class Field implements IField {
id: string = "";
name: string = "";
value?: string = "";
label?: string;
type?: FieldType;
placeholder?: string;
colors?: any;
width?: string;
height?: string;
margin?: string;
min?: number;
max?: number;
step?: number;
items?: Item[] | string[];
passwordChar?: string;
multiSelect?: boolean;
readonly?: boolean;
variant?: "outlined" | "material" | "default";
validate?: string[];
startIcon?: boolean;
endIcon?: boolean;
validators?: any[];
constructor(props: Partial<IField>) {
Object.assign(this, props);
}
}
function isEqual(obj1, obj2) {
let props1 = Object.getOwnPropertyNames(obj1);
let props2 = Object.getOwnPropertyNames(obj2);
if (props1.length != props2.length) {
return false;
}
for (let i = 0; i < props1.length; i++) {
let prop = props1[i];
let bothAreObjects = typeof(obj1[prop]) === ‘object’ && typeof(obj2[prop]) === ‘object’;
if ((!bothAreObjects && (obj1[prop] !== obj2[prop]))
|| (bothAreObjects && !isEqual(obj1[prop], obj2[prop]))) {
return false;
}
}
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment