С появлением поддержки Vue.js в новом October, открылись новые возможности по созданию виджетов и прочих интересных вещей в админке нашей любимой CMS.
Так как документации по поводу использования Vue.js в настоящий момент еще нет и автор не претендует на звание Кунг-Vue мастера, то публикуем этот гайд исходя из опыта, полученного в процессе изучения исходников новой системы.
Задача
Задача простая, бесполезная, но сгодится для примера - это создание formWidget'а для получения адреса, с использованием Яндекс.Карт
Подготовка
Перед тем как начать - создадим тестовый плагин, назовем его Address.
Данный материал предполагает, что читатель знаком с базовыми командами по созданию плагинов, моделей и пр. в OctoberCMS.
// Создадим плагин для экспериментов, назовем его Sandbox
php artisan create:plugin sandbox.address
// Создадим модель и контроллер для плагина
php artisan create:model sandbox.address address
php artisan create:controller sandbox.address addresses
Далее ускоренный прогон - миграция, модель, контроллер.
// Миграция create_addresses_table.php
<?php namespace Sandbox\Address\Updates;
use Schema;
use October\Rain\Database\Schema\Blueprint;
use October\Rain\Database\Updates\Migration;
class CreateAddressesTable extends Migration
{
public function up()
{
Schema::create('sandbox_address_addresses', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('address');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('sandbox_address_addresses');
}
}
// =========================================
// Модель Address
<?php namespace Sandbox\Address\Models;
use Model;
/**
* Address Model
*/
class Address extends Model
{
/**
* @var string table associated with the model
*/
public $table = 'sandbox_address_addresses';
}
// =========================================
// Контроллер Addresses
<?php namespace Sandbox\Address\Controllers;
use BackendMenu;
use Backend\Behaviors\FormController;
use Backend\Behaviors\ListController;
use Backend\Classes\Controller;
/**
* Addresses Back-end Controller
*/
class Addresses extends Controller
{
public $implement = [
FormController::class,
ListController::class
];
public $formConfig = 'config_form.yaml';
public $listConfig = 'config_list.yaml';
public function __construct()
{
parent::__construct();
BackendMenu::setContext('Sandbox.Address', 'address', 'addresses');
}
}
Не забываем обновить файл updates/version.yaml
1.0.1:
- First version of address
- create_addresses_table.php
и выполнить:
php artisan plugin:refresh sandbox.address
Не стану описывать, что и как - мы только что создали простой скелет, для того, чтобы было куда "прицепить" наш formWidget.
Создание виджета
Пробежимся по созданию виджета - следуя документации. Назовем наш виджет MapPicker
php artisan create:formwidget sandbox.address MapPicker
В итоге у нас будет создан шаблон нашего виджета, с ним и будем работать.
Сразу зарегистрируем виджет (согласно документации) в файле Plugin.php нашего плагина SandBox:
public function registerFormWidgets()
{
return [
Sandbox\Address\FormWidgets\MapPicker::class => 'mapPicker',
];
}
И добавим поле address с типом mapPicker в yaml-файл sandbox/address/models/address/fields.yaml
# ===================================
# Form Field Definitions
# ===================================
fields:
id:
label: ID
disabled: true
address:
label: MapPicker
type: mapPicker
Теперь в браузере перейдем на страницу /backend/sandbox/address/addresses/create
и увидим стандартный сгенерированный шаблон виджета. С ним и будем работать.
Итак, что мы хотим здесь видеть:
- Поле ввода адреса
- Кнопку, которая откроет в модальном окне карту
- Собственно карту, при клике по которой, мы получим адрес и вставим его в поле ввода
Поле и кнопка
Поправим стандартный шаблон партиалки, добавив в него кнопку и немного css.
// sandbox/address/formwidgets/mappicker/partials/_mappicker.htm
<?php if ($this->previewMode): ?>
<div class="form-control">
<?= $value ?>
</div>
<?php else: ?>
<div id="widget-<?= $this->getId('input') ?>" class="mappicker-widget">
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control"
autocomplete="off"/>
<button type="button" class="btn btn-primary">
Карта
</button>
</div>
<?php endif ?>
/*
* sandbox/address/formwidgets/mappicker/assets/css/mappicker.css
*/
.mappicker-widget{
display: flex;
gap: 15px;
}
Получили примерно то, что надо =)
Далее используем стандартный компонент <backend-component-modal>, немного покопавшись в исходниках модуля editor и подглядев, как его вызывать.
Немного модифицируем нашу партиалку:
// sandbox/address/formwidgets/mappicker/partials/_mappicker.htm
<?php if ($this->previewMode): ?>
<div class="form-control">
<?= $value ?>
</div>
<?php else: ?>
<div id="widget-<?= $this->getId('input') ?>" class="mappicker-widget">
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control"
autocomplete="off"/>
<button type="button" class="btn btn-primary" @click="$refs.modal.show()">
Карта
</button>
<backend-component-modal
ref="modal"
:aria-labeled-by="modalTitleId"
:unique-key="uniqueKey"
size="large"
:draggable="false"
>
<template v-slot:content>
<div class="modal-header">
<h4 class="modal-title">Заголовок</h4>
</div>
<div class="modal-body">Текст</div>
</template>
</backend-component-modal>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
new Vue({
el: '#widget-<?= $this->getId('input') ?>',
data: function () {
return {
uniqueKey: $.oc.domIdManager.generate('modal-mappicker'),
modalTitleId: $.oc.domIdManager.generate('modal-mappicker-title'),
};
},
});
});
</script>
<?php endif ?>
Проверяем:
Ура, получилось! Дело за малым, наполнить модалку картой и функционалом.
Яндекс.Карты
Достаточно просто и красиво всё описано тут - https://vue-yandex-maps.github.io/
Немного модифицируем файл виджета, добавив в метод загрузки ассетов одну строку
// sandbox/address/formwidgets/MapPicker.php
public function loadAssets()
{
$this->addCss('css/mappicker.css', 'sandbox.address');
$this->addJs('js/mappicker.js', 'sandbox.address');
$this->addJs('https://unpkg.com/vue-yandex-maps', 'sandbox.address');
}
Ну вот и всё - согласно документации, мы подключили плагин карт напрямую, и теперь дело техники, подпилить нашу партиалку для работы с ним.
Долго описывать не будем - покажем результат модифицированной партиалки
// sandbox/address/formwidgets/mappicker/partials/_mappicker.htm
<?php if ($this->previewMode): ?>
<div class="form-control">
<?= $value ?>
</div>
<?php else: ?>
<div id="widget-<?= $this->getId('input') ?>" class="mappicker-widget">
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control"
v-model="address"
autocomplete="off"/>
<button type="button"
class="btn btn-primary"
@click="$refs.modal.show()"
>
Карта
</button>
<backend-component-modal
ref="modal"
:aria-labeled-by="modalTitleId"
:unique-key="uniqueKey"
size="large"
:draggable="false"
>
<template v-slot:content>
<div class="modal-header">
<h4 class="modal-title">
<span v-if="address">{{ address }}</span>
<span v-else>Кликните по карте, для получения адреса</span>
</h4>
</div>
<div class="modal-body">
<div id="yandex-map">
<yandex-map
:coords="coords"
:controls="controls"
zoom=10
@click="onMapClick"
>
<ymap-marker
:coords="coords"
marker-id="marker-<?= $this->getId('input') ?>"
:icon="markerIcon"
:key="address"
/>
</yandex-map>
</div>
<button type="button"
class="btn btn-primary pull-right m-b"
:disabled="!address"
@click="$refs.modal.hide()"
>
Выбрать этот адрес
</button>
</div>
</template>
</backend-component-modal>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
new Vue({
el: '#widget-<?= $this->getId('input') ?>',
data: function () {
return {
address: '',
uniqueKey: $.oc.domIdManager.generate('modal-mappicker'),
modalTitleId: $.oc.domIdManager.generate('modal-mappicker-title'),
coords: [55.753994, 37.622093],
controls: [],
markerIcon: {
content: '',
}
};
},
methods: {
onMapClick(event) {
this.coords = event.get('coords');
this.getAddress(this.coords);
},
getAddress(coords) {
vm = this;
vm.markerIcon.content = '';
ymaps.geocode(this.coords).then(function (res) {
let firstGeoObject = res.geoObjects.get(0);
vm.address = firstGeoObject.getAddressLine();
vm.markerIcon.content = vm.address;
});
}
},
async mounted() {
await window['vue-yandex-maps'].loadYmap({
apiKey: "YOUR-API-KEY",
lang: "ru_RU",
coordorder: "latlong",
version: "2.1"
});
},
});
});
</script>
<?php endif ?>
И немного докинем стилей в css
// sandbox/address/formwidgets/mappicker/assets/css/mappicker.css
/*
* This is a sample StyleSheet file used by MapPicker
*
* You can delete this file if you want
*/
.mappicker-widget {
display: flex;
gap: 15px;
}
#yandex-map {
height: 400px;
margin-bottom: 20px;
}
.ymap-container {
height: 100%;
}
Результат
Собственно, чтобы не придумывать велосипед - небольшое видео-демонстрация
Ссылки
Оригинал статьи
Исходный код примера