Web DOM

In the previous chapter, we've learned how to setup a playground, and how to run a console application inside the playground. We've also introduced components.

In this chapter we will work with the DOM in Live Elements.

Components and the DOM

In Live Elements, each html tag or (DOM element) is a separate component. For example. the <p> tag corresponds to the P component. The <h1> tag corresponds to the H1 component. All of these DOM components, like the H1 and P are declared in live-web.dom module. We've already learned that creating a component is done by using curly braces after the component name, so creating an html elements like <h1> is simply created like this: H1{}.

Let's try it out in the playground. We need to import live-web.dom and we can create a paragraph:

import live-web
import live-web.dom // import live-web.dom to make P available

component App < Application{
    run: () => {
        const p = P{} // create P
        console.log(p)
    }
}

We will see the console output: [object Object].

This is great, but we should add this paragraph to our page so we can see it. Let's first add some text to our paragraph. We can do this using the text T component:

import live-web
import live-web.dom

component App < Application{
    run: () => {
        const p = P{ T{ text: 'Hello' } }
        console.log(p)
    }
}

Then, we can use P.expandTo() function, which takes a javascript dom element. We'll use document.body:

import live-web
import live-web.dom

component App < Application{
    run: () => {
        const p = P{ T{ text: 'Hello' } }
        p.expandTo(document.body)
    }
}

The paragraph is now visible on our web page! This is great, let's replace it with a title:

import live-web
import live-web.dom

component App < Application{
    run: () => {
        const h1 = H1{ T{ text: 'Hello' } }
        h1.expandTo(document.body)
    }
}

Great, now the 'Hello' text is a title! Let's add both the title and paragraph:

import live-web
import live-web.dom

component App < Application{
    run: () => {
        const h1 = H1{ T{ text: 'Hello' } }
        h1.expandTo(document.body)
        const p = P{ T{ text: 'World' } }
        p.expandTo(document.body)
    }
}

The title is not visible, and that's because Live Elements clears the html dom node on which it expands. So, when p is expanded, the h1 that was appended to the document.body is now cleared. To show both the title and paragraph, we can wrap everything inside a Div and then expand the Div to the document.body:

import live-web
import live-web.dom

component App < Application{
    run: () => {
        const div = Div{
            H1{ T{ text: 'Welcome' } }
            P{ T{ text: 'Hello' } }
        }
        div.expandTo(document.body)
    }
}

Notice we've added H1 and P as children to the Div. We've introduced components that support children in the previous chapter.

We can make use of another concept to shorten our code even more. Creating the T component is quite long:

T{ text: 'Welcome' }

The T component however has a constructor taking a string argument. We can create it this way:

T`Welcome`

The application code now becomes:

import live-web
import live-web.dom

component App < Application{
    run: () => {
        const div = Div{
            H1{ T`Welcome` }
            P{ T`Hello` }
        }
        div.expandTo(document.body)
    }
}

It's shorter, but we can do better. P and H1 also have constructors taking a string argument:

import live-web
import live-web.dom

component App < Application{
    run: () => {
        const div = Div{
            H1`Welcome`
            P`Hello`
        }
        div.expandTo(document.body)
    }
}

Both components will create a T element internally when they are created this way. Not all components support this, Div for example doesn't have a constructor taking a string argument, so Div`Text` won't create the text with the div.

Events & Listeners

Components in Live Elements can declare events, and also listeners for those events. In live-web.dom all components have all the DOM events available in html: click, mousenter, mouseleave, focus, load, resize, etc. We can listen to those events using the on <eventName> syntax. Let's create a Button to see how this works:

import live-web
import live-web.dom

component App < Application{
    run: () => {
        const div = Div{
            H1`Welcome`
            P`Hello`
            Button{
                on click: () => { console.log('Clicked') }

                T`Click me`
            }
        }
        div.expandTo(document.body)
    }
}

The on click listener takes a function as it's argument, and will execute that function every time the button is clicked.

Creating a counter

In the previous chapter we've learned about property bindings. Using the Button and the concept of property bindings we can create a counter in Live Elements quite easily:

import live-web
import live-web.dom

