7 April 2010

Выбор изображений просто и эффективно

CakePHP
Добрый день. Рассмотрим следующую функциональность приложения: добавление изображения к тексту. Изображения не встраиваются в сам текст (как, например, в википедии), а существуют отдельно. Текст может иметь одно или несколько изображений. Процедуру привязки хотелось сделать максимально дружелюбной по отношению к пользователю.
Выбор пал на представление данных с помощью двух областей: слева находятся все картинки, доступные для выбора, в правой — выбранные. Пользователь может выбирать изображения как с помощью мышки, так и с помощью дополнительных кнопок.
Пример работы плагина


Предпосылки


Далее в статье речь пойдет про фреймворк CakePHP. Я предполагаю, что читатель с ним знаком и такие слова как «контроллер», «поведение», «представление»(view) его не пугают. Знакомство с MeioUpload будет плюсом.

Полный исходный код плагина находится на GitHub'е. Здесь же я расскажу только об основных моментах и использовании.

Почему плагин?


Плагины в CakePHP позволяют легко добавлять функциональность к уже существующему коду. Плагин содержит в своих недрах все необходимое для своей работы: контроллеры, модели данных, правила CSS, файлы с функциями на javascript'e и т.д. Приложение может использовать плагин через наследование. Мой плагин состоит из контроллера ImageSelects, поведения ImageUpload, представления preview и дополнительных файлов CSS, javascript.

Реализацию плагина можно разбить на несколько подзадач:
  • сделать поддержку загружаемых файлов;
  • сделать панели для вывода изображений, обеспечить минимальную функциональность по выбору изображений.

Отдельным пунктом идет использование плагина. Идея в том, что контроллер, связанный с таблицей в базе данных, наследуется от плагина. Затем этот контроллер используется в представлении другого контроллера через AJAX.

Загрузка файлов


Для загрузки файлов я модифицировал поведение MeioUpload. Мое поведение ImageUpload унаследованно от MeioUpload и вносит в него некоторые изменения. MeioUpload хранит все загруженные файлы в одной папке. Я ввел параметр maxFiles, который определяет максимальное кол–во файлов в одной директории. Структура папок проста: <base_dir>/<i>, где base_dir это базовая директория, а i порядковый номер. Нумерация начинается с 1. В каждой папке с номером создается дополнительная папка для хранения миниатюр.

В отличии от MeioUpload, который использует оригинальное название файла, я генерирую уникальное имя файла
$new_filename = md5(uniqid(rand(), true));


Поскольку мое поведение наследуется от MeioUpload, то все настройки, применимые к MeioUpload, можно использовать и с ImageUpload. С помощью них можно задать максимальный размер загружаемого файла, допустимые типы файлов, размеры миниатюр и т.д.

В базу данных записывается информация о файле, а так же относительный путь к файлу, например, «uploads/1». В будущем планирую записывать в базу относительный путь до миниатюры изображения, который может выглядеть так «uploads/1/thumb320».

Выбор изображений


Для выбора изображений используется контроллер ImageSelects. Он состоит из одного метода — preview. В качестве параметров в этот метод можно передать массив идентификаторов выбранных изображений. Это понадобится, когда пользователь захочет изменить уже существующий набор привязанных картинок.

  1. function preview()
  2. {
  3. $this->model_instance->recursive = 0;
  4. // get the id of selected items:
  5. if (!empty($this->params['form']['selected']))
  6. {
  7. $selected = $this->params['form']['selected'];
  8. $selected_ids = array('id' => $selected);
  9. $conditions = array(«NOT» => $selected_ids);
  10. $this->set('allselected', $this->model_instance->find('all', array('conditions' => $selected_ids)));
  11. }
  12. else
  13. {
  14. $conditions = array();
  15. $this->set('allselected', array());
  16. }
  17. $data = $this->paginate($conditions);
  18. $this->set('allphotos', $data);
  19. $this->set('modelClass', $this->modelClass);
  20. // Point that we are using plugin and should use plugin's .ctp for rendering
  21. $this->plugin = 'image_select';
  22. $this->render(false, null, '/image_selects/preview');
  23. }

Функция возвращает два массива allphotos и allselected, а так же немного подправляет механизм вывода. Отображать страницу нужно через представление плагина.

Функциональность самого выбора реализована с помощью jQuery. Все доступные картинки находятся в контейнере с идентификатором leftimgs. Каждая картинка находится внутри контейнера, у которого есть атрибут selected. Допустимые значение атрибута: строка selected и пустая строка. С помощью jQuery можно легко найти все отмеченные изображения:
$("#leftimgs > div[selected='selected']")


