Blog | Vývoj aplikace v Clojure III. díl: formuláře

Vývoj aplikace v Clojure III. díl: formuláře

Celkem asi 15 hodin jsem už věnoval tomu, abych si vytvořil co nejlepší formuláře. Tedy pro mé potřeby co nejlepší.

Co od formulářů potřebuju:

  • popis datové struktury, který bude prakticky spojovat šablonu a databázi
  • možnost vložit inputy, labely, errory do HTML šablony
  • velmi jednoduché vytváření formulářů tvořených 1 hiddenem a 1 submitem

Formuláře a datové struktury

Vytvořit formulář pro datovou strukturu (jak create, tak edit) se mi zmrsklo na velmi jednoduchý kousek kódu. Výňatek z IS, který vyvíjím.

(defn project-form
  "responsible-people Zodpovedne osoby [[person.id person.name][person.id ...]]
  statuses Typy zakazek (nova, upsell...)"
  [responsible-people statuses]
  (make-form :project-form (ordered-map
                        ; jen v edit
                        :id {:type :hidden}

                        :name {:label "Název zakázky"}
                        :users_id {:type :select :label "Zodpovědná osoba" :options responsible-people}
                        :company {:label "Jméno zákazníka"}
                        :status {:type :select :label "Kategorie zakázky" :options statuses}
                        :deadline_at {:class "datepicker" :label "Termín dokončení"}
                        :licences {:label "Počet licencí"}
                        :price_first {:label "Jednorázová faktura"}
                        :price_before {:label "Původní měsíční cena"}
                        :price_monthly {:label "Měsíční fakturace"}
                        :price_difference {:label "Peníze měsíčně (rozdíl)"}
                        :note {:type :textarea :label "Poznámka"}
                        :dohled {:type :checkbox :label "Zařazeno v dohledu"}
                        :billing {:type :checkbox :label "Zařazeno v billingu"}
                        :first_invoice {:type :checkbox :label "Jednorázová faktura odeslána"}
                        :finished {:type :checkbox :label "Projekt je dokončen"}
                        :submit {:type :submit :class "btn btn-success" :value "Uložit"})))

(defn project-edit-form [responsible-people statuses default-values]
  (set-defaults default-values (project-form responsible-people statuses)))

