Creating a web application by writing reusable components
*********************************************************

Now that you know how to write pages in claw, lets move to a further step: writing components.

A real claw web application is made of a lisplet, several pages and resources, and, of course, many reusable components that go into pages.

Using reusable components, may dramatically improve your productivity. You can then create custom components libraries that will give to your web application a crystal clear style, and speed up the creation of repetitive piece of HTML code, as page templates for instance.

So said, let's create our first claw component, a site template.

     (defcomponent site-template () ())

     (defmethod wcomponent-parameters ((o site-template))
       (list :title :required :home-page "/claw/test/index.html"))

     (defmethod wcomponent-template ((o site-template))
       (html>
        (head>
         (title>
          (wcomponent-parameter-value o :title)))
        (body>
         (wcomponent-informal-parameters o)
         (div>
          :style "background-color: #DBDFE0;padding: 3px;"
          (a> :href (wcomponent-parameter-value o :home-page) "home"))
         (htcomponent-body o))))

Thought this is not the best template you can do, it's a nice starting point to explain how components are created (and used).

First let's analyze the defcomponent instruction: this macro has the same signature of the defclass macro, except that it creates a class that is always a WOCOMPONENT subclass.

defcomponent also creates a function whose symbol is the name of the component plus the character '>', SITE-TEMPLATE> in the specific case, that instantiate the corresponding object, and is meant to be used just like any other standard function tag.

The overriding of the method wocomponent-parameters must return an associative list where, if the key value is :REQUIRED, it means that it is mandatory for the constructor function. In our case study, a call to SITE-TEMPLATE> must contains also the keyword :TITLE followed by its value. If the :TITLE is not provided, an error is signaled during the component instantiation.

The overriding of the method wocomponent-template is in charge for the graphic aspect of the component, as you can imagine. Inside this method we have used calls to other three very important component methods:

  - wcomponent-parameter-value is used to retrieve a parameter passed to the constructor function.

  - wcomponent-informal-parameters renders as an associative list of all the parameters not directly declared with the method wocomponent-parameters, but are present in the constructor function, such as may be a :CLASS attribute.

  - htcomponent-body renders the content body of the component

So, a call to the constructor function of our new fresh component, might have this shape:

     (site-template> :title "this is the page title"
                     :class "foo"
                     (p>
                       Hello world))

and will render as

          <html>
            <head>
              <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
              <title>this is the page title</title>
            </head>
            <body class="foo">
              <p>Hello world</p>
              <script type="text/javascript">
          //<!--
          document.addEventListener('DOMContentLoaded', function(e) {}, false);
          //-->
              </script>
            </body>
          </html>

Ouch, this is nearly what we expected, but it seems there are two extraneous tags, do you see them?

They are the <meta> and the <script> tags.

The <meta> tag is inserted by the HTHEAD component, that we have instantiated with HEAD>. The value of the content attribute is taken from the PAGE-CONTENT-TYPE slot method of the PAGE class, whose default is HUNCHENTOOT:*DEFAULT-CONTENT-TYPE*. It is recomended to have ti value set to "text/html; charset=UTF-8".

The <script> tag is used when claw components want to inject their instance javascripts. So, for example, we could create a component that, when clicked, it shows a js alert containing the html component of another component:

     (defcomponent inspector () ())

     (defmethod wcomponent-parameters ((inspector inspector))
       (list :id :required :ref-id :required))

     (defmethod wcomponent-template ((inspector inspector))
       (div> :static-id (htcomponent-client-id inspector)
             (htcomponent-body inspector)))

     (defmethod htcomponent-instance-initscript ((inspector inspector))
       (format nil "document.getElementById\('~a').onclick =
               function \() {
                 alert\(document.getElementById\('~a').innerHTML);
                 };"
               (htcomponent-client-id inspector)
               (wcomponent-parameter-value inspector :ref-id)))

Ok, now we can use our new inspector component inside our page:

     (defmethod page-content ((some-page some-page))
       (let ((hidden-component-id (generate-id "hidden"))
             (rnd-value (prin1-to-string (random 10000))))
         (site-template> :title "this is the page title"
                     :class "foo"
                     (p>
                       (div> :static-id hidden-component-id
                             :style "display: none;" rnd-value)
                       (inspector> :id "inspector"
                                   :ref-id hidden-component-id "Show value")))))

and will render as:

          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
                    "http://www.w3.org/TR/html4/strict.dtd">
          <html>
            <head>
              <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
              <title>this is the page title</title>
            </head>
            <body class="foo">
              <p>
                <div id="hiddenComp" style="display: none;">2351</div>
                <div id="inspector">Show value</div>
              </p>
              <script type="text/javascript">
          //<!--
          document.addEventListener('DOMContentLoaded', function(e) {
          document.getElementById('inspector').onclick =
                    function () {
                      alert(document.getElementById('hiddenComp').innerHTML);
                      };
          }, false);
          //-->
              </script>
            </body>
          </html>