суббота, 28 мая 2011 г.

JQuery: Slider + Table или я в роли доктора Франкенштейна

Для тех, кто меня не очень хорошо знает, этот пост может показаться несколько бессмысленным (слишком он в духе К.О.), поэтому объясню. Уже пару лет я занимаюсь по большей части backend приложениями (как в рабочее, так и в свободное время). Тут предоставилась возможность поработать над прототипом с web интерфейсом. Вот и хотел поделится своими ощущениями, что сейчас это уже выглядит не так страшно, вроде бы.

Стоит задача сделать что-то похожее на Google Finance StockScreener. В первом приближении необходима приятная табличка (с сортировкой и разбитием на страницы) и слайдер, с помощью которого можно отфильтровать данные, отображаемые в этой таблице. Слайдер уже есть практически из коробки: JQuery UI Slider. В качестве таблицы выбор пал на Data Tables. Осталось только их соединить так сказать вместе и об этом и будет этот небольшой пост.

По хорошему все данные должны получаются из вебсервисов, но в рамках этого поста, это не интересно. Тем не менее у нас будет Servlet, в котором зашит сет данных. И интерфейс как того требует время будет интерактивным, без всяких перезагрузок всей страницы. Большую часть работы за нас делает удачно выбранная таблица Data Tables. В самом html нам надо создать обычный table с заголовком содержащим нужны нам колонки.


<table cellpadding="0" cellspacing="0" border="0" class="display" id="companies">
 <thead>
  <tr>
   <th width="40%">Company name</th>
   <th width="20%">Symbol</th>
   <th width="20%">Market cap.</th>
   <th width="20%">Price</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td colspan="4" class="dataTables_empty">Loading data from server</td>
  </tr>
 </tbody>
</table>

Теперь было бы неплохо её заполнить. Для этого, после создания документа необходима создать datatable на этот table элементе. Важными являются атрибуты bServerSide - указывает, что данные будут загружаться с сервера, sAjaxSource - url, по которому можно будет получить данные для отображения (можно указать относительный). Я установил ещё пару не принципиальных, как например bJQueryUI, чтобы таблица и слайдер, который берется из поставки jquery ui имели похожий вид.


var companiesTable = $( "#companies" ).dataTable( {
   "bProcessing" : true,
   "bServerSide" : true,
   "bJQueryUI" : true,
   "sAjaxSource" : "data"
         });

Теперь поговорим о серверной части. Весь код я сюда вставлять не буду, он добавлен в соответствующий проект на GitHub, если интересно, можно посмотреть, но на самом деле он прост. Обсудим, что от нас ожидает dataTable.

Ожидает он json ответ. Но чтобы нам было проще ответить, в get (можно настроить post) запрос передается ряд параметров: sEcho - хитрый параметр для безопасности, мы его просто потом и вернём, как получили :) iDisplayStart, iDisplayLength - начиная с какого элемента и сколько надо вернуть (используются при переходе по страницам). iSortCol_0, sSortDir_0 - номер колонки, по которой производится сортировка и её направление (asc, desc). Вообще сортировать можно по нескольким колонкам, поэтому в названии параметра и присутствует 0, лично мне достаточно одной колонки. Кроме них передается ещё ряд параметров, но они не особо важны. С помощью полученных параметров мы можем выбрать правильный, отсортированный набор данных и вернуть. (кстати, можно заметить, что в названии параметров первая буква отражает его тип ;))

Возвращаемый json имеет достаточно простую структуру. sEcho возвращает то, что и получили :) iTotalRecords - фактически сколько вообще в системе зарегистрировано объектов. iTotalDisplayRecords - сколько из них может быть отображено, но не на странице, а вообще, т.е. после фильтации. Если мы к примеру вернём iTotalRecords: 15, iTotalDisplayRecords: 11 (и возвратим 10 объектов), то увидим внизу вот такую надпись Showing 1 to 10 of 12 entries (filtered from 15 total entries) и можно будет перейти на вторую страницу. И последний элемент aaData содержит двух мерный массив, фактически таблицу, по горизонтали строки таблицы, а по вертикали значения колонок. Если вернуть меньше или больше значений, чем есть в колонке, dataTables ругнётся, но отобразит те значения что поместятся. Вот примерчик:


