-
Notifications
You must be signed in to change notification settings - Fork 3
Adding and working with form elements
The templating system for SNAC works by using Twig
templating system. The templates are located in the directory:
snac/src/snac/client/webui/templates/
If we need to add a new field to an already existing form, we'll first search for it in the templates folder (and subfolders). For example, if we were to work on any of the forms used on edits, the most likely file would be:
snac/src/snac/client/webui/templates/edit_components.html
When you open the template, you'll see it contains various twig macros that can be resumed in other templates.
We provide an interactive interface to our editors. Most of the data elements are shown in the editorial interface in two modes, view and edit mode. In view mode, we display the current value of elements. In edit mode, we present the editor with a form to update information on the current data model.
Many of our data models have multiple elements of the same type (e.g. a Constellation can have various names). Because of this, the editorial interface will show multiple entries of the same kind, and each element is identified by a sequential number that is part of every HTML element's name.
To make this clear, imagine we are working with a form that represents a data element that can exist more than once. We'll have an element that its name/id contains a sequence to distinguish it form others:
<input id="sameAs_uri_{{i}}" name="sameAs_uri_{{i}}" type="hidden" class="form-control" value="{{ other.uri }}"/>
We could have many uri
s and each will have a different i
.
That is the basic idea of handling multiple elements. If we follow the naming conventions, we can make use of code that handles the transition between show and edit modes for us. Let's have a look at some examples.
We can have many types of input fields (texts, checkboxes, selects, etc.). More likely than not, we'll already have JavaScript code that handles the basic cases for each type. So any time you add a new field, and you are not certain of its existence, search for it in the javascript
folder located in:
snac/src/virtualhosts/www/javascript/
I encourage you to check select_loaders.js
for an example of the available functions. If we are unsure of what is the expected structure or naming, this will be the place to consult.
I'll give an example of the structure for the most common type text
and then for a more advanced type a select
that gets its options from the vocabulary
table.
The basic expected structure is the following:
<div class="col-sm-10" id="sameAs_datapart_{{i}}">
<div class="form-group">
<label for="sameAs_uri_{{ i }}" class="control-label col-xs-4" data-content="{{X.sameAsURI.tooltip}}" data-toggle="popover" data-placement="top">
{{ X.sameAsURI.display }}
</label> <!-- Label is options just showing for the sake of completion -->
<div class="col-xs-8" id="text_sameAs_uri_{{i}}">
<input id="sameAs_uri_{{i}}" name="sameAs_uri_{{i}}" type="hidden" class="form-control" value="{{ other.uri }}"/>
<p class="form-control-static">{{ other.uri }}</p>
</div>
</div>
</div>
Let's explore the structure of the name. Our elements naming conventions consist of:
shortName_name_sequence
For our example:
shortName = sameAs
name = uri
identifier = the value of i
Our scripts will expect the data to be bundled inside a div
. This div should be identified with _datapart
as part of its ID. Hence, the name of our container is sameAs_datapart_{{ sequence }}
. Inside that container, we'll have all the data/input fields.
NOTE: The naming is not only crucial for the JavaScript code, but it is also what our ConstellationPostMapper
expects as the data. If the elements are correctly named, they can be extracted into an Object that represents the data gathered from the forms.
If you observe, you'll notice that the input field is set as hidden
and that there is a p
element that contains the current value. Our implementation expects the form to be written in the "view mode" and the JavaScript will be in charge of modifying it and creating the appropriate input fields when the form is requested to be rendered (usually, by the click of the edit
button).
If you use that structure, everything should work properly, and you won't have to call any JavaScript function manually. What happens behind the scenes is that the function makeEditable
is being called for the specific shortName
. If you are creating an entirely new form you'll have to add the call to makeEditable
, check the code on the file edit_scripts.js
.
The function makeEditable
in turn calls the function subMakeEditable
which is in charge of transforming from text (the "view mode") to the expected input fields(text, select, checkbox etc.). It'll be a good idea to check the code in edit_scripts.js
to familiarise yourself with how the code works.
In general that is the workflow followed by the JavaScript code, you can check specifics of how each function works (e.g. textToInput
, textToTextArea
, textToSelect
, textToCheckbox
, etc.).
Let's see a more complex element, a select (dropdown) that gets its options from the vocabulary
table.
The structure of our markup will look like this:
<div class="col-sm-10" id="sameAs_datapart_{{i}}">
<div class="form-group">
<label for="sameAs_uri_{{ i }}" class="control-label col-xs-4" data-content="{{X.sameAsURI.tooltip}}" data-toggle="popover" data-placement="top">
{{ X.sameAsURI.display }}
</label> <!-- Label is options just showing for the sake of completion -->
<div class="col-xs-8" id="text_sameAs_uri_{{i}}">
<input id="sameAs_uri_{{i}}" name="sameAs_uri_{{i}}" type="hidden" class="form-control" value="{{ other.uri }}"/>
<p class="form-control-static">{{ other.uri }}</p>
</div>
</div>
<div class="form-group">
<label for="sameAs_baseuri_{{ i }}" class="control-label col-xs-4" data-content="{{X.sameAsBaseURI.tooltip}}" data-toggle="popover" data-placement="top">
{{ X.sameAsBaseURI.display }}
</label>
<div class="col-xs-8" id="select_sameAs_baseuri_{{i}}">
<input id="sameAs_baseuri_id_{{i}}" name="sameAs_baseuri_id_{{i}}" type="hidden" class="form-control" value="{{ 400605 }}"/>
<input id="sameAs_baseuri_term_{{i}}" name="sameAs_baseuri_term_{{i}}" type="hidden" class="form-control" value="{{ WorldCat }}"/>
<input id="sameAs_baseuri_vocabtype_{{i}}" name="sameAs_baseuri_vocabtype_{{i}}" type="hidden" class="form-control" value="external_related_resource_domains"/>
<input id="sameAs_baseuri_minlength_{{i}}" name="sameAs_baseuri_minlength_{{i}}" type="hidden" class="form-control" value="0"/>
<p class="form-control-static">{{ other.uri }}</p>
</div>
</div>
</div>
This structure is more complicated than the previous one. We left the input text of the previous example to show you a complete example. Let's dissect what is going on here.
We see that we are following a similar naming convention:
select_sameAs_baseuri_{{i}}
The difference here is that the select begins with a select_
string, and also there are a few additional fields that might be confusing inside that div
. Remember, this is the case where we want to populate the options of the dropdown with values from the vocabulary
table.
As before, we are creating the "view mode" representation of our form, and the JavaScript will be in charge of changing it to the expected input fields when needed.
The "view mode" representation of a select that uses options from the vocabulary
is a div
container wrapping inputs
that will be used to filter the data from the vocabulary
table. To view how it is precisely parsed, you'll have to check the code for textToSelect
in the file edit_scripts.js
.
In the textToSelect
function you can add any specific code if you select needs special behaviour. For example, for the sameAs
case shown before there is a validation to call a specific method after the select has been created to populate the options with specific parameters:
if (name == "citation")
scm_source_select_replace($("#"+shortName+"_"+name+"_id_"+idStr), "_"+idStr);
else if (shortName == "sameAs" && name == "baseuri")
loadVocabSelectOptions($("#"+shortName+"_"+name+"_id_"+idStr), "external_related_resource_domains", "Base URI", true);
else
vocab_select_replace($("#"+shortName+"_"+name+"_id_"+idStr), "_"+idStr, vocabtype, minlength);
As you can see it calls the loadVocabSelectOptions
function (located in select_loaders.js
), that will create the options displaying the description
instead of the value
as the dropdown text.