-
Notifications
You must be signed in to change notification settings - Fork 5
extended example
Now that we know about the different components of Retina, let us try a more complex example to get a better feel of the flow. As a base we assume that you have completed the previous tutorial steps.
For the extended example you additionally need to copy widget.tutorial2.js from the Retina/tutorial directory to your widgets directory and tutorial2.html to your base html directory. If you open this file in your browser, you will see the working app. The app queries a book API for books of a certain author. Then it creates a table that shows the results. It has an input field to change the author and repeat the search for that author.
In this section we will walk through the different parts that make the app work. The changes to the HTML file are pretty minimal.
jQuery(document).ready(function() {
stm.init({});
Retina.init({ widget_resource: "widgets" });
Retina.load_widget("tutorial2").then( function () {
Retina.Widget.create("tutorial2", { "target": document.getElementById("content")});
});
});Essentially this is the same setup as before, except that the widget name has changed from tutorial to tutorial2. Then we adjust the title h2 a bit and put the content into a class content div from bootstrap to make it look a little bit nicer.
<div class="container">
<div style="text-align: center;">
<h2>Welcome to the Bookstore</h2>
</div>
<div id="content"></div>
</div>Looking at the widget.tutorial2.js the changes are a bit more extensive. First we add a widget-wide variable to hold the name of the author that is being searched and default it.
widget.author = "Stephen King";Since we want more than just a table in our app, we create multiple spaces in the display function to hold the different parts.
var authorSelect = document.createElement('div');
target.appendChild(authorSelect);
authorSelect.setAttribute('style', 'text-align: center;');
authorSelect.innerHTML = '<div class="input-append input-prepend"><span class="add-on">author</span><input type="text" id="authorselect" value="'+widget.author+'"><button class="btn" onclick="Retina.WidgetInstances.tutorial2[1].search();">search</button></div>';
var info = document.createElement('div');
target.appendChild(info);
info.setAttribute('style', 'text-align: center;');
info.innerHTML = '<div id="info" class="alert alert-info" style="display: none;"></div>';
var rendererTarget = document.createElement('div');
target.appendChild(rendererTarget);
rendererTarget.innerHTML = '<div style="text-align: center;"><img src="Retina/images/loading.gif"></div>';The author selection uses a bootstrap input-append-prepend to have a nice input box for the author selection along with a button to submit the request. The button defaults to our widget.author variable we created earlier. The button calls the search function of the widget. Note how the reference is done.
Retina.WidgetInstances.tutorial2[1].search()Retina has a storage for all widgets that are created called WidgetInstances. The properties of that object are the names of the loaded widgets. The 0 index represents the reference instance that all newly created instances are spawned from, 1 is the first created instance. We will take a look at the search function a bit later.
The info element hosts a bootstrap style info box, but is initially hidden. We can unhide this whenever we want to show a message to the user.
The target div for the table is initialized with an animated gif that indicates that data is loading. Since we get the data from an API, we do not know how long it will take until we can actually display the data.
Now we create the table and also store it in a widget wide variable. Note that we are not yet passing data to the table. It is only initialized with a target. This is fine, as we are not rendering the table at this time.
widget.table = Retina.Renderer.create('table', { 'target': rendererTarget } );Now we add the API to stm as a repository. This looks a bit complicated, but actually it is pretty straight forward.
stm.add_repository({ "url": RetinaConfig.api_url,
"name": "books",
"data": "docs",
"total_count": "numFound",
"id": "cover_i" });The base url of the API we configured in the file js/config.js. This is where we put configuration variables that we do not want in the application code. Then we need to give the repository a name, so we can access it later. Actually this is not as important in the example case, as we only have one repository. In this case it automatically becomes the default repository and does not need to be specified by name.
The next three parameters are configuring what the API calls certain things. The return value stores the data items in a property which is typically called data. If it is called differently, e.g. docs as in the case of the book API, the data parameter provides a mapping. The same is true for total_count which the book API calls numFound. Finally we have to define which attribute of a data item is the unique key that it will be stored by in the stm.DataStore. In case of our app we chose the cover_i attribute from the data attributes of the items returned by the book API.
In the final step we make a call to the widget.loadData function. Since we will call this multiple times, the functionality is compartmentalized into its own function.
The search function is very basic. It reads the text from the author select input, stores it in the widget.author variable and then calls the widget.loadData function.
widget.search = function () {
var widget = this;
widget.author = document.getElementById('authorselect').value;
widget.loadData();
return widget;
};The loadData function handles the call to get the data from the API and then makes a call to the showTable function. First it clears the previous data in the stm.DataStore.books, and puts our loading image where the table was. The info field is hidden, just in case it was visible before.
Since the API interaction is handled by stm, all we need to do is make an stm.get_objects call. It returns a promise that fulfills when the data is there, which is when we want to call the widget.showTable function.
widget.loadData = function () {
var widget = this;
widget.table.settings.target.innerHTML = '<div style="text-align: center;"><img src="Retina/images/loading.gif"></div>';
stm.DataStore.books = {};
document.getElementById('info').style.display= 'none';
stm.get_objects({'type': 'search.json', 'target': 'books', 'options': { 'author': widget.author, 'limit': 100 } }).then(
function () {
var widget = Retina.WidgetInstances.tutorial2[1];
widget.showTable();
}
);
return widget;
};The main purpose of the widget.showTable function is to format the data returned by the book API into the format needed for the table. It checks if there is data returned and sets an error message into the info field we created otherwise.
Then it iterates of the data in stm.DataStore.books and pushes the fields we want to display into the table data array. Whenever you parse data from an external source, make sure to catch erroneous data.
row.publish_year && row.publish_year.length ? row.publish_year[0] : "-"an infix if statement like this is useful to set defaults an make sure that the parsing code does not err out due to a missing property.
With the data parsed, we can update the widget.table.settings to the new data and call the render function to show our updates.
widget.showTable = function () {
var widget = this;
var data = stm.DataStore.books;
var header = [ 'title', 'year', 'publisher' ];
var tdata = [];
var k = Retina.keys(data);
if (! k.length) {
document.getElementById('info').style.display = '';
document.getElementById('info').innerHTML = 'no books found for that author';
return;
} else {
document.getElementById('info').style.display = 'none';
}
for (let i=0; i<k.length; i++) {
var row = data[k[i]];
tdata.push([row.title ? row.title : "-", row.publish_year && row.publish_year.length ? row.publish_year[0] : "-", row.publisher && row.publisher.length ? row.publisher[0] : "-" ]);
}
widget.table.settings.target.innerHTML = '';
widget.table.settings.tdata = null;
widget.table.settings.data = { "data": tdata, "header": header };
widget.table.settings.sorttype = { 0: "string", 1: "number", 2: "string" };
widget.table.settings.filter_autodetect = true;
widget.table.render();
return widget;
};This completes the tutorial. For specific details about retina or stm function or renderers, use the according links in the menu on the right.