marshmallow / nova-multiselect-field
A multiple select field for Laravel Nova.
Installs: 6 991
Dependents: 3
Suggesters: 0
Security: 0
Stars: 0
Watchers: 2
Forks: 0
Open Issues: 0
Requires
- php: >=8.0
- laravel/nova: ^4.27|^5.0
- nova-kit/nova-packages-tool: ^1.12|^2.0
- outl1ne/nova-translations-loader: ^5.0
Requires (Dev)
- nova-kit/nova-devtool: ^1.2.1
README
This Laravel Nova package adds a multiselect to Nova's arsenal of fields.
Important
This package was originally forked from outl1ne/nova-multiselect-field. Since we were making many opinionated changes, we decided to continue development in our own version rather than submitting pull requests that might not benefit all users of the original package. You’re welcome to use this package, we’re actively maintaining it. If you encounter any issues, please don’t hesitate to reach out.
Requirements
php: >=8.1
laravel/nova: ^5.0
Features
- Multi- and singleselect with search
- Asynchronous search
- Reordering functionality with drag & drop
- Dependency on other Multiselect instances
- Distinct values between multiple multiselects
- Fully compatible with light and dark modes
Installation
Install the package in a Laravel Nova project via Composer:
composer require marshmallow/nova-multiselect-field
Usage
The field is used similarly to Nova's native Select field. The field type in the database should be text-based (ie string
, text
or varchar
), selected values are stored as a stringified JSON array.
use Marshmallow\MultiselectField\Multiselect; public function fields(Request $request) { return [ Multiselect::make('Football teams') ->options([ 'liverpool' => 'Liverpool FC', 'tottenham' => 'Tottenham Hotspur', ]) // Optional: ->placeholder('Choose football teams') // Placeholder text ->max(4) // Maximum number of items the user can choose ->saveAsJSON() // Saves value as JSON if the database column is of JSON type ->optionsLimit(5) // How many items to display at once ->reorderable() // Allows reordering functionality ->singleSelect() // If you want a searchable single select field ->distinct('football') // Disables values used by other multiselects in same distinct group ->taggable() // Possible to add values ("tags") on the fly // Async model querying Multiselect::make('Artists') ->asyncResource(Artist::class), // If you want a custom search, create your own endpoint: ->api('/api/multiselect/artists?something=false', Artist::class), ]; }
Option groups
Option groups are supported. Their syntax is the same as Laravel's option group syntax.
In this example (from Nova docs), all values are grouped by the group
key:
->options([ 'MS' => ['label' => 'Small', 'group' => 'Men Sizes'], 'MM' => ['label' => 'Medium', 'group' => 'Men Sizes'], 'WS' => ['label' => 'Small', 'group' => 'Women Sizes'], 'WM' => ['label' => 'Medium', 'group' => 'Women Sizes'], ])
Dependencies
You can make a Multiselect depend on another by using optionsDependOn
. The value from the optionsDependOn
Multiselect has to be the key to the options and the value must be a key-value dictionary of options as usual.
Usage:
Multiselect::make('Country', 'country') ->options([ 'IT' => 'Italy', 'SG' => 'Singapore', ]), Multiselect::make('Language', 'language') ->optionsDependOn('country', [ 'IT' => [ 'it' => 'Italian', ], 'SG' => [ 'en' => 'English', 'ms' => 'Malay', 'zh' => 'Chinese', ] ]), // Optionally define max number of values ->optionsDependOnMax([ 'IT' => 1, 'SG' => 3, ])
Belongs-To-Many
You can use this field for BelongsToMany
relationship selection.
// Add your BelongsToMany relationship to your model: public function categories() { return $this->belongsToMany(\App\Models\Category::class); } // Add the field to your Resource for asynchronous option querying: Multiselect::make('Categories', 'categories') ->belongsToMany(\App\Nova\Resources\Category::class), // Alternatively, you can set the second argument to 'false' to // query the options on page load and prevent the user from having // to first type in order to view the available options. Note: Not // recommended for unbounded relationship row counts. Multiselect::make('Categories', 'categories') ->belongsToMany(\App\Nova\Resources\Category::class, false),
Options
Possible options you can pass to the field using the option name as a function, ie ->placeholder('Choose peanuts')
.
Option | type | default | description |
---|---|---|---|
options |
Array|callable | [] | Options in an array as key-value pairs (['id' => 'value'] ). |
api($path, $resource) |
String, String (Resource) | null | URL that can be used to fetch options asynchronously. The search string is provided in the search query parameter. The API must return object containing key-value pairs (['id' => 'value'] ). |
asyncResource($resource) |
String (Resource) | null | Provide a Resource class to fetch the options asynchronously. |
placeholder |
String | Field name | The placeholder string for the input. |
max |
Number | Infinite | The maximum number of options a user can select. |
groupSelect |
Boolean | false | For use with option groups - allows the user to select whole groups at once |
singleSelect |
Boolean | false | Makes the field act as a single select which also means the saved value will not be an array. |
saveAsJSON |
Boolean | false | When you have a SQL JSON column, you can force the field to save the values as JSON. By default, values are saved as a stringified array. |
optionsLimit |
Number | 1000 | The maximum number of options displayed at once. Other options are still accessible through searching. |
nullable |
Boolean | false | If the field is nullable an empty select will result in null else an empty array ([] ) is stored. |
reorderable |
Boolean | false | Enables (or disables) the reordering functionality of the multiselect field. |
optionsDependOn |
String, Array | null | Determines which Multiselect this field depends on. |
belongsToMany |
String (Resource) | null | Allows the Multiselect to function as a BelongsToMany field. |
belongsTo |
String (Resource) | null | Allows the Multiselect to function as a BelongsTo field. |
taggable |
Boolean | false | Makes the Multiselet to support tags (dynamically entered values). |
clearOnSelect |
Boolean | false | Clears input after an option has been selected. |
distinct |
String | Field Attribute | Syncs options between multiple multiselects in the same group and disables the options that have already been used. |
indexDelimiter |
String | ', ' |
Sets delimiter used to join values on index view |
indexValueDisplayLimit |
Number | 9999 | Define how many values can be displayed at once on index view |
indexCharDisplayLimit |
Number | 40 | Set char limit for index display value |
Localization
The translations file can be published by using the following publish command:
php artisan vendor:publish --provider="Marshmallow\MultiselectField\FieldServiceProvider" --tag="translations"
You can then edit the strings to your liking.
Overwriting the detail field
You can overwrite the detail view value component to customize it as you see fit.
Create a new component for NovaMultiselectDetailFieldValue
and register it in your app.js
. The component receives two props: field
and values
. The values
prop is an array of selected labels.
// in NovaMultiselectDetailFieldValue.vue <template> <div class="relative rounded-lg bg-white shadow border border-60" v-if="values"> <div class="overflow-hidden rounded-b-lg rounded-t-lg"> <div class="border-b border-50 cursor-text font-mono text-sm py-2 px-4" v-for="(value, i) of values" :key="i"> {{ value }} </div> </div> </div> <div v-else>—</div> </template> <script> export default { props: ['field', 'values'], }; </script>
// in app.js import NovaMultiselectDetailFieldValue from './NovaMultiselectDetailFieldValue'; Nova.booting((Vue, router, store) => { Vue.component('nova-multiselect-detail-field-value', NovaMultiselectDetailFieldValue); });
Overwriting the form tag field
You can overwrite the tag template in the form-field component to customize it as you see fit.
Create a new component for FormFieldTag
and register it in your app.js
. The component receives two props: option
and remove
. The option
prop is an object with, for example, the label
.
// in FormFieldTag.vue <template> <span class="multiselect__tag"> <span>{{ option.label.trim() }}</span> <i class="multiselect__tag-icon" @click="remove(option)"></i> </span> </template> <script> export default { props: ['option', 'remove'], }; </script>
// in app.js import FormFieldTag from './FormFieldTag'; Nova.booting((Vue, router, store) => { Vue.component('form-multiselect-field-tag', FormFieldTag); });
Credits
License
This project is open-sourced software licensed under the MIT license.