Pull to refresh

iPhone версия ROR сайта

Reading time6 min
Views1K
Как бы вы не относились к iPhone, вы не можете отрицать, что этот телефон захватил хороший кусок рынка мобильных устройств. И, по моему мнению, именно он дал возможность нормально пользоваться интернетом с телефона. Но хотя встроенный safari полноценен (спасибо, Webkit), многим хочется сделать специальную версию сайта для iPhone, выглядящую как настоящее айфоновское приложение (например, iweather.yandex.ru).
iweather  iphone.livecookbook.ru
Одной из наиболее популярных библиотек для создания адаптированной версии сайта является iUI.

Приготовления


Для начала надо сделать специальный mime-тип, прописав его в initializers/mime_types.rb:
Mime::Type.register_alias "text/html", :iphone

Теперь erb-шаблоны для сайта будет называться как *.iphone.erb (например, index.iphone.erb).
Так же надо написать before-фильтр в ApplicationController'e, который будет переключать версии сайта:
before_filter :adjust_format

def adjust_format
  request.format = :iphone if iphone_request?
end

def iphone_request?
  request.subdomains.first == "iphone"
end

Тут есть один тонкий момент — вам нужно определиться, когда вы хотите отображать iphone-версию сайта. Я для себя выбрал переключение по домену, но никто не мешает вам определять iPhone по заголовку HTTP_USER_AGENT и не заморачиваться с доменами.

Основы iUI


С приготовлениями покончили, приступаем к созданию iphone-версии сайта. На сайте iUI скачиваем последнюю версию библиотеки. К сожалению, документация на iUI довольно скудная, по сути ограничивается парой примеров сайтов — open source имеет свои недостатки :-).

Установка iUI заключается в копирование скриптов, стилей (файлы iui* или iuix* — минимизированная версия) и картинок в соответствующие директории сайта.
Делаем новый erb-шаблон (applitation.iphone.erb):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta id="viewport" name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
  <title>@page_title</title>
  <%= stylesheet_link_tag 'iui' %>
  <%= javascript_include_tag 'iui' %>
</head>
<body>
  <div class="toolbar">
    <h1 id="pageTitle"></h1>
    <a id="backButton" class="button" href="#"></a>
  </div>
  <%= yield %>
</body>
</html>

В iUI необычный подход к навигации — страница может разбиваться на несколько «экранов» (физически, экран — это корневой div или ul), активный при этом считается тот, кто содержит параметр selected, и только он и отображается. Выглядит это примерно так:
<ul title="Главная" selected="true">
  <li><%= link_to "список покупок", shoplist_path(current_user.shoplist), :target => "_self" %></li>
  <li><%= link_to "все рецепты", "#recipes" %></li>
</ul>
<ul id="recipes" name="recipes">
  <li><%= link_to "новые", recipes_path(:order => "date"), :target => "_self" %></li>
  <li><%= link_to "лучшие", recipes_path(:order => "quality"), :target => "_self" %></li>
  <li><%= link_to "популярные", recipes_path(:order => "popular"), :target => "_self" %></li>
</ul>

При загрузке будет отображаться первый список, по клику на ссылку «все рецепты» произойдет смена экрана на второй список (фирменное айфоновское перелистывание). Для того, что бы корректно сработала внешняя ссылка, необходимо аттрибуту target выставить значение "_self".

Реализация формы


Теперь страница авторизации (app/views/sessions/new.iphone.erb):
<% form_tag sessions_path, :id => "auth", :name => "auth", :class => "panel", :selected => "true", :title => "Авторизация", :target => "_self" do -%>
<script>
function toggleSession() {
  document.auth.foreign_computer.value = (document.auth.foreign_computer.value == "1") ? "0" : "1";
}
</script>
<fieldset>
  <div class="row">
    <label>Ваше имя:</label>
    <%= text_field_tag 'login' %>
  </div>
  <div class="row">
    <label>Пароль:</label>
    <%= password_field_tag 'password' %>
  </div>
  <div class="row">
    <label>Чужой iPhone?</label>
    <div class="toggle" onclick="toggleSession()" toggled="true">
      <span class="thumb"></span>
      <span class="toggleOff">да</span>
      <span class="toggleOn">нет</span>
    </div>
    <input type="hidden" name="foreign_computer" id="foreign_computer" value="0" />
  </div>
</fieldset>
<%= submit_tag 'Войти' %>
<% end -%>

Обычный чекбокс не рулит, запоминать ли сессию делается фирменным переключателем (см. фикцию с div.toggle и javascript:toggleSession()) :-). Кстати, обратите внимание, класс «panel» делает фон страницы как в настройках нашей мобилки.

Кастомизация ссылок — ajax обновление экрана


Всем, кто еще не закрыл браузер, расскажу подробнее про ссылки. Ссылки в iUI сделаны довольно хитро, в комплекте идет target="_replace", позволяющий создавать подгружаемый список(рабочий пример). Но, как это часто бывает, стандартных средств нехватает — переход со списка на конкретный объект мне хотелось сделать без загрузки новой страницы, что бы была возможность вернуться назад в список, и он был в исходном состоянии. Для этих целей я модифицировал слушатель кликов в iui.js, добавив новый таргет:
addEventListener("click", function(event) {
...
else if (link.target == "_custom_replace")
{
link.setAttribute("selected", "progress"); // включаем спиннер
// старый добрый ajax
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
  if (req.readyState == 4) {
    // подменяем контент в нужном месте
    var frag = $(link.getAttribute("target_elem"));
    frag.innerHTML = req.responseText;
    // гасим спинер
    setTimeout(unselect, 1000, true);
    // красивый переход на страницу объекта
    iui.showPageById(link.getAttribute("target_elem"));
  }
};
req.open(link.getAttribute("target_method") || "GET", link.href, true);
req.send(null);
}
...
}

Список объектов я сделал следующим образом:
<% if @first_page? %>
  <ul title="Рецепты" selected="true">
<% end %>

<% @recipes.each do |recipe| %>
  <li>
    <%= link_to recipe.name, recipe_path(recipe), :target => "_custom_replace", :target_elem => "recipe" %>
  </li>
<% end %>
<% if @page < @recipes.total_pages %>
  <li><%= link_to "больше рецептов...", params.merge(:page => @page+1), :target => "_replace" %></li>
<% end %>

<% if @first_page? %>
  </ul>
<% end %>

<% if @first_page? %>
  <div id="recipe" name="recipe" class="panel"/>
<% end %>

Список этот догружаемый, «digg-styled», и возврат со страницы объекта к списку работает, и работает правильно, т.е. вернувшись пользователь видит список в том состоянии, в котором он был оставлен.

Заключение


iUI — удобный инструмент для разработки iPhone-версии сайта, позволяющий не отвлекаться на верстку, и при этом он легко кастомизируется под нужды разработчика. По сути, это js+css+пачка картинок, так что использовать его можно вне зависимости от языка и фреймворка разрабатываемой системы.

Результаты моей работы можно посмотреть на iphone.livecookbook.ru (форма авторизации, список объектов, «список покупок»), без авторизации доступен описанный список рецептов. Модифицированный iui.js так же легко доступен.
Tags:
Hubs:
Total votes 45: ↑37 and ↓8+29
Comments25

Articles