40ants / reblocks

A fork of Weblocks Common Lisp web framework
https://40ants.com/reblocks/
Other
61 stars 10 forks source link

Rendering in a table issue: widget is rendered as a div with no structure #10

Open vindarel opened 4 years ago

vindarel commented 4 years ago

Hi,

I have an issue rendering a widget as a row inside a table. It is rendered inside a div and it doesn't respect the markup of my render function.

This reminds me of a fix we implemented some months ago. We corrected get-html-tag for it to consult Spinneret and return a :tr or a :td if we are inside a table, and a :div otherwise.

I start like this:

(defmethod render ((widget book-widget))
  (let ((book (book widget)))
    (with-html
      (:td (bookshops.models:title book))
      (:td (bookshops.models:authors book))
      (:td (bookshops.models:price book) "€")
      (:td (format nil "x ~a" (bookshops.models:quantity book)))
      (:td (with-html-form (:POST (lambda (&key &allow-other-keys)
                                    (add-book widget)))
             (:input :type "submit"
                     :title "Add 1 copy to your stock"
                     :value "+ 1"))))))

add-book updates the widget:

(defun add-book (book-widget)
  "Add one copy to the default place."
  (let ((book (book book-widget)))
    (bookshops.models:add-to *place*
                             book)
    (update book-widget)))

I render the table inside my main widget:

    (:table
     (:tbody
      (loop for elt in (books widget)
         do (with-html
              (:tr (render elt))))))))

and it displays the table correctly. When I click on the +1 button, a new widget is inserted above the widget that was clicked, inside the tr, it is rendered as a div and it doesn't contain any :td, only the plain text title + author + price + quantity concatenated.

<tr>  <-- tr of first page load
  // the thing appearing after the click:
  <div class="widget book-widget" id="dom54">Le langage lisp
   // everything is inlined, rendered as text, no td:
    Cayrol
    13.72 €€
    x 7
    <form action="/" method="post" onsubmit="initiateFormAction(&quot;1402%3A031f2f8e626ed376d62fe6024238df39b2eac76d&quot;, $(this), &quot;&quot;); return false;">

      <span>
        <input type="submit" title="Add 1 copy to your stock" value="+&nbsp;1">
        <input name="action" type="hidden" value="1402:031f2f8e626ed376d62fe6024238df39b2eac76d"></span>
    </form>

  </div>

  // the widget that was clicked:
  <td>Le langage lisp </td>
  <td>Cayrol</td>

Selection_045

  1. tracing my render function: it is called.

  2. logging get-html-tag and thus spinneret:get-html-path: the path is NIL (??)

  3. I tried rendering my widget with an enclosing :tr: it doesn't change that the path is NIL and the rendered html as no td but all the fields as text.


Do you have any pointers or best practices to share?

ps: code

vindarel commented 4 years ago

Reproducible example:

(defpackage testtable
  (:use #:cl
        #:weblocks-ui/form
        #:weblocks/html)
  (:import-from #:weblocks/widget
                #:render
                #:update
                #:defwidget)
  (:import-from #:weblocks/actions
                #:make-js-action)
  (:import-from #:weblocks/app
                #:defapp)
  (:import-from #:weblocks-navigation-widget
                #:defroutes))

(in-package :testtable)

(defapp testtable :prefix "/")

(weblocks/debug:on)

(defparameter *quantity* 0 "Simulate a book's quantity in the DB.")

(defwidget book-widget (weblocks-ui:ui-widget)
  ())

(defun add-book (book-widget)
  "Add one copy to the default place."
  (incf *quantity*)
  (update book-widget))

(defmethod render ((widget book-widget))
  (with-html
    (:td "Title: On Lisp")
    (:td "quantity:" *quantity*)
    (:td (with-html-form (:POST (lambda (&key &allow-other-keys)
                                  (add-book widget)))
           (:input :type "submit"
                   :value "Add 1")))))

(defwidget book-list-widget ()
  ())

(defmethod render ((widget book-list-widget))
  (with-html
    (:table
     (:tbody
      (dolist (elt (list (make-instance 'book-widget)
                         (make-instance 'book-widget)))
         do (with-html
              (:tr (render elt))))))))

(defmethod weblocks/session:init ((app testtable)) 
  (declare (ignorable app))
  (make-instance 'book-list-widget))

(defun start ()
  (weblocks/server:start :port 8910))
vindarel commented 4 years ago

idea: the tbody.

(defmethod get-html-tag ((widget t))
  (let ((path (get-html-path)))
    (log:debug path)
    (case (first path)
      (:table
       :tr)
      (:tr
       :td)
      (t
       :div))))

but (get-html-path) still returns NIL, hence get-html-tag a DIV.

svetlyak40wt commented 4 years ago

Excuse me, can't dive into this problem now.