Недавно один из пользователей попросил добавить эту мелкую фичу. Как обычно, дольше размышлять чем делать... Некоторые из посетивших мыслей решил записать в маленькую статью, в качестве пояснения/сопровождения к коммиту в гитхабе. Ну и без бага не обошлось - глупо, но поучительно :)
(не рассматривайте эту статью в качестве поучения "как надо писать" - код несколько архаичен и коряв, и реализация на KV таблице может выглядеть спорно)
Постановка Задачи
На форуме один из незнакомых, новых пользователей пишет такое пожелание. Мол, сделайте возможность отмечать задачи звёздочкой. И дальше поясняет типичный "юзкейс" - если не удаётся решить задачу и хочется отложить её на потом.
Сознаюсь, сперва я не так понял что он хочет - подумал что такие отметки будут видны всем. Хотел вежливо отказаться. Потом дошло, это просто персональный лист закладок. Просто он их не "bookmarks" обозвал а "star", я и попутал в уме с ситуацией когда звёздочками отмечаются сложные задачи.
После уточняющего пары уточняющих комментариев все расставилось по местам. Однако сразу задумываюсь, как это в интерфейс должно вписаться. Как отметить, понятно - сделаем "звёздочку" где-то на странице задач - и делов-то. А вот что где потом список отмеченных показывать? Заводить отдельную страничку (кроме персонального списка решенных и нерешенных в профиле) - вроде жирно. Наконец подумал - сделаю просто в общем списке задач выделение цветом для тех которые отмечены звездочкой. И при дефолтной сортировке закину их наверх.
Реализация
Самый назойливый вопрос - где хранить "букмарки" пользователя. Как человек ленивый я ужасно не люблю под такие мелочи добавлять новые таблицы - да и добавлять новые колонки в существующие тоже. К счастью есть волшебная табличка "для всего" в стиле key-value (кто-то называет такие Service Table). Вот туда и будем добавлять - ключом является star.$userid
а значение - список номеров отмеченных задач одной строкой, через запятую. Немного вульгарно, но т.к. нам не нужно искать по заданной задаче кто её отметил, это вполне сойдёт.
Таким образом вся логика нового функционала умещается в новый контроллер ctl/task/Star.php
однако кроме него приходится сделать мелочные добавления ещё там и сям. Вот список файлов в коммите:
modified: css/common_bs.css
modified: ctl/task/List.php
new file: ctl/task/Star.php
modified: ctl/task/View.php
modified: js/task/_view.js
modified: pages/task/list.php
modified: pages/task/view.php
я люблю вообще крохотные коммиты - максимум 2-3 файла, но здесь поскольку затрагивается UI, выходит немножко больше...
В шаблоне страницы pages/task/view.php
добавилась звёздочка - долго думал куда её деть - прилепил рядом с заголовком H1
- нельзя сказать что интуитивно, но там есть подсказка всплывающая, разберутся (также изменение в контроллер ctl/task/View.php
- чтобы подгружать состояние звёздочки при открытии страницы).
Реакция на клики по этой звёздочке попала в великий и ужасный js/task/_view.js
- он уже давно выглядит крупнее чем хотелось бы - но ведь и сама страница для работы над задачей - основная на которой пользователи проводят больше времени, выходит, там много функционала. М.б. когда-то дойдут руки почикать на меньшие кусочки.
Джаваскрипт немудряще посылает запрос новому контроллеру и меняет состояние звёздочки. Сами звёздочки отображены символами шрифта, а не картинками - но минимальный стиль для них в css/common_bs.css
добавить пришлось.
Остались небольшие изменения в отображение списка задач (шаблон pages/task/list.php
и контроллер ctl/task/List.php
).
Бажок
Через несколько недель заметил что на форуме жалуются на баг. Поставить звёздочку удаётся, а снять... Она визуально "выключается" но не снимается на самом деле.
Трудно понять как и я и автор идеи пропустили это при начальном тестировании. очень глупая ошибка - когда удаляется номер задачи из списка, это делается функцией array_splice
:
$exists = array_search($taskid, $starred); // поискали есть ли уже эта задача в списке
// ...
array_splice($starred, $exists, 1); // удалили из списка
Это исключительно нелепо, но по рассеянности изначально данная строчка была написана так:
$starred = array_splice($starred, $exists, 1);
скорее всего в этот момент в голове была аналогия с функцией array_slice
- та возвращает изменённый массив а исходный не трогает. Здесь же массив меняется (передан по ссылке) а возвращаемое значение - это удалённые элементы.
Возможно не первый раз я об это спотыкаюсь. Возможно это один из случаев за которые поругивают PHP - похожие фукнции непохоже работающие с параметрами - но в целом конечно нужно быть внимательнее или использовать какой-нибудь полезный статический анализатор кода. Да и тесты неплохо бы писать :)
Заключение
В очередной раз убеждаюсь что даже к мнению новых-зелёных пользователей полезно прислушаться. По большому счету вся фича заняла что-то около человеко-часа, хотя первая реакция конечно "да зачем это делать" :) Реализацию на использующейся tag-value таблице быть может когда-то захочется заимпрувить если появятся новые пожелания к этому функционалу - в то же время пока нет уверенности что фича нужна большому числу пользователей (и каждый из них будет отмечать много задач) то лучше не распаляться на отдельные таблицы и подобное.
Вот когда руки дойдут лайки к комментариям на форуме приделать - тут пожалуй без отдельной таблички не стоит и пытаться.