-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved API for UI components, reducing boilerplate for forwarding attributes and events #5983
Comments
Hmm, I don't know about that but for components like that I think you could use JSX or |
https://github.com/doximity/vue-genome For us this would be a great. We wrap all inputs in a label for styling and ux purposes. I agree that we could drop down to jsx instead but the templates are so much easier for everyone to follow. |
@Austio, unfortunately, that's the payback for templates... wait... maybe we could think of a way to |
I like this feature personally. But it seems to break the consistency of the v-bind behavior, like sometimes I still need to bind So how about use a pair of directives as getter and setter like: Inside the component, define a <input v-expose="foo" /> When using it: <the-component v-define:foo="{propA: '', propB: ''}"></the-component>
<!-- or maybe use v-bind for it directly -->
<the-component :foo="{propA: '', propB: ''}"></the-component> |
@jkzing, that looks awesome, but again, that looks like a basic spread and with potential problems like how would you define |
you can't just |
The thing we are talking about it is to bring something like spread for template users
|
@jkzing, in the link from the description there's a lot of |
@nickmessing Um...As for |
@jkzing, that was the whole concept of |
@nickmessing , Can't be sure about the original proposal, but I don't think an event listener should be considered as |
@jkzing, probably not, but considering the common example of |
I personally use So this |
@cristijora We're using |
Is this really feasible with a directive? |
@posva, I don't think this will work as a directive per se, but that can be a part of vue template engine that does something like spread internally + some event propagation |
@posva Not a user-built directive I don't think, so I might be using the wrong language. What I mean is just a "special attribute". |
@chrisvfritz do you have any thoughts on an API for how it would be used (specifying what to expose and how to add to the child) I could see this being similar in use to provide/inject concept. |
@Austio I might not be understanding the question, but I provide some thoughts on the API in the original post. |
Hey Chris, meant additional thoughts on using similar to provide inject where you declare what is to be exposable in the parent and then using that in the child. |
Ah, I see. I'm not sure there's a need for that. Information can already be passed via props and slots - and even private properties on the parent can be accessed with @Austio Is there a particular use case you're thinking of? |
@chrisvfritz how would that work in render functions? I think maybe it would be better to:
That would have the added benefit of working practically identical in JSX/render functions |
@LinusBorg I like the way you think. 😄 Your way is much more intuitive. As a sidenote, I think with this API in place, the next major version of Vue could even remove attribute auto-inheritance altogether, so that cross-component communication could remain explicit on both sides. |
v-expose
directive to define the element(s) that accept attributes
It would be possible to depreciate or remove this behaviour, yes. If that's worth the possibly required changes on many components in libs etc. is to be decided and should be discussed with the community, especially UI collection authors. A about the prob posed feature: this information is already available in functional components via |
Yes, exactly. The main purpose I have in mind is to make work simpler for UI component authors (both 3rd-party and internal). There are currently a lot of cases where something like this is necessary: <input
v-bind:id="id"
v-bind:accept="accept"
v-bind:alt="alt"
v-bind:autocomplete="autocomplete"
v-bind:autofocus="autofocus"
v-bind:checked="checked"
v-bind:dirname="dirname"
v-bind:disabled="disabled"
v-bind:form="form"
v-bind:formaction="formaction"
v-bind:formenctype="formenctype"
v-bind:formmethod="formmethod"
v-bind:formnovalidate="formnovalidate"
v-bind:formtarget="formtarget"
v-bind:list="list"
v-bind:max="max"
v-bind:maxlength="maxlength"
v-bind:min="min"
v-bind:multiple="multiple"
v-bind:name="name"
v-bind:pattern="pattern"
v-bind:placeholder="placeholder"
v-bind:readonly="readonly"
v-bind:required="required"
v-bind:src="src"
v-bind:step="step"
v-bind:type="type"
v-bind:value="value"
v-on:keydown="emitKeyDown"
v-on:keypress="emitKeyPress"
v-on:keyup="emitKeyUp"
v-on:mouseenter="emitMouseEnter"
v-on:mouseover="emitMouseOver"
v-on:mousemove="emitMouseMove"
v-on:mousedown="emitMouseDown"
v-on:mouseup="emitMouseUp"
v-on:click="emitClick"
v-on:dblclick="emitDoubleClick"
v-on:wheel="emitWheel"
v-on:mouseleave="emitMouseLeave"
v-on:mouseout="emitMouseOut"
v-on:pointerlockchange="emitPointerLockChange"
v-on:pointerlockerror="emitPointerLockError"
v-on:blur="emitBlur"
v-on:change="emitChange($event.target.value)"
v-on:contextmenu="emitContextMenu"
v-on:focus="emitFocus"
v-on:input="emitInput($event.target.value)"
v-on:invalid="emitInvalid"
v-on:reset="emitReset"
v-on:search="emitSearch"
v-on:select="emitSelect"
v-on:submit="emitSubmit"
> A new <input
v-bind="$attributes"
v-on:keydown="emitKeyDown"
v-on:keypress="emitKeyPress"
v-on:keyup="emitKeyUp"
v-on:mouseenter="emitMouseEnter"
v-on:mouseover="emitMouseOver"
v-on:mousemove="emitMouseMove"
v-on:mousedown="emitMouseDown"
v-on:mouseup="emitMouseUp"
v-on:click="emitClick"
v-on:dblclick="emitDoubleClick"
v-on:wheel="emitWheel"
v-on:mouseleave="emitMouseLeave"
v-on:mouseout="emitMouseOut"
v-on:pointerlockchange="emitPointerLockChange"
v-on:pointerlockerror="emitPointerLockError"
v-on:blur="emitBlur"
v-on:change="emitChange($event.target.value)"
v-on:contextmenu="emitContextMenu"
v-on:focus="emitFocus"
v-on:input="emitInput($event.target.value)"
v-on:invalid="emitInvalid"
v-on:reset="emitReset"
v-on:search="emitSearch"
v-on:select="emitSelect"
v-on:submit="emitSubmit"
> Though then I suppose it'd still be nice to have some way of also exposing events. Maybe an empty <input
v-bind="$attributes"
v-on
> Or if there do end up being multiple concerns we want to bundle, we might be back to something like <input v-expose> This has turned into a broader discussion of how to simplify the building of UI components, rather than a specific feature request, so I'll relabel this issue. 🙂 |
I'm late to this topic, but I have some thoughts as well.
|
Coming from an argument about API surface in another issue, I must repeat that I'm not a fan of the One thing I respect about React folks is their commitment to a slim API and using the language's features as much as possible. In that spirit, re-using a pattern we already have for props for attributes seems much better than introducing another abstraction. <my-input
type="file"
mode="dropdown"
> <template>
<div>
<input v-bind="$attributes">
<dropdown v-bind="{ ...$props, $attributes.type }"/>
</div>
</template |
Ahh, I see what you're saying now. And I like it! Is this currently available? |
Re-reading your comments. I'm tracking now 👍 |
Yes, Also, we would need an option to turn of the current default behaviour of applying attributes to the root element, like this: <template>
<div>
<input v-bind="$attributes">
</div>
</template>
<script>
export default {
applyComponentAttrsToRoot: false, // defaults to true, name is tbd, obviously
data() {
},
// ... options and stuff
}
</script>
This could then become a default setting in Vue 3.0 if we then decide to do this resulting in a breaking change. |
@LinusBorg What are your thoughts on dealing with the events side of things? To follow the same strategy, I supposed we could also add a {
input: function () { /* ... */ },
focus: function () { /* ... */ },
// ...
} Then perhaps <input v-bind="$attributes" v-on="$listeners"> One issue that I foresee is with |
Also, regarding the It might also be nice to be able to disable this for the entire application via |
I recently had a similar idea about
So we would end up with There's also #5578 asking for But I'm unsure about the .native modifier. To make this work with both component events and native listeners, the API would end up much more complicated, and the use is questionable, since a native event listener applied to the root element would still catch the desired event bubbling up, so it might mnot be necessary to assign it to a specific element in the template. |
In general, I'd say for low-level component libs, render functions should be preferred when templates are getting awkward to work with. But I agree that the following would be valuable:
|
ia kie like vue ,simple tools |
Just want to say the PR for this in v2.4 is excellent! 👍 |
From releases note
Seems nice but that's not quite true, since these kind of components are designed to work with v-model and as far as I know v-model on wrapping component is not working out the box. Is there any example of how to forward v-model from a wrapping component onto a input for example ? Maybe with functional component working along template it would be more straightforward |
Why would you think that? v-model is just syntax sugar for a prop and an event listener, both will be in $attr/$props and thus can be easily passed on. I suppose the only thing that would require knowledge of the child options would be if the child changed the But it would be possible to read those, depending on the circumstances. |
Sure it is a syntaxic sugar, but I mean it could be confusing to read
when actually based on the example https://github.com/almino/semantic-ui-vue2/blob/master/src/elements/Input.vue, you can't just pass listeners directly, to achieve the same control. ( eg: you have to use v-on:input="emitInput($event.target.value)" ) Anyways, this PR is valuable, good job! |
@AlexandreBonaventure This is because I think you're correct that it would be desirable for Maybe a non-enumerable property could be added to
One downside is now we're throwing away data. If someone wants to access a @yyx990803 @LinusBorg What are your thoughts on feasibility? Any edge cases I'm missing? |
Oh I see, you are referring to v-model on rral. Form elements, I was thinking about it on components. You cant/shouldnt use that on props anyway, with or without this PR. And in advanced apps, it's rather uncommon to use it (though achievable). |
@LinusBorg Just want to make sure we're on the same page. Given a <div>
<input v-bind="$attrs" v-on="$listeners">
<div> You wouldn't expect the code below to work? <CustomInput v-model="myValue" /> |
I would expect it to work - but the way I understood alexandre, he was referring to v-model on the element, not the component - which eventually only works with mutating local state. |
I was trying to say what @chrisvfritz explained in his latter post. (English not my native language sorry :)) |
@LinusBorg the problem with doing this in the latest release is that it's still considered an anti-pattern and triggers a warning about mutating the state. It's extremely useful to have the above working where the value property is something other than a string. Take for example a combo component where I am trying to use enums imported from my own library as values for select options: <template>
<select class="combo" v-model="value" v-on="$listeners">
<option v-for="(item, key) in items" :value="item">{{key}}</option>
</select>
</template>
<script>
export default {
props: {
items: {
type: Object,
required: true
},
value: {}
}
}
</script> This is an example of one of the lists I use for items in the parent: execList: {
"None": ACT_EXEC_TYPES.NONE,
"Function": ACT_EXEC_TYPES.FUNCTION,
"Code": ACT_EXEC_TYPES.CODE
} And how I use the combo component: <combo :items="execList" v-model="selectedAction.execType"/> I've been trying to make this work for 2 days now and still getting really frustrated. The problem is that $event.target.value is always a string and not evaluated like it should be in |
@LinusBorg @AlexandreBonaventure @RobertBColton I just opened an issue where we can focus future discussion of this problem. |
What problem does this feature solve?
There are many cases where attributes passed to a Vue component should not be added to the root element, but rather a sub-element. For example, in this UI component, an incredible amount of props must be used to ensure that attributes are added to the
input
element, instead of the wrapperdiv
.Additionally, it's often desirable to expose all event listeners on a form element to the parent, which also requires a lot of boilerplate currently if the element is not the root (in which case, the
.native
modifier can solve the problem).What does the proposed API look like?
EDIT: Start here to catch up on the discussion.
Currently by default, the "exposed" element (the one that arbitrary attributes can be added to) is always the root element. A new directive could be used to define a different exposed element. Some ideas for the name of the directive:
v-expose
(probably my personal favorite)v-expose-attrs
(probably clearer, but lengthier)v-main
v-primary
If
v-expose
is added to an element, it will accept attributes passed to its component - and these attributes will no longer be passed to the root element.Other features that may be nice:
v-expose
could accept a string or array of strings (e.g.v-expose="class"
orv-expose="['class', 'type', 'placeholder']"
). In this case, these attributes would be added to the element (again, instead of to the root element), but all other attributes would be added to the root element or to the element(s) with a valuelessv-expose
.The text was updated successfully, but these errors were encountered: