Как стать автором
Обновить

Элемент Zend_Form для выбора изображения

Время на прочтение5 мин
Количество просмотров2.1K
Здравствуйте. Без долгих вступлений, хочу показать как выглядит элемент, о создании которого я собираюсь рассказать:

Элемент Zend_Form RadioImage

Я решил назвать это RadioImage.

Недавно понадобилось предоставить пользователю возможность загрузки и выбора иконки для статусов продуктов в интернет-магазине. Раньше задачу выбора маленьких иконок я решал с помощью различных jQuery плагинов (эта картинка не моя):

Елемент Select с иконками

(и то, кстати очень ленился и не писал отдельный декоратор/элемент, а просто обходился JavaScript'ом).

Но в этот раз иконки могут быть разного размера и вообще мельчить не хочется, а если сделать select с большими картинками, то он будет выбиваться из общего вида формы.

Вспомнил, что я уже делал подобное и полез смотреть:
Чекбоксы с картинками

В принципе нормальный вариант, но я думаю, нормальный для нас — разработчиков. Для некоторых может быть непонятно, зачем тут нужен такой посредник как checkbox. Плюс ко всему, это опять же не элемент Zend_Form типа MultiImageCheckbox, а просто сгенерированный html прямо в скрипте вида — не хорошо. Если пригодилось во второй раз, нужно сделать по человечески.

На этот раз мне нужно предоставить выбор только одной иконки, поэтому наш элемент не позволит множественный выбор (но это не сложно поправить, создав другой элемент на основе этого, CheckImage например).

Еще есть одно требование — jQuery, я привык использовать хелпер вида ZendX_JQuery, но вы можете подключать файл с js кодом используя Zend_VIew_Helper_HeadScript.

Поехали, код с комментариями. Начну задом наперед — с формы:

application/forms/ProductStatus.php
<?php
class Form_ProductStatus extends Zend_Form
{
    public function init()
    {
        $this->setMethod('post');
        $this->setName('statusform');
        $this->setAttrib('enctype', 'multipart/form-data');
        
	$this->addElement('text', 'prodstatus_name', array(
	    'required'    => true,
	    'label'       => 'Status Name',
	    'filters'     => array('StringTrim')
	));
             
        // Инициализируем наш новый элемент
        $img = new App_Form_Element_RadioImage('prodstatus_icon', array(
            'label' => 'Select Icon',
            // HTML аттрибуты вроде этого будут применены к каждой картинке <img />
            'width' => '48'
        ));

        // BaseUrl думаю все знают он в основном для того,
          // если ваше приложение лежит НЕ в корне веб-сервера
        $bu = $this->getView()->baseUrl();

        // для простоты убрал сканирование директории/запрос к БД, 
          // покажу сразу каким должен быть массив
        $icons = array(
            "black_new.png"    => $bu.'/icons/black_new.png',
            "black_sale.png"   => $bu.'/icons/black_sale.png',
            "blue_new.png"     => $bu.'/icons/blue_new.png',
            "label_sale.png"   => $bu.'/icons/label_sale.png',
            "new_blue.png"     => $bu.'/icons/new_blue.png',
            "new_red.png"      => $bu.'/icons/new_red.png',
            "sale_blue.png"    => $bu.'/icons/sale_blue.png',
            "sale_green.png"   => $bu.'/icons/sale_green.png',
            "sale_yellow.png"  => $bu.'/icons/sale_yellow.png',
            "sticker_blue_sale.png" => $bu.'/icons/sticker_blue_sale.png'
        );

        // тут $key - значение элемента при отправке формы 
        // т. е. В контроллер придет [«prodstatus_icon»] => $key (если изображение выбрано)
        // $val это путь к картинке для тега img т.е. <img src="$val"/>
        foreach ($icons as $key => $val) {
            // и просто добавляем каждую картинку (так же как опции в элемент select)
              // можно конечно добавить используя addMultiOptions() без цикла 
              // но для наглядности я сделал цикл
            $img->addMultiOption($key, $val);
        }
        $this->addElement($img);

        $this->addElement('submit', 'Save');
    }
}


Может на первый взгляд выглядит как будто много кода, в живом проекте у меня это занимает несколько строк:
    $img = new App_Form_Element_SelectImage('prodstatus_icon', array(
        'label' => 'Select Icon',
        'width' => '48'
    ));
    $icons = App_Tool::scandir(PUBLIC_PATH.'/modules/products/icons', 'png');
    foreach ($icons as $icon) {
        $img->addMultiOption($icon, $this->getView()->baseUrl().'/modules/products/icons/'.$icon);
    }
    $this->addElement($img);


По-моему инициализация элемента достаточно проста — самое то, для повторного использования. Вот как это реализовано в библиотеке:

App/Form/Element/RadioImage.php
<?php
require_once 'Zend/Form/Element/Multi.php';

/**
 * RadioImage form element
 *
 * @category   App
 * @package    App_Form
 * @subpackage Element
 */
class App_Form_Element_RadioImage extends Zend_Form_Element_Multi
{
    /**
     * @var string
     */
    public $helper = 'FormRadioImage';
}


По аналогии с многими встроенными элементами Zend_Form, наш элемент является только интерфейсом к хелперу FormRadioImage, а вся логика в нем:

App/View/Helper/FormRadioImage.php
<?php
require_once 'Zend/View/Helper/FormElement.php';

/**
 * @category   App
 * @package    App_View
 * @subpackage Helper
 * @uses ZendX_Jquery
 */
class App_View_Helper_FormRadioImage extends Zend_View_Helper_FormElement
{
    /**
     * @param string|array $name Название элемента для параметра "name" тэга <input />
     * @param mixed $value Выбраное значение  по-умолчанию что бы 
     *                  пометить выбранное изображение.
     * @param array|string $attribs Html атрибуты для всех картинок.
     * @param array $options массив содержащий значение и путь для каждой картинки.
     * @return string конечный html
     */
    public function formRadioImage($name, $value = null, $attribs = null, $options = null)
    {

        $info = $this->_getInfo($name, $value, $attribs, $options);
        extract($info); // name, value, attribs, options

        // Убедимся что у нас именно массив (с путями и значениями для картинок)
        $options = (array) $options;

        $xhtml = '';
        $list  = array();
        // вот самый главные элемент несущий функционал, остальное интерфейс
        $list[]  = '<input type="hidden" id="'.$name.'" name="'.$name.'" value="'.$value.'" />';
        
        require_once 'Zend/Filter/Alnum.php';
        $filter = new Zend_Filter_Alnum();

        // Можно указать CSS класс для иллюстрации выбранности элемента
            // по умолчанию это class="selected"
        $selectedClass = (isset($attribs['selectedClass']) 
                && !empty ($attribs['selectedClass']))?$attribs['selectedClass']:'selected';
        $selectedClass = $filter->filter($selectedClass);
        if(!isset($attribs['class']))
            $attribs['class'] = null;
        // сохраняем указанные при инициализации элемента CSS классы (если указаны)
        $classBck = $attribs['class'];
        
        // начинаем добавлять изображения
        foreach ($options as $optVal => $imgPath) {

            // сгенерировать id для тэга <img />
            $imgId = $id . '-' . $filter->filter($optVal);

            // если выбран, добавляем к указанным класам еще и selected
            if ($optVal == $value) {
                $attribs['class'] .= " ".$selectedClass;
            }

            // сам код для картинки
            $list[] = '<img '
                    . 'src="'.$imgPath.'" '
                    . 'id="'.$imgId.'" '
                    . 'rel="'.$optVal.'" '
                    . $this->_htmlAttribs($attribs)
                    . '/>';
            
            // убрать класс selected, что бы остальные картинки не стали выбранными
            if(strstr($attribs['class'], $selectedClass))
                $attribs['class'] = $classBck;
        }
        // Добавить возможность отменить выбор, можно сделалть тут иконку вместо текста
        $list[]  = '<br /><a href=\"javascript;\">Reset Selection</a>'.PHP_EOL;
        $xhtml .= implode(PHP_EOL, $list);
        
        // подсветить выбранную картинку с jQuery
            // а так же подставить нужное значение в hidden элемент
        $this->view->jQuery()->addOnLoad("
            // клик по изображению
            $('#$name-element img').click(function(){
                $('#$name').val($(this).attr('rel'));
                $('#$name-element img').removeClass('$selectedClass');
                $(this).addClass('$selectedClass');
            });
            // кнопка отмены выбора
            $('#$name-element a').click(function(){
                $('#$name-element img').removeClass('$selectedClass');
                $('#$name').val('')
                return false;
            });
        ");
        // стиль для выбранного изображения (по-умолчанию)
            // можно указать другой css класс и описать его у себя в CSS
        $this->view->headStyle("
            #$name-element img {cursor:pointer; border:3px solid white}
            #$name-element img.selected {border:3px solid blue}
        ");

        return $xhtml;
    }
}


Все. Кажется ничего не забыл.

Картинка еще раз, что бы не скролить вверх:

Элемент Zend_Form RadioImage


Спасибо за внимание.
Теги:
Хабы:
+22
Комментарии37

Публикации