Skip to content
This repository has been archived by the owner on Jan 3, 2024. It is now read-only.

OcSelect: add support for showing default values #1634

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog/unreleased/change-ocselect-label
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Change: Label for OcSelect

We've added a configurable `<label>` for OcSelect accessible via the `label` property.
This shadows the `label` property of `vue-select`. Hence we introduced the `optionLabel`
property on OcSelect which maps to the `label` property of `vue-select`.

https://github.com/owncloud/owncloud-design-system/pull/1570/
https://github.com/owncloud/owncloud-design-system/issues/1503
8 changes: 8 additions & 0 deletions changelog/unreleased/enhancement-ocselect-default-value
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Enhancement: Add support for showing default values in OcSelect

We've added a `defaultValue` prop to OcSelect.

The value provided as `defaultValue` is selected when the actual `value` is `null`.
When the default value is shown the clear button is hidden because you can't clear the default.

https://github.com/owncloud/owncloud-design-system/pull/1634
232 changes: 204 additions & 28 deletions src/components/OcSelect.vue
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
<template>
<vue-select
ref="select"
:disabled="disabled"
:filter="filter"
class="oc-select"
v-bind="$attrs"
v-on="$listeners"
>
<template #search="{ attributes, events }">
<input class="vs__search" v-bind="attributes" @input="userInput" v-on="events" />
</template>
<template v-for="(index, name) in $scopedSlots" #[name]="data">
<slot v-if="name !== 'search'" :name="name" v-bind="data" />
</template>
<div slot="no-options" v-translate>No options available.</div>
<template #spinner="{ loading }">
<oc-spinner v-if="loading" />
</template>
</vue-select>
<div>
<label :for="id" v-text="label" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<label :for="id" v-text="label" />
<label :for="id" class="oc-label" v-text="label" />

