https://github.com/gomyar/turtlegui
The imperative style of programming (which most of us are used to) does not work well with Graphical User Interface development. I've found declarative systems to work much better for GUI development. TurtleGUI is designed in this way.
A simple breakdown of how it works - color coded:
blue - HTML
green - javascript code
orange - reference to in-scope javascript variable(s)
The Basics.
Custom element parameters are used to control the structure of the DOM. i.e.:
<div gui-text="data.name"></div>
"gui-text" is a TurtleGUI directive which will set the text value on the div to the value of "data.name" when turtlegui.reload() is called.
This will result in:
<div gui-text="data.name">Bob</div>
The data in data.name can be set on page load or through an ajax call:
document.addEventListener("DOMContentLoaded", function(){
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
data.name = response.name;
turtlegui.reload();
}
};
xmlhttp.open("GET", '/data.json', true);
xmlhttp.send();
});
Note: TurtleGUI will only update the DOM when turtlegui.reload() is called.
Lists.
Being able to construct lists on the fly is crucial to any GUI. TurtleGUI achieves this using directives which re-create child elements based on list data and setting scope variables for each item in the list:
Given a data variable with the structure:
var data = [
{"name": "Apple"},
{"name": "Orange"},
{"name": "Banana"}
];
<div gui-list="data.shopping_list" gui-item="product">
<div gui-text="product.name"></div>
</div>
This is analagous to "for each product in data.shopping_list"
On a call to turtlegui.reload() the inner div element will be stored in the parent, and cloned for each element in the data.shopping_list array. For each item in the array, the variable "product" is a reference to the item.
So, for the first item in the list, product refers to {"name": "Apple"}, for the second it is set to {"name": "Orange"}, and for the third: {"name": "Banana"}. This means that each item in the list can be referred to prefixed by "product.".
This will expand the DOM to:
<div gui-list="data.shopping_list" gui-item="product">
<div gui-text="product.name">Apple</div>
<div gui-text="product.name">Orange</div>
<div gui-text="product.name">Banana</div>
</div>
Show / Hide panels.
For element such as tabs or popups, the ability to show and hide these elements can be accomplished using gui-show:
<div gui-show="popup.is_shown">
<div>This is a popup, shown when gui-show evaluates to true</div>
</div>
An example of tabs:
<div gui-switch="panel.shown_tab">
<div gui-case="first">This is the first tab</div>
<div gui-case="second">This is the second tab</div>
<div gui-case="third">This is the third tab</div>
</div>
On a turtlegui.reload(), the subelement of the gui-switch element will be shown if it's gui-case directive matches the value of panel.shown_tab. So, given the variable:
var panel = {
shown_tab: "second"
}
We would see:
<div gui-switch="panel.shown_tab">
<div gui-case="first" style="display: none">This is the first tab</div>
<div gui-case="second">This is the second tab</div>
<div gui-case="third" style="display: none">This is the third tab</div>
</div>
Note: css which displays a nice tab has been omitted for brevity.
Events.
Click events are the most used, and have their own directive - gui-click; other events in the DOM can be controlled using gui-bind / gui-event.
Expanding the above example to allow click events for each item:
<div gui-list="data.shopping_list" gui-item="product">
<div gui-text="product.name" gui-click="select_product(product)"></div>
</div>
This will, for each item in the expanded DOM, call the following function, passing a reference to "product":
function select_product(product) {
alert("You selected " + product.name);
}
This approach preserves the scope of the list items, passing in the correct "product" variable for each.
Note: the "this" keyword always refers to the element the event was triggered from.
The data model.
The data model is any Javascript variables. If a variable has been set at global scope, it can be accessed by TurtleGUI. This is to allow simple loading of various kinds of data.
For a given directive, a variable will be resolved in much the same way as a javascript variable. It is restricted to JS objects and lists, and other simple types, and functions. It does not use eval() and cannot contain any Javascript proper. It can however be reasonably complex:
<div gui-text="parsing_function(data.name)"></div>
<div gui-text="spacing_function(data.name, data.shopping_list)"></div>
<div gui-text="complex_structure[key_name].inner_child.play()"></div>
CSS.
TurtleGUI is a tool for manipulating the DOM, so only really touches off styling through the gui-css and gui-class directives. It has a helpful starter turtlegui.css file included, but only as a suggestive.
Reload().
TurtleGUI will only manipulate the DOM when turtlegui.reload() is called through javascript. This is a design decision to allow explicit reloading of the GUI. Different applications will want their data loaded and their GUI refreshed in different ways which are impossible to predict.
Finally - why use this tool?
In the current environment of largescale development projects spanning multiple teams across many projects, the likes of Angular and React have come to the fore. These tools use the same patterns and practices of middleware development to ease the leap from middleware generated markup to full fledged Single Page Application.
There is, however, still a place for the simple, fast SPA. The amount of time it takes to get used new stacks like React is non-trivial, and an expert subject in itself. Some developers do not have the time to learn a new stack, but still want the power of an SPA. They also don't necessarily need to support their front end codebase across multiple years and versions.
TurtleGUI is designed to fill the gap between developers who want to make their careers in the front end, and those who want to remain fluid in their choice of technology, but still be able to add an SPA when they need to.