Это старая версия документа.
В системе существует объект Entity. Необходимо добавить к нему возможность управлять фотографиями, которые группируются в альбомы.
Исходя из этих требований попробуем спроектировать соответствующие таблицы.
По базе данных все достаточно просто, единственное, что мы добавили - это поле createdAt timestamp default now()
на всякий случай.
smallImageId
и bigImageId
- ссылки на стандартную таблицу vfsFiles
.
Т.к. объектов будет не очень много, то все управление поместится в одну дополнительную вкладку.
Вот что в итоге у нас должно получиться (вид с отображением ошибок).
У альбома есть название и описание (необязательное поле).
У фотографии есть маленькая и большая картинка, из необязательных полей - название.
В случае случайного удаления должна быть ручная возможность восстановления данных.
В базе необходимые поля присутствуют.
Должна быть возможность сортировки фотографий и альбомов.
Порядок элементов меняется путем перетаскивания строчек.
Поддержка начальной массовой загрузки фотографий.
Сначала все файлы загружаем в VFS, а потом выбираем в фотогалерею.
Все первоначальные требования выполнены. Приступим к реализации задачи.
Для начала нам нужно создать объекты в MFD, после добавить в data.tmpl.php новый таб «Фотогалерея» и вынести его в другой шаблон photos.tmpl.php. Для реализации пользовательского интерфейса мы будем использовать JQuery Templates, поэтому не забудем добавить его в vt/elements/header.tmpl.php. Далее в photos.tmpl.php создадим шаблоны для jquery templates, весь javascript вынесем в отдельный файл в js://vt/entity-photos.js и подключим его в photos.tmpl.php. В entity-photos.js будет располагаться основной код управления интерфейсом. После перейдем к серверной части и допишем SaveEntityAction.php.
entityPhotos
и entityAlbums
в MFD с флагом WithoutTemplate (CanPages ставить не нужно).После того, как мы добавили листы к объектам, у нас будет рекурсивно работать метод IFactory::GetFromRequest(). Это нам пригодится в получении объектов с формы.
В шаблоне data.tmpl.php добавим новый таб «Фотографии».
<ul class="tabs-list"> <li><a href="#page-0">{lang:vt.common.commonInfo}</a></li> <li><a href="#page-1">Фотографии</a></li> </ul>
И подключим его после <div id="page-0" class="tab-page rows">...</div>
{increal:tmpl://vt/entities/photos.tmpl.php}
На этом работа с файлом закончена.
Данный шаблон будет работать с двумя переменными из Response: $data
и $albumErrors
.
В $data
содержится подготовленный массив с альбомами и фотографиями, который мы будем отдавать в качестве источника данных для JQuery Templates. Можно было бы конечно сделать сразу json_encode( $object→albums )
, но так мы получим избыток данных. В подготовленной переменной такого нет. Во время создания шаблона в var data = ..
лежал заранее известный массив, который потом стал основой для php-массива (с помощью заранее готового массива можно проще отлаживать шаблон).
В $albumErrors
находится массив с ошибками валидации альбомов и фотографий. Его нельзя положить в обычный массив $errors
, т.к. у него своя сложная структура.
<? /** @var array $data */ ?> <? /** @var array $albumErrors */ ?> <? JsHelper::PushFile( 'js://vt/entity-photos.js' ); ?> <div id="page-1" class="tab-page rows albums"> <div data-row="photos" style="display: none;"></div> <div class="row"> <ul class="actions"> <li class="edit"><a href="#" id="add-album">Добавить альбом</a></li> </ul> </div> </div> <script type="text/javascript"> var data = <?= ObjectHelper::ToJSON( $data ); ?>; <? if ( !empty( $albumErrors ) ) { ?> var albumErrors = <?= ObjectHelper::ToJSON( $albumErrors )?> <? } ?> </script>
Создадим шаблон albumTemplate
, который будет инициализироваться строчкой (entity-photos.js)
$("#albumTemplate").tmpl(data, { counter: albumCounter, photoCounter: photoCounter } ).appendTo(".albums");
data
- наш источник данных.albumCounter
- объект, с помощью которого мы будем держать текущий индекс альбома (photoCounter
- аналогично с фото). Данный объект передается в tmpl() в качестве параметра для рендеринга (доступен в $item в шаблоне)./** * Counters for album index */ var Counter = function() { this.index = -1; } Counter.prototype.nextIndex = function() { this.index ++; return this.index; } var albumCounter = new Counter(); var photoCounter = new Counter();
Опишем шаблон для создания строчки с альбомом.
<script id="albumTemplate" type="text/x-jquery-tmpl"> <div class="row album sort" id="album-${ $item.counter.nextIndex() }"> <input type="hidden" name="{$prefix}[albums][${ $item.counter.index }][entityAlbumId]" value="${id}" /> <br /> <table class="objects" style="width:auto;"> <tbody> <tr> <td class="handle">Альбом</td> <td class="left"><input type="text" name="{$prefix}[albums][${ $item.counter.index }][title]" value="${title}" /></td> <td colspan="2" class="left"><input type="text" name="{$prefix}[albums][${ $item.counter.index }][description]" value="${description}" size="60" style="width: auto;" /></td> <td width="10%"> <ul class="actions"> <li class="edit"><a href="#" class="add-photo" data-album-id="${ $item.counter.index }">Добавить</a></li> <li class="delete"><a href="#" class="delete-album" title="Удалить" data-album-id="${ $item.counter.index }">Удалить</a></li> </ul> </td> </tr> {{each(i, photo) photos}} {{tmpl( photo , { counter: $item.photoCounter, albumIndex: $item.counter.index }) "#photoTemplate"}} {{/each}} </tbody> </table> </div> </script><script>''</script>
${ $item.counter.nextIndex() }
- пробелы между {} нужны для того, чтобы шаблонизатор в Eaze не подумал, что это переменная {$item}
.</script><script>''</script>
- workaround для IDE.${id}
- получение свойства id из элемента массива data.{{each(i, photo) photos}}
- цикл в шаблоне для отображения фотографий. photos
- свойство из элемента массива data.{{tmpl( photo , { counter: $item.photoCounter, albumIndex: $item.counter.index }) "#photoTemplate"}}
- вызов шаблона photoTemplate, передача дополнительных параметров для рендеринга.Шаблон для отображения конкретной фотографии
<script id="photoTemplate" type="text/x-jquery-tmpl"> <tr class="sort"> <td><input type="hidden" name="{$prefix}[albums][${ $item.albumIndex }][photos][${ $item.counter.nextIndex() }][entityPhotoId]" value="${id}" /></td> <td><input type="text" name="{$prefix}[albums][${ $item.albumIndex }][photos][${ $item.counter.index }][title]" value="${title}" class="title-${ $item.albumIndex }-${num}" size="60" style="width: auto;" /></td> <td class="left"><input type="hidden" class="vfsFile" name="{$prefix}[albums][${ $item.albumIndex }][photos][${ $item.counter.index }][smallImageId]" rel="smallImageId-${ $item.albumIndex }-${num}" id="photo-small-${ $item.counter.index }" vfs:previewType="image" {{if smallImage}}value="${ smallImage.id}" vfs:src="{web:vfs://}${ smallImage.src}" vfs:name="${ smallImage.name}"{{/if}}/></td> <td class="left"><input type="hidden" class="vfsFile" name="{$prefix}[albums][${ $item.albumIndex }][photos][${ $item.counter.index }][bigImageId]" rel="bigImageId-${ $item.albumIndex }-${num}" id="photo-big-${ $item.counter.index }" vfs:previewType="image" {{if bigImage}}value="${ bigImage.id}" vfs:src="{web:vfs://}${ bigImage.src}" vfs:name="${ bigImage.name}"{{/if}}/></td> <td width="10%"> <ul class="actions"> <li class="delete"><a class="delete-photo" href="#" title="Удалить">Удалить</a></li> </ul> </td> </tr> </script><script>''</script>
${num}
- это дополнительное свойство (индекс массива), который используется для поиска элемента при отображении ошибок ($item.counter.nextIndex()
имеет сквозную нумерацию, а ${num}
начинается с 0 для каждого альбома).