component App < Application{
    run: () => {
        // declare a Counter
        component Counter{ 
            number value: 0 
        }; 

        // create the counter
        const counter = Counter{} 
        
        const div = Div{
            // bind to counter value property
            P{ T{ text: counter.value }} 
            Button{
                on click: () => { counter.value++ }
                T`Increment`
            }
        }
        div.expandTo(document.body)
    }
}

T.text property is bound to counter.value. Every time the button is clicked, we increment the value, which will change the text property.

Styling

You will notice the playground also has a stylesheet file index.css. Modifying the file, like for example making the paragraph red: p{ text: red; } will have no effect. This is because we are not referencing the stylesheet anywhere in Index.lv. The console application won't be able to directly do that. This is why we will switch to a new project structure, one used specifically to create Live Elements pages. You can go to the 'Open' icon, and select 'New Page'.

This will open up the following Index.lv file:

import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style

component Index < PageView{
    static any[] use = [ScopedStyle{ src: './index.css'}]
}

Here the Index component inherits PageView. PageView does not have a run function, but rather it supports adding children as part of the page. Try appending the following header and save the file:

import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style

component Index < PageView{
    static any[] use = [ScopedStyle{ src: './index.css'}]
    H1`Hello`
}

The 'Hello' message should now be visible. To transfer the counter example we implemented above, we can add the counter as a property to the Index component:

import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style

component Counter{ 
    number value: 0 
}

component Index < PageView{
    id: index // this can now be referenced via 'index'
    static any[] use = [ScopedStyle{ src: './index.css'}]

    // counter property is now a new counter component
    Counter counter: Counter{}
    
    Div{
        P{ T{ text: index.counter.value }} 
        Button{
            on click: () => { index.counter.value++ }
            T`Increment`
        }
    }
}

We declare the Counter component outside of the Index component. We also define an id for the Index component so we can reference it in it's children. Then we access the counter value property via index.counter.value.

Notice the Index component also defines a static property use:

static any[] use = [ScopedStyle{ src: './index.css'}]

The property let's the server know it needs to include the index.css file as part of the page. Whenever the server will load this PageView, it will also include the index.css stylesheet.

We can now style the page via the index.css file:

p{ 
    color: blue; 
    font-size: 30px; 
}

css classes and selectors

To set a class for the paragraph we can use the classes property, which takes an array of classes:

P{
    classes: ['text-large', 'border-blue']
}

This is equivalent to the following html:

<p class="text-large border-blue"></p>

We can modify the previous example to color the number blue if the number is even, or purple otherwise:

import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style

component Counter{ 
    number value: 0 
}

component Index < PageView{
    id: index // this can now be referenced via 'index'
    static any[] use = [ScopedStyle{ src: './index.css'}]

    // create the counter component
    Counter counter: Counter{}
    
    Div{
        P{ classes: [index.counter.value % 2 === 0 ? 'even' : 'odd']
            T{ text: index.counter.value }
        } 
        Button{
            on click: () => { index.counter.value++ }
            T`Increment`
        }
    }
}

And the css:

p.odd{ 
    color: blue; 
    font-size: 30px; 
}
p.even{
    color: purple; 
    font-size: 30px; 
}

Other component properties

All components in live-web.dom besides the text node T inherit from DOMElement from which they share the following properties:

  • glid: the global id. The same as id in html.
  • classes: an array of classes. Similar to the class attribute in html.
  • style: an object with style properties. The same as the style attribute in html.
  • dom: the html dom element, if any has been assigned. This is assigned once the element is expanded to the dom.
  • props: any additional attributes assigned to the element.

As an example on how to use some of these properties, the following paragraph is declared in both Live Elements and html:

P{ classes: ['paragraph'] glid: 'main-paragraph' style: { marginLeft: '10px'} props = { data = { info='custom-paragraph' } }  }
<p class="paragraph" id="main-paragraph" style="margin-left:10px;" data-info="custom-paragraph" ></p>

Additionaly to the properties shared from DOMElement, some components add their own properties. For example the anchor Acomponent has the href property:

A{ href: '/link/to/other/page' T`Link to other page` }

Conclusion & Next Step

This chapter covered using Live Elements to work with the html DOM, styling the page and working with events and user interactions. The following chapter will cover creating reusable components.