Components - PageAmp/pageamp GitHub Wiki

Custom Tags

PageAmp supports defining custom tags implemented as server-side macros.

You can define one like this:

<:define tag="my-product">
  <div>Name: [[name]]</div>
  <div>Price: [[price]]</div>
</:define>

and then use it like this:

<my-product :name="Thingy" :price="2$"/>

which results in this output:

<div>
  <div>Name: Thingy</div>
  <div>Price: 2$</div>
</div>

The <:define> tag gives a name to an HTML block. By default the root of the block is a DIV, but you can specify what the define extends, e.g.:

<:define tag="my-code:code">...</:define>

which would generate:

<code>...</code>

Note: custom tag names must include a - character. This is done to encourage proper naming. Project-wide tags may start with my- or app- prefixes, for example. Reusable components should be given names prefixed with a common collection name, as it's done with kits.

Defaults

A custom tag can declare default values for the attributes it relies on:

<:define tag="my-product" :name="Product" :price="0">
  <div>Name: [[name]]</div>
  <div>Price: [[price]]</div>
</:define>

this means if you don't specify them where you use the tag:

<my-product/>

the output will have be populated with the defaults:

<div>
  <div>Name: Product</div>
  <div>Price: 0</div>
</div>

Slots

HTML added to a custom tag is appended to its root by default.

For example given this definition:

<:define tag="my-product">
  <div>Name: [[name]]</div>
  <div>Price: [[price]]</div>
</:define>

you can add custom markup to its contents like this:

<my-product :name="Thingy" :price="2$">
  <img src="..."/>
</my-product>

which generates this output:

<div>
  <div>Name: Thingy</div>
  <div>Price: 2$</div>
  <img src="..."/>
</div>

This definition would generate exactly the same output, but it explicitly defines where the the custom tag wants custom markup to be added:

<:define tag="my-product">
  <div>Name: [[name]]</div>
  <div>Price: [[price]]</div>
  <:slot name="default"/>
</:define>

and it does the same as above because custom markup is added to the default slot by... er... default.

When a custom tags defines more than one slot:

<:define tag="my-product">
  <div>Name: [[name]]</div>
  <div>Price: [[price]]</div>
  <div>
    <:slot name="details"/>
  </div>
  <!-- the following tag is reduntant, it would be
       implicitly added anyway -->
  <:slot name="default"/>
</:define>

you can chose where your custom markup goes using the :plug attribute:

<my-product :name="Thingy" :price="2$">
  <span :plug="details">Thingy does things</span>
  <img :plug="default" src="..."/>
</my-product>

Note: :plug="default" above is reduntant, custom markup targets the default slot by... uh... default.

which would generate:

<div>
  <div>Name: Thingy</div>
  <div>Price: 2$</div>
  <div>
    <span>Thingy does things</span>
  <div>
  <img src="..."/>
</div>

Inheritance

A custom tag can extend another custom tag.

For example, given this base definition:

<:define tag="my-product">
  <div>Name: [[name]]</div>
  <div>Price: [[price]]</div>
  <div>
    <:slot name="details"/>
  </div>
</:define>

you can define this specialized version:

<:define tag="my-bundle:my-product">
  <span :plug="details">This is a bundle</span>
</:define>

which generates this when used:

<div>
  <div>Name: ...</div>
  <div>Price: ...</div>
  <div>
    <span>This is a bundle</span>
  </div>
</div>

Components

Custom tags can be defined and used in the context of a project without too much thought: they are mainly used to avoid repeating code and make it more readable by giving meaningful names to blocks of HTML.

When a custom tag proves useful in general, and could be reused in other projects, it's a good idea to turn it into a proper component.

Components are defined in their own module and include their default styling.

For example a module /components/app-product.htm could look like this:

<lib>

  <style>
    .app-product {
      border: 1px solid rgba(0,0,0,.15);
    }
    .app-product > .name {
      font-weight: bold;
    }
    .app-product > .price {
      font-style: italic;
    }
  </style>

  <:define tag="app-product"
      class="app-product"
      :name="Product"
      :price="0">
    <div class="name">Name: [[name]]</div>
    <div class="price">Price: [[price]]</div>
  </:define>

</lib>

Component definitions are meant to be imported into your <head> tag. For example this page:

<html>
  <head>
    <:import src="/components/app-product.htm"/>
  </head>
  <body>
    <app-product :name="Thingy" :price="2$"/>
  </body>
</html>

would generate this output:

<html>
  <head>
    <style>
      .app-product {
        border: 1px solid rgba(0,0,0,.15);
      }
      .app-product > .name {
        font-weight: bold;
      }
      .app-product > .name {
        font-style: italic;
      }
    </style>
  </head>
  <body>
    <div class="app-product">
      <div class="name">Name: Thingy</div>
      <div class="price">Price: 2$</div>
    </div>
  </body>
</html>

Component can be restyled by overriding their CSS:

<html>
  <head>
    <:import src="/components/app-product.htm"/>
    <style>
      .app-product {
        background: #345;
        color: #fff;
        border: 0;
      }
    </style>
  </head>
  <body>
    <app-product :name="Thingy" :price="2$"/>
  </body>
</html>

Or, they can provide styling options themselves:

<lib :app_product_fg="#000"
     :app_product_bg="#fff"
     :app_product_border="1px solid rgba(0,0,0,.15)">

  <style>
    .app-product {
      color: [[app_product_fg]];
      background: [[app_product_bg]];
      border: [[app_product_border]];
    }
    .app-product > .name {
      font-weight: bold;
    }
    .app-product > .name {
      font-style: italic;
    }
  </style>

  <:define tag="app-product"
      class="app-product"
      :name="Product"
      :price="0">
    <div class="name">Name: [[name]]</div>
    <div class="price">Price: [[price]]</div>
  </:define>

</lib>

Thanks to <:include> and <:import> behaviour, the :app_product_* module attributes are available as <head> attributes, and can be personalized there:

<html>
  <head :app_product_fg="#fff"
        :app_product_bg="#345"
        :app_product_border="0">
    <:import src="/components/app-product.htm"/>
  </head>
  <body>
    <app-product :name="Thingy" :price="2$"/>
  </body>
</html>

To recap, components:

  • are defined in their own eponymous modules
  • include their default CSS classes, prefixed with their name
  • provide one or more custom tags, also prefixed with their name.

PageAmp components are the building blocks upon which kits, coherent and visually themable collections of components, are based.

⚠️ **GitHub.com Fallback** ⚠️