We will take a look at the client side javascript libraries that help interact with Nodea generated applications.
In order to optimize our generated applications, we have implemented a bundle system for JS and CSS resources. The objective is to avoid asking the client to make x server call for x resources, and therefore to generate a minified bundle which will gather all the x resources.
The definition of these bundles is in the file /app/public/bundle.json
.
The bundles are stored in /app/public/bundle/
, the filename structure is: BUNDLE_NAME.bundle.TYPE
(For example: my_js_bundle.bundle.js)
The file structure is as follows:
{
"BUNDLE_NAME": {
"type": "css OR js",
"files": [
"@app/public/js/my_js.js",
"@core/public/js/core_js.js"
]
}
}
As you can see above it is possible to use @app and @core aliases in file paths.
Once the bundle is specified, it will be generated when your application server is started if it does not exist in /app/public/bundle/
.
Also, here is some commands you can do in your application folder:
npm run bundle # Generate all missing bundles
npm run bundle bundle_name # Generate only the specified bundle
npm run bundle all # Regenerate / rewrite all the bundles
For developers the bundler also generate dust files in /views/bundle
that place all the file without minifying. Usefull when working on a file that is supposed to be in a bundle.
All you have to do is to include the dust version instead of the JS/CSS version:
<!-- Change -->
<link href="/core/bundle/nodea_main_css.bundle.css" rel="stylesheet">
<!-- To -->
{>"bundle/nodea_main_css"/}
Of course do not forget to revert this for production deployment.
Nodea defines the behavior of global javascript elements of an application. It is included in main_layout.dust
and is available in every page.
It provides utility functions and initialize elements.
_core/public/js/nodea.js
will expose global functions.
Some global utility functions that can be used anywhere and be overwritten if necessary :
navigate()
: The only use for this function is to switch between modules. It is bound to the change event of select#module-select
in every module dust layout (ie: `layout_m_home.dust` `layout_m_administration.dust`)doModal(title, content)
: Will display a modal with title and content set to the provided valuesgenerateFileViewer(base64)
: Mainly used in show
and list
pages, it will display a preview of a file from the given base64 content.It will also expose Nodea(enables={})
that handles different elements initialization.
Nodea(enables = {
inputGroup: true,
labelFocus: true,
copyBtn: true,
confirmBtn: true,
documentTemplateHelp: true,
blockDoubleClick: true,
inlineHelp: true,
toastr: true,
sidebar: true,
sidebarSave: true
});
By default everything is enabled. Customization is still a work in progress but at the moment you can disable any initialization you don't want or wish to change.
Here's the details of what is initialized and what can be disabled :
input
contained in an .input-group
element.copy-button
element.confirm()
box when clicking on a .btn-confirm
element.help-new-window
.btn.btn-primary, .btn.btn-default, .btn.btn-info, .btn.btn-warning, .btn.btn-danger, .btn.btn-success
elementstoastrArray
array and triggers a toastr for each entry. toastrArray
depends on dust variables in main_layout.dust
NodeaForms is the library that handles <form>
content, initialization, validation and submission. It is also used to initialize UI elements.
NodeaForms should be used to initialize / validate / submit a <form>
, or just manually initialize a form element.
Its file (_core/public/js/nodeaForms.js
) is included in the main_layout.dust
file and therefore should always be available.
For each element or function, a default behavior is provided and can be overwritten. Additional definition can be provided to NodeaForms globally or upon initialization.
nodeaForms.js
exposes a global function NodeaForms(context=document, overrideDefaults={})
function NodeaForms(context=document, {
handleSubmit: (form, event, overrideDefaults = {}) => {}, // Called on submit event of a form
handleSuccess: (data) => {}, // Success callback of ajax submit
handleError: (jqXHR, status, error) => {}, // Error callback of ajax submit
validate: (form, overrideDefaults = {}) => {}, // Form validation called before submitting
elements: {
'elemType': {
selector: '', // CSS selector of element
initializer: (element, placeholder) => {}, // Element initialization
validator: (element, form) => {}, // Element validation. Optionally returns an array of functions to modify the form upon validation success
}
}
}
// Default initialization
NodeaForms();
When calling NodeaForms
, the NodeaForms.elements
and overrideDefaults.elements
(if provided) objects will be used to initialize the given context.
If a <form>
is found, its submit event will be bound to NodeaForms.handleSubmit
and validated by NodeaForms.validate
.
These can be overwritten by providing definitions using the overrideDefaults
parameter of NodeaForms()
.
Initialization will be applied on the given context, or whole document if none is provided. If multiple <form>
are present in the context, they will all be initialized.
If no form is present, only form elements found will be initialized, and no submission/validation will be handled.
NodeaForms prototype includes access to the following default definitions :
NodeaForms.elements
NodeaForms.handleSubmit
NodeaForms.handleSuccess
NodeaForms.handleError
NodeaForms.validate
They can be overwritten globally right after nodeaForms.js
inclusion in main_layout.dust
file.
NodeaForms.elements
is an object in which properties represent the element's type. An element definition is represented by 3 properties :
elements: {
'elemType': {
selector: '', // CSS selector of element
initializer: (element, placeholder) => {}, // Element initialization
validator: (element, form) => {}, // Element validation
}
}
initializer
and validator
are optional even though at least one should be provided along with selector
to avoid a selection that has no effect.
When calling NodeaForms()
, every element's selector
will be applied to the context. Found elements are then initialized, and validated before form submission.
A CSS selector as a string used to match elements in the given context.
A function called on each element found with selector
. It takes the jQuery element and a potential placeholder as parameters. Any plugin initialization, initial value verification, event binding etc should be done in this function.
No return value is expected.
A function called on each element found with selector
before submitting the form. It takes the jQuery element and the form as parameters.
It should return false
to block form submission. Other elements will still be validated after one validator returned false.
For example if you want to check if an input val is a number:
NodeaForms($("#myForm"), {
elements: {
'checkNumber': {
selector: 'input.isNumber',
validator: (element, form) => {
if(isNaN(element.val()) {
toastr.warning('Please, fill a number in the concerned field');
return false;
}
}
}
}
});
You can optionally return an array of function that apply modifications on the form before submission. It will be applied only if every element's validation is successful and form is about to be submitted.
For example if you want to format a date before posting form data:
NodeaForms($("#myForm"), {
elements: {
'dateFormat': {
selector: 'input.datePicker',
validator: (element, form) => {
const inputDate = element.val();
return [_ => {
const formattedDate = moment(inputDate, 'MM/DD/YYYY').format('DD/MM/YYYY');
element.val(formattedDate);
}];
}
}
}
}
The overrideDefaults
parameter of NodeaForms()
allow you to provide modified or new element types to use.
To create a type, just provide its definition. Lets create an input that doesn't accept vowels.
Before submitting, append an input “vowelCount” with the number of replaced vowels to the form :
const vowelsRegex = new RegExp(/[aeiouyAEIOUY]/, 'g');
let vowelsCount = 0;
NodeaForms($("#myForm"), {
elements: {
'no_vowel': {
// Specify what element is of `no_vowel` type
selector: '.no_vowel',
// Bind change event
initializer: (element) => {
element.on('change', function() {
let vowels = element.val().match(vowelsRegex);
vowelsCount += vowels ? vowels.length : 0;
element.val(element.val().replace(vowelsRegex, '-');
}
},
// Return a function that append an input with the vowelCount. Executed only if whole form is valid
validator: (element, form) => {
return [_ => {
form.append(`<input type="number" name="vowelCount" value="${vowelsCount}">`);
}];
}
}
}
});
Overwriting an element is pretty much the same. What you provide will be used, or default for what is missing. If we want to change the selector
of an element, but don't need to change its initializer
or validator
, only provide the selector
.
Lets change the selector
of currency
type. By default, currency element are selected by attribute data-type="currency"
. We want to keep this rule, but add the .currency
class to the selector.
Use the default and append the class :
NodeaForms($("#myForm"), {
elements: {
currency: {
// Append `.currency` class to currency's default selector
selector: NodeaForms.elements.currency.selector + ', .currency'
// Default initializer will be used
// Default validator will be used
}
}
});
Here we've overwritten the currency selector
, but the default initializer
and validator
will be used. If for example you want to remove any validation, provide an empty function :
currency: {
selector: '.custom-selector', // Custom selector
// Default initializer() will be used
validator: _ => {} // No validation
}
Name | Selector | Initializer | Validator |
---|---|---|---|
ajax_select | "select.ajax" | Call to ajax_select() |
Append an input with no value if no option is selected |
select | "select:not(.ajax):not(.regular-select):not(.custom-select)" | select2 plugin initialization | Append an input with no value if no option is selected |
checkbox | "input[type='checkbox']:not(.no-icheck)" | iCheck plugin initialization | Handles empty values |
radio | "span.radio-group[data-radio]" | iCheck plugin initialization | |
relatedtomany_checkbox | ".relatedtomany-checkbox" | Split checkbox display | Manualy check required attribute |
textarea | "textarea:not(.regular-textarea):not(.note-codable)" | Summernote plugin | |
number | "input[type='number']" | Bind length check on keyup | |
decimal | "input[data-custom-type='decimal']" | Bind input check on keyup | |
"input[data-type='email']" | Inputmask plugin | Check mask validity | |
phone | "input[type='tel']" | Inputmask plugin | Tel regex on input value |
picture | "img[data-type='picture']" | Display text when no image | |
file_preview | ".preview_file" | Bind preview on click. Ajax call loads the file into a modal | |
timepicker | ".timepicker" | datetimepicker plugin | |
datepicker | ".datepicker" | datepicker plugin | Format date |
datetimepicker | ".datetimepicker" | datetimepicker + inputmask plugin | Format date |
qrcode | "input[data-type='qrcode']" | QRCode plugin | |
barcode | "input[data-type='barcode']" | JsBarcode plugin | Validate barcode |
currency | "input[data-type='currency']" | maskMoney plugin | Fix decimal precision |
url | "input[type='url']" | Prepend "http://" on blur | Check url validity |
file | ".nodea-dropzone" | Binds dropzone file upload and drag and drop | Check value if required |
address | ".address_component" | Loads address data and plugins | |
status | ".status" | Bind status comment modal on click |
The handleSubmit
function is triggered by the submit
event on a <form>
. It does multiple things :
submit
event is fired while the form is already being submitted, it will be ignored.NodeaForms.validate()
and blocks submission if validation fails..ajax
class on the form, it will do one of these :
form.ajax
), it will submit the form using jQuery's $.ajax()
function and return false
to block the default submission. NodeaForms.handleSuccess
and NodeaForms.handleError
will be provided as callbacks of the ajax call. Overwrites of these 3 functions can be provided using overrideDefaults
parameter upon initialization.true
It's the callback of a successful ajax form submission. By default, it uses NodeaTabs
to close the tab overlay if there is one, and reload the current tab if one is opened.
It can be overwritten via parameter to NodeaForms
or globally with NodeaForms.handleSuccess
It's the callback of an unsuccessful ajax form submission. By default, it parses the error and try to display a toastr
of the message if provided, or default unknown error text.
It can be overwritten via parameter to NodeaForms
or globally with NodeaForms.handleError
The validate
function is called by default in NodeaForms.handleSubmit
.
For each element of the form it will call its validator
function. If any validator
return false
, validate
will return false
too and nothing more.
If no validator
return false
, it will apply any modification function returned by the validators, then return true
You might want to initialize an element without a form. To do so you can call the element's initializer
function.
For example, you want a datepicker that is not part of a form, it only change UI display client side :
NodeaForms.elements.datepicker.initializer($("#myDatepicker"));
In this example we want to make file-type fields only accept files with a .doc or .docx extension:
NodeaForms(document, {
elements: {
file: {
validator: (element, form) => {
const input = element.parents('.form-group').find("input[type=file]");
const valide_ext = ['doc', 'docx'];
const files = input[0].files;
for(const idx in files) {
const file = files[idx];
if(!file.name || !file.type)
continue;
const file_ext = file.name.split('.').pop();
if(!valide_ext.includes(file_ext)) {
toastr.error("The uploaded file do not have valid format: .doc ou .docx");
return false;
}
}
}
}
}
});
We're gonna take a look at <table>
definition and usage in a Nodea application.
Generated tables rely on the DataTables plugin (https://datatables.net/manual/), wrapped in a Nodea specific plugin called NodeaTable.
We can separate their roles in two main axes :
<table>
's HTML to know, for each column, where to fetch the data, how to render and filter it or how to handle click events.NodeaTable's file is found at /_core/public/js/nodeaTable.js
and must be included in the page along with DataTables plugin /app/public/js/plugins/datatables/jquery.dataTables.min.js
.
Read the plugin's documentation to learn how to use it and what is available https://datatables.net/manual/
DataTables option can be provided to NodeaTable through the params.dataTableOptions
parameter. Any option except column
and columnDef
will override defaults if any, or simply be added to DataTables initialization parameter.
NodeaTable reserve the use of options column
and columnDef
, as they are built based on the table's HTML and NodeaTable options. They will be ignored if provided.
To use NodeaTable you should never initialize your tables using DataTables function. NodeaTable will do it for you under the hood.
By default on page load, NodeaTable will check automatically all your <table>
and try to initialize it with NodeaTable().
They are all loaded with ajax calls, paginated, searchable and orderable.
On the <table>
tag it looks for :
id
: id of the table to initialize.data-url
: Url used by ajax calls to fetch data from the server.On the thead.main th
tags it looks for :
data-col
: Path of the column's data. It must represent the field to display, doted notation is expected to descend into associations (ex: r_user.r_child.id
)data-type
: The type of the column. It should match a built in column type definition or one provided through param
argument of NodeaTable (We will talk about it soon). If no column type matches, default definition will be used.data-hidden
: Forces the column to be hidden. Other ways to define column visibility exist, but data-hidden
has the highest priority.data-searchable
: Boolean to specify if the column can be filtered.data-orderable
: Boolean to specify if the column can be ordered.data-default-order
: A string that specifies that this column must be used for default ordering. Value can be one of 'DESC'
or 'ASC'
.You can disable auto init of NodeaTable on your <table>
by adding a .no-init
class.
HTML table that were intitialized with NodeaTable are what we call a "Nodea DataTable".
NodeaTable should not be called more than once on a given table.
Here is the way to init a HTML table directly with javascript.
NodeaTable('#' + tableId, {
hide: [], // List of column to hide
columns: {}, // Columns definition, see below
filters: {}, // Filters definition, see below
bindings: {}, // Bindings definition, see below
buttons: {}, // Buttons definition, see below
context: document, // Context of the table
defaultOrder: { // Default table order (coming in 3.1)
idx: -1, // Column idx on the table, from left to right
direction: 'DESC'
},
dataTableOptions: {}, // Default datatable.js option, see the datatable.js plugin documentation
debug: true // Will log more informations in the client console (coming in 3.1)
}, {
// Additional data that will be send to all columns rendering method
});
Each column as a type (data-type
) that will define how it is rendered, filtered and bound (click event). All existing types are available in NodeaTable.defaults
.
You can create your own types either globally by adding them to NodeaTable.defaults
, or for a specific table by providing it to NodeaTable upon initialization.
If a type is undefined, default behavior will be used (ie: NodeaTable.defaults.column.default
, NodeaTable.defaults.filters.default
, NodeaTable.defaults.bindings.default
).
Types are defined by 3 elements :
columns
: An object that provide column options and render function of a type.columns: {
'colType': {
render: ({value, row, column, entity, additionalData}) => value, // Returns the data to display of each column cell
search: ({column, title, savedFilter, searchTh, triggerSearch, additionalData}) => value, // Same as filters definition, see below
searchable: bool, // Default false, render a filter <th> for the column
orderable: bool, // Default false, allows ordering of the column
binding: bool, // Default true, enable click event
htmlencode: bool, // Default true, encode the value provided to the render function
}
}
filters
: A function that returns the HTML element to use as a filter input. Custom bindings and initialization (ex: datepicker) should be done before returning the element. To trigger the filtering, triggerSearch(value, type='string')
function must be used. It will add a delay, handle filter saving and trigger DataTables search. Default will create an input.filters: {
'colType': ({column, title, savedFilter, filterTh, triggerSearch, additionalData}) => {}
}
bindings
: A function that defines what happens on cell click. Default binding look for a .btn-show
button on the row and triggers it if found.bindings: {
'colType': ({column, columnDef, entity, element, event, additionalData}) => {}
}
NodeaTable exposes a global function NodeaTable('tableId', params={}, additionalData={})
that initialize the given table.
function NodeaTable(tableId, {
context: document, // Context in which to find the table
dataTableOptions: {}, // DataTables plugin options
hide: [], // Array of columns to hide. Integer will be treated as column index, string will match data-col or data-type
columns: {}, // Column definition. Overwrite existing types or provide new ones
filters: {}, // Filter definition. Overwrite existing types or provide new ones
bindings: {}, // Bindings definition. Overwrite existing types or provide new ones
}, additionalData={}); // Addtional data will be passed along with columns render, filters, or bindings functions
// Default initialization
NodeaTable('#table_e_user');
NodeaTable()
will return the table object of DataTables plugin. It is also set on the DOM element with jQuery $("#table").data("table")
so it can be accessed at anytime.
The DataTables object returned also contains a tableOptions
property. It is the object provided to DataTables plugin.
NodeaTable prototype includes a defaults
object containing all the default values and functions that are used to initialize a table.
For example here you will remove all default buttons of all nodeaTable in your application.
<script type="text/javascript">
NodeaTable.defaults.buttons = [];
</script>
See all defaults in _core: NodeaTable _core defaults
It can be altered to globally change the behavior of NodeaTable, or it can be used to emulate functions in custom column types.
The columns
, filters
, and bindings
properties of params
argument allow you to customize what will be used by NodeaTable.
Each of these properties are object in which properties are the column types, and values their definition.
Columns side:
Lets say we need to change the behavior of a column type color
. The default renders a Font Awesome icon with the field's color as background.
We want to insert this icon in a <button>
tag for the table id="myTable"
.
We still want the Font Awesome icon with the right color, so we'll use the default color function, and modify the result :
function wrapIcon(data) {
let coloredIcon = NodeaTable.defaults.columns.color(data); // Default color column render
return `<button data-icon-color="${data.row.f_color}">${coloredIcon}</button>`; // Wrapped icon
}
// Change the color column type render for a specific table :
NodeaTable('#myTable', {
columns: {
...NodeaTable.defaults.columns.color,
render: wrapIcon
}
});
// Change the color column type render globally, right after NodeaTable inclusion :
NodeaTable.defaults.columns.color.render = wrapIcon;
Filters side:
Now we want to add a custom filter for the color type. We need a <select>
with defined colors to choose from :
function filterColor({column, title, savedFilter, filterTh, additionalData, triggerSearch}) {
// Create DOM filter element
const colorSelect = $(`
<select>
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
`);
// Bind filter trigger on select value change
colorSelect.on('change', function() {
triggerSearch(colorSelect.val(), 'color');
});
// Return element so it can be added to table's thead.filters row
return colorSelect;
}
// Specific table :
NodeaTable('#myTable', {
filters: {
color: filterColor
}
});
// Globally :
NodeaTable.defaults.filters.color = filterColor;
That's all it takes client side, but you'll still need to handle this custom filter type server side, usually in /entity/datalist
route.
Bindings side:
Now we want to change the background of an element #colorHolder
to the color of the clicked color cell :
function spreadColor({element}){
$("#colorHolder").css("background-color", element.find('button').data('icon-color'));
}
// Specific table :
NodeaTable('#myTable', {
bindings: {
color: spreadColor
}
});
// Globally :
NodeaTable.defaults.bindings.color = spreadColor;
Creating a type is the same as overwriting one. If you only provide a columns
definition, the default filters
and bindings
will be used. Same thing if columns
is missing.
Lets say we don't want to overwrite our color
type, instead we want to add a new column to the table to display the colored icon in a button.
We'll create button_color
type :
<!--
- First add a <th> to your table's thead.main, using the same data-col as your regular color column (here f_color) but with a different data-type
- Note the `.no-init` class on the table. We need it to disable automatic initialization, since we're providing custom definition
-->
<table id="myTable" class="no-init">
<thead class="main">
<th data-col="f_color" data-type="color"></th>
<th data-col="f_color" data-type="button_color"></th>
</thead>
</table>
// Specify the new `button_color` column type at table initialization :
NodeaTable("#myTable", {
columns: {
button_color: {
...NodeaTable.defaults.color,
render: wrapIcon
}
},
filters: {
button_color: filterColor
},
bindings: {
button_color: spreadColor
}
}
// Or globally :
NodeaTable.defaults.columns.button_color = {render: wrapIcon};
NodeaTable.defaults.filters.button_color = filterColor;
NodeaTable.defaults.bindings.button_color = spreadColor;
Disable filtering, ordering and click event for a column type :
NodeaTable("#myTable", {
columns: {
color: {
...NodeaTable.defaults.columns.color,
searchable: false, // No filter
binding: false, // No click handle,
orderable: false // No ordering
}
},
filters: {
color: _ => {} // This will be ignored
},
bindings: {
color: _ => {} // This will be ignored
}
}
Hide columns :
NodeaTable("#myTable", {
hide: [5, 'color', 'f_login'] // Respectively hide column index 5, columns type 'color' and column data 'f_login'
})
For anything that doesn't concern columns / filters / bindings, see the DataTables documentation.
You can add your custom DataTables options using params
parameter or by modifying it globally.
As an example, lets display an alert when the table is done drawing :
// Specific
NodeaTable('#myTable', {
dataTableOptions: {
drawCallback: _ => alert('Table drawn')
}
});
// Globally
NodeaTable.defaults.dataTableOptions.drawCallback = _ => alert('Table drawn');
Here are the built-in types
<a>
tag with mailto
<a>
tag with tel
<img>
tag with picture data<a>
tag with the url<button>
redirecting to the /show
page of the row<button>
redirecting to the /update
page of the row<button>
to delete the row<select>
with values true/false/none to the filter row<input>
and triggers filtering with ‘currency’ type<input>
to the filter row<form>
.btn-show
on row and triggers a click on itWe want to add a button on each line of the tables that redirect on a specific URL.
So first we need to declare the new button to NodeaTables on our specific table, so we add this javascript code at the end of the /views/my_entity/list.dust file:
<script>
NodeaTable("#table_e_my_entity", {
columns: {
my_button: {
render: ({value, row, column, entity, additionalData}) => {
var aTag = `
<a href="/my_specific_url/${parseInt(row.id)}" class="btn btn-info">
<i class="fa fa-link fa-md"></i>
</a>`;
return aTag;
},
search: false,
binding: false,
orderable: false
}
}
});
</script>
And then the only thing left to add is the in the /views/my_entity/list_fields.dust file:
<th data-type="my_button"></th>
Now your new button should appear on your my_entity NodeaTable on each line.
Just use the NodeaTable.defaults object in your main_layout.dust to override default behauvior for all futur NodeaTable instanciation.
<script type="text/javascript">
NodeaTable.defaults.columns.show = {
render: ({value, row, column, entity, additionalData}) => {
var aTag = `
<a class="btn-show ajax" href="/${entity.substring(2)}/show?id=${row.id}">
<button class="btn btn-primary">
<!-- Changing fa-desktop to fa-eye -->
<i class="fa fa-eye fa-md"></i>
</button>
</a>`;
return aTag;
},
search: false,
binding: false,
orderable: false
};
</script>
We have a number column in a NodeaTable and we want to disable the ordering for it.
<th data-field="f_number" data-col="f_number" data-type="number">
My Number
</th>
We can disable it locally in list.field javascript
$(function() {
$("table:not(.no-init)").each(function() {
NodeaTable("#"+$(this).attr('id'), {
columns: {
number_no_order: {
...NodeaTable.defaults.columns.number,
orderable: false
}
}
});
});
});
Or globally disabled it on all number column in main_layout.dust js
NodeaTable.defaults.columns = {
number_no_order: {
...NodeaTable.defaults.columns.number,
orderable: false
}
};
Since 3.1 you can also just add data-orderable directly on the th
<th data-field="f_number" data-col="f_number" data-orderable="false" data-type="number">
My Number
</th>
Tabs are found in show_fields.dust
pages. They allow the display of complementary information within the show page of an entity. Default tabs are either components or associated data of the source entity.
NodeaTabs handle these tabs behavior and functionalities. Its file (/_core/public/js/nodeaTabs.js
) is included by default in every show.dust
page of an application.
NodeaTabs depends on a tab's HTML definition to know what should be loaded when a click on the tab's .nav-tabs > li > a
element occurs.
By default, almost every tab is ajax loaded. Only the source entity's data are fetched and displayed through the /[entity]/show
server request, and are displayed in the main tab, identifiable by its #home
id.
Whether a tab should be ajax loaded or not depends on the presence of .ajax
class on its div.tab-pane
element. If no .ajax
class is found, NodeaTabs won't take any action upon clicking the tab's carret, since the tab's HTML should already be in the page.
A tab, offers two ways to display data :
.ajax-content
element, present in every ajax tab by default. Upon refreshing the tab, only .ajax-content
HTML is reloaded. Source of the tab can be specified by setting a data-url
attribute on the .tab-pane
element. If none provided, the tab's url will be built using different elements to obtain an url like /${sourceName}/loadtab/${sourceId}/${subentityAlias}?ajax=true&…
:
$("input[name=sourceId]").val()
$("input[name=sourceName]").val()
$(".tab-pane").prop("id")
data-asso-*
attributes :
data-asso-alias
: association aliasdata-asso-foreignkey
: association foreignkey (depends on the association relation)data-asso-flag
: source entity iddata-asso-url
: association display value.ajax-content
will be hidden and .ajax-overlay
appended to the tab. An overlay doesn't modify the .ajax-content
HTML. Clicking on a .tab-pane a.ajax
element will trigger the loading of an overlay in the current tab, based on the element's data-href
or href
attribute./_core/public/js/nodeaTabs.js
expose a global object NodeaTabs
NodeaTabs = {
current, // Current opened tab
loadTab, // function to load a tab
reloadTab, // function to reload the current tab
loadOverlay, // function to load an overlay
closeOverlay, // function to close an overlay
buildAssociationHref // function to build association href depending on the tab's HTML definition
}
Bindings are applied when the DOM is ready :
.nav-tabs > li > a
will trigger a loadTab()
'.tab-pane a.ajax'
will trigger a loadOverlay()
It will also automatically triggers a click on #[hash]-click
on page load if it finds a tab id that match the current location.hash
.
current
is an object containing information about the currently loaded tab :
{
tab, // jQuery element of the current tab
href, // href attribute of the current tab's ".nav-tab > li > a" element. Usualy `#[associationAlias]`
id: tab.attr('id'), // id of the current tab
overlay: false // whether an overlay is currently opened or not
}
loadTab(selector)
will find the tab to load using selector
and switch display to it.
If it's an ajax tab, it will build the ajax request and load its HTML result as is into the tab's .ajax-content
element. A loading icon is set during the request's execution, and an error icon is displayed if the request fails.
The url of the request can be provided through .tab-pane
data-url
attribute. If not provided, the url will be built as explained in the general knowledge part earlier.
Simply reloads the current tab by triggering a click on its .nav-tabs > li > a
element
loadOverlay(url)
takes an url as parameter that defines what to load into the overlay. An ajax request will be made using this url and its response HTML included as is into .ajax-overlay
element of the tab.
.ajax-overlay
is not present by default and will be inserted after the .ajax-content
once hidden.
The overlay doesn't change the content of a tab, it simply displays data in its place.
Close current's tab overlay. It will remove the .ajax-overlay
element from the DOM and display the .ajax-content
back.
This function returns a string built with all tab association's infomation as url parameters.
Nodea uses the select2 library for its selectors. Besides the basic instantiation which is done via the NodeaForms (see Built-in element) there are some things to clarify.
Here is an example of a select2 generated by Nodea:
<select class="ajax form-control" name="r_myteam" data-source="team" data-using="f_name" width="100%"></select>
Here NodeaForms will detect the select.ajax and therefore instantiate the select2 to retrieve the data via a server call.
data | Description | Required | Default | Example |
---|---|---|---|---|
data-source | Source entity targeted by the select. | Yes | data-source="my_entity" |
|
data-using | Field from data-source entity that will be display in the select. | No | id | data-using="f_field" | data-using="id,f_field,f_field2" |
data-url | Let you overwrite the default url called for ajax data call. | No | /[data-source]/search |
data-url="/my_custom_url" |
data-myvalue (since 3.0.2) | You can add as many value as you want and it will be sent to the ajax route in data.req.body.attrData . |
No | data-myvalue="My value" |