Инструменты пользователя

Инструменты сайта


eaze:samples:добавление_фотогалереи_в_vt_к_объекту

Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Both sides previous revision Предыдущая версия
Следущая версия
Предыдущая версия
eaze:samples:добавление_фотогалереи_в_vt_к_объекту [2011/09/21 09:21]
sergeyfast
eaze:samples:добавление_фотогалереи_в_vt_к_объекту [2011/09/21 11:30] (текущий)
sergeyfast
Строка 147: Строка 147:
   * ''<​nowiki>​{{each(i,​ photo) photos}}</​nowiki>''​ - цикл в шаблоне для отображения фотографий. ''​photos''​ - свойство из элемента массива data.   * ''<​nowiki>​{{each(i,​ photo) photos}}</​nowiki>''​ - цикл в шаблоне для отображения фотографий. ''​photos''​ - свойство из элемента массива data.
   * ''<​nowiki>​{{tmpl( photo , { counter: $item.photoCounter,​ albumIndex: $item.counter.index }) "#​photoTemplate"​}}</​nowiki>''​ - вызов шаблона photoTemplate,​ передача дополнительных параметров для рендеринга.   * ''<​nowiki>​{{tmpl( photo , { counter: $item.photoCounter,​ albumIndex: $item.counter.index }) "#​photoTemplate"​}}</​nowiki>''​ - вызов шаблона photoTemplate,​ передача дополнительных параметров для рендеринга.
 +  * ''​{$prefix}''​ - стандартная переменная для data.tmpl.php (префикс объекта).
  
 Шаблон для отображения конкретной фотографии Шаблон для отображения конкретной фотографии
