J'ai cette ressource appelée Distributeur
ID::make()->sortable(), Text::make('Name') ->creationRules('required'), BelongsTo::make('Region') ->creationRules('required') ->searchable(), BelongsTo::make('Country') ->creationRules('required') ->searchable(),
Tout est en place jusqu'à présent. Mais le modèle du Pays
doit dépendre du modèle de la Région
, donc lorsque je sélectionne une région, je souhaite afficher les options avec les pays liés à cette région.
Région et Country sont déjà liés dans leurs modèles basés sur des relations belongsToMany
.
Y a-t-il un moyen de faire en sorte que ces champs fonctionnent ensemble?
3 Réponses :
Je me rends compte que cette question a presque un an maintenant, mais je me suis dit que je répondrais comme 1. la question continue de générer du trafic et 2. nous avons récemment rencontré un problème identique et avons été déçus par le manque de disponibilité informations.
Autant que je sache, ce problème pourrait également être résolu avec des requêtes pertinentes, mais nous avons fini par ajouter un champ personnalisé pour diverses raisons. La documentation officielle pour les champs personnalisés est assez rare, mais devrait suffit pour commencer.
Notre champ personnalisé est resté assez simple du côté de Vue. La seule vraie logique que Vue gère est d'extraire les pays / états de notre API et de les remplir dans les listes déroulantes. Du côté PHP, nous avons fini par avoir besoin de surcharger deux fonctions dans le contrôleur de notre champ: fillAttributeFromRequest () et résoudre (). Voir ci-dessous:
CountryState.php:
CountryState::make('Country and State')->rules('required')
FormField.vue
<template> <span>{{ field.value }}</span> </template> <script> export default { props: ['resourceName', 'field',], } </script>
IndexField.vue
<template> <default-field :field="field" :errors="errors"> <template slot="field"> <select name="country" ref="menu" id="country" class="form-control form-select mb-3 w-full" v-model="selectedCountryId" @change="updateStateDropdown" > <option :key="country.id" :value="country.id" v-for="country in countries" > {{ country.name }} </option> </select> <select v-if="states.length > 0" name="state" ref="menu" id="state" class="form-control form-select mb-3 w-full" v-model="selectedStateId" > <option :value="state.id" :key="state" v-for="state in states"> {{ state.name }} </option> </select> </template> </default-field> </template> <script> import { FormField, HandlesValidationErrors } from "laravel-nova"; export default { mixins: [FormField, HandlesValidationErrors], props: { name: String }, data() { return { countries: [], states: [], allStates: [], selectedCountryId: null, selectedStateId: null }; }, created: function() { this.fetchCountriesWithStates(); }, methods: { updateStateDropdown() { this.states = this.allStates.filter( item => item.country_id === this.selectedCountryId ); this.selectedStateId = this.states.length > 0 ? this.states[0].id : null; }, async fetchCountriesWithStates() { const countryResponse = await Nova.request().get("/api/v1/countries"); const stateResponse = await Nova.request().get("/api/v1/states"); this.countries = countryResponse.data; this.allStates = stateResponse.data; this.updateStateDropdown(); }, fill(formData){ formData.append('country_id', this.selectedCountryId); formData.append('state_id', this.selectedStateId); }, }, }; </script>
Enfin, dans le tableau des champs de notre ressource Nova:
namespace Gamefor\CountryState; use Laravel\Nova\Fields\Field; class CountryState extends Field { public $component = 'country-state'; /** * Hydrate the given attribute on the model based on the incoming request. * * @param \Laravel\Nova\Http\Requests\NovaRequest $request * @param string $requestAttribute * @param object $model * @param string $attribute * @return void */ protected function fillAttributeFromRequest($request, $requestAttribute, $model, $attribute) { parent::fillAttributeFromRequest($request, $requestAttribute, $model, $attribute); if ($request->exists('state_id')) { $model->state_id = $request['state_id']; } if ($request->exists('country_id')) { $model->country_id = $request['country_id']; } } /** * Resolve the field's value for display. * * @param mixed $resource * @param string|null $attribute * @return void */ public function resolve($resource, $attribute = null) { // Model has both country_id and state_id foreign keys // In the model, we have // // public function country(){ // return $this->belongsTo('App\Country', 'country_id', 'id'); // } // // public function state(){ // return $this->belongsTo('App\State', 'state_id', 'id'); // } $this->value = $resource->country['name'] . ', ' . $resource->state['name']; } }
Ces exemples auraient certainement besoin d'être peaufinés avant d'être "prêts pour la production", mais j'espère qu'ils aideront tous ceux qui osent s'aventurer dans la nature trou de lapin qui est la personnalisation Nova.
La réponse d'Erics m'a beaucoup aidé. Merci!
Mais au lieu d'écrire mes propres fonctions de remplissage et de résolution dans le champ Nova personnalisé, je viens d'hériter du champ BelongsTo:
<template> <default-field :field="field" :errors="errors"> <template slot="field"> <select name="country" ref="menu" id="country" class="form-control form-select mb-3 w-full" v-model="selectedCountryId" @change="onCountryChange" > <option :key="country.id" :value="country.id" v-for="country in countries" > {{ country.name }} </option> </select> <select v-if="regions.length > 0" name="region" ref="menu" id="region" class="form-control form-select mb-3 w-full" v-model="selectedRegionId" > <option :value="region.id" :key="region" v-for="region in regions"> {{ region.name }} </option> </select> </template> </default-field> </template> <script> import { FormField, HandlesValidationErrors } from "laravel-nova"; export default { mixins: [FormField, HandlesValidationErrors], props: ['resourceName', 'field'], data() { return { countries: [], regions: [], selectedCountryId: null, selectedRegionId: null }; }, created: function() { this.fetchCountries(); }, methods: { async fetchCountries() { const countryResponse = await Nova.request().get("/api/destinations"); this.countries = countryResponse.data; // Add 'null' option to countries this.countries.unshift({ name: '-', id: null }); if (this.field.parent_id) { this.selectedCountryId = this.field.parent_id; this.selectedRegionId = this.field.belongsToId || null; } else { this.selectedCountryId = this.field.belongsToId || null; } this.updateRegionDropdown(); }, async updateRegionDropdown() { if (!this.selectedCountryId) { return; } // Get all regions of the selected country const regionResponse = await Nova.request().get("/api/destinations/" + this.selectedCountryId); this.regions = regionResponse.data; // Add 'null' option to regions this.regions.unshift({ name: '-', id: null }); }, onCountryChange() { // De-select current region and load all regions of new country this.selectedRegionId = null; this.updateRegionDropdown(); }, fill(formData) { if (this.selectedRegionId) { formData.append('destination', this.selectedRegionId); } else if (this.selectedCountryId) { formData.append('destination', this.selectedCountryId); } }, }, }; </script>
Les données supplémentaires dans la fonction jsonSerialize peut ensuite être utilisé, pour pré-remplir votre élément de sélection frontend:
<?php namespace Travelguide\DestinationSelect; use App\Models\Destination; use Laravel\Nova\Fields\Field; use Laravel\Nova\Http\Requests\NovaRequest; class DestinationSelect extends \Laravel\Nova\Fields\BelongsTo { /** * The field's component. * * @var string */ public $component = 'destination-select'; /** * Prepare the field for JSON serialization. * * @return array */ public function jsonSerialize() { $parentId = null; $parentName = null; if (isset($this->belongsToId)) { $destination = Destination::where('id', $this->belongsToId)->first(); if (isset($destination) && isset($destination->parent)) { $parentId = $destination->parent->id; $parentName = $destination->parent->name; } } return array_merge([ 'parent_id' => $parentId, 'parent_name' => $parentName, ], parent::jsonSerialize()); } }
Il existe un package laravel nova pour cela:
https://novapackages.com/packages/orlyapps/nova-belongsto-depend
Peut-être la façon la plus simple de le faire!