Давно что-то я не писал, а тут выдался свободный денек, чтобы рассказать еще об одной плюшке, которую я узнал. Все приведенное ниже не претендует на ортодоксальную правильность, это лишь мое мнение и мое решение.
Имеется, к примеру, у нас вопросы и ответы и мы, вместо того, чтобы создавать сначала вопрос и потом привязывать к нему по одному ответы, будем делать это все на одной странице.
Решение
В решении нам помогут gem simple_form, который в разы облегчает работу с формами и coffescript, который хоть и транслируется в javascript, но по синтаксису очень похож на руби со своим “сахаром”.
Модели
Как ясно из название, у нас будут две модели: Question и Answer, в них нет ничего сложного
Вопрос
1 2 3 4 5 6 7 8 9 |
|
Ответ
1 2 3 4 5 6 7 |
|
Единственное, что может показаться странным с первого взгляда, так это строка accepts_nested_attributes. Которая говорит, что модель Question может принимать атрибуты и для ответа, т.е. один post запрос может нам и вопрос создать и ответы к нему.
Я для экономии времени использовал twitter-bootstrap-rails gem и scaffold, поэтому все получилось почти готовое. Лишь небольшие правки я внес в форму app/views/questions/form.html.erb_
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Я обернул ответы на вопрос классом .answers, чтобы они визуально отличались, добавил вложенную форму для вопросов с помощью simple_form и две кнопки: “+” – для добавления ответа и “–” – для его удаления
Сейчас задача сводится к тому, чтобы динамически добавлять/удалять ответы на странице без ее перезагрузки, взглянем на генерируемый html
1
|
|
При добавлении нового элемента возрастает цифра в айдишнике и в имени текстового поля. Для того, чтобы создать новый ответы мы будем действовать так: склонируем последний ответ на странице с помощью $.clone(), поменяем все элементы name и id, заменив у них цифру в середине. Заменять будем на new Date().getTime(), который генирует случайное число, хоть и большое, но оно нам подходит. Также нам нужно будет не забыть о лейблах, а то получится, что мы кликаем на лейбл, а в фокус попадает совсем не соответствующий элемент. Итак, поехали.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Осталось добавить обработчик события удаления ответа
1 2 3 4 5 6 |
|
Метод $.closest находит ближайший элемент с классом .answer, поднимаясь вверх по дереву DOM. Находим его, скрываем, а после проигрыша анимации удаляем.
Что можно сделать лучше
- Добавить валидацию на количество ответов в модели, к примеру,
validates_length_of :answers, :minimum => 1
- Добавить проверку, чтобы не удалить последний ответ, после удаления которого мы не сможем добавить новый – клонировать некого :)
- Не использовать скаффолд, который генерирует много ненужного мусора, по крайней мере, для данной задачи
- Да и вообще предела-то совершенству нет :)
Надеюсь, данная неидеальная реализация кому-нибудь в жизни пригодится, вопросы, советы? – велком в комментарии.
Чуть не забыл ссылку на репозиторий на гитхабе