Glossary use case

Objective

We want to provide a tool to manage a list of terms and their definitions:

../_images/glossary-1.png

Every time one of these terms appears on a page of our site, it will be wrapped into a <abbr> tag, the title of which will be the definition, so when we hover a term, we get a small popup indicating its definition:

../_images/glossary-2.png

Application structure

rapido/
    glossary/
        blocks/
            all.html
            all.py
            all.yaml
            term.html
            term.py
            term.yaml
        glossary.js
rules.xml

Rules.xml

<after css:theme-children="body">
  <script src="/tutorial/++theme++test/rapido/glossary/glossary.js"></script>
</after>

This rule inserts in all our pages a javascript file in charge of replacing matching words with <abbr> tags.

The term block

This block is a form allowing to create/edit/delete a glossary term. It contains two field elements and three actions.

  • term.html

    <p><label>Term</label> {term}</p>
    <p><label>Definition</label> {definition}</p>
    {_save} {_delete} {close}
    
  • term.yaml

    target: ajax
    elements:
        term: TEXT
        definition: TEXT
        close:
            type: ACTION
            label: Close
        _save:
            type: ACTION
            label: Save
        _delete:
            type: ACTION
            label: Delete
    
  • term.py

    def close(context):
        return context.app.get_block('all').url
    
    def on_save(context):
        return context.app.get_block('all').url
    
    def on_delete(context):
        return context.app.get_block('all').url
    

If we click on any action in this block, we want to be redirected to the main management page. We do that by returning the all block URL (when an action returns a string, it is used as a redirection URL).

The all block

This block lists all the existing terms in a table. When we click on a term, we open it in the term block in edit mode, and a button allows opening a blank term block to create a new term.

  • all.html

    <table class="listing"><tr><th>Term</th><th>Definition</th></tr>
    {list}
    </table>
    {new_term}
    
  • all.yaml

    target: ajax
    view:
        id: glossary
        with_theme: true
    elements:
        list: BASIC
        new_term:
            type: ACTION
            label: Add term
    

    The view setting allows to render the all block as Plone view named @@glossary, so we can call http://localhost:8080/Plone/@@glossary to see it.

  • all.py

    def list(context):
        html = u""
        for record in context.app.records():
            html += """<tr><td><a href="%s/edit" target="ajax">%s</a></td><td>%s</td></tr>""" % (
                record.url,
                record['term'],
                record['definition'],
            )
        return html
    
    def new_term(context):
        return context.app.get_block('term').url
    

The list function builds a table row for each existing record, displaying the term value and the definition value. The link we put on the term targets the record URL (plus /edit to open it in edit mode) and we have added target=”ajax” so the resulting page is not displayed as a full page, it is just loaded into the current block in AJAX mode.

The Javascript

  • glossary.js

    require(['jquery'], function($) {
        if($('.template-edit').length > 0) {
            return
        }
        $.getJSON('/tutorial/@@rapido/glossary/records', function(data) {
            var keys = [];
            var values = {};
            for(var i=0; i<data.length; i++) {
                term = data[i].items.term;
                definition = data[i].items.definition;
                keys.push(term);
                values[term] = definition;
            }
            var re = RegExp("(\\W)(" + keys.join("|") + ")(\\W)", "g");
            function replaceNodeText() {
             if (this.nodeType === 3) {
                    var parent = $(this).parent();
                    var html = parent.html();
                    if(html) {
                        var newvalue = html.replace(re, function(){
                            var term = arguments[2],
                                before = arguments[1],
                                after = arguments[3];
                            term = '<abbr title="'+values[term]+'">'+term+'</abbr>';
                            return before + term + after;
                        });
                        parent.html(newvalue);
                    }
             } else {
                 $(this).contents().each(replaceNodeText);
             }
            }
            $("#content-core").contents().each(replaceNodeText);
        });
    });
    

First thing we do is to check if we are in edit mode, and if we are, we stop, as we do not want to manipulate the HTML currently being edited in TinyMCE or in any input field.

Then we load the glossary terms with the following JSON call: /tutorial/@@rapido/glossary/records

Using the term values we have loaded, we build a regular expression able to match those terms in any text.

Then we iterate on the page main content (#content-core) elements, and every time we find a text node, we use our regular expression to replace the matching words with an <abbr> tag where the title attribute is the associated definition.