пʼятницю, 23 грудня 2011 р.

Створення простого AJAX-сайту з Sinatra та JQuery

За основу цієї статті взятий скрінкаст Building a Simple AJAX Website with Sinatra & jQuery. Зараз у вільний час я працюю над сайтом isit.heroku.com, який основну свою ідею почерпнув саме цього скрінкасту.

У цій статті ви дізнаєтеся:
  • Як створити свій власний веб-сайт із таймером зворотного відліку
  • Як визначити в Sinatra, якщо запит XMLHttpRequest чи ні
  • Як відключити шаблони(layouts) для XHR запитів у Sinatra
У цьому епізоді ми будемо використовувати Sinatra, HAML і JQuery.

"Is It" веб-сайт

Ви можливо зустрічали цей жанр сайтів раніше. Це прості веб-сайти з "Так" або "Ні" в середині сторінки. Вони відповідають на питання типу "Це Різдво?", "Це Новий Рік?" або інші термінові питання. Іноді на сторінці також є таймер зворотного відліку.

Розробку нашого "Is It" веб-сайту розпочнемо з огляду структури додатку на Sinatra.
app.rb - це головний файл нашого додатку, який містить єдиний маршрут до кореневої URL-адреси. Також ви помітили, у ньому підключається файл lib/countdown.rb.

## app.rb
# -*- encoding: utf-8 -*-
require 'sinatra'
require 'haml'

require_relative 'lib/countdown.rb'

get '/' do

end


Якщо подивитися у клас BirthdayCountdown, ми побачимо кілька методів. Але використовувати ми будемо тільки конструктор initialize , і методи isit? і seconds_to_go. Інші методи є допоміжними для цих трьох методів і використовуються для кращої читабельності коду.

## countdown.rb
# -*- encoding: utf-8 -*-

require 'date'

class BirthdayCountdown

  # Cache @month, @day and @year on initialization
  def initialize(month, day)
    @birthday_month = month
    @birthday_day   = day

    # Current date
    @month = DateTime.now.month
    @day   = DateTime.now.day
    @year  = DateTime.now.year
  end
  
  # Is it birthday?
  def isit?
    @month == @birthday_month && @day == @birthday_day
  end

  # For the countdown
  def seconds_to_go
    ( (next_birthday - DateTime.now) * 24 * 60 * 60 ).to_i
  end

  :private
 
  # Is is the current month after the birthday month?
  def passed_birthday_month?
    @month > @birthday_month
  end
  
  # Is the current month the same month as the birthday month
  # and the day
  def passed_birthday_day?
     @month == @birthday_month && @day > @birthday_day
  end
  
  # Happened this year
  def already_happend?
    passed_birthday_month? || passed_birthday_day?
  end
  
  # Returns the next birthday
  def next_birthday
    if already_happend?
      # Add 1 year if it's already happened
      DateTime.new(@year + 1 , @birthday_month, @birthday_day) 
    else
      # Create this years date
      DateTime.new(@year, @birthday_month, @birthday_day)
    end
  end
 
end

Конструктор приймає два аргументи month і day, які запам'ятовуються як @birthday_month і @birthday_day відповідно. Крім того для зручності, щоб постійно не повторювати Time.now, запам'ятовуємо @month, @day і @year для поточної дати.

У методі isit? ми перевіряємо чи поточний місяць і день співпадають з @birthday_month і @birthday_day.

Метод seconds_to_go повертає скільки секунд залишилося до святкування. Він використовує один з допоміжних методів next_birthday.

Ви помітили, що у каталозі views є макет layout.haml і два представлення: yes.haml та no.haml, один відображається коли день народження, інший - коли ні.
-# layout.haml
!!!
%html
  %head
    %title Зворотній відлік
  %body
    =yield
-# yes.haml
%h2 Так!
-# no.haml
%h2 Ні!
%p= "Time to Go: #{@countdown.seconds_to_go}"

Додамо деяку логіку, у маршрут до кореневої URL-адреси, щоб показати представлення :yes або :no.

Ініціалізуємо BirthdayCountdown, шляхом передачі йому 1, 1, як місяць і день:
get '/' do
   @countdown = BirthdayCountdown.new(1, 1)
end

Тепер для представлення потрібно відобразити :yes або :no. Для цього реалізуємо метод to_view у класі BirthdayCountdown:
def to_view
  isit? ? :yes : :no
end

І змінюємо наш маршрут в app.rb:
get '/' do
  @countdown = BirthdayCountdown.new(1, 1)
  haml @countdown.to_view
end 

Тепер запустимо наш додаток, набравши ruby app.rb і перейдемо за адресою http://localhost:4567. Ми побачимо, що код працює. Оскільки сьогодні не Новий рік, він говорить "Ні!".

Ми бачимо зворотній відлік знизу сторінки. Але він є статичним на даний момент. Так що давайте додамо JQuery , щоб зворотній відлік змінювався кожну секунду, тому що ми просто не можемо чекати!

Підключаємо бібліотеку JQuery у файлі шаблону layout.haml:
!!!
%html
  %head
    %title Зворотній відлік
    %script(src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js")
    :javascript
      
  %body= yield

Під фільтром :javascript пишемо код JavaScript.

Створимо функцію getUpdate(), в всередині неї будемо завантажувати кореневий шлях у елемент $("body"). І встановимо таймаут 1000 мілісекунд, або 1 секунду, щоб викликати метод getUpdate.
$(getUpdate);
function getUpdate(){
  $("body").load("/");
  setTimeout(getUpdate,1000);
}

Якщо ми оновлюємо вікно браузера, ми побачимо, що таймер оновлюється. Це все, що нам потрібно було отримати.

Ну майже все. Якщо ви подивитесь у веб-інспектор, ви побачите, що Sinatra відсилає повну сторінку, шаблон і представлення, під час кожного запиту Ajax. Це може мати небажані побічні ефекти у майбутньому.
Все, що ми хочемо зробити, це відправляти представлення без шаблону. Щоб вирішити цю проблему нам потрібно вимкнути шаблон, коли виконується XHR запит.

За допомогою методу request.xhr? Sinatra дозволяє з'ясувати чи є запит Ajax запитом?

Також Sinatra дозволяє відключити або вказати розташування шаблону. Це робиться шляхом встановлення параметру :layout у false, щоб відключити шаблон, або можна передати ім'я шаблону, як символ, щоб включити його.
get '/' do
  @countdown = BirthdayCountdown.new(1, 1)
  haml @countdown.to_view, :layout => (request.xhr? ? false : :layout)
end

ОК, давайте перезапустимо додаток Sinatra, і повернемося до нашого веб-інспектор. Тепер ми бачимо всі нові запити містять тільки частину розмітки з представлення. Чудово.

На цьому це все!

Немає коментарів: