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>