wearebraid / vue-formulate

⚡️ The easiest way to build forms with Vue.
https://vueformulate.com
MIT License
2.24k stars 245 forks source link

Theming and Field Groups #41

Closed go4cas closed 4 years ago

go4cas commented 4 years ago

I worked through the documentation, and I have the following questions (in case I've missed something in the docs):

justin-schroeder commented 4 years ago

Good questions that require a long answer since there are lots of ways to skin these cats.

External libraries/theming:

External libraries come in different shapes and sizes. Some are component libraries written in Vue, some are semantic css classes you need to adhere to (bootstrap), some are just collections of presentational classes (tailwind). Here are some solutions:

Custom elements (ideal): The goal of Vue Formulate has been and will continue to be a simple, unified API when composing forms (single input component). Other solutions, like scoped slots dont always lend themselves to this unified API. In otherwords you would need to wrap a scoped slot implementation in another component so instead of using <FormulateInput> for every element, ones you wrap would be <AntTextField> or anything you chose to name it...and generally with different props. This is a bit antithetical to the developer experience Vue Formulate is trying to provide.

So, Vue Formulate tackles this challenge by allowing you to extend the library of types and then canonize those into a plugin. This is available today. I would absolutely love for some community member to come up with the ant-design plugin, the buefy plugin, or the vux plugin etc. This would allow for everyone to keep using single element <FormulateInput> to compose forms, but also leverage some of the great features of Vue Formulate.

import VueFormulate from '@braid/vue-formulate'
import VueFormulateAntDesign from '@your/vue-formulate-ant-design'

Vue.use(VueFormualte, {
  plugins: [ VueFormulateAntDesign ]
})

...

<FormulateInput
  type="ant-checkbox"
/>

This is the goal for where Vue Formulate is heading, but admitedly I need some community developers to help write some plugins to get it there.

Scoped slots (soon): What’s most commonly used to solve this problem is scoped slots. This is already mostly supported but not currently a public API or documented — but its on the roadmap (#34) for the 2.3.0 release. If you'd like to leverage it, checkout the context object, and the scoped slots already in use on FormulateInput. Again, this isn't directly in line with Vue Formulate’s goals, but sometimes it's just a necessity for a fields on a project.

Class lists functions (2.4.0?): There still some libraries that are just class driven (tailwind, bootstrap, etc). This is currently not well solved, but the current working strategy for how to bring support is by implementing class list functions that would allow that kinda of flexibility. This is still a roadmap feature. In the meantime users of these frameworks will have to extend their base classes in css.

Field Groups (soon)

This is a hugely needed feature, and something I wanted to get out in 2.2.0. It is technically possible right now using a v-for itterator on a FormulateInput, but the field values dont collapse into a nice api. The API will be:

<FormulateInput
  name="people"
  type="group"
  limit="3"
>
  <FormulateInput
    type="text"
    name="name"
  />
  <FormulateInput
    name="birthday"
    type="date"
  />
</FormulateInput>

The above would produce a repeating group of fields with an + Add button (overridable with a scoped slot), and the model results would collect as an array:

{
  people: [
    { name: 'Tom', birthday: '12/12/1980' },
    ...
  ]
}

How does that API sound right to you @go4cas?

go4cas commented 4 years ago

@justin-schroeder ... first off, thanks for taking the time to work through my questions! It's great to see authors of open source libraries still putting thought into their designs!

Your comments on "Theming" actually made me relook at your docs, specifically the section on Custom Inputs. Surely I could do something similar to your auto-complete component example, but instead of using a normal <input> inside the <template>, I could use the <a-input> (from the vue ant-design lib)? Or, using a css library (e.g. Tailwind) just style the <input> with tailwind classes inside the <template>? Would these use cases work?

Then, in terms of "Field Groups", I may not have explained my requirement correctly. I am not looking at repeatable groups of similar components, but rather having the ability to group certain components together in a "section" on the form. This is useful when you have forms with a large number of components, and having them in related sections, increases usability. What do you think?

justin-schroeder commented 4 years ago

@go4cas Yes, you can definitely implement what you're wanting to do with Custom Inputs. If you want to roll up many/most of ant-design's fields into a plugin I would be highly supportive :)

I definitely did misinterpret the field groups question. Vue Formulate does not aim to be a layout tool, but you do have the freedom to place FormulateInput elements at any depth under a FormulateForm element, so for accessibility reasons I would suggest using a fieldset + legend + some css. Here's an example I threw into codepen:

https://codepen.io/justin-schroeder/pen/ExjRdRZ

Let me know if that doesn't fully answer your question, or if you'd like any help building a published ant design plugin :)

gilesbutler commented 4 years ago

Hey @justin-schroeder,

Do you have a rough eta on the 2.3.0 release?

I've only just come across your library and it's fantastic! I'm a heavy Tailwind user though so I'd like better access to the elements via scoped slots so I can create markup similar to this...

<div>
  <label for="email" class="block text-sm font-medium leading-5 text-gray-700">Email</label>
  <div class="mt-1 relative rounded-md shadow-sm">
    <input id="email" class="form-input block w-full sm:text-sm sm:leading-5" placeholder="you@example.com" />
  </div>
</div>

I've tried targeting the elements via the data-classification attributes but unfortunately I need to add the form-input class to input as it's part of a Tailwind plugin and doesn't work with Tailwind's @apply method.

I'm happy to try this now with 2.2.8 if you think it's possible?

Thanks

justin-schroeder commented 4 years ago

Soon