Уже выбранные изображения находятся в контейнере с идентификатором rightimgs. Работать с ними можно аналогично картинкам из контейнера leftimgs.

Код отмечающий изображение по одиночному щелчку мыши:
  1. $(document).ready(function()
  2. {
  3. $("#leftimgs > div[selected]").click( function(event) { toggle_img(this); });
  4. }
  5. function toggle_img(div)
  6. {
  7. var isselected = div.getAttribute('selected') == 'selected';
  8. if (isselected)
  9. {
  10. div.style.backgroundColor = '';
  11. div.childNodes[4].childNodes[0].checked = false;
  12. div.setAttribute('selected', '');
  13. }
  14. else
  15. {
  16. div.style.backgroundColor = '#3961af';
  17. div.childNodes[4].childNodes[0].checked = true;
  18. div.setAttribute('selected', 'selected');
  19. }
  20. }

Как видно из кода, я устанавливая цвет фона в ручную. Правильным будет изменение CSS класса объекта.

Использование


Для использования всего этого нам нужно создать таблицу в базе данных и все необходимое для неё: контроллер, модель данных и представления. Путь таблица называется photos, соответствующий контроллер — PhotosController, модель — Photo. Нам так же понадобится еще одна таблица и все, что с ней связанно. Пусть ее название будет cities, контроллер CitiesController и т.д. как это принято в CakePHP.

Исходный код помещаем в папку «app/plugins/image_select».

Использование поведения


Поведение используется в модели данных контроллера PhotosController (файл photo.php):
  1. <?php
  2. class Photo extends AppModel {
  3. var $name = 'Photo';
  4. var $actsAs = array(
  5. 'ImageSelect.ImageSelect' => array(
  6. 'filename' => array(
  7. 'dir' => 'uploads',
  8. 'create_directory' => true,
  9. 'generateName' => true,
  10. 'maxFiles' => 5,
  11. 'useTable' => true,
  12. 'thumbsizes' => array(
  13. 'my320' => array('width' => 150, 'height' => 150),
  14. ),
  15. )
  16. )
  17. );
  18. }
  19. ?>

На странице добавления фотографии пишем:
<?php
echo $form->create('Photo', array('type' => 'file'));
...
echo $form->input('Photo.filename', array('type' => 'file'));
?>

Вот и все. Загрузка картинок готова!

Использование контроллера


Созданный контроллер PhotosController наследует ImageSelectsController:
App::import('Controller', 'ImageSelect.ImageSelects');
class PhotosController extends ImageSelectsController
{
}

Таким образом, PhotosController получает доступ к методу preview из ImageSelectsController.

Привязка выбора картинок к другому контроллеру


Вспоминаем, что мы еще создали CitiesControlles. На странице добавления нового города (app/views/cities/add.ctp) мы хотим добавить выбор картинок. В файле add.ctp нужно подключить CSS и Javascript от плагина:
<?php
echo $javascript->link('/image_select/js/image_upload.js', false); // load js in header
echo $html->css('/image_select/css/image_select.css', false);
?>

Панели и кнопки будут храниться в контейнере с идентификатором imageList:
<div id=«imageList»></div>

В этот контейнер с помощью AJAX загружаются данные из представление preview контроллера ImageSelectsController. За загрузку отвечает функция loadPiece, которая находится в файле «app/plugins/image_select/vendors/js/image_upload.js». Вот она:
  1. function loadPiece(href, divName, data)
  2. {
  3. $(divName).load(href, data, function()
  4. {
  5. var divPaginationLinks = divName+" #pagination a";
  6. $(divPaginationLinks).click(function()
  7. {
  8. var thisHref = $(this).attr(«href»);
  9. loadPiece(thisHref, divName, data);
  10. return false;
  11. });
  12. });
  13. }


Вернемся к файлу app/views/cities/add.ctp. Нам нужно добавить к нему еще вызов функции loadPiece:
<script type=«text/javascript»>
var selected = new Array();
//selected[0] = 2; // possible id for the selected images
//selected[1] = 38;
var data = new Object();
data.selected = selected;
var reload_url = "<?php echo $html->url(array('controller'=>'photos', 'action'=>'preview'));?>";
$(document).ready(function()
{
loadPiece(reload_url, "#imageList", data);
});
</script>


На этом установка заканчивается. Все готово! На плечах разработчика останется только получить идентификаторы выбранных изображений и сохранить их в базе данных. Получить список выбранных изображений можно с помощью jQuery.
Tags:cakephpразработкаплагиныphpjquerymultiselectimage selection
Hubs: CakePHP
-2
4.3k 5
Leave a comment