Laravel Nova-поле выпадающего списка загрузки, основанное на связи с другим выпадающим списком
У меня есть этот ресурс под названием Distributor
ID::make()->sortable(),
Text::make('Name')
->creationRules('required'),
BelongsTo::make('Region')
->creationRules('required')
->searchable(),
BelongsTo::make('Country')
->creationRules('required')
->searchable(),
До сих пор все на месте. Но модель Country
должна зависеть от модели Region
, поэтому, когда я выбираю регион, я хочу отобразить параметры со странами, связанными с этим регионом.
Регион и страна уже связаны в своих моделях, основанных на отношениях belongsToMany
.
Есть ли способ сделать так, чтобы поля работали вместе?
Ответы - Laravel Nova-поле выпадающего списка загрузки, основанное на связи с другим выпадающим списком / Laravel Nova - Load dropdown field based on relationship with of another dropdown

16.01.2020 03:13:51
Я понимаю, что этому вопросу уже почти год, но я решил, что отвечу как 1. вопрос все еще в том, чтобы получить трафик и 2. недавно мы столкнулись с аналогичной проблемой и были разочарованы отсутствием доступной информации.
Насколько я знаю, эта проблема также может быть решена с помощью релятивных запросов, но в конечном итоге мы добавили пользовательское поле по разным причинам. Официальная документация для пользовательских полей довольно скудна, но ее должно быть достаточно, чтобы начать работу.
Наше пользовательское поле оставалось довольно простым на стороне Vue. Единственная реальная логика, которую обрабатывает Vue, заключается в том, чтобы вытащить страны/государства из нашего API и заполнить их в выпадающие списки. На стороне PHP нам пришлось переопределить две функции в контроллере нашего поля: fillAttributeFromRequest () и resolve (). Смотреть ниже:
CountryState.РНР :
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'];
}
}
FormField.Ву
<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>
Индексное поле.Ву
<template>
<span>{{ field.value }}</span>
</template>
<script>
export default {
props: ['resourceName', 'field',],
}
</script>
Наконец, в массиве полей нашего ресурса Nova:
CountryState::make('Country and State')->rules('required')
Эти образцы определенно нуждаются в настройке, прежде чем они будут "готовы к производству", но, надеюсь, они помогут любому, кто осмелится рискнуть в дикую кроличью нору, которая является настройкой Nova.

04.02.2020 07:57:20
Ответ Эрика очень помог мне. Спасибо!
Но вместо того, чтобы писать свои собственные функции заполнения и разрешения в пользовательском поле Nova, я просто унаследовал поле BelongsTo:
<?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());
}
}
Дополнительные данные в функцию jsonSerialize затем может использоваться, чтобы предварительно заполнить свой интерфейс выбора элемента:
<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>