2026-02-04 15:26:44 -05:00
{ #
/**
* Copyright (C) 2020 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
#}
{% extends "authed.twig" %}
{% import "inline.twig" as inline %}
{% import "forms.twig" as forms %}
{% block title %} {{ "DataSets" | trans }} | {% endblock %}
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
{% block actionMenu %} {% endblock %}
2026-02-04 15:26:44 -05:00
{% block pageContent %}
<div class="ots-displays-page">
<div class="page-header ots-page-header">
<h1> {% trans "DataSets" %} </h1>
<p class="text-muted"> {% trans "Manage structured data sources." %} </p>
</div>
Refactor dashboard card classes to use 'content-card' instead of 'dashboard-card'
- Updated various views to replace 'dashboard-card' with 'content-card' for consistency in styling.
- Modified filter and table card classes across multiple pages including applications, campaigns, commands, datasets, dayparts, displays, display groups, display profiles, fonts, layouts, libraries, menu boards, modules, player software, playlists, resolutions, schedules, settings, sync groups, tags, tasks, templates, transitions, users, and user groups.
2026-02-11 09:17:45 -05:00
<div class="widget content-card ots-displays-card">
2026-02-04 15:26:44 -05:00
<div class="widget-body ots-displays-body">
<div class="XiboGrid" id=" {{ random ( ) }} " data-grid-name="dataSetView">
Refactor dashboard card classes to use 'content-card' instead of 'dashboard-card'
- Updated various views to replace 'dashboard-card' with 'content-card' for consistency in styling.
- Modified filter and table card classes across multiple pages including applications, campaigns, commands, datasets, dayparts, displays, display groups, display profiles, fonts, layouts, libraries, menu boards, modules, player software, playlists, resolutions, schedules, settings, sync groups, tags, tasks, templates, transitions, users, and user groups.
2026-02-11 09:17:45 -05:00
<div class="XiboFilter card mb-3 bg-light content-card ots-filter-card">
2026-02-04 15:26:44 -05:00
<div class="ots-filter-header">
<h3 class="ots-filter-title"> {% trans "Filter DataSets" %} </h3>
<button type="button" class="ots-filter-toggle" id="ots-filter-collapse-btn" title=" {% trans 'Toggle filter panel' %} ">
<i class="fa fa-chevron-up"></i>
</button>
</div>
<div class="ots-filter-content" id="ots-filter-content">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline" onsubmit="return false">
{% set title %} {% trans "Name" %} {% endset %}
{{ inline .inputNameGrid ( 'dataSet' , title ) }}
{% set title %} {% trans "Code" %} {% endset %}
{% set helpText %} {% trans "Show items which match the provided code" %} {% endset %}
{{ inline .input ( "code" , title , "" , helpText ) }}
{% set title %} {% trans "Owner" %} {% endset %}
{% set helpText %} {% trans "Show items owned by the selected User." %} {% endset %}
{% set attributes = [
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
{ name: "data-width", value: "100%" },
2026-02-04 15:26:44 -05:00
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-search-url", value: url_for("user.search") },
{ name: "data-search-term", value: "userName" },
{ name: "data-search-term-tags", value: "tags" },
{ name: "data-id-property", value: "userId" },
{ name: "data-text-property", value: "userName" },
{ name: "data-initial-key", value: "userId" },
] %}
{{ inline .dropdown ( "userId" , "single" , title , "" , null , "userId" , "userName" , helpText , "pagedSelect" , "" , "" , "" , attributes ) }}
{{ inline .hidden ( "folderId" ) }}
</form>
</div>
</div>
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
<div class="grid-with-folders-container ots-grid-with-folders">
Refactor dashboard card classes to use 'content-card' instead of 'dashboard-card'
- Updated various views to replace 'dashboard-card' with 'content-card' for consistency in styling.
- Modified filter and table card classes across multiple pages including applications, campaigns, commands, datasets, dayparts, displays, display groups, display profiles, fonts, layouts, libraries, menu boards, modules, player software, playlists, resolutions, schedules, settings, sync groups, tags, tasks, templates, transitions, users, and user groups.
2026-02-11 09:17:45 -05:00
<div class="grid-folder-tree-container p-3 content-card ots-folder-tree" id="grid-folder-filter">
2026-02-04 15:26:44 -05:00
<input id="jstree-search" class="form-control" type="text" placeholder=" {% trans "Search" %} ">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
<label class="form-check-label" for="folder-tree-clear-selection-button" title=" {% trans "Search in all folders" %} "> {% trans "All Folders" %} </label>
</div>
<div class="folder-search-no-results d-none">
<p> {% trans 'No Folders matching the search term' %} </p>
</div>
<div id="container-folder-tree"></div>
</div>
<div id="datatable-container">
Refactor dashboard card classes to use 'content-card' instead of 'dashboard-card'
- Updated various views to replace 'dashboard-card' with 'content-card' for consistency in styling.
- Modified filter and table card classes across multiple pages including applications, campaigns, commands, datasets, dayparts, displays, display groups, display profiles, fonts, layouts, libraries, menu boards, modules, player software, playlists, resolutions, schedules, settings, sync groups, tags, tasks, templates, transitions, users, and user groups.
2026-02-11 09:17:45 -05:00
<div class="XiboData card py-3 content-card ots-table-card">
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
<div class="ots-table-toolbar">
Refactor toolbar buttons across various pages to unify styling
- Updated button classes for consistency in the playersoftware-page, playlist-page, resolution-page, schedule-page, settings-page, syncgroup-page, tag-page, task-page, template-page, transition-page, user-page, and usergroup-page.
- Removed unnecessary text from button titles and ensured all buttons have the 'ots-toolbar-btn' class for uniformity.
- Cleaned up the code by removing commented-out sections and ensuring proper indentation.
2026-02-07 14:50:40 -05:00
<button type="button" id="folder-tree-select-folder-button" class="btn btn-sm btn-outline-secondary ots-toolbar-btn" title=" {% trans "Open / Close Folder Search options" %} "><i class="fas fa-folder"></i></button>
<div id="breadcrumbs"></div>
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
{% if currentUser .featureEnabled ( "dataset.add" ) %}
Refactor toolbar buttons across various pages to unify styling
- Updated button classes for consistency in the playersoftware-page, playlist-page, resolution-page, schedule-page, settings-page, syncgroup-page, tag-page, task-page, template-page, transition-page, user-page, and usergroup-page.
- Removed unnecessary text from button titles and ensured all buttons have the 'ots-toolbar-btn' class for uniformity.
- Cleaned up the code by removing commented-out sections and ensuring proper indentation.
2026-02-07 14:50:40 -05:00
<button class="btn btn-sm btn-success ots-toolbar-btn XiboFormButton" title=" {% trans "Add a new DataSet" %} " href=" {{ url_for ( "dataSet.add.form" ) }} "><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
{% endif %}
Refactor toolbar buttons across various pages to unify styling
- Updated button classes for consistency in the playersoftware-page, playlist-page, resolution-page, schedule-page, settings-page, syncgroup-page, tag-page, task-page, template-page, transition-page, user-page, and usergroup-page.
- Removed unnecessary text from button titles and ensured all buttons have the 'ots-toolbar-btn' class for uniformity.
- Cleaned up the code by removing commented-out sections and ensuring proper indentation.
2026-02-07 14:50:40 -05:00
<button class="btn btn-sm btn-primary ots-toolbar-btn" id="refreshGrid" title=" {% trans "Refresh the Table" %} " href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
Add new pages for managing tags, tasks, transitions, users, user groups, and their respective JavaScript functionalities
- Implemented tag management page with filtering, data table, and AJAX functionality.
- Created task management page with task listing, filtering, and AJAX data loading.
- Developed transition management page with a data table for transitions.
- Added user management page with comprehensive user details, filtering options, and AJAX support.
- Introduced user group management page with filtering and data table for user groups.
- Enhanced JavaScript for data tables, including state saving, filtering, and AJAX data fetching for all new pages.
2026-02-06 23:54:21 -05:00
</div>
2026-02-04 15:26:44 -05:00
<table id="datasets" class="table table-striped" data-state-preference-name="dataSetGrid" style="width: 100%;">
<thead>
<tr>
<th> {% trans "ID" %} </th>
<th> {% trans "Name" %} </th>
<th> {% trans "Description" %} </th>
<th> {% trans "Code" %} </th>
<th> {% trans "Remote?" %} </th>
<th> {% trans "Real time?" %} </th>
<th> {% trans "Owner" %} </th>
<th> {% trans "Sharing" %} </th>
<th> {% trans "Last Sync" %} </th>
<th> {% trans "Data Last Modified" %} </th>
<th class="rowMenu"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce=" {{ cspNonce }} ">
var table;
$(document).ready(function() {
{% if not currentUser .featureEnabled ( "folder.view" ) %}
disableFolders();
{% endif %}
table = $("#datasets").DataTable( {
"language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
stateDuration: 0,
responsive: true,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
searchDelay: 3000,
"order": [[ 0, "asc"]],
ajax: {
"url": " {{ url_for ( "dataSet.search" ) }} ",
"data": function(d) {
$.extend(d, $("#datasets").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
}
},
"columns": [
{ "data": "dataSetId", responsivePriority: 2 },
{ "data": "dataSet", "render": dataTableSpacingPreformatted, responsivePriority: 2 },
{ "data": "description", responsivePriority: 4 },
{ "data": "code", responsivePriority: 3 },
{
"data": "isRemote",
responsivePriority: 3,
"render": dataTableTickCrossColumn
},
{
data: 'isRealTime',
responsivePriority: 3,
render: dataTableTickCrossColumn,
},
{ "data": "owner", responsivePriority: 3 },
{
"data": "groupsWithPermissions",
responsivePriority: 3,
"render": dataTableCreatePermissions
},
{
"data": "lastSync",
responsivePriority: 4,
"render": dataTableDateFromUnix
},
{
"data": "lastDataEdit",
responsivePriority: 4,
"render": dataTableDateFromUnix
},
{
"orderable": false,
responsivePriority: 1,
"data": dataTableButtonsColumn
}
]
});
table.on('draw', function(e, settings) {
dataTableDraw(e, settings);
// Upload form
$(".dataSetImportForm").click(function(e) {
e.preventDefault();
var template = Handlebars.compile($("#template-dataset-upload").html());
var data = table.row($(this).closest("tr")).data();
var columns = [];
var i = 1;
$.each(data.columns, function (index, element) {
if (element.dataSetColumnTypeId === 1) {
element.index = i;
columns.push(element);
i++;
}
});
// Handle bars and open a dialog
bootbox.dialog( {
message: template( {
trans: {
addFiles: " {% trans "Add CSV Files" %} ",
startUpload: " {% trans "Start upload" %} ",
cancelUpload: " {% trans "Cancel upload" %} ",
processing: " {% trans "Processing..." %} "
},
upload: {
maxSize: {{ libraryUpload .maxSize }} ,
maxSizeMessage: " {{ libraryUpload .maxSizeMessage }} ",
validExt: " {{ libraryUpload .validExt }} ",
utf8Message: " {% trans "If the CSV file contains non-ASCII characters please ensure the file is UTF-8 encoded" %} "
},
columns: columns
}),
title: " {% trans "CSV Import" %} ",
size: 'large',
buttons: {
main: {
label: " {% trans "Done" %} ",
className: "btn-primary btn-bb-main",
callback: function() {
table.ajax.reload();
XiboDialogClose();
}
}
}
}).on('shown.bs.modal', function() {
// Configure the upload form
var url = " {{ url_for ( "dataSet.import" , { id : ':id' } ) }} ".replace(":id", data.dataSetId);
var form = $(this).find("form");
var refreshSessionInterval;
// Initialize the jQuery File Upload widget:
form.fileupload( {
url: url,
disableImageResize: true
});
// Upload server status check for browsers with CORS support:
if ($.support.cors) {
$.ajax( {
url: url,
type: 'HEAD'
}).fail(function () {
$('<span class="alert alert-error"/>')
.text('Upload server currently unavailable - ' + new Date())
.appendTo(form);
});
}
// Enable iframe cross-domain access via redirect option:
form.fileupload(
'option',
'redirect',
window.location.href.replace(
/\/[^\/]*$/,
'/cors/result.html?%s'
)
);
form.bind('fileuploadsubmit', function (e, data) {
var inputs = data.context.find(':input');
if (inputs.filter('[required][value=""]').first().focus().length) {
return false;
}
data.formData = inputs.serializeArray().concat(form.serializeArray());
inputs.filter("input").prop("disabled", true);
}).bind('fileuploadstart', function (e, data) {
// Show progress data
form.find('.fileupload-progress .progress-extended').show();
form.find('.fileupload-progress .progress-end').hide();
if (form.fileupload("active") <= 0)
refreshSessionInterval = setInterval("XiboPing('" + pingUrl + "?refreshSession=true')", 1000 * 60 * 3);
return true;
}).bind('fileuploaddone', function (e, data) {
if (refreshSessionInterval != null && form.fileupload("active") <= 0)
clearInterval(refreshSessionInterval);
}).bind('fileuploadprogressall', function (e, data) {
// Hide progress data and show processing
if(data.total > 0 && data.loaded == data.total) {
form.find('.fileupload-progress .progress-extended').hide();
form.find('.fileupload-progress .progress-end').show();
}
}).bind('fileuploadadded fileuploadcompleted fileuploadfinished', function (e, data) {
// Get uploaded and downloaded files and toggle Done button
var filesToUploadCount = form.find('tr.template-upload').length;
var $button = form.parents('.modal:first').find('button.btn-bb-main');
if(filesToUploadCount == 0) {
$button.removeAttr('disabled');
} else {
$button.attr('disabled', 'disabled');
}
});
});
});
});
table.on('processing.dt', dataTableProcessing);
dataTableAddButtons(table, $('#datasets_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
table.ajax.reload();
});
});
function dataSetFormOpen(dialog) {
// Bind the remote dataset test button
$(dialog).find("#dataSetRemoteTestButton").on('click', function() {
var $form = $(dialog).find("form");
XiboRemoteRequest(" {{ url_for ( "dataSet.test.remote" ) }} ", $form.serializeObject(), function(response) {
if (!response.success || !$.trim(response.data.entries)) {
response.data = response.message;
}
$("#datasetRemoteTestRequestResult").html('<pre style="height: 300px; overflow: scroll">' + JSON.stringify(response.data, null, 3) + '</pre>');
});
});
// Set up some dependencies between the isRemote checkbox and the tabs related to remote datasets
onRemoteFieldChanged(dialog);
// show data source dropdown if real time is checked
onIsRealTimeFieldChanged(dialog);
$(dialog).find("#isRemote").on('change', function() {
onRemoteFieldChanged(dialog);
});
$(dialog).find("#isRealTime").on('change', function() {
onIsRealTimeFieldChanged(dialog);
});
// Auth field
onAuthenticationFieldChanged(dialog);
$(dialog).find("#authentication").on('change', function() {
onAuthenticationFieldChanged(dialog);
});
// remote DataSet source
onSourceFieldChanged(dialog);
$(dialog).find('#sourceId').on('change', function() {
onSourceFieldChanged(dialog);
});
// Validate form manually because
// uri field depends on isRemote being checked
if (forms != undefined) {
const $form = $(dialog).find('form');
forms.validateForm(
$form, // form
$form.parent(), // container
{
submitHandler: XiboFormSubmit,
rules: {
uri: {
required: function(element) {
return $form.find('#isRemote').is(':checked')
},
},
},
},
);
}
}
function onIsRealTimeFieldChanged(dialog) {
var isRealTime = $(dialog).find("#isRealTime").is(":checked");
var dataSourceField = $(dialog).find("#dataSourceField");
var dataConnectorSource = $(dialog).find("#dataConnectorSource");
if (isRealTime) {
// show and enable data connector source
dataSourceField.removeClass("d-none");
dataConnectorSource.prop('disabled', false)
} else {
// hide and disable data connector source
dataSourceField.addClass("d-none");
dataConnectorSource.prop('disabled', true)
}
}
function onRemoteFieldChanged(dialog) {
var isRemote = $(dialog).find("#isRemote").is(":checked");
var $remoteTabs = $(dialog).find(".tabForRemoteDataSet");
if (isRemote) {
$remoteTabs.removeClass("d-none");
} else {
$remoteTabs.addClass("d-none");
}
}
function onAuthenticationFieldChanged(dialog) {
var authentication = $(dialog).find("#authentication").val();
var $authFieldUserName = $(dialog).find(".auth-field-username");
var $authFieldPassword = $(dialog).find(".auth-field-password");
if (authentication === "none") {
$authFieldUserName.addClass("d-none");
$authFieldPassword.addClass("d-none");
} else if (authentication === "bearer") {
$authFieldUserName.addClass("d-none");
$authFieldPassword.removeClass("d-none");
} else {
$authFieldUserName.removeClass("d-none");
$authFieldPassword.removeClass("d-none");
}
}
function onSourceFieldChanged(dialog) {
var sourceId = $(dialog).find('#sourceId').val();
var $jsonSource = $(dialog).find(".json-source-field");
var $csvSource = $(dialog).find(".csv-source-field");
if (sourceId == 1) {
$jsonSource.removeClass('d-none');
$csvSource.addClass('d-none');
} else {
$jsonSource.addClass('d-none');
$csvSource.removeClass('d-none');
}
}
function deleteMultiSelectFormOpen(dialog) {
{% set message = 'Delete any associated data?' %}
var $input = $('<input type=checkbox id="deleteData" name="deleteData"> {{ message | trans | e }} </input>');
$input.on('change', function() {
dialog.data().commitData = { deleteData: $(this).val()};
});
$(dialog).find('.modal-body').append($input);
}
</script>
{% endblock %}
{% block javaScriptTemplates %}
{{ parent ( ) }}
{% verbatim %}
<script type="text/x-handlebars-template" id="template-dataset-upload">
<form class="form-horizontal" method="post" enctype="multipart/form-data" data-max-file-size=" {{ upload .maxSize }} " data-accept-file-types="/(\.|\/)csv/i">
<div class="row fileupload-buttonbar">
<div class="card p-3 mb-3 bg-light">
{{ upload .maxSizeMessage }} <br>
{{ upload .utf8Message }}
</div>
<div class="col-md-7">
<!-- The fileinput-button span is used to style the file input field as button -->
<span class="btn btn-success fileinput-button">
<i class="fa fa-plus"></i>
<span> {{ trans .addFiles }} </span>
<input type="file" name="files">
</span>
<button type="submit" class="btn btn-primary start">
<i class="fa fa-upload"></i>
<span> {{ trans .startUpload }} </span>
</button>
<button type="reset" class="btn btn-warning cancel">
<i class="fa fa-ban"></i>
<span> {{ trans .cancelUpload }} </span>
</button>
<!-- The loading indicator is shown during file processing -->
<span class="fileupload-loading"></span>
</div>
<!-- The global progress information -->
<div class="col-md-4 fileupload-progress fade">
<!-- The global progress bar -->
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%;">
<div class="sr-only"></div>
</div>
</div>
<!-- The extended global progress information -->
<div class="progress-extended"> </div>
<!-- Processing info container -->
<div class="progress-end" style="display:none;"> {{ trans .processing }} </div>
</div>
</div>
<div class="row">
<div class="col-md-12">
{% endverbatim %}
{% set title %} {% trans "Overwrite existing data?" %} {% endset %}
{% set helpText %} {% trans "Erase all content in this DataSet and overwrite it with the new content in this import." %} {% endset %}
{{ forms .checkbox ( "overwrite" , title , "" , helpText ) }}
{% set title %} {% trans "Ignore first row?" %} {% endset %}
{% set helpText %} {% trans "Ignore the first row? Useful if the CSV has headings." %} {% endset %}
{{ forms .checkbox ( "ignorefirstrow" , title , "" , helpText ) }}
{% set message %} {% trans "In the fields below please enter the column number in the CSV file that corresponds to the Column Heading listed. This should be done before Adding the file." %} {% endset %}
{{ forms .message ( message ) }}
{% verbatim %}
{{ # each columns }}
<div class="form-group row">
<label class="col-sm-2 control-label" for="csvImport_ {{ dataSetColumnId }} "> {{ heading }} </label>
<div class="col-sm-10">
<input class="form-control" name="csvImport_ {{ dataSetColumnId }} " type="number" id="csvImport_ {{ dataSetColumnId }} " value=" {{ index }} " />
</div>
</div>
{{ / each }}
</div>
</div>
<!-- The table listing the files available for upload/download -->
<table role="presentation" class="table table-striped"><tbody class="files"></tbody></table>
</form>
</script>
<!-- The template to display files available for upload -->
<script id="template-dataset-upload" type="text/x-tmpl">
{% for ( var i = 0 , file ; file = o .files [ i ] ; i + + ) { %}
<tr class="template-upload">
<td>
<span class="fileupload-preview"></span>
</td>
<td class="title">
{% if ( file .error ) { %}
<div><span class="label label-danger"> { %=file.error%}</span></div>
{ % } %}
{% if ( ! file .error ) { %}
<label for="name[]"><input name="name[]" type="text" id="name" value="" /></label>
{ % } %}
</td>
<td>
<p class="size"> { %=o.formatFileSize(file.size)%}</p>
{% if ( ! o .files .error ) { %}
<div class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%;">
<div class="sr-only"></div>
</div>
</div>
</div>
{ % } %}
</td>
<td class="btn-group">
{% if ( ! o .files .error & & ! i & & ! o .options .autoUpload ) { %}
<button class="btn btn-primary start">
<i class="fa fa-upload"></i>
</button>
{ % } %}
{% if ( ! i ) { %}
<button class="btn btn-warning cancel">
<i class="fa fa-ban"></i>
</button>
{ % } %}
</td>
</tr>
{ % } %}
</script>
<!-- The template to display files available for download -->
<script id="template-dataset-download" type="text/x-tmpl">
{% for ( var i = 0 , file ; file = o .files [ i ] ; i + + ) { %}
<tr class="template-download">
<td>
<p class="name" id=" { %=file.storedas%}" status=" {% if ( file .error ) { %} error { % } %}">
{ %=file.name%}
</p>
{% if ( file .error ) { %}
<div><span class="label label-danger"> { %=file.error%}</span></div>
{ % } %}
</td>
<td>
<span class="size"> { %=o.formatFileSize(file.size)%}</span>
</td>
</tr>
{ % } %}
</script>
{% endverbatim %}
</div>
</div>
</div>
{% endblock %}