Виджет для symfony. Выборка нескольких элементов из списка.

Для работы понадобилось написать виджет, я встречал видео с таким виджетом(который мне необходим) - но автор не выложил его кода(или я не нашёл) - по этому написал свой. И покажу как это сделал - для того чтоб вы меня поправили если я в чём то не прав или можно было сделать проще - или же сами чему то научились бы. Приступим.

 Для начала суть: необходима выборка из списка нескольких элементов, по умолчанию symfony генерирует форму с sfWidgetFormPropelChoice он довольно не удобен, по этому его лучше заменить на sfWidgetFormPropelChoice. Однако заказчику он не нравился и был его явный минус - если список большой то выводить все его элементы на страницу - не приемлемо. Так что добавим строку с автодополнением. Так же надо учесть что в выпадающий список не должны попадать уже добавленные категории. А чтоб минимизировать его место на основной форме - вынесем его в отдельное окно

 Так как я уже сделал его то чтоб удобнее было понять о чём идёт речь вот скришоты:



По клику "показать список" - появляется форма на которой можно выбрать категории товаров или же добавить новую.

Схема бд:













Виджет будет использоваться для таблици contrctors_goods_buy, он будет выводится при добавления "контрактора".
Если у вас девственно чистый проект - то необходимо создать папку widget в lib, в ней создадим файл который будет классом виджета. Далее будут приводить его код с комментариями:

<?php
 
class sfWidgetFormManyJQueryChoise extends sfWidgetFormSelectCheckbox
{
  public function __construct($options = array(), $attributes = array())
  {
    $options['choices'] = array();
    parent::__construct($options, $attributes);
  }

Наследуем виджет от существующего sfWidgetFormSelectCheckbox - соответственно чекбоксы будем формировать с такой же структурой как и в нём. Первоначально наш список пуст для этого мы определяем опцию choices и далее вызываем родительский конструктор.