@gilesbutler ha! I've been wondering when the first tailwind user would start asking questions :). Honestly, I wanted to get v2.3.0 out last weekend. Timelines are hard with software, but I've learned they're even harder with OSS. The good news is progress is still being made on this daily, and the scoped slot portion is already done and working on a feature branch (feature/scoped-slots) that will get merged into release/2.3.0. The first pass at docs are written as well https://vueformulatecom-git-release-230.braid.now.sh/guide/inputs/slots/.

That structure might might change a bit between now and release, but not much, and it gives you pretty much full scoped slot capability.

Future

Now, that said, I've done a lot of thinking about how this package should handle "you people 😉" who are super gung-ho about tailwind or other class-happy ui frameworks, and I think the ideal situation would be to have maps of classes that can be overwritten globally, by classification, or per input. The default would be something like:

function classMap(context) {
  return {
    "outer": `formulate-input`,
    "outer-wrapper": `formulate-input-wrapper`,
    "element-wrapper": `formulate-input-element formulate-input-element--${type}`
    "element": '',
    "help": "formulate-input-help",
    "errors": "formulate-input-errors",
    "error": "formulate-input-error"
  }
}

The idea is that classMap function would be defined globally for all inputs, and then each sub-type could override it, and then as granular as individual input fields could also override each:

<FormulateInput
  type="text"
  classmap-element="form-input block w-full sm:text-sm sm:leading-5"
/>
justin-schroeder commented 4 years ago

@gilesbutler I've created a new proposal for this: https://github.com/wearebraid/vue-formulate/issues/66

I'd love to get feedback from you or anyone else in the Tailwind community if this would be a good way to handle authoring these components for tailwind.

gilesbutler commented 4 years ago

Haha thanks for the awesome response @justin-schroeder!

I've been wondering when the first tailwind user would start asking questions :) I'm honoured to be the first ha! Also my people appreciate the future forethought for our beloved css utility frameworks 🤴

I read through your updated docs for scoped slots, makes perfect sense. I completely understand your goals and objectives for the library. Personally I think scoped slots are the way to go as an advanced use case as I feel like they're widely accepted as that in the Vue community as well.

I'll respond in #66 with proposal feedback. I'll also post it in the Tailwind Discord to see if we can drive some more feedback from the community 👍

gilesbutler commented 4 years ago

Congratulations on the 2.3.0 release @justin-schroeder - looking forward to trying it out over the next few days 🙌

justin-schroeder commented 4 years ago

Thanks @gilesbutler! Classmaps are not in this release yet, but scoped slots are 👍 hope those fulfill you needs short term!

gilesbutler commented 4 years ago

Awesome thanks @justin-schroeder.

I've just found an issue with field groups. Will run it by you here first but happy to open an issue too if need be.

Given the following code...

<FormulateInput type="group" name="landlord">
  <FormulateInput
    v-if="company.abn"
    type="text"
    name="abn"
    :value="company.abn"
  />

  <FormulateInput
    v-if="company.name"
    type="text"
    name="name"
    :value="company.name"
  />

  <FormulateInput
    v-if="company.organizationId"
    type="text"
    name="organizationId"
    :value="company.organizationId"
  />
</FormulateInput>

Am I doing something wrong or is this a bug?

justin-schroeder commented 4 years ago

@gilesbutler What you're doing is not at all outside the scope of groups, but I think there are a few things at play here:

  1. A group input always expects expects/emits an array of objects, even when in singular form.
  2. the value prop sounds like its doing what is intended — setting the initial value, but not updating after the fact. Use v-model to allow reactive manipulation.
  3. I'm not sure using your company object to v-if on your fields is really what you want. If you had maintained reactivity (ie using v-model instead of :value) then your fields would have disappeared when you deleted the last character in each field, is that intended?
  4. There are several ways to set initial field values, but you can do it automatically by either using value or v-model on the group input itself (much like a form). Just remember it expects an array!

Here's an example using your code and a v-model on the group:

https://codepen.io/justin-schroeder/pen/PoPxyLx

And if you really need the data without the array wrapper, I'd recommend just using a computed property.

Is this helpful?

gilesbutler commented 4 years ago

🤦‍♂️ Sorry @justin-schroeder I was using :value instead of v-model! You're right that is the correct behaviour. Adding v-model fixed things and yes very helpful thanks.

I realised the example I posted was actually using text inputs (for debugging) as opposed to hidden inputs which is what we're actually aiming for...

<FormulateInput
  v-model="company.abn"
  type="hidden"
  name="abn"
/>

The reason we're doing this is because we have a FormulateInput that is wrapped in a renderless component that passes down methods and data via scoped slots. It takes a company business number as input which we then search for using an async request in the renderless component (exposed via a scoped slot prop). Some of the resulting data needs to be displayed and other bits don't, but they do need to be submitted with the form hence they're added to the hidden fields.

We're doing it this way as the company business search needs to be reused across multiple components so renderless and scoped slots seemed the best way to go. Not sure if it's the best way to but seems to be working ok.

Thanks for your help 👍

hmaesta commented 4 years ago

unfortunately I need to add the form-input class to input as it's part of a Tailwind plugin and doesn't work with Tailwind's @apply method.

Oh man! I just lost ~3 hours trying to figure a solution for that. ☹️

It's my first time using Tailwind (coming from Bulma) and I am still confused about PostCSS, PurgeCSS, etc. Currently using Nuxt + SCSS + Tailwind module + Tailwind UI, so it's even worse to understand what is going on under the hood.

The official suggestion is to use @extend .form-input; but with my setup I have failed multiple times.

@gilesbutler If you are not using Scss maybe this can help: https://github.com/tailwindcss/custom-forms/issues/12#issuecomment-496001713

Did you achieve an workaround for now?

gilesbutler commented 4 years ago

Hey @hmaesta,

Thanks for that, yep you can use scoped slots in the 2.3.0 release.