React*js and Angularjs are very powerful technologies to develop solid RIA and I particularly love React, however, for simple projects these frameworks can be somewhat excessive.
This CRUD experiment/tutorial will demonstrate how to build similar, low-level functionality for much smaller projects without the need to learn complex frameworks.

This tutorial is for the front-end developer who:


Demo Download files
Download and extract to your web server.
The sample application has quite a lot of comments and code for demo purposes which you can remove.

Sections:

1. Datasource & Dynamic output

We have a datasource and we want to output this data in HTML, if we have multiple records of data we also want this HTML to be dynamically repeated to show all records, or to update the HTML when the data changes.
For convenience this tutorial uses a json file as a datasource; in a real-world application the datasource will typically be aquired via an ajax request.

1. We add a sgc attribute to the HTML to denote a template i.e. sgc="templateStudents"
2. We add data markers in the format {{key}} which corresponds to the keys in our datasource - see /data/students-1.json
3. We optionally add inline callbacks in the format {{callback:functionname:arg1:argNth}}.
4. We optionaly add a child template sgc="templateSubjects" inside sgc="templateStudents" , as we need to also iterate over each student's subjects - more about this later.

<tbody>
    <tr sgc="templateStudents" >                               
        <td>{{callback:alterData}} {{user_id}} - sgcId: {{sgcId}} </td>
        <td>{{firstname}}</td>
        <td>{{surname}}</td>
        <td>
            <div sgc="templateSubjects">
                <div><b>{{name}}</b> - {{grade}}</div>
            </div>
        </td>
        <td style="background-color: {{callback:genderStyle}}">{{gender_readable}}</td>
        <td>
            {{callback:paidIcon:foo:bar:baz}} {{paid_readable}} 
        </td>
        <td><a href="#" rel="{{sgcId}}" myevent="edit" class="edit-button">edit</a></td>
        <td><a href="#" rel="{{sgcId}}" myevent="delete" class="delete-button">delete</a></td>
    </tr>
</tbody>

2. Data Parser

I've written a small Javascript & jQuery parser (/js/sgc-parser.js) which is used to map the datasource to the HTML template.

If you open the parser file you will see all available methods in the returned _util object.
In addition some of the methods returns a Promise, which you can hook into to take further actions asynchronously.

1. sgcParser adds a new unique key-value sgcId to each of the supplied data records before parsing, sgcId is used to map the data records to it matching HTML template node.
2. Use your developer tool to inspect the rendered template and note that sgcParser always adds an attribute of sgc-row="xxxxx" to the HTML template, sgc-row will have the unique ID sgcId.
3. You can use sgcId to find a HTML node as well as its corresponding data record, have a look at the "edit" hyperlink in /index.html template and the edit click event in /js/app.js.
4. callbacks are called on each iteration of the template, before the template is written to the DOM.
5. afterRender is called on each iteration of the template, after the template has been written to the DOM.
6. parseInterval Can be set in milliseconds (1000/1sec), which affects the iterator render intervals/speed. Change the subjectsParser's interval to 2000 and see how rendering takes place asynchronously.
sgcParcer does not use Javascript's eval(), all custom output functionality is done via callbacks.

//Code snippet, create new instance of parser sgcParser - see /js/app.js   
this.studentListParser = new sgcParser({
    parseInterval: 100, //milliseconds
    tpl: 'templateStudents',
    callbacks: {
        paidIcon: function (rowData, index, args) {          
            return rowData.paid ? '<i class="fa fa-check-circle"></i>' : '<i class="fa fa-times-circle"></i>';
        },
        genderStyle: function (rowData, index) {
            return rowData.gender == 'f' ? '#e6add8' : '#ADD8E6';
        },
        alterData: function (rowData, index) {
            rowData.paid_readable = rowData.paid === 1 ? 'Yes' : 'No';
            rowData.gender_readable = rowData.gender === 'f' ? 'Female' : 'Male';
            return '';
        }
    },
    afterRender: function (element, rowData) {

        rowData.paid === 0 ? $(element).css({'color': 'red'}) : null;

        //find student's matching subjects by id
        var subjects = [];
        for (var k in rowData.subject_ids) {
            for (var s in self.dataSubjects) {
                if (rowData.subject_ids[k] === self.dataSubjects[s].id) {
                    subjects.push(self.dataSubjects[s]);
                }
            }
        }

        var subjectsParser = new sgcParser({
            tpl: 'templateSubjects',            
            context: element,//NB: on each iteration assign within CONTEXT of the newly created DOM node templateStudents
            parseInterval: 1 //milliseconds
        });

        subjectsParser.addRows(subjects);
    }
});
//add data rows and run parser
this.studentListParser.addRows(studentDatasource);


When sgcParser is instantiated it will try find the supplied HTML template name in the DOM, clone the node and then remove it from the DOM, it will also set the context for this template based on the first parent node of the template.
There after all render calls i.e. addRows, addRow, removeRow etc. will run in context of the template's parent node.
Problem: This is needed to prevent possible clashes with other templates, but poses a problem if you have more than one template inside the same parent node, because sgcParser uses $.append or $.replaceWith, the order of your templates might change.

Solution: Make sure your templates are contained in its own parent node (not nested), or supply a template and context when instantiating a new scgParser - see /js/app.js approx. line 49.
sgcParser might replace child template data markers with parent data values, because the parser is not aware of child templates.
I could have solved this with a template/context manager, but its quite a challenge to work with nested contexts and this is something I might revisit in future - for now just make sure your parent and child datasources do not have key name conflicts, or supply a template and context to sgcParser for any child instance.

3. App setup

Have a look at /js/app.js for a complete example of how the app is bootstrapped and the CRUD methods setup.
You can use any design pattern to build your app, js/app.js is merely a solution that I chose.
Also look at the header of index.html to see the required scripts.