В предыдущей статье нам удалось создать приложение, которое позволило бы пользователям продемонстрировать свои навыки работы с HTML, CSS и JavaScript, создав «загадки» — небольшие фрагменты HTML, CSS и JavaScript. В качестве дополнительного бонуса он также позволил пользователям использовать SCSS для стилизации и Markdown для HTML. В этой части мы собираемся добавить некоторые улучшения в приложение, чтобы оно больше походило на сайты, которые оно клонирует .
плавающие фреймы
Первое, что мы сделаем, это поместим каждую загадку в отдельный фрейм. Это означает, что загадка будет эффективно находиться в его собственном HTML-документе. Это имеет то преимущество, что каждая загадка ведет себя точно так же, как если бы это была автономная веб-страница, поэтому пользователи смогут загружать внешние библиотеки и веб-шрифты, что раньше было невозможно.
Чтобы реализовать iframe, нам нужно изменить представление show, чтобы оно выглядело так:
@@show | |
h1.title== @riddle.title | |
#riddle | |
iframe src=«/#{@riddle.id}« |
Нам нужен обработчик маршрута для iframe. В атрибуте src iframe мы использовали маршрут «/# [email protected]}», который в основном является идентификатором загадки. Нам это нужно, чтобы мы могли найти загадку в базе данных, вот нужный нам обработчик маршрута:
get ‘/:id’ do | |
@riddle = Riddle.get(params[:id]) | |
slim :riddle, layout: false | |
end |
Это получает Загадку, которая соответствует идентификатору, данному из базы данных. Затем мы отображаем новый вид под названием «загадка». Обратите внимание, что для макета установлено значение false, в противном случае наш макет будет снова отображаться в iframe, а это не то, что нам нужно. Мы хотим, чтобы у iframe были собственные HTML-заголовок и тело. Они перейдут непосредственно в представление, как вы можете видеть ниже:
@@riddle | |
doctype html | |
html lang=«en» | |
head | |
title== @riddle.title | |
meta charset=«utf-8» | |
style | |
== scss @riddle.css | |
script | |
== @riddle.js | |
body | |
== markdown @riddle.html |
Как видите, теперь мы используем встроенный стиль и теги сценариев для отображения CSS и JavaScript. Это нужно для того, чтобы каждый раз не попадать в базу данных. И поскольку каждая загадка имеет свой собственный CSS и JavaScript, использование встроенных стилей и сценариев будет хорошо. Это означает, что нам не понадобятся маршруты для них, поэтому мы можем удалить следующие обработчики маршрутов:
get ‘/css/riddle/:id/styles.css’ do | |
riddle = Riddle.get(params[:id]) | |
scss «#riddle #{riddle.css}« | |
end | |
get ‘/js/riddle/:id/script.js’ do | |
riddle = Riddle.get(params[:id]) | |
content_type ‘text/javascript’ | |
render :str, riddle.js, :layout => false | |
end |
Нам также нужно удалить следующие строки, которые ссылаются на них в нашем макете:
— if @riddle | |
link rel=«stylesheet» href=«/css/riddle/#{@riddle.id}/styles.css» | |
script src=«/js/riddle/#{@riddle.id}/script.js» |
Если вы протестируете это, оно не будет выглядеть по-другому, но теперь мы помещаем весь код в iframe. Вы можете заметить, что iframe по умолчанию выглядит очень маленьким. Это можно решить, добавив следующую строку в наше представление @@ styles:
iframe {width: 100%; min-height: 600px; border: none; } |
Редактирование Загадок
Следующая функция, которую нужно добавить, позволяет пользователям редактировать Загадки. Если кто-то видит что-то, что ему нравится, было бы хорошо позволить ему внести некоторые изменения и изменения. Прежде всего, давайте добавим кнопку, которая позволяет пользователям редактировать загадку, когда они находятся на странице загадки. Мы поместим его в макет, прямо рядом с кнопкой «Новая загадка», но мы хотим, чтобы он отображался только в том случае, если есть загадка для редактирования. Этот код должен сделать это:
— if @riddle && @riddle.id | |
a.button href=«/edit/riddle/#{@riddle.id}« Edit this Riddle |
Нам нужно проверить, есть ли объект @riddle, но также есть ли у него идентификатор. Это ограничит его использованием только тех загадок, которые были сохранены, поскольку им присваивается идентификатор только после сохранения (на «новой» странице есть объект @riddle, где мы не хотим кнопку редактирования). Теперь мы можем щелкнуть, чтобы отредактировать загадку, которую нам нужно реализовать, позволяя пользователям редактировать их.
Проблема в том, что мы не хотим обновлять саму загадку… люди не хотят, чтобы их хорошая работа была изменена кем-то другим! И мы не хотим вмешиваться во все проблемы с пользователями. Решение простое — просто создайте новую загадку, используя атрибуты загадки, которые мы хотим отредактировать. Это можно сделать в DataMapper с помощью следующего кода:
Riddle.new(riddle.attributes.merge(id: nil)) |
Обратите внимание, что мы хотим установить id nil
как это поле с автоинкрементом, которое будет автоматически задано базой данных при сохранении новой загадки, давая ей свой уникальный URL-адрес. Все, что нам нужно сделать, чтобы это произошло, это добавить следующий обработчик маршрута в наш код:
get ‘/edit/riddle/:id’ do | |
riddle = Riddle.get(params[:id]) | |
@riddle = Riddle.new(riddle.attributes.merge(id: nil)) | |
slim :new | |
end |
Это относится к URL, который мы использовали в ссылке кнопки редактирования. Прежде всего, мы получаем загадку, которую хотим редактировать из базы данных. Затем мы «клонируем» его как новый несохраненный объект и затем отображаем новое представление, которое является просто формой. К сожалению, в данный момент это просто покажет пустые поля, поэтому нам нужно внести небольшое изменение в форму, чтобы поля заполнялись любыми значениями по умолчанию:
@@new | |
form action=«/riddle» method=«POST» | |
label for=«title» Title | |
input#title name=»riddle[title]» value=»#{@riddle.title}» | |
label for=«html» HTML | |
textarea#html cols=60 rows=10 name=»riddle[html]»[email protected] | |
label for=«css» CSS | |
textarea#css cols=60 rows=10 name=»riddle[css]»[email protected] | |
label for=«js» JS | |
textarea#js cols=60 rows=10 name=»riddle[js]»[email protected] | |
input.button type=«submit» value=«Save» |
Начни свои двигатели
В начале первой части я упоминал, что Sinatra использует Tilt для реализации множества различных движков представления. На самом деле мы еще не использовали это. Было бы здорово, если бы мы позволили пользователям выбирать, какой движок они хотели бы использовать для отображения HTML, использовать препроцессор для CSS или использовать CoffeScript вместо JavaScript. Синатра делает это невероятно легким для достижения. Прежде всего нам нужно требовать соответствующие драгоценные камни для всех механизмов представления, которые мы планируем использовать. Добавьте следующее в начало main.rb:
require ‘haml’ | |
require ‘RedCloth’ | |
require «coffee-script» | |
require «v8» | |
require «liquid» | |
require «markaby» | |
require «less» |
RedCloth
камень RedCloth
используется для обработки текстиля, а гем v8
встраивает движок javascript v8 в Ruby, который требуется для поддержки CoffeeScript. Все остальные, надеюсь, говорят сами за себя.
Теперь нам нужно изменить нашу базу данных, включив в нее некоторые поля для хранения используемых движков представления. Для этого нам сначала нужно обновить класс Riddle, чтобы включить эти новые свойства:
class Riddle | |
include DataMapper::Resource | |
property :id, Serial | |
property :created_at, DateTime | |
property :updated_at, DateTime | |
property :title, String | |
property :html, Text | |
property :html_engine, String | |
property :css, Text | |
property :css_engine, String | |
property :js, Text | |
property :js_engine, String | |
def title=(value) | |
super(value.empty? ? «Yet another untitled Riddle» : value) | |
end | |
end | |
DataMapper.finalize |
Чтобы убедиться, что эти изменения внесены в нашу базу данных, все, что нам нужно сделать, это использовать мощную функцию миграции DataMapper с использованием irb. Введите в терминал следующее:
$ irb ruby-1.9.2-p180 :001 > require './main' ruby-1.9.2-p180 :001 > Riddle.auto_migrate!
Важно отметить, что с помощью auto_migrate!
Метод уничтожит все загадки, хранящиеся в данный момент в базе данных. Если вы не хотите, чтобы это произошло, вы можете использовать auto_upgrade!
метод вместо
Затем нам нужно изменить нашу форму, чтобы показать выпадающее меню, которое позволяет пользователям выбирать, какой движок они хотят использовать:
@@new | |
form action=«/riddle» method=«POST» | |
label for=«title» Title | |
input#title name=»riddle[title]» value=»#{@riddle.title}» | |
select name=«riddle[html_engine]» | |
option value=«markdown» HTML | |
option value=«markdown» MARKDOWN | |
option value=«textile» TEXTILE | |
option value=«haml» HAML | |
option value=«slim» SLIM | |
option value=«erb» ERB | |
option value=«liquid» LIQUID | |
option value=«markaby» MARKABY | |
textarea#html cols=60 rows=10 name=»riddle[html]»[email protected] | |
select name=«riddle[css_engine]» | |
option value=«css» CSS | |
option value=«scss» SCSS | |
option value=«sass» SASS | |
option value=«less» LESS | |
textarea#css cols=60 rows=10 name=»riddle[css]»[email protected] | |
select name=«riddle[js_engine]» | |
option value=«javascript» JAVASCRIPT | |
option value=«coffee» COFFEESCRIPT | |
textarea#js cols=60 rows=10 name=»riddle[js]»[email protected] | |
input.button type=«submit» value=«Save Riddle» |
Это немного долго и мучительно запрограммировано, но делает ту работу, которую мы хотим сейчас. Важным является атрибут value с каждой опцией. Это будет значение, которое хранится в базе данных, и оно важно, поскольку в большинстве случаев это то же самое, что и вспомогательный метод, который Sinatra использует для визуализации представления с использованием этого конкретного механизма. Как вы увидите, это делает рендеринг с помощью конкретного движка чрезвычайно простым. Все, что нам нужно сделать сейчас, это изменить код iframe в представлении загадки следующим образом:
@@riddle | |
doctype html | |
html lang=«en» | |
head | |
title== @riddle.title | |
meta charset=«utf-8» | |
style | |
— if @riddle.css_engine ==«css» | |
== @riddle.css | |
— else | |
== send(@riddle.css_engine, @riddle.css) | |
script | |
— if @riddle.js_engine == «javascript» | |
== @riddle.js | |
— else | |
== send(@riddle.js_engine, @riddle.js) | |
body | |
== send(@riddle.html_engine, @riddle.html) |
Давайте подробнее рассмотрим, как мы отображаем HTML. Следующая строка будет использовать свойство html_engine загадки для визуализации HTML-кода с использованием правильного движка:
== send(@riddle.html_engine, @riddle.html)
Метод send
используется для вызова метода, который хранится в свойстве html_engine. Вот почему имя значений в форме было так важно, поскольку они должны точно соответствовать именам методов. Я также немного обманул, так как нет вспомогательного html
метода, но, как я заметил в первой части, уценка в любом случае будет просто отображать сырой HTML, и поэтому, если кто-то выберет опцию HTML, это на самом деле «уценка» это хранится в поле базы данных.
Мы должны сделать немного по-другому для CSS и JavaScript. У Sinatra нет специального помощника для CSS или JavaScript, но, поскольку они уже находятся в тегах <style>
и <script>
соответственно, мы можем просто обслужить это, если пользователь выберет «css» или «javascript» из меню. В противном случае, если они выбирают препроцессор CSS или CoffeScript, тогда значение, хранящееся в поле базы данных, совпадает с именем помощника Sinatra, поэтому мы можем использовать метод send
таким же образом, как и при выборе движка HTML, как вы можете видеть в фрагмент ниже показывает, как CSS-код вставляется в блок стиля:
style | |
— if @riddle.css_engine ==«css» | |
== @riddle.css | |
— else | |
== send(@riddle.css_engine, @riddle.css) |
Заканчивать
Мы практически там! Все, что нам нужно сделать, это немного привести в порядок и добавить немного стиля, чтобы все выглядело немного лучше. Вот стили, которые я использовал:
@@styles | |
@import url(http://fonts.googleapis.com/css?family=Pacifico); | |
$purple:#639; | |
$green:#396; | |
body{ font: 13px/1.4 arial, sans-serif; } | |
header{ overflow: hidden; } | |
.logo{float:left;overflow: hidden;} | |
.logo a{ color: $purple; font: 64px/1 pacifico; text-decoration: none; &:hover{color:$green;}} | |
.title{ color: $green; font: 32px/1 pacifico; } | |
.button {text-decoration: none; font-weight: bold; padding: 4px 8px; border-radius: 10px; background: $green; color: white; border:none; &:hover{background:$purple;}} | |
header .button{ float:left; margin: 36px 10px 0;} | |
form label, input.button {display: block;} | |
form select {display: block;} | |
iframe {width: 100%; min-height: 600px; border: none; } |
И это в значительной степени это! Это работает довольно хорошо, и у нас все еще намного меньше 160 строк кода (включая представления и CSS)! Вы можете увидеть весь код в репозитории GitHub, и готовое приложение работает на Heroku .
Вот несколько дополнений, которые могут быть сделаны, чтобы сделать это, что, вероятно, не будет слишком сложно выполнить:
- Включите опцию включения некоторых библиотек JavaScript в Riddle (это можно легко сделать, просто связавшись со ссылкой CDN для каждой соответствующей библиотеки JS).
- Включите предварительный просмотр загадки, когда пользователь печатает ее (это можно сделать с помощью JQuery и некоторого Ajax)
- Размещайте загадки на github ( этот драгоценный камень поможет с этим)
- Возможность делиться и встраивать загадки (использование iframes делает это очень простым)
- Подсветка кода в текстовых областях
- … и стиль может быть лучше!
Не стесняйтесь раскошелиться на репозиторий GitHub и добавить любую из этих функций!
Я надеюсь, что вы нашли это полезным, а также нашли некоторые полезные вещи о том, как работает Синатра. Пожалуйста, оставляйте любые отзывы или идеи в комментариях ниже.