Skip to content

Forms ​

control HTML forms easily, set form default values, reset, handleSumbit, reactive set and get form fields, all of that with support nested keys by dot notation fields names


html
<!DOCTYPE html>
<form onsubmit="handleSubmit(onSubmit, event)">
  <input type="text" name="fname" />
  <input type="text" name="user.name" />
  <input type="text" name="user.fname" />
  <input type="text" />
  <button type="submit">submit</button>
</form>

<p></p>

<script>
// TODO: make stores for forms to get it's functions, or attach reset(), and others to component
// TODO: attach handlers to new elements added to form, and delete removed (only solutions available are use MutationObserver or export function to regenerate form.elements)

const proxyHandler = (form) => ({
  set(target, key, value) {
    form.elements[key].value = value

    // convert dot notation to nested objects
    // was target[key] = value
    const keys = key.split('.')
    let currentTarget = target

    for (let i = 0; i < keys.length - 1; i++) {
      const currentKey = keys[i];
      if (!currentTarget[currentKey] || typeof currentTarget[currentKey] !== 'object') {
        currentTarget[currentKey] = {};
      }
      currentTarget = currentTarget[currentKey];
    }
    
    currentTarget[keys[keys.length - 1]] = value;

    return true
  },
  get(target, key) {
    const keys = key.split('.');
    let value = target;
    for (const key of keys) {
      if (value && typeof value === 'object') {
        value = value[key];
      } else {
        return undefined;
      }
    }
    return value;
  }
})

const attach = (element) => {
  element.oninput = (event) => {
    controls[element.name] = event.target.value
  };
};

function assignNestedProperties(target, obj, parent = null) {
  for (const key in obj) {
    const value = obj[key];
    const prop = parent? `${parent}.${key}` : key
    if (typeof value === 'object' && !Array.isArray(value)) {
      assignNestedProperties(target, value, prop);
    } else {
      target[prop] = value;
    }
  }
}

function useForm({ defaultValues }, selector = 'form') {
  const defaults = JSON.parse(JSON.stringify(defaultValues));
  const form = document.querySelector(selector);
  let controls = new Proxy(defaultValues ?? {}, proxyHandler(form));

  // Attach event handlers to existing form elements
  // for (element of Array.from(form.elements).filter(el => el.name))
  //   attach(element)
  Array.from(form.elements).forEach((element) => {
    if (element.name) {
      attach(element);
      element.value = controls[element.name]
    }
  });

  return {
    handleSubmit: (callback, event) => {
      event.preventDefault();
      callback(controls);
    },
    controls,
    reset: (values) => {
      if (!values) form.reset()
      assignNestedProperties(controls, values);
    }
  };
}

const { handleSubmit, controls, reset } = useForm({
  defaultValues: {
    fname: 'ali',
    user: { name: 'ali', fname: "dodo" }
  }
})

function onSubmit(data) {
  console.log(data)
  console.log(data['user.fname'])
}

setTimeout(() => {
  controls.fname = 'test'
  controls['user.name'] = 'testo'
}, 3000)

setTimeout(() => {
  reset({
    fname: 'ali',
    user: { name: 'ali', fname: "bobo" }
  })
}, 5000)
</script>