{
    "sEcho":"1",
    "iTotalDisplayRecords":15,
    "iTotalRecords":15,
    "aaData":[
                ["3M Company","MMM","66.53B",93.47],
                ["AT&T Inc.","T","185.3B",31.29],
                .....
            ]
}

На json.org можно найти подходящую библиотеку под наверно любой более менее используемый язык. Раз поставил к посту тег Java, то вот хотя бы один кусок java кода приведу :)


final JSONObject jsonResponse = new JSONObject();
jsonResponse.put("sEcho", paramModel.getEcho());
jsonResponse.put("iTotalRecords", companyResult.getTotal());
jsonResponse.put("iTotalDisplayRecords", companyResult.getFilteredIn());

final JSONArray jsonCompanies = new JSONArray();

for (final Company company : companyResult.getCompanies()) {
     final JSONArray jsonCompany = new JSONArray();
     jsonCompany.put(company.getName())
                .put(company.getSymbol())
                .put(company.getFormatedMktCap())
                .put(company.getPrice());
     jsonCompanies.put(jsonCompany);
}

jsonResponse.put("aaData", jsonCompanies);

Со слайдером всё ещё проще. Блага он сразу сделан правильно и установив флаг range, он будет показывать ползунок с обеих концов. Комментировать даже нечего. В таком видел он был стырен из стандартных примеров. Вмести с текст боксом куда отображаются текущие значения


<p>
 <label for="price-amount">Price range:</label>
 <input type="text" id="price-amount" style="border:0; color:#f6931f; font-weight:bold;" />
</p>

<div id="price-slider"></div>


$( "#price-slider" ).slider({
 range: true,
 min: 0,
 max: 170,
 values: [ 0, 170 ],
 slide: function( event, ui ) {
  $( "#price-amount" ).val( "$" + ui.values[ 0 ] + " - $" + ui.values[ 1 ] );
 }
}
});
$( "#price-amount" ).val( "$" + $( "#price-slider" ).slider( "values", 0 ) +
 " - $" + $( "#price-slider" ).slider( "values", 1 ) );

Теперь надо собрать наше чудовище :) Чего мы хотим? Чтобы когда сдвигаешь ползунок в таблицу загружались новые отфильтрованные данные. Как можно заметить мы уже определили функцию slide, которая срабатывает при каждом движении. Но так часто нам не надо перегружать данные. Это неожиданно оказалось просто. Определим ещё функцию stop для slider'а, в которой будем запускать перезагрузку таблицы, вызвав у неё соответствующие функции.


stop: function( event, ui ) {
 companiesTable.fnClearTable( 0 );
 companiesTable.fnDraw();
}

Теперь, если поводить ползунок, то данные перегружаются. Но это не совсем нам надо, так как они продолжают загружаться в полном объеме, а нам хотелось бы применить фильтрацию. Тут нас в очередной раз искренне радует отличная библиотека data tables. У таблицы можно определить функцию fnServerData, в которой к уже существующим параметрам можно добавить свои. Именно тут мы достанем из ползунка выбранные значения и добавим их к пересылаемым параметрам на сервер, а там уже всё просто (см. GitHub :)).


"fnServerData": function ( sSource, aoData, fnCallback ) {
 aoData.push( { 
  "name": "dMinPrice", 
  "value": $( "#price-slider" ).slider( "values", 0 ) } );
 aoData.push( { 
  "name": "dMaxPrice", 
  "value": $( "#price-slider" ).slider( "values", 1 ) } );
 $.getJSON( sSource, aoData, function (json) { 
  fnCallback(json)
 });
}

