Reusable Components

In the previous chapter, we've shown how to create a simple counter application. Starting from that example, we will create a reusable counter component to keep track of the number of attendees in a classroom.

Creating a list of components

Let's assume we have 3 classrooms. and we have to keep track of the number of attendees for each classroom. We will need a list to display each classroom, and next to each item we will need a counter where we can increment or decrement that number.

We can start by simply listing the 3 classrooms as a list:

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'}]
    
    Ul{
        Li{ T`Classroom A` }
        Li{ T`Classroom B` }
        Li{ T`Classroom C` }
    }
}

Now, we could start adding increment and decrement buttons to each item in the list, but that would be repeating ourselves too much. What we should do is extract each list as a separate component:

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

component Classroom < Li{
    T`Classroom ...`
}

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

Notice we've extended the Li component, so now Classroom will simply act as an Li. The actual html output will still be an <li>. On top of that, we are adding the text T to each classroom automatically. The only issue is all the classes will now have the same name. We can fix that with a label property:

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

component Classroom < Li{
    id:  classroom
    string label: ''

    T{ text: 'Classroom ' +  classroom.label }
}

component Index < PageView{
    static any[] use = [ScopedStyle{ src: './index.css'}]
    
    Ul{
        Classroom{ label: 'A' }
        Classroom{ label: 'B' }
        Classroom{ label: 'C' }
    }
}

We've now created a label property for the Classroom component. In the Classroom component, we've setup an id for the component, then we used the id to reference the label property when populating the text:

T{ text: 'Classroom ' +  classroom.label }

Creating the counter

Now we can add the counter to the Classroom component. We will need an attendeeCount property to store the number of attendees, an increment button, a decrement button, and a text showing the actual count:

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

component Classroom < Li{
    id:  classroom
    string label: ''
    number attendeeCount: 0

    T{ text: 'Classroom ' +  classroom.label }
    Button{ // decrement attendeeCount
        on click: () => { 
            if (  classroom.attendeeCount > 0 ) 
                 classroom.attendeeCount--
        }
        T`-`
    }
    Span{ T{ text:  classroom.attendeeCount } } // display attendeeCount
    Button{ // increment attendeeCount
        on click: () => {  classroom.attendeeCount++ }
        T`+`
    }
}

component Index < PageView{
    static any[] use = [ScopedStyle{ src: './index.css'}]
    
    Ul{
        Classroom{ label: 'A' }
        Classroom{ label: 'B' }
        Classroom{ label: 'C' }
    }
}

And we now have a working list of classrooms with counters.

Separating the state

In larger applications, it's a good ideea to have the state separate from the view, as it's easier to manage, read and also access from the view hierarchy. In Live Elements, we do this by simply moving the state properties into their own component. In the counter example, we have move the label and attendeeCount properties to a ClassroomData component:

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

component ClassroomData{
    string label: ''
    number attendeeCount: 0
}

component Classroom < Li{
    id: classroom
    ClassroomData data: ClassroomData{}

    T{ text: 'Classroom ' + classroom.data.label }
    Button{
        on click: () => { 
            if ( classroom.data.attendeeCount > 0 )
                classroom.data.attendeeCount--
        }
        T`-`
    }
    Span{ T{ text: classroom.data.attendeeCount } }
    Button{
        on click: () => { classroom.data.attendeeCount++ }
        T`+`
    }
}

component Index < PageView{
    static any[] use = [ScopedStyle{ src: './index.css'}]
    
    Ul{
        Classroom{ data: ClassroomData{ label: 'A' } }
        Classroom{ data: ClassroomData{ label: 'B' } }
        Classroom{ data: ClassroomData{ label: 'C' } }
    }
}

Classroom now has a data assigned with a ClassroomData instance, which we now access via classroom.data.

Conclusion & Next Step

In this chapter we've looked at how to create reusable components and separate their data. Next, we will look at styling these components.