Tutorial

Back to Tutorials

Custom Assignment Widget

Build an Assignment widget in the HTML widget

Explore using Frog for CPD... Professional Development Platform

How I made it


Developer Advocate Chris Smith shows us how he built a Assignment Widget in the HTML Widget.
 

One of the busiest threads on the Frog forum this week has been a discussion about a "widget" built by one of our customers! It was great to read how the community banded together to develop the initial concept. What was more interesting for me personally, was tracking how the code evolved and how people attempted to reverse engineer parts of FrogOS to get the results they desired. 

I decided to pitch in; and fill the gaps that the community weren't able to complete. Lets start with the HTML.
 

<style>
    .row-template {
        display: none;
    }
</style>

<table class="table table-striped">
    <thead>
        <tr>
            <th>&nbsp;</th>
            <th>Title</th>
            <th>Description</th>
            <th>Available on</th>
            <th>Due Date</th>
        </tr>
    </thead>
    <tbody>
        <tr class="row-template">
            <td><!-- icon --></td>
            <td class="name"></td>
            <td class="description"></td>
            <td class="start-date"></td>
            <td class="due-date"><</td>
        </tr>
    </tbody>
</table>


Lets examine what's happening here. We are borrowing the concept of the template element to more easily allow us to edit the table in the future. To ensure full support across all browsers; we're going to assume a shim regardless of browser support. This is ultimately less code, and is easier to teach compared to the compilations of the TemplateElement api.

 

Simply, the first row in the table is invisible; and we clone it and put our data into it. So lets see some of that in action. Lets look at the JavaScript!

 

FrogOS.fdp({
    url: 'assignment/getAssigned',
    data: {
        status: 'open',
        limit: 5,
        order: 'start desc'
    },
    dataType: 'json assignments',
    converters: {
        'json assignments': function(resp) {
            return Frog.Model.models(
                Object.values(resp.response.assignments)
                    .map(function(assignment) {
                        return assignment.assignment;
                    })
                );
        }
    }
}).done(function(assignments) {
    var $template = this.element.find('.row-template'),
        $table = this.element.find('table');

    assignments.each(function(idx, assignment) {
        var $row = $template.clone().removeClass('row-template');

        $row.data('link', assignment.attr('link'));
        $row.children('.title').text(assignment.attr('name'));
        $row.children('.description').text(assignment.attr('description'));
        $row.children('.start_date').text(
            moment(assignment.attr('start'), 'X').format('Do MMM YYYY')
        );
        $row.children('.due_date').text(
            moment(assignment.attr('end'), 'X').format('Do MMM YYYY')
        );

        $row.on('click', function(el, ev) {
            FrogOS.openSite({
                site: el.data('link')
            });
        }.bind(this, $row));

        $table.find('tbody').append($row);
    }.bind(this));

}.bind(this));


There is a lot happening here so lets break down the key concepts:

  • Data Converters
  • Element Templating
  • Events and triggers
  • Applying Scope

 

We are using data converters to manipulate the data before we get to the functional part of our code. This separation provides focus and logical separation of concerns; overal providing more maintainable code. Another advantage, relevant in this scenario, is we can simplify the data structure before we have to use it.
 

FrogOS.fdp({
    url: 'assignment/getAssigned',
    data: {
        status: 'open',
        limit: 5,
        order: 'start desc'
    },
    dataType: 'json assignments',
    converters: {
        /*
         * Instructions for how to deserialize the network response
         * See: https://api.jquery.com/jQuery.ajax/#using-converters
         *
         * @param jqXHr Network Response
         * @returns Frog.Model.List List of Assignments
         */
        'json assignments': function(resp) {
            return Frog.Model.models(
                Object.values(resp.response.assignments)
                    .map(function(assignment) {
                        return assignment.assignment;
                    })
                );
        }
    }
}).done(
    /*
     * @param Frog.Model.List Transformed data
     */
    function(assignments) {
       // functional code 
    }
);


Now we have some data, we need to display it. Previously, we created an "invisible" element. We are going to clone the element, and insert it into the table. By having a "physical" DOMTreeFragment, we can easily modify it at a future date, or conceptually re-style as we develop further.

Whilst iterating over the data and constructing the various rows, it makes sense to bind the event listeners we need. Remember, .click() and .onclick don't play well on iOS.
 

// Cache the elements we require. We'll use the principle of hoisting
// to access them when we need them.
var $template = this.element.find('.row-template'),
    $table = this.element.find('table');

// Iterate over the collection of assignments
// See: http://frogasia.github.io/javascriptmvc-3.2-docs/#!jQuery.Model.List.prototype.each
assignments.each(function(idx, assignment) {
    var $row = $template.clone().removeClass('row-template'); // Clone the template

    $row.data('link', assignment.attr('link')); // Encapsulate the data we will need later
    // Place the data in the table row in the correct cells 
    $row.children('.title').text(assignment.attr('name'));
    $row.children('.description').text(assignment.attr('description'));
    $row.children('.start_date').text(
        moment(assignment.attr('start'), 'X').format('Do MMM YYYY')
    );
    $row.children('.due_date').text(
        moment(assignment.attr('end'), 'X').format('Do MMM YYYY')
    );

    // Before we append row onto the table, add the "click" listener.
    $row.on('click', function(el, ev) {
        FrogOS.openSite({
            site: el.data('link') // Get the data from the element we clicked
        });
    }.bind(this, $row)); // Apply the current scope, and set the first parameter to the current row

    $table.find('tbody').append($row); // Finally, append the row. 
}.bind(this)); // Apply the current scope to the iterator.


Lastly, we'll examine "Applying Scope". We've already touched on this in the previous example. In the following (and final) example, we'll add a custom closure to the done callback. In the future, this will be done automatically by the HTML Widget.
 

FrogOS.fdp( /* AJAX Options */)
    .done(function() {
        // functional code goes here
        this.element; // HTML Widget
    }.bind(this)
);


So there you have it; the individual components which make up my solution, the reasons I choose each component and how they all sit together. Continue through to the next page to see how I further develop the widget.


Tutorials in this series...

Quicklinks
 

BLOGS