Понятное дело, всё так просто из за микроскопичности проекта. Но на самом деле и не так страшно. JavaScript уже не так пугает, а J6EE да и другие Java Frameworks, к примеру Spring, начинают добавлять поддержку аннотаций и можно уже забывать о тонах xml...

А ещё. Посмотрел я на этот небольшой проект, да и на другие уже реальные проекты и задумался, что теперь наверно уже действительно можно по хорошему отделить backend от client side для web. По сути, на сервере как и прежде должна идти вся обработка, но при этом возвращается не html, а json (как уже запросы будут идти по REST или как тараканы в голове подскажут не суть важно). А клиента можно делать на голом html + js, который лишь общается с сервером. В топку эту html генерацию!

Ах да, ссылка на код.

9 комментариев:

  1. Растолкуй для меня тупого что нужно ставить и как запускать проект твой гитхабовский. Если можно эклипсом - было б идеально. Я хочу поиграцца и может быть написать на какой-нибудь нежаве клон.

    ОтветитьУдалить
  2. Нужен Eclipse EE edition. И какой-нибудь J6EE servlet container. Можно взять Tomcat 7.0.

    Я сейчас вкомитал в проект эклипсовские файлы, поэтому просто импортируй его в эклипс.

    ОтветитьУдалить
  3. Не вижу комита твоего. 

    А чё, нужно кроме эклипса чёто ставить реально? Мрак мрак.

    ОтветитьУдалить
  4. > Не вижу комита твоего. 

    Сорри, не в push'ался, всё теперь точно есть :)

    > А чё, нужно кроме эклипса чёто ставить реально? Мрак мрак.

    Виктор, какой мрак? Кроме эклипса надо только web сервер, под которым приложение будет развёртоваться поставить. Например Tomcat 7. Да и поставить это громко сказано, просто zip распаковатью

    ОтветитьУдалить
  5. Не собирается искаропки. The import javax.servlet cannot be resolved

    ОтветитьУдалить
  6. Потому что надо настроить eclipse на твой eclipse.

    Добавляешь его в Windows -> Preferences -> Server -> Runtime Enviroments

    Если у тебя Tomcat 7.0 то должно заработать. Иначе надо подправить проект. Project -> Java build path -> Libraries -> Add library -> Server runtime -> твой сервер

    ОтветитьУдалить
  7. Ну и чё это, не мрак разве? В рельсах проекты сполтычка заводятся на встроенном вебрике.

    И да, почему в джаве нету встроенного тупого хотя бы веб-сервера?

    Почему в эклипсе нету встроенного томката?

    Почему эклипс заточен под возюканье мышкой? Это ж не вижла.

    Я тут у себя в археологических директориях нашёл среди прочих вот такой файл например: E:\epam\trunk\bin\ContentStudio\content_studio\plugins\org.eclipse.tomcat_4.1.130.v20060601\tomcat-http11.jar . Это чё, томкат чтоли лежит там? Может его можно поюзать?

    ОтветитьУдалить
  8. > И да, почему в джаве нету встроенного тупого хотя бы веб-сервера?

    Слу, а тебя не заботит, что вместе с эклипсом не идёт какой-нибудь ORM?

    У Java другой подход.

    > Почему эклипс заточен под возюканье мышкой? Это ж не вижла.

    Пжди, при чём здесь мышка? Как ты сетинги настроишь без мышки, пруф линки?

    > Это чё, томкат чтоли лежит там? Может его можно поюзать?

    Это какое-то старье. Я юзаю Servlet API 3.0. Т.е. нужен сервер поддерживающий спецификацию J6EE. Т.е. Tomcat 7 и выше. Вот прямая линка: http://ftp.byfly.by/pub/apache.org/tomcat/tomcat-7/v7.0.14/bin/apache-tomcat-7.0.14.zip

    ОтветитьУдалить