  public function configure($options = array(), $attributes = array())
  {
    $this->addRequiredOption('model');
    $this->addRequiredOption('jsonurl');
    $this->addRequiredOption('addurl');

Теперь переопределяем функцию configure указываем что опции model, addurl и jsonurl должны быть обязательно определены. В первой будет указана модель(CategoryOfGoods) по которой осуществлять поиск а вторая линк к добавлению категории товара и в третьей обработка запроса по поиску.

Необходимо определить внешний вид виджета и прицепить скрипты, для этого определяем переменные: template.html и template.javascript. Во второй описываем функцию добавление элемента: appli{div.id}, привязываем к "Добавить" аяксовый запрос, устанавливаем параметры диалога и автодополнения

template.html

      <input type="button" value="Показать список" id="{show.id}" />
      <br>
      <br>
      <div id="{div.id}" class="{div.class}" >
        <ul class="checkbox_list" id="{checkbox.list.id}">
        </ul>
      </div>
      <div id="{dialog.div.id}" class="{div.class}" title="{div.id}">
        <input type="text" id="{input.add.id}" />&nbsp;&nbsp;<input type="button"
 id="{add.button.id}" class="{div.class}" value="Добавить" />
        <br>
        <ul class="checkbox_list" id="{dialog.checkbox.list.id}">
          {checkbox.lists}
        </ul>
      </div>

template.javascript

<script type="text/javascript">
 
        function appli{div.id}(name, id) {
          jQuery("#{dialog.checkbox.list.id}").append("<li><input name="{checkbox
.name}" type="checkbox" value=""+id+"" id="{div.id}_"+id+"" checked>&nbsp;<label
 for="{div.id}_"+id+"">"+name+"</label></li><br>");
        }
 
        jQuery("#{add.button.id}").click(function(){
          $.ajax({
              url: "{link.add}",
              dataType: "json",
              data: {
                  name: jQuery("#{input.add.id}").val()
              },
              success: appli{div.id}(data.name, data.name)
          });
          jQuery("#{add.button.id}").addClass("{div.class}");
          jQuery("#{input.add.id}").val("");
        });
 
        jQuery("#{show.id}").click(function() {
          jQuery("#{dialog.div.id}").dialog({
            width: 300,
            zIndex: 100,
            modal: true,
            close: function() {
              jQuery("#{checkbox.list.id}").html(jQuery("#{dialog.checkbox.list.id}").html());
            }
          });
        });
 
        var setA{show.id} = jQuery("#{input.add.id}").autocomplete({
          source: function(request, response) {
              $.ajax({
                  url: "{link.sourse}",
                  dataType: "json",
                  data: {
                      term : request.term,
                      already: function(){
                        var arr = new Array();
                        jQuery("#{dialog.checkbox.list.id} input:checkbox").each(function(index) {
                          arr.push(jQuery(this).val());
                        });
                        return arr;
                      } 
                  },
                  success: function(data) {
                      if (data.length == 0)
                        jQuery("#{add.button.id}").removeClass("{div.class}");
                      else
                        jQuery("#{add.button.id}").addClass("{div.class}");
                      response(data);
                  }
              });
          },
          minLength: 2,
          delay: 200,
          select: function( event, ui ) {
            appli{div.id}(ui.item.label, ui.item.value);
            setA{show.id};
          },
          close: function( event, ui ) { jQuery("#{input.add.id}").val(""); },
          open: function(){
              $(this).autocomplete("widget").css("z-index", 300);
              return false;
          }
 
        });
      </script>


  public function getJavascripts()
  {
    return array(
      'jquery-1.9.1.js',
      'jquery-ui.js'
    );
  }
 
  public function getStylesheets()
  {
    return array(
      'http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css' => 'screen'
      'sfWidgetFormManyJQueryChoise.css' => 'screen'
); }

Для работы автозоавершения необходимо добавить js-скрипты, а для красоты выпадающего списка css всё взято с http://jqueryui.com


public function render($name, $value = null, $attributes = array(), $errors = array())
  {
 
    $template_vars = array(
      '{div.id}'                  => $this->generateId($name),
      '{dialog.div.id}'           => $this->generateId($name.'[dialog]'),
      '{div.class}'               => 'class_hide_div',
      '{input.add.id}'            => $this->generateId($name.'[add]'),
      '{add.button.id}'           => $this->generateId($name.'[add_button]'),
      '{checkbox.list.id}'        => $this->generateId($name.'[checkbox]'),
      '{dialog.checkbox.list.id}' => $this->generateId($name.'[dialog_checkbox]'),
      '{show.id}'                 => $this->generateId($name.'[show]'),
      '{checkbox.lists}'          => "",
      '{link.sourse}'             => $this->getOption('jsonurl'),
      '{checkbox.name}'           => $name.'[]',
    );
 
    if ($value !== null) {
      $class = constant($this->getOption('model').'::PEER');
      $c = new Criteria();
      $c->add($class::ID, $value, Criteria::IN);
 
 
      foreach ($class::doSelect($c) as $elem) {
        $template_vars['{checkbox.lists}'] .= "<li><input name="".$name."[]" type
="checkbox" value="".$elem->getId()."" id="".$template_vars['{div.id}']."_".$elem
->getId()."" checked>&nbsp;<label for="".$template_vars['{div.id}']."_".$elem->ge
tId()."">".(string)$elem."</label></li><br>";
      }
    }
 
    return strtr(
      $this->getOption('template.html').$this->getOption('template.javascript'),
      $template_vars
    );
  }

И так один из сложных моментов функция render. В начале генерируем id и имена классов это делаем за тем чтоб они были индивидуальны - что позволит использовать виджет несколько раз на странице. Далее если мы находимся в добавлении нового элемента то $value будет пустым - однако в случае с редактированием нам надо добавить уже существующие элементы для этого и служит данный if. $value - храни айдишники категорий товаров привязанных к данному "контрактору" по этому используем Criteria::IN. Далее просто наполняем опцию {checkbox.lists} содержимым.

Виджет описан однако это ещё не всё нам необходимо обработать запрос по поиску ссылка для запроса генерируется с учётом привязки экшена к текущему классу(contractors). Логичнее было бы повесить на категории товаров но я не генерировал для них модуля

class contractorsActions extends autoContractorsActions
{
 public function executeSearchgoods($request)
 {
   $this->getResponse()->setContentType('application/json');
   $goods = CategoryOfGoods::selectForAutoComplite($request->getParameter('term'), $request->getParameter('already'));
   return $this->renderText(json_encode($goods));
 }
 
 public function executeAddgoods($request)
 {
   $this->getResponse()->setContentType('application/json');
   $good = new CategoryOfGoods();
   $good->setName($request->getParameter('name'));
   $goodId = $good->save();
 
   $res = array('name' => $request->getParameter('name'), 'id' => $goodId);
   return $this->renderText(json_encode($res));
 } 
}

мы указываем в каком формате следует отослать ответ после чего вызываем функцию selectForAutoComplite с строкой в которой хранятся введёны буквы и список уже добавленных, далее возвращаем полученный результат. (из моего названия функции можно определить что ссылка на запрос будет оканчиваться на : contractors/searchgoods )

Теперь определим функцию selectForAutoComplite:

 static public function selectForAutoComplite($query, $already)
 {
  $criteria = new Criteria();
  $criteria->add(CategoryOfGoodsPeer::NAME, '%'.$query.'%', Criteri
a::LIKE);
  $criteria->add(CategoryOfGoodsPeer::ID, explode(",",$already), Criteria::NOT_IN);
  $criteria->addAscendingOrderByColumn(CategoryOfGoodsPeer::NAME);
  $criteria->setLimit(3);
 
  $goods = array();
  foreach (CategoryOfGoodsPeer::doSelect($criteria) as $good)
  {
    $goods[$good->getId()] = array('label' => (string) $good, 'valu
e' => $good->getId());
  }
 
  return $goods;
 } 

Реализация такая же как в плагине sfWidgetFormPropelJQueryAutocompleter. Тут думаю всё ясно массиве указывается имя и id.
Ещё добавим стиль чтоб список выводился без точек и скрывался диалог:
ul li {
 list-style-type:none;
}
.class_hide_div {
 display: none;
}

Теперь остаётся только определить виджет для необходимого нам поля:
 $this->setWidget('contractors_goods_buy_list', new sfWidgetFormManyJQueryChoise(
 array('model' => 'CategoryOfGoods', 
 'jsonurl' => sfContext::getInstance()->getController()->genUrl('contractors/searchgoods'),
 'addurl' => sfContext::getInstance()->getController()->genUrl('contractors/addgoods'))));

Генерировать так url - не верно - однако другого способа я не знаю. Буду благодарен если подскажете.

Комментарии

Популярные сообщения из этого блога

Bitrix: кнопка добавить в корзину

Битрикс: какого х*я ты ищешь в неактивных разделах

Битрикс: highloadblock значения в свойстве список