Строка 165: Строка 166:
 </​code>​ </​code>​
   * ''​${num}''​ - это дополнительное свойство (индекс массива),​ который используется для поиска элемента при отображении ошибок (''​$item.counter.nextIndex()''​ имеет сквозную нумерацию,​ а ''​${num}''​ начинается с 0 для каждого альбома).   * ''​${num}''​ - это дополнительное свойство (индекс массива),​ который используется для поиска элемента при отображении ошибок (''​$item.counter.nextIndex()''​ имеет сквозную нумерацию,​ а ''​${num}''​ начинается с 0 для каждого альбома).
 +
 +На этом работа с photos.tmpl.php завершена.
 +
 +===== entity-photos.js =====
 +//​Данный файл не претендует на идеальность :)//
 +
 +Начинается данный файл с описания счетчиков для альбомов и фотографий (см. код выше).
 +
 +После этого опишем поведение кнопок добавления и удаления альбомов и фотографий
 +<code javascript>​
 +    /**
 +     * Album Controls
 +     */
 +    $(function() {
 +        $("#​add-album"​).live('​click',​ function(e) {
 +            $("#​albumTemplate"​).tmpl(null,​ { counter: albumCounter } ).appendTo("​.albums"​);​
 +            rebindAlbumControls();​
 +            e.preventDefault();​
 +        });
 +
 +        $("​.add-photo"​).live('​click',​ function(e) {
 +            var id = $(this).data("​albumId"​);​
 +            $("#​photoTemplate"​).tmpl( null, { counter: photoCounter,​ albumIndex: id } ).appendTo("#​album-"​ + id + " .objects"​);​
 +            vfsSelector.Init();​
 +            e.preventDefault();​
 +        });
 +
 +        $("​.delete-photo"​).live('​click',​ function(e) {
 +            $(this).parent().parent().parent().parent().remove();​
 +            e.preventDefault();​
 +        });
 +
 +        $("​.delete-album"​).live('​click',​ function(e) {
 +            var id = $(this).data("​albumId"​);​
 +            $("#​album-"​ + id).remove();​
 +            e.preventDefault();​
 +        });
 +    });
 +</​code>​
 +
 +''​rebindAlbumControls()''​ необходимо для переинициализации vfs и сортировок
 +<code javascript>​
 +    function rebindAlbumControls() {
 +        vfsSelector.Init();​
 +
 +        $('​.objects'​).sortable({
 +            '​items':​ '​tr.sort'​
 +            , '​forceHelperSize':​ true
 +            , '​forcePlaceholderSize':​ true
 +            , '​handle':​ '​td:​first-child'​
 +        });
 +
 +        $('​.albums'​).sortable({
 +            '​items':​ '​.album'​
 +            , '​forceHelperSize':​ true
 +            , '​forcePlaceholderSize':​ true
 +            , '​handle':​ '​.handle'​
 +        });
 +    }
 +</​code>​
 +
 +При загрузки страницы запустим рендеринг
 +<code javascript>​
 +    $(function() {
 +        $("#​albumTemplate"​).tmpl(data,​ { counter: albumCounter,​ photoCounter:​ photoCounter } ).appendTo("​.albums"​);​
 +        rebindAlbumControls();​
 +        displayAlbumErrors();​
 +    });
 +</​code>​
 +
 +И отобразим ошибки
 +<code javascript>​
 +    function displayAlbumErrors() {
 +        if ( typeof( albumErrors ) == '​undefined'​ ) {
 +            return;
 +        }
 +
 +        $.each( albumErrors,​ function( albumIndex, album ) {
 +            $.each( album, function( albumField, albumData ) {
 +                if ( albumField == '​photos'​ ) {
 +                    $.each( albumData, function(photoIndex,​ photoField ){
 +                        $.each( photoField, function( fieldName, fieldError ) {
 +                            $( '​[rel='​ + fieldName + '​-'​ + albumIndex + '​-'​ + photoIndex + '​]'​ ).parent().append( ​ ' <span style="​color:​red;">​*</​span>'​ );
 +                        });
 +                    });
 +                } else {
 +                    $("​input[name='​entity[albums]["​ + albumIndex + "​]["​+ albumField + "​]'​]"​).parent().append( ' <span style="​color:​red;">​*</​span>'​ );
 +                }
 +            });
 +        });
 +    }
 +</​code>​
 +
 +Со клиентской частью мы закончили.
 +Данного кода достаточно для управления альбомами без серверной части, только они не будут сохранятся и валидироваться,​ но в ''​$object''​ данные попадут,​ т.к. мы используем полные пути к свойствам объекта (например ''​{$prefix}[albums][${ $item.albumIndex }][photos][${ $item.counter.index }][smallImageId]''​ ) - GetFromRequest соберет объект так, как нужно.
 +
 +Попробуем модифицировать SaveEntityAction таким образом,​ чтобы он сохранял альбомы в базу.
 +
 +===== SaveEntityAction.php =====
 +Прежде всего мы должны помнить о том, что albums у объекта Entity - лист. ''​EntityFactory::​GetById( $id, array( BaseFactory::​WithLists => true ) )''​ выберет только альбомы,​ без фотографий. Фотографии будем выбрать вручную.
 +Создадим метод ''​refillAlbumPhotos()'',​ который добавляет к альбомам фотографии.
 +<code php>
 +protected function refillAlbumPhotos( $object ) {
 +    $photos = EntityPhotoFactory::​Get( array( '​entityId'​ => $object->​entityId ) );
 +    $photos = ArrayHelper::​Collapse( $photos, '​entityAlbumId'​ );
 +
 +    if ( !empty( $photos ) ){
 +        foreach( $object->​albums as $album ) {
 +            if ( isset( $photos[$album->​entityAlbumId] ) ) {
 +                $album->​photos = $photos[$album->​entityAlbumId];​
 +            }
 +        }
 +    }
 +}
 +</​code>​
 +  * дополнительно в EntityPhotoFactory мы добавили поиск по entityId.
 +
 +Далее сделаем так, чтобы фотографии грузились только тогда, когда мы открыли объект для редактирования.
 +<code php>
 +/**
 + * Set Json Albums Data to Template
 + * @return void
 + */
 +protected function beforeSave() {
 +    if ( $this->​action != self::​UpdateAction && !empty( $this->​currentObject->​entityId ) ) {
 +        $this->​refillAlbumPhotos( $this->​currentObject );
 +    }
 +
 +    Response::​setParameter( '​data',​ EntityAlbumUtility::​PrepareAlbumsData( $this->​currentObject ) );
 +}
 +</​code>​
 +  * EntityAlbumUtility::​PrepareAlbumsData() - метод, который подготавливает переменную ''​data''​ для шаблона photos.tmpl.php.
 +
 +Теперь,​ если у объекта есть альбомы и фотографии,​ то мы уже увидим их в шаблоне :).
 +
 +Добавим валидацию альбомов и фотографий.
 +<code php>
 +protected function validate( $object ) {
 +    $errors = parent::​$factory->​Validate( $object );
 +
 +    $albumErrors = EntityAlbumUtility::​ValidateAlbums( $object->​albums );
 +    if ( !empty( $albumErrors ) ) {
 +        $errors['​fields'​]['​photos'​]['​format'​] = '​format';​
 +        Response::​setParameter( '​albumErrors',​ $albumErrors );
 +    }
 +
 +    return $errors;
 +}
 +</​code>​
 +  * fields => photos поможет нам подсветить таб "​Фотографии",​ если на нем были ошибки (''<​div data-row="​**photos**"​...''​ в photos.tmpl.php) и не дать сохранить ошибочный объект в базу.
 +
 +Теперь можно перейти к сохранению. Для этого сначала нужно заполнить $originalObject фотографиями (в оригинальном объекте они нужны для того, чтобы удалить те фотографии,​ которые мы удалили с формы).
 +<code php>
 +protected function beforeAction() {
 +    if ( !empty( $this->​originalObject ) ) {
 +       ​$this->​refillAlbumPhotos( $this->​originalObject );
 +   }
 +}
 +</​code>​
 +
 +add() и update() будут обернуты в транзакцию.
 +<code php>
 +/**
 + * Add Object
 + *
 + * @param Entity $object
 + * @return bool
 + */
 +protected function add( $object ) {
 +    ConnectionFactory::​BeginTransaction();​
 +
 +    $result = parent::​$factory->​Add( $object, array( BaseFactory::​WithReturningKeys => true ) );
 +    $result = $result && EntityAlbumUtility::​SaveAlbums( $object, $this->​originalObject );
 +
 +    ConnectionFactory::​CommitTransaction( $result );
 +
 +    return $result;
 +}
 +
 +
 +/**
 + * Update Object
 + *
 + * @param Entity $object
 + * @return bool
 + */
 +protected function update( $object ) {
 +    ConnectionFactory::​BeginTransaction();​
 +
 +    $result = parent::​$factory->​Update( $object );
 +    $result = $result && EntityAlbumUtility::​SaveAlbums( $object, $this->​originalObject );
 +
 +    ConnectionFactory::​CommitTransaction( $result );
 +    ​
 +    return $result;
 +}
 +</​code>​
 +
 +Последний штрих. При сохранении фоток мы не получаем их идентификаторы,​ из-за этого неправильно работает кнопка "​Применить"​ в режиме редактирование. Исправляется это путем переполучения фотографий только при успешном сохранении.
 +<code php>
 +protected function afterAction( $success ) {
 +    if ( $this->​redirect == '​view'​ && $success ) {
 +        $this->​refillAlbumPhotos( $this->​currentObject );
 +        Response::​setParameter( '​data',​ EntityAlbumUtility::​PrepareAlbumsData( $this->​currentObject ) );
 +    }
 +}
 +</​code>​
 +
 +Не забудьте посмотреть код [[eaze:​samples:​добавление_фотогалереи_в_vt_к_объекту_EntityAlbumUtility.php|EntityAlbumUtility.php]].
 +
 +
 +====== Итог ======
 +Поставленной цели мы добились. Можно было бы конечно сделать массовый загрузчик на flash и потом и мышкой раскидать фотографии по альбомам,​ но для этого нужно написать ещё больше javascript'​а.
 +
 +Осталось рассмотреть плюсы и минусы подхода.
 +
 +===== Плюсы =====
 +  * Не нужно дополнительно обрабатывать получение связанных объектов из формы на PHP (albums, photos).
 +  * Не нужно дублировать шаблон отображения album и photo сначала в PHP, потом на JS. Всего используется один шаблон.
 +  * Минимальное количество кода в SaveEntityAction (в основном - только получение листов второго и последующих уровней).
 +
 +===== Минусы =====
 +  * GetFromRequest на втором уровне получает каждый объект через GetById (соответственно сколько файлов - столько запросов при сохранении). //​можно исправить,​ но сложновато//​
 +  * Если сохранить страницу при выключенном JS - то все фотографии удалятся (потому что не пришли с формы). //​можно исправить через дополнительную переменную,​ которая выставляется через JS//
 +  * При сохранении страницы для не измененных данных каждый раз выполняется UPDATE. //​можно исправить путем добавления проверки на эквивалентность объектов//​
 +
 +//Ещё раз повторюсь,​ что данный пример не претендует на идеальность и универсальность. Он пытается рассказать,​ как работать в VT со сложными объектами. Пожелания и дополнения приветствуются. ;)//
 +
 +
 +
 +
 +
 +
 +
  
eaze/samples/добавление_фотогалереи_в_vt_к_объекту.1316582490.txt.gz · Последние изменения: 2011/09/21 09:21 — sergeyfast