(defn project-create-form [responsible-people statuses]
  (let [form (project-form responsible-people statuses)
        fields (apply dissoc (form :fields)
                      '(:id :dohled :billing :first_invoice :finished))]
    (assoc form :fields fields))) 

Snadné, skoro není co testovat. Přesto mám na generování těchto formulářů i jednoduché testy.

(fact "create form is form"
       (> (-> (project-create-form [] []) :fields count) 0) => true
       (> (-> (project-edit-form [] [] {}) :fields count) 0) => true
       (contains? (-> (project-create-form [] []) :fields) :fields) => false
       (contains? (-> (project-edit-form [] [] {}) :fields) :fields) => false
      ) 

Jak vidíte, vypisuju téměř jen to nezbytné minimum. V budoucnu přibudou určitě ještě nějak řešené validace, které zatím obstarává jen model. Nejrůznější validátory se dají najít v <a href"http://www.clojure-toolbox.com/">Clojure Toolboxu, tak si jeden vyberu a ten použiju.

Když chci získat hodnoty pro uložení v modelu, zavolám jen:

(get-values form request) 

A dostanu pole klíč/hodnota. Checkboxy už převádím na boolean. Ještě se musím rozhodnout, jestli inputy typu date převádět na <a href"https://github.com/clj-time/clj-time">clj-time a jak naložit s inputy typu file.

Jednotlačítkové formuláře

Občas potřebuju formulářové odkazy typu “Hotovo”, “Archivovat”, “Smazat” a jen chci, aby se odeslaly POSTem. Jsou to jednotlačítkové formuláře.

Přidat si 8 jednořádkových formulářů je jen na 10 řádek s použitím funkce make-delete-form ve formulářích:

; v implementaci obecneho form DSL

(defn make-delete-form
  [submit-label & {:keys [action id] :or {action "delete" id nil}}]
  (let [base-fields {
                     :action {:type :hidden :value action}
                     :submit {:type :submit :value submit-label}}
        fields (if (nil? id)
                 base-fields
                 (assoc base-fields :id {:type :hidden :value id}))]
    (make-form :delete-form fields)))

; v namespace s formulari primo v aplikaci

(defn project-submit-form [action label id]
  (assoc-in (make-delete-form label :id id) [:params :action] action))

(defn portace-finished-form [id] (project-submit-form "/project/a-finished" "Hotovo" id))
(defn datovka-finished-form [id] (project-submit-form "/project/b-finished" "Archivovat" id))
(defn aplikace-finished-form [id] (project-submit-form "/project/c-finished" "Smazat" id))
(defn billing-finished-form [id] (project-submit-form "/project/d-finished" "Aktivovat" id))
(defn dohled-finished-form [id] (project-submit-form "/project/e-finished" "Přenést do mobilu" id))
(defn crm-finished-form [id] (project-submit-form "/project/f-finished" "Zálohovat" id))
(defn first-invoice-finished-form [id] (project-submit-form "/project/g-finished" "Obnovit ze zálohy" id))
(defn project-finished-form [id] (project-submit-form "/project/h-finished" "Dokončit projekt" id)) 

Šablony

Další věc, v které dělám vývoj, jsou šablony. V šablonách je užitečné mít 2 možnosti:

  • mít možnost vyrenderovat celý form 1 příkazem defaultním generátorem
  • mít možnost získat jako string začátek formu, konec, labely, inputy, errory a vyrenderovat je hromadně

Jiné způsoby, jako dekorátory v Zend Frameworku, se mi neosvědčily a věci jako XML+XPATH pro tvorbu formulářů, se mi ani nechce zkoušet.

Mé formuláře mají obojí:

(render-form form) 

Vyrenderuje formulář jako string obsahující HTML.

(render-form-parts form) 

Vyrenderuje pro formulář (obsahující prvky policko1, policko2, policko3, polickoN) datovou strukturu:

{:form-begin String
 :form-end String
 :policko1 {:input String :label String :errors String :row String}
 :policko2 {:input String :label String :errors String :row String}
 :policko3 {:input String :label String :errors String :row String}
 :polickoN {:input String :label String :errors String :row String} }

Formulář předaný do šablony z controlleru takto:

(defn project-create-page []
  (access/required-right* :can_edit_projects (fn []
              (layout/render "project-create.html" {
                        :form (form/render-form-parts (make-project-create-form)) }))))

Vypadá v šabloně takto:

{% extends "projectlist_clj/views/templates/base.html" %}
{% block content %}
{{form.form-begin|safe}}
<div class"form-inputs">
    <div class"name">{{form.name.label}} <div class"name-input">{{form.name.input|safe}} {{form.name.errors|safe}}
    <div class"form-row">{{form.users_id.label|safe}} {{form.users_id.input|safe}} {{form.users_id.errors|safe}}
    <div class"form-row">{{form.status.label|safe}} {{form.status.input|safe}} {{form.status.errors|safe}}
  <div class"form-row">{{form.company.label|safe}} {{form.company.input|safe}} {{form.company.errors|safe}}
</div>
{{form.submit.input|safe}}
{{form.form-end|safe}}
{% endblock %}

Rád bych se v budoucnu zbavil nutnosti psát filtr safe. Do příštího článku o formulářích o tom určitě napíšu, jak jsem to vyřešil.

Když chci vypsat pro M řádek N formulářů (modifikátorů), vypadá zdroják takto:

; v realizacich formularu

(defn render-project-forms [id] {
                                 :portace_finished (render-form (portace-finished-form id))
                                 :datovka_finished (render-form (datovka-finished-form id))
                                 :aplikace_finished (render-form (aplikace-finished-form id))
                                 :billing_finished (render-form (billing-finished-form id))
                                 :dohled_finished (render-form (dohled-finished-form id))
                                 :crm_finished (render-form (crm-finished-form id))
                                 :first_invoice_finished (render-form (first-invoice-finished-form id))
                                 :project_finished (render-form (project-finished-form id)) })

; controller

(defn add-modificator-forms [projects]
  (map #(into % {:form (project-form/render-project-forms (% :id))}) projects))

(defn project-list-page []
  (access/required-right* :can_edit_projects
                          (fn []
                            (layout/render "list.html"
                                 {:page-name "List projektů"
                                  :projects (add-modificator-forms (projectlist-clj.models.db/unfinished-projects))}))))

Vypsání potom v šabloně lze udělat snadno:

{% for project in projects %}
<div class"row">
 {{project.name}}
  {{project.form.portace_finished}}
 {{project.form.aplikace_finished}}
    {{project.form.datovka_finished}}
 {{project.form.billing_finished}}
 {{project.form.dohled_finished}}
  {{project.form.crm_finished}}
 {{project.form.first_invoice_finished}}
   {% if project.can_be_finished %}
      {{project.form.project_finished}}
 {% endif %}
</div>
{% endfor %}

Je pravda, že psát si své formuláře může vypadat jako znovuvynalézání kola. Na druhou stranu, dal jsem tomu jen 15 hodin a výsledek se rychle blíží tomu, co mi bude další roky sloužit. Tipuju ještě 15 hodin a frekvence úprav obecných formulářů se výrazně sníží. A budu mít nástroj, kde snadno budu moci snadno vytvářet a editovat libovolnou datovou strukturu. Formuláře budou dobře vypadat a dobře se používat a objem programování bude minimální.

Programování

Předejte zkušenosti i dalším a sdílejte tento článek!

Následující článek


Jiří Knesl
Business & IT konzultant

Jiří Knesl poprvé začal programovat v roce 1993. Od té doby, díky skvělým učitelům a později zákazníkům, měl možnost neustále růst v oboru vývoje webových aplikací a informačních systémů. v roce 2002 se přidal zájem o ekonomii a v roce 2006 o organizaci práce. Vším tím se konstantně profesně zabývá jak ve svém podnikání, tak i u zákazníků. Za posledních 5 let vydal na tato témata přes 400 článků.

Prohlédněte si moje reference

Mám zkušenosti z rozsáhlých projektů pro korporace, velké podniky, střední i malé firmy, ale i pro startupy v cloudu. Zvyšoval jsem jejich know-how, pomáhal nastavovat jejich organizační strukturu, byl lektorem a mentorem v náročných situacích. Podívejte se, jak vidí můj přínos samotní klienti.

Sledujte mé postřehy na sociálních sítích