Just checked how the update journey in web would look like and we seem to give all labels this class, which doesn't style anything initially but sometimes is used to target the label for styling

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense but possibly belongs into the original pr

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but that one is merged already (and I somehow ended here without understanding this is not the original ;D my bad, could you include it either way?

<vue-select
ref="select"
:clearable="showClearButton"
:disabled="disabled"
:filter="filter"
:input-id="id"
class="oc-select"
:value="displayValue"
@input="onInput"
@option:selecting="onOptionSelecting"
v-bind="additionalAttributes"
v-on="listeners"
>
<template #search="{ attributes, events }">
<input class="vs__search" v-bind="attributes" @input="userInput" v-on="events" />
</template>
<template v-for="(index, name) in $scopedSlots" #[name]="data">
<slot v-if="name !== 'search'" :name="name" v-bind="data" />
</template>
<div slot="no-options" v-translate>No options available.</div>
<template #spinner="{ loading }">
<oc-spinner v-if="loading" />
</template>
</vue-select>
</div>
</template>

<script>
import Fuse from "fuse.js"
import uniqueId from "../utils/uniqueId"
import VueSelect from "vue-select"
import "vue-select/dist/vue-select.css"

Expand All @@ -38,6 +47,42 @@ export default {
inheritAttrs: true,

props: {
/**
* The ID of the element.
*/
id: {
type: String,
required: false,
default: () => uniqueId("oc-select-"),
},
/**
* Whether or not the select component should have a dedicated button for clearing the selection.
*/
clearButtonEnabled: {
type: Boolean,
required: false,
default: true,
},
/**
* Value to preselect when no value is provided
* This does not set `value` automatically.
* The user needs to explicitly select the value to set it.
*/
defaultValue: {
required: false,
default: null,
},
/**
* Disable the select component
*/
disabled: {
type: Boolean,
required: false,
default: false,
},
/**
* Function to filter items when searching
*/
filter: {
type: Function,
required: false,
Expand All @@ -59,10 +104,54 @@ export default {
return search.length ? fuse.search(search).map(({ item }) => item) : fuse.list
},
},
disabled: {
type: Boolean,
required: false,
default: false,
/**
* Label of the select component
* ATTENTION: this shadows the vue-select prop `label`. If you need access to that use `optionLabel`.
*/
label: {
type: String,
default: null,
},
/**
* Key to use as label when option is an object
* NOTE: this maps to the vue-select prop `label`
*/
optionLabel: {
type: String,
default: null,
},
/**
* Current value of the select component.
*/
value: {
default: null,
},
},

computed: {
additionalAttributes() {
const additionalAttrs = {}
if (this.optionLabel) {
additionalAttrs["label"] = this.optionLabel
}
const attrs = { ...this.$attrs, ...additionalAttrs }
delete attrs.clearable
return attrs
},
displayValue() {
return this.value || this.defaultValue
},
listeners() {
const listeners = this.$listeners

// Delete listeners for events which are emitted via methods
delete listeners["input"]
delete listeners["change"]

return listeners
},
showClearButton() {
return this.clearButtonEnabled && this.value !== null
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.disabled needs to be taken into account.

},
},

Expand All @@ -83,6 +172,23 @@ export default {
*/
this.$emit("search:input", event.target.value)
},
onInput(value) {
// selecting a value is handled via onOptionSelecting, we just nead to handle clear events here
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*need

if (value !== null) {
return
}

this.onOptionSelecting(null)
},
/**
* We use the option:selecting event as it's the only one that fires when an option is reselected
* When we are showing a `defaultValue` we need to allow the user to explicitly set that value
* to override the default because a default might change one day
*/
onOptionSelecting(value) {
this.$emit("input", value)
this.$emit("change", value)
},
},
}
</script>
Expand Down Expand Up @@ -164,7 +270,7 @@ For detailed documentation (props, slots, events, etc.), please visit https://vu
```js
<template>
<div class="oc-docs-width-medium">
<oc-select v-model="selected" :options="['Bannana', 'Orange', 'Pear']" />
<oc-select label="Custom label" v-model="selected" :options="['Bannana', 'Orange', 'Pear']" />
</div>
</template>
<script>
Expand Down Expand Up @@ -195,6 +301,28 @@ prevent clearing the selected value by hitting `delete`.
</script>
```

### Providing a default value
We can provide a `defaultValue` to `oc-select` that is preselected when `value` is `null`.
When the `defaultValue` is displayed the clear button is automatically disabled.

```js
<template>
<div class="oc-docs-width-medium">
<oc-select v-model="selected" :options="['Apple', 'Bannana', 'Orange', 'Pear']" default-value="Orange" />
<p>
Selected: {{ selected === null ? "null" : selected }}
Copy link
Member Author

@dschmidt dschmidt Sep 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Selected: {{ selected === null ? "null" : selected }}
Selected: {{ selected || "null" }}

</p>
</div>
</template>
<script>
export default {
data: () => ({
selected: null
})
};
</script>
```

### Multiple selection
```js
<template>
Expand Down Expand Up @@ -236,16 +364,64 @@ To prevent user from filtering options by typing a serach query into the `oc-sel
</script>
```

### Use objects as options
If we want to select from a list of option objects, we can use `option-label` to select the key of the object to use as label.

```js
<template>
<div class="oc-docs-width-medium">
<oc-select
label="Custom Label"
option-label="title"
:options="options"
v-model="selected"
class="oc-mb-m"
/>
</div>
</template>
<script>
const options = [
{
title: 'Apple',
desc: 'An apple is an edible fruit produced by an apple tree (Malus domestica)'
},
{
title: 'Bannana',
desc: 'Bannana is a genus of goblin spiders (family Oonopidae) native to Xishuangbanna prefecture, Yunnan Province, China, where it lives in the leaf-litter of tropical rainforest'
},
{
title: 'Orange',
desc: 'The orange is the fruit of various citrus species in the family Rutaceae'
},
]

export default {
data: () => ({
selected: options[1],
options
})
}
</script>
```




### Using slots to display complex options
Sometimes we need to display more complex options. This can include e.g. an option with a title and a description. To
still display all those values exactly as we want to, we need to use scoped slot called `option`.
We can then retrieve all the values that we want to display from the slots parametres. It is important to specify the
`label` prop on the `oc-select` component which will specify which key should be used as the option label.
still display all those values exactly as we want to, we need to use scoped slots called `option` and `selected-option`.
We can then retrieve all the values that we want to display from the slot parameters.
It is important to specify the `option-label` prop on the `oc-select` to make filtering work.

```js
<template>
<div class="oc-docs-width-medium">
<oc-select v-model="selected" :options="options" label="title" class="oc-mb-m">
<oc-select
label="Custom Label"
:options="options"
v-model="selected"
class="oc-mb-m"
>
<template v-slot:option="{ title, desc }">
<span class="option">
<strong v-text="title" />
Expand Down