Related
sorry for a noob question, just started with Knockout.js. I have an array of objects and I want to update the view when object property favorite: changes but every time I click on an icon that triggers the change nothing happens. When I add a new object to an array UI gets rerendered. I would really appreciate some help with this. Thanks
<div id="container" data-bind="foreach:savedSearches">
<div class="save-search-item" data-bind="attr:{'data-name': $data.name, 'data-id':$data.id, 'favourite':$data.favorite() === 1}">
<div data-bind="text: $data.name"></div>
<div class="icons">
<a href="#" class="favourite-search">
<i class="fas fa-star" data-bind="css: {favourite: $data.favorite() === 1}"></i>
</a>
<a href="#" class="edit-search">
<i class="fas fa-edit"></i>
</a>
<a href="#" class="delete-search">
<i class="fas fa-trash-alt"></i>
</a>
</div>
</div>
</div>
var searches = [
{
activation_time: null,
activation_time_ms: null,
favourite: 1,
enabled: 1,
id: 66,
name: "adfdfafs"
},
{
activation_time: null,
activation_time_ms: null,
favourite: 0,
enabled: 1,
id: 66,
name: "adfdfafs"
}
];
ko.applyBindings(AppViewModel, $('#container'));
function AppViewModel(data) {
self.savedSearches = ko.observableArray([]);
self.favourite = ko.observable();
self.populateSavedSearches = function(data) {
data.forEach(function(search) {
search.favorite = ko.observable();
});
self.savedSearches(data);
}
}
$('.favourite-search').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
// get parent element with id
var parent = e.currentTarget.closest('.save-search-item');
var searchId;
var isFavourite = false;
if (parent) {
searchId = parseInt(parent.getAttribute('data-id'));
isFavourite = parent.getAttribute('favourite');
searches.map(function(search) {
if (search.id === searchId) {
search.favorite = 0;
ko.populateSavedSearches(search);
}
});
}
});
When using knockout, you should not add your own event listeners via jQuery.
In this case, use the click binding to react to user behavior.
I did the bare minimum to make your snippet work, but I think it gets the point across:
You already found out you have to make the favorite property observable! Great start
I added a toggle function to each of the searches that swaps the favorite observable between 1 and 0
In the view, I added a click binding that calls toggle
In the view, I moved your favourite attribute binding to be a css binding. This makes sure favorited searches get the favourite class
In CSS, I styled .favourite elements to have a yellow background.
In applyBindings, I use new to create a new viewmodel and pass the app container using [0]
You can see these changes in action in the snippet below.
var searches = [
{
activation_time: null,
activation_time_ms: null,
favourite: 1,
enabled: 1,
id: 66,
name: "adfdfafs"
},
{
activation_time: null,
activation_time_ms: null,
favourite: 0,
enabled: 1,
id: 66,
name: "adfdfafs"
}
];
ko.applyBindings(new AppViewModel(searches), $('#container')[0]);
function AppViewModel(data) {
const self = this;
self.savedSearches = ko.observableArray([]);
self.favourite = ko.observable();
self.populateSavedSearches = function() {
data.forEach(function(search) {
search.favorite = ko.observable(search.favorite);
search.toggle = function() {
search.favorite(search.favorite() ? 0 : 1);
}
});
self.savedSearches(data);
}
self.populateSavedSearches();
}
.favourite { background: yellow }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div id="container" data-bind="foreach: savedSearches">
<div class="save-search-item" data-bind="
click: toggle,
attr: {
'data-name': $data.name,
'data-id':$data.id
},
css: { 'favourite': $data.favorite() === 1 }
">
<div data-bind="text: $data.name"></div>
<div class="icons">
<a href="#" class="favourite-search">
<i class="fas fa-star" data-bind="css: {favourite: $data.favorite() === 1}"></i>
</a>
<a href="#" class="edit-search">
<i class="fas fa-edit"></i>
</a>
<a href="#" class="delete-search">
<i class="fas fa-trash-alt"></i>
</a>
</div>
</div>
</div>
I have a grid of styled books and their images, created with the gridstack library:
I also have a "save" button that calls a saveGrid() function to save the location of each book on the grid, and loads its serialized data with grid.load(serializedData)
HTML:
<div>
<h1 class="title">Your Books</h1>
<button onclick="saveGrid(); grid.load(serializedData)">Save</button>
</div>
<div class="grid-stack"></div>
JS:
saveGrid = () => {
serializedData = [];
grid.engine.nodes.forEach((node) => {
serializedData.push({
x: node.x,
y: node.y,
width: node.width,
height: node.height,
noResize: true,
el: node.el
});
});
};
The problem is that, when the "save" button is clicked, the positions of the items are saved but not the actual HTML images and content (like buttons), as shown below:
I've figured out the problem and edited my saveGrid() function, as well as made a new loadGrid() function that loads the grid after saving and an addBooks() function that loads books from an array upon page load. Below is the HTML and the full JS code:
HTML:
<div class="grid-stack"></div>
JS:
var booksArray = [
{"ISBN": '0385504209', "bookURL": "https://images.penguinrandomhouse.com/cover/9780767926034"},
{"ISBN": '0143039431', "bookURL": "https://upload.wikimedia.org/wikipedia/commons/a/ad/The_Grapes_of_Wrath_%281939_1st_ed_cover%29.jpg"},
{"ISBN": '0743273567', "bookURL": "https://prodimage.images-bn.com/pimages/9780743273565_p0_v8_s550x406.jpg"},
{"ISBN": '0743297334', "bookURL": "https://upload.wikimedia.org/wikipedia/commons/8/8b/The_Sun_Also_Rises_%281st_ed._cover%29.jpg"},
{"ISBN": '0553283685', "bookURL": "http://books.google.com/books/content?id=wDVV6y-8YHEC&printsec=frontcover&img=1&zoom=1&source=gbs_api"}
]
var grid = GridStack.init({
column: 8,
animate: true,
//kinda wonky --> float: true,
removable: true,
rtl: true
})
const addBooks = () => {
grid.batchUpdate()
booksArray.forEach((book, index) => {
grid.addWidget(`
<div>
<div class="grid-stack-item-content">
<img src="${book.bookURL}" class="book-cover" id=${book.ISBN}></img>
<div class="button-group">
<i class="fa fa-minus-circle"></i>
<button class="btn button4" data-toggle="modal" data-target="#exampleModalLong" style="background-color:#4f21cf"><i class="fa fa-info-circle"></i></button>
<i class="fa fa-check"></i>
</div>
</div>
</div>`, {
width: 2,
height: 3,
noResize: true,
staticGrid: true
})
grid.engine.nodes[index].ISBN = book.ISBN
grid.engine.nodes[index].bookURL = book.bookURL
console.log(document.querySelector('.book-cover').id)
})
grid.commit()
console.log(grid.engine.nodes)
}
const saveGrid = () => {
serializedData = []
console.log(grid.engine.nodes)
grid.batchUpdate()
grid.engine.nodes.forEach((node, index) => {
console.log(node.el)
console.log(node.ISBN)
console.log(node.bookURL)
serializedData.push({
x: node.x,
y: node.y,
width: 2,
height: 3,
noResize: true,
ISBN: node.ISBN,
bookURL: node.bookURL
})
})
serializedData.sort()
grid.commit()
console.log(JSON.stringify(serializedData, null, ' '))
console.log(grid.engine.nodes.length)
}
const loadGrid = () => {
// empty the grid first:
grid.removeAll({detachNode:false}) //{detachNode: false}
// grid.assign()
console.log(serializedData)
var items = GridStack.Utils.sort(serializedData)
console.log(items)
grid.batchUpdate()
items.forEach((node, index) => {
grid.addWidget(`
<div>
<div class="grid-stack-item-content">
<img src="${node.bookURL}" class="book-cover"></img>
<div class="button-group">
<i class="fa fa-minus-circle"></i>
<i class="fa fa-info-circle"></i>
<i class="fa fa-check"></i>
</div>
</div>
</div>`, node)
grid.engine.nodes[index].ISBN = node.ISBN
grid.engine.nodes[index].bookURL = node.bookURL
console.log(node)
})
grid.commit()
console.log(grid.engine.nodes.length)
console.log(grid.engine.nodes)
}
window.onload = addBooks()
I implemented the creation of tables (based od Datatables library). The strange thing is that I getting DataTables warning: (table id=scoringTableI - Cannot reinitialize DataTable on my server) on the server-side but on the localhost everything works perfectly. Bellow text is PrintScreen and my source code. Does anyone know where is a problem?
scoringDetailView.js
/*
timer to reinit datatable after it is properly loaded
*/
var reinitInterval;
var scoreDown = false;
const scaleHeight = 20; // Height of the canvases on which to draw the score scales
const sliderHeight = 15; // Height of the slider controlling the scores
$(document).ready(function() {
console.log('document ready');
initTables();
reinitScoringTable();
initSliders();
$('.alert-dismissable').delay(2500).fadeOut();
$('[data-toggle="popover"]').popover();
$(document).keydown(function(event) { // bind function called when key is released
if (event.keyCode == 16){
scoreDown = true;
}
});
$(document).keyup(function() { // click method to call keydown() function
scoreDown = false;
});
});
/*
init all tables for the first time
*/
function initTables(){
$( ".table-bordered" ).each(function( index ) {
var id = '#' + this.getAttribute('id');
initTable(id);
console.log(id);
if($(this).has('input.score-slider').length){
// Add event listener to recalculate slider size on data table column width change
$(this).on('column-sizing.dt', function (){
initSliders();
});
}
// reinit table page to avoid empty buttons
$(id).on( 'page.dt', function () {
var reinitInterval = setInterval(function(){ reinitScoringTable() }, 10);
});
//reinit table on click of the first field.
//If the table shrunk due to the responsiveness the expanding buttons
//is placed there
$(id).DataTable().on( 'select', function ( e, dt, type, indexes ) {
dt.deselect();
if ( dt[0][0].column == 0) {
reinitScoringTable();
}
});
});
}
/*
options of one single table
*/
function initTable(id){
console.log('initTable(id=' + id + ')');
$(id).DataTable({
paging : true,
lengthChange: true,
pageLength : 10,
searching : true,
ordering : true,
info : true,
autoWidth : false,
responsive : true,
aaSorting : [],
language: {
paginate: {
previous: '<i class="fa fa-angle-left" aria-hidden="true"></i>',
next: '<i class="fa fa-angle-right" aria-hidden="true"></i>',
}
},
lengthMenu : [[5, 10, 25, 50, -1], [5, 10, 25, 50, "All"]],
select: {
style: 'single',
items: 'cell'
},
dom: "<'row'<'col-12 col-sm-12 col-md-12 col-lg-12 additional-buttons'B><'float-right col-12 col-sm-8 col-md-8 col-lg-8'f><'col-12 col-sm-4 col-md-4 col-lg-4'l>>" +
"<'row'<'col-md-12't>><'row'<'col-md-12'ip>>",
buttons: [
{
extend: 'pdf',
className: 'btn btn-default h-spacer-3',
exportOptions: {
columns: ':visible'
}
},
{
extend: 'print',
className: 'btn btn-default h-spacer-3',
exportOptions: {
columns: ':visible'
}
},
{
extend: 'excel',
className: 'btn btn-default h-spacer-3',
exportOptions: {
columns: ':visible'
}
},
{
extend: 'csv',
className: 'btn btn-default h-spacer-3',
exportOptions: {
columns: ':visible'
}
},
{
extend: 'copy',
className: 'btn btn-default h-spacer-3',
exportOptions: {
columns: ':visible'
}
},
]
});
console.log('initTable(id=' + id + ')' + ' finished');
}
/*
fixes detached buttons, corrects displayed values in table buttons,
recalculates responsive table
*/
function reinitScoringTable() {
console.log('reinitScoringTable()');
clearInterval(reinitInterval);
$( ".table-bordered" ).each(function( index ) {
var id = '#' + this.getAttribute('id');
$(id).DataTable().columns.adjust().responsive.recalc();
});
//rebind all buttons. Used for AOI scoring
$('.btn-scoring').unbind();
$('.btn-scoring').click(function() {
var form = $($(this).attr('form-id'));
var typeId = form.attr('type-id');
var min = parseFloat(form.attr('min'));
var max = parseFloat(form.attr('max'));
var stepsize = parseFloat(form.attr('stepsize'));
var view = $(this).attr('view');
//will only be executed in the beginning and will update all scores to minimum
if(isNaN(parseFloat(form.val()))){
var newVal = min;
//updates the rest of the row
$('.' + form.attr('class').split(' ').join('.')).each(function( index ) {
$(this).val(parseFloat($(this).attr('min')));
updateButtons();
});
}else{
//normal increment by stepsize
var val = parseFloat(form.val()) == 0 ? form.val() : parseFloat(form.val()) || min;
if(scoreDown){
a = val-min-stepsize
b = max-min+stepsize
var newVal = ((a%b)+b)%b+min;
}else {
var newVal = (val-min+stepsize)%(max-min+stepsize)+min;
}
}
form.val(newVal);
updateSystemScores(form.attr('class'), form, view);
setTypeLabel(typeId, (newVal-min)/stepsize, this, newVal);
});
// Slider for image scoring
$('.score-slider').off("input change");
$('.score-slider').on('input change', function(){
var newVal = $(this).val();
var form = $($(this).attr('form-id'));
var typeId = form.attr('type-id');
var min = parseFloat($(this).attr('min'));
var stepsize = parseFloat($(this).attr('step'));
var label = $($(this).parent().parent().find('.score-label'));
// If no score was saved yet, initialise all to minimum
if(isNaN(parseFloat(form.val()))){
$('.' + form.attr('class').split(' ').join('.')).each(function(){
$(this).val(parseFloat($(this).attr('min')));
});
// Set the current one to the new value
form.val(newVal);
updateSliderLabels();
} else {
form.val(newVal);
}
setTypeLabel(typeId, (newVal-min)/stepsize, label, newVal);
// The saving of the new score is only done on slider release
if(event.type == 'change'){
var view = $(this).attr('view');
updateSystemScores(form.attr('class'), form, view);
}
})
updateButtons();
updateAllSystemScorings();
}
//set values of all buttons in table
function updateButtons(){
$( ".btn-scoring" ).each(function( index ) {
var form = $($(this).attr('form-id'));
if(isNaN(parseFloat(form.val()))){
$(this).html('n.d.');
}else{
var typeId = form.attr('type-id');
var val = parseFloat(form.val()) || 0;
var min = parseFloat(form.attr('min'));
var max = parseFloat(form.attr('max'));
var stepsize = parseFloat(form.attr('stepsize'));
setTypeLabel(typeId, (val-min)/stepsize, this, val);
}
});
}
/*
* Set values for all slider labels
*/
function updateSliderLabels(){
$('.score-slider').each(function(){
var form = $($(this).attr('form-id'));
var label = $($(this).parent().parent().find('.score-label'));
// If no value for this type yet, label is 'n.d.'
if(isNaN(parseFloat(form.val()))){
label.html('n.d.');
} else { // Else set label to current value
var val = parseFloat(form.val()) || 0;
setTypeLabel(form.attr('type-id'), (val - parseFloat($(this).attr('min'))/parseFloat($(this).attr('step'))), label, val);
}
})
}
// recalc responsive on resizing
$(window).resize( function(){
if(document.readyState === 'complete'){
reinitScoringTable();
}
});
/*
Save type scores, recalculate system score and load the result
*/
function updateSystemScores(rowClass, form, view){
$('.active .scoring-status-symbol').attr( "class" , 'fa fa-spinner fa-spin scoring-status-symbol');
var data = $('.' + rowClass.split(' ').join('.')).serialize() + "&imageId=" + $('#image_id').attr('value');
if(form[0].hasAttribute('subSystem-id')){
data = data + '&subSystemId=' + form.attr('subSystem-id');
}
$.ajax({
type: 'POST',
url: "/scoring/systemscore",
data: data,
success: function(data, textStatus, xhr){
if(xhr.status == 200){
var scores = data[0];
var summary = data[1];
// Refill the score system scores with the new calculated scores
for (var score in scores) {
$(form.attr('score-ref') + '_' + scores[score]['score_system_id']).attr('value',scores[score]['result']);
}
updateAllSystemScorings();
// Reload summary view
$("#summary-pane" + view).html(summary);
$('.active .scoring-status-symbol').attr( "class" , 'fa fa-check scoring-status-symbol');
}else{
$('.active .scoring-status-symbol').attr( "class" , 'fa fa-exclamation scoring-status-symbol');
}
},
error: function() {
console.log("AJAX save failed")
$('.active .scoring-status-symbol').attr( "class" , 'fa fa-exclamation scoring-status-symbol');
}
});
}
/**
* sets right system scores in scoring tables
*/
function updateAllSystemScorings(){
$( ".lab-scoring" ).each(function( index ) {
var form = $($(this).attr('form-id'));
var val = form.attr('value');
$(this).html(val);
});
}
/*
reinit on tab change
*/
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
reinitScoringTable();
});
/**
* Delete image (div_id = I) or AOI (div_id = AOI) scores
* #param String div_id, indicates if request for deletion is coming from AOI or Image
* #param Int subSystemId potential subSystem to delete the scores from
*/
function deleteScores(div_id, subSystemId = null){
if(subSystemId){
var url = '/scoring/deletetimepointscores/';
url = url + $('#image_id').attr('value') + '/' + subSystemId;
} else {
var url = (div_id.indexOf('AOI') !== -1) ? '/scoring/deleteaoiscores/' : '/scoring/deleteimgscores/';
url = url + $('#image_id').attr('value');
}
$('.active .scoring-status-symbol').attr('class', 'fa fa-spinner fa-spin scoring-status-symbol');
$.ajax({
type: 'POST',
url: url,
success: function(data, textStatus, xhr){
console.log('Ajax successful');
$('.active .scoring-status-symbol').attr('class', 'fa fa-exclamation scoring-status-symbol');
if(xhr.status == 200){
var summary = data[0];
var score_labels = data[1];
$('#summary-pane' + div_id).html(summary);
$('#scoringTable' + div_id).find('.btn-scoring').each(function(index){
var form = $($(this).attr('form-id'));
// Reset buttons to n.d
$(this).html('n.d.');
form.val('n.d');
// Reset labels to minimum values
for (var score_label in score_labels) {
$(form.attr('score-ref') + '_' + score_labels[score_label][0]).attr('value', score_labels[score_label][1]);
}
});
if(subSystemId){
var scoringTableId = '#scoringTable' + subSystemId;
} else {
var scoringTableId = '#scoringTable' + div_id;
}
$(scoringTableId).find('.score-slider').each(function(index){
var form = $($(this).attr('form-id'));
var typeId = form.attr('type-id');
// Set slider to minimun
setSliderToMin($(this));
form.val('n.d');
$($(this).parent().parent().find('.score-label')).html('n.d.');
for (var score_label in score_labels) {
$(form.attr('score-ref') + '_' + score_labels[score_label][0]).attr('value', score_labels[score_label][1]);
}
})
updateAllSystemScorings();
$('.active .scoring-status-symbol').attr( "class" , 'fa fa-check scoring-status-symbol');
}else{
$('.active .scoring-status-symbol').attr( "class" , 'fa fa-exclamation scoring-status-symbol');
}
},
error: function(){
console.log('Ajax failed');
$('.active .scoring-status-symbol').attr( "class" , 'fa fa-exclamation scoring-status-symbol');
}
});
}
/*
* Display modal for delete confirm and callback the delete function
* #param element: button triggering the function
* #param text: description text to display in the delete modal
* #param title: title for the delete modal
* remark: text and title should not be undefined, otherwise the modal message will not be related to its function anymore
*/
function ConfirmDeleteScores(element, text, title)
{
setUpDeleteScoresModal(text, title);
var div_id = (element.id).replace('delete-scores', ''); // check if AOI or image scores
$('#confirm-delete-modal').find('.modal-footer #modal-delete-btn').on('click', function(){
$("#confirm-delete-modal").modal("hide");
deleteScores(div_id, element.getAttribute('subSystem'));
return true;
});
return false;
}
$("#delete-scoresAOI").unbind();
$("#delete-scoresI").unbind();
documentation.blade.php
#if($scoreConfig->scoring_enabled)
<div class="row">
<div class="card ml-2" style="width: 100%;">
<div class="card-body" id="score-button-small" style="height: 10px;">
<button class="btn btn-box-tool collapse-tab-content float-right" href="#note-tabs">
<i style="color: #1e88e5;" class="fa fa-minus"></i>
</button>
</div>
<div class="card-body">
<div class="row">
<div class="nav-tabs-custom nav-tabs-documentation" style="width: 100%;">
<ul class="nav nav-tabs customtab" id="score-tabs">
#php
$activePane = "active";
#endphp
#if($scoreConfig->scoring_enabled && $scoreConfig->image_based_score)
<li class="nav-item {{$activePane}}">
#php
$activePane = "";
#endphp
<a class="nav-link active" href="#{{trans('scoring.scoring')}}" data-toggle="tab" aria-expanded="true">
<span data-toggle="tooltip" data-placement="top" title="{{trans('scoring.scoring_per_img')}}">
<span class="hidden-lg-up">ScI <i class="fa fa-check scoring-status-symbol"></i></span> </span>
<span class="hidden-md-down">{{trans('scoring.scoring_per_img')}} <i class="fa fa-check scoring-status-symbol"></i></span>
</a>
</li>
#if(in_array(\Auth::user()->userGroup->id,config('usergroups.summaryScoring')))
<li class="nav-item">
<a class="nav-link" href="#summary-paneI" data-toggle="tab" aria-expanded="true">
<span data-toggle="tooltip" data-placement="top" title="{{trans('scoring.summary_per_img')}}">
<span class="hidden-lg-up">SuI</span> </span>
<span class="hidden-md-down">{{trans('scoring.summary_per_img')}}</span>
</a>
</li>
#endif
#endif
#if($scoreConfig->scoring_enabled && $scoreConfig->aoi_based_score)
<li class="nav-item {{$activePane}}">
<a class="nav-link" href="#{{trans('scoring.scoring')}}-aoi" data-toggle="tab" aria-expanded="true">
<span data-toggle="tooltip" data-placement="top" title="{{trans('scoring.scoring_per_AOI')}} ">
<span class="hidden-lg-up">ScA <i class="fa fa-check scoring-status-symbol"></i></span> </span>
<span class="hidden-md-down">{{trans('scoring.scoring_per_AOI')}} <i class="fa fa-check scoring-status-symbol"></i></span>
</a>
</li>
#if(in_array(\Auth::user()->userGroup->id,config('usergroups.summaryScoring')))
<li class="nav-item ">
<a class="nav-link" href="#summary-paneAOI" data-toggle="tab" aria-expanded="true">
<span data-toggle="tooltip" data-placement="top" title="{{trans('scoring.summary_per_AOI')}} ">
<span class="hidden-lg-up">SuA</span> </span>
<span class="hidden-md-down">{{trans('scoring.summary_per_AOI')}}</span>
</a>
</li>
#endif
#endif
<div class="box-tools ml-auto" id="score-button-big">
<button class="btn btn-box-tool collapse-tab-content" href="#note-tabs">
<i style="color: #1e88e5;" class="fa fa-minus"></i>
</button>
</div>
</ul>
<div class="tab-content" id="note-tabs">
#php
$activePane = "active";
#endphp
#if($scoreConfig->scoring_enabled && $scoreConfig->image_based_score)
<div class="tab-pane {{$activePane}}" id="{{trans('scoring.scoring')}}">
#php
$activePane = "";
#endphp
#if($scoreConfig->scoreSuperSystem)
#include('scoring.image.subsystem')
#elseif($scoreConfig->types->isEmpty())
#include('scoring.demo.scoring')
#else
#include('scoring.image.scoring')
#endif
</div>
<div class="tab-pane" id="summary-paneI">
#if($scoreConfig->scoreSuperSystem)
#include('scoring.image.subsystemsummary')
#elseif($scoreConfig->types->isEmpty())
#include('scoring.demo.summary')
#else
#include('scoring.image.summary')
#endif
</div>
#endif
#if($scoreConfig->scoring_enabled && $scoreConfig->aoi_based_score)
#php
$demoAOI = 1;
#endphp
<div class="tab-pane {{$activePane}}" id="{{trans('scoring.scoring')}}-aoi">
#if($scoreConfig->types->isEmpty())
#include('scoring.demo.scoring')
#else
#include('scoring.aoi.scoring')
#endif
</div>
<div class="tab-pane" id="summary-paneAOI">
#if($scoreConfig->types->isEmpty())
#include('scoring.demo.summary')
#else
#include('scoring.aoi.summary')
#endif
</div>
#endif
</div>
</div>
</div>
#endif
</div>
</div>
</div>
<link rel="stylesheet" type="text/css" href="{{asset('/plugins/DataTablesBootstrap4/bootstrap4.min.css') }}"/>
<link rel="stylesheet" type="text/css" href="{{asset('/plugins/DataTablesBootstrap4/responsive.bootstrap4.min.css') }}"/>
<link rel="stylesheet" type="text/css" href="{{asset('/plugins/DataTablesBootstrap4/datatables.min.css') }}"/>
<script src="{{asset('/plugins/DataTablesBootstrap4/jquery.dataTables.min.js') }}"></script>
<script src="{{asset('/plugins/DataTablesBootstrap4/dataTables.bootstrap4.min.js') }}"></script>
<script src="{{asset('/plugins/DataTablesBootstrap4/dataTables.responsive.min.js') }}"></script>
<script src="{{asset('/plugins/DataTablesBootstrap4/responsive.bootstrap4.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('/plugins/datatables/1.10.16/datatables.min.js') }}"></script>
<script type="text/javascript" src="{{asset('/plugins/DataTablesBootstrap4/dataTables.buttons.min.js') }}"></script>
<script type="text/javascript" src="{{asset('/plugins/DataTablesBootstrap4/buttons.flash.min.js') }}"></script>
<script type="text/javascript" src="{{asset('/plugins/DataTablesBootstrap4/jszip.min.js') }}"></script>
<script type="text/javascript" src="{{asset('/plugins/DataTablesBootstrap4/pdfmake.min.js') }}"></script>
<script type="text/javascript" src="{{asset('/plugins/DataTablesBootstrap4/vfs_fonts.js') }}"></script>
<script type="text/javascript" src="{{asset('/plugins/DataTablesBootstrap4/buttons.html5.min.js') }}"></script>
<script type="text/javascript" src="{{asset('/plugins/DataTablesBootstrap4/buttons.print.min.js') }}"></script>
<script src="{{ asset('/js/jquery.form.js') }}"></script>
<script src="{{ asset('/js/scoring/scoringDetailView.js?v=2.25.0') }}"></script>
<script src="{{ asset('/js/scoring/scoring.js?v=2.25.0') }}"></script>
scoring.blade.php
<style>
input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
.dataTables_wrapper {
width: 100%;
}
</style>
{!! Form::open(['route' => ['documentation.scoring.scoring',$image->id], 'onSubmit' => '$.fn.showPleaseWait();', 'id' => 'scoringFormI']) !!}
<div style='position: absolute; visibility: hidden'>
#foreach ($scoreConfig->types as $type)
{!! Form::number('scoring',
$value = $prevScoresI->isEmpty() ? 'n.d.' : $prevScoresI->get($type->id)->score ,
['min' => $type->min, 'max' => $type->max,'stepsize' => $type->stepsize,
'class' => 'btn btn-default btn-datarow-image',
// id : id which is set as form-id in the according button,
// number code is the same as in the name field
'id' => 'scores'.$type->id,
// href to the system score div
'score-ref' => '#systemScoreImage',
// array field in which the score is saved
'name' => 'scores['.$type->id.']',
'type-id' => $type->id,
'style' => 'outline: none; visibility: hidden','required','readonly']) !!}
#endforeach
#if ($systemScoresI->count())
#foreach ($systemScoresI as $key => $scoreI)
<div id="systemScoreImage_{{$scoreI->systemScore->scoreSystem->id}}" style="visibility: hidden;" value="
{{ $scoreI->systemScore->result($scoreI, $scoreI->systemScore->scoreSystem->style->name) }}"></div>
#endforeach
#else
#foreach ($scoreConfig->scoreSystems as $key => $systems)
<div id="systemScoreImage_{{$systems->id}}" style="visibility: hidden;" value="
{{$systems->scoreSystemScores[0]->result(NULL, $systems->style->name)}}"></div>
#endforeach
#endif
</div>
<div class="card-body">
<div class="row">
<div class="table-responsive">
<table id='scoringTableI' class="table table-striped table-bordered">
<thead>
<tr>
<th style="width: 30%">{{trans('scoring.parameter')}}</th>
<th>{{trans('scoring.score')}}</th>
</tr>
</thead>
<tbody>
#foreach ($scoreConfig->types as $type)
<tr>
<!-- If the type has a description, show it, otherwise show the name -->
#if(isset($type->description))
<th title="{{$type->name}}">{{$type->description}} ({{$type->min}}-{{$type->max}})</th>
#else
<th>{{$type->name}} ({{$type->min}}-{{$type->max}})</th>
#endif
<td>
<div class="row">
<!-- The JS of this view relies on the position of the slider and its label in the DOM.
If the DOM structure is changed, please update the JS file accordingly
regarding the slider event listener which also updates the label -->
#php
if($prevScoresI->isEmpty()){
$savedScore = 'n.d.';
$hasScore = false;
} else {
$savedScore = $prevScoresI->get($type->id)->score;
$hasScore = true;
}
$savedScore = $prevScoresI->isEmpty() ? 'n.d.' : $prevScoresI->get($type->id)->score;
#endphp
<div class="col-lg-9 col-md-6 col-sm-6 col-xs-12">
<canvas class="score-scale" style="position: absolute"></canvas>
<input style="position: inherit; width: 80%;" class="score-slider" type="range" min="{{$type->min}}" max="{{$type->max}}" step="{{$type->stepsize}}" value="#if($hasScore){{$savedScore}}#else{{$type->min}}#endif" style=" position: absolute;" view="I" form-id="{{'#scores'.$type->id}} "class="form-control-range">
</div>
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-12">
<label class="score-label float-right">{{$savedScore}} #if(count($type->labels) && $hasScore)({{$type->labels->where('type_index', ($savedScore - $type->min) / $type->stepsize)->pluck('label')->first()}})#endif </label>
</div>
</div>
</td>
</tr>
#endforeach
</tbody>
</table>
</div>
</div>
#if($scoreConfig->scoreSystems->count())
<div class="row">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th style="width: 30%">{{trans('scoring.scoreSystem')}}</th>
<th>{{trans('scoring.score')}}</th>
</tr>
</thead>
<tbody>
#foreach ($scoreConfig->scoreSystems as $key => $system)
<tr>
<td>{{$system->name}}</td>
<td><label class="lab-scoring" form-id="#systemScoreImage_{{$system->id}}"></label></td>
</tr>
#endforeach
</tbody>
</table>
</div>
#endif
</div>
{!! Form::close() !!}
<div class ='card-footer' style="background-color: white;">
<div class="row justify-content-center">
<div class='col-md-2'>
<button id='delete-scoresI' class='btn btn-default btn-2icons btn-wrapper' onclick="return ConfirmDeleteScores(this, '{{trans('modals.delete_current_image_scores')}}', '{{trans('modals.delete_current_image_scores_title')}}')" data-toggle='tooltip' data-placement='top' title="{{trans('tooltips.delete_current_image_scores')}}">
<i class='fa fa-trash'></i>
</button>
</div>
</div>
</div>
I'm trying to use Angular Datetime Picker as a Angular Formly input type. I've got it working such that I can edit and set a value that is correctly added to the binded model.
However, I can't get the validation error messages to display like on the regular input fields.
JS Bin with what I've got so far. As you can see the red color doesn't appear when you exit the field, only when you try to submit. And the error message never shows up.
Formly Config:
formlyConfigProvider.setType({
name: 'datepicker',
templateUrl: "custom-template.html",
overwriteOk: true,
wrapper: ['bootstrapHasError'],
defaultOptions: function defaultOptions(options) {
return {
templateOptions: {
validation: {
show: true
}
}
};
}
});
formlyConfigProvider.setWrapper({
name: 'validation',
types: ['input', 'datepicker'],
templateUrl: 'error-messages.html'
});
Fields
vm.fields = [
{
key: 'text',
type: 'input',
templateOptions: {
label: 'Text',
placeholder: 'Write something',
required: true
},
},
{
key: 'date',
type: 'datepicker',
templateOptions: {
label: 'Date',
placeholder: 'Pick a date',
required: true
},
}
];
Templates
<script type="text/ng-template" id="custom-template.html">
<div class="form-group">
<label class="control-label" for="{{::id}}">{{to.label}} {{to.required ? '*' : ''}}</label>
<div class="dropdown">
<a class="dropdown-toggle" id="dropdown-{{options.key}}" role="button" data-toggle="dropdown">
<div class="input-group">
<input id="{{::id}}" name="{{::id}}" type="text" data-date-time-input="YYYY-MM-DD" class="form-control" data-ng-model="model[options.key]"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
</div>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<datetimepicker
data-ng-model="model[options.key]"
data-datetimepicker-config="{ dropdownSelector: '#dropdown-' + options.key, minView: 'day', startView: 'year', modelType: 'YYYY-MM-DDTHH:mm:ssZ'}"/>
</ul>
</div>
</div>
</script>
<script type="text/ng-template" id="error-messages.html">
<formly-transclude></formly-transclude>
<div ng-messages="fc.$error" ng-if="form.$submitted || options.formControl.$touched" class="error-messages">
<div ng-message="{{ ::name }}" ng-repeat="(name, message) in ::options.validation.messages" class="message">{{ message(fc.$viewValue, fc.$modelValue, this)}}</div>
</div>
</script>
After investigating this deeper, I can see the control you used Angular Datetime Picker is not fully compatible with Angular Formly.
This is because of the reason that it's overwriting the AngularJS's
ngModelController.$render() method and hence not setting the value
for $touched like other input controls.
Another reason in your code is, the config and the template
error-messages.html are treating the custom control as single
element with fc.$touched, fc.$error and fc.$viewValue whereas
DatePicker is rendering as group of elements (array).
To get rid of all these issues, you can have a custom directive to set $touched as below,
app.directive('setTouched', function MainCtrl() {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return; // do nothing if no ng-model
element.on('blur', function() {
var modelControllers = scope.$eval(attrs.setTouched);
if(angular.isArray(modelControllers)) {
angular.forEach(modelControllers, function(modelCntrl) {
modelCntrl.$setTouched();
});
}
});
}
};
});
And in custom-template.html,
<div class="input-group">
<input set-touched="options.formControl" id="{{::id}}" name="{{::id}}" type="text" data-date-time-input="YYYY-MM-DD" class="form-control" data-ng-model="model['date1']"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
</div>
And add fc[0].$touched in below configuration to take care of array of fields,
app.run(function run(formlyConfig, formlyValidationMessages) {
formlyConfig.extras.errorExistsAndShouldBeVisibleExpression = 'form.$submitted || fc.$touched || fc[0].$touched';
formlyValidationMessages.addStringMessage('required', 'This field is required');
});
And also add below section in error-messages.html to take care of array of fields,
<div ng-messages="fc[0].$error" ng-if="form.$submitted || options.formControl[0].$touched" class="error-messages">
<div ng-message="{{ ::name }}" ng-repeat="(name, message) in ::options.validation.messages" class="message">{{ message(fc[0].$viewValue, fc[0].$modelValue, this)}}</div>
</div>
This changes will fix the issue.
As you can see a bit of design issue with the error message which is displayed further down,
You can change the custom-template.html as below by removing the div wrapper <div class="form-group">,
<script type="text/ng-template" id="custom-template.html">
<label class="control-label" for="{{::id}}"
uib-popover="{{options.templateOptions.desc}}"
popover-trigger="mouseenter"
popover-placement="top-left"
popover-popup-delay="500"
popover-append-to-body="true">{{to.label}} {{to.required ? '*' : ''}}</label>
<div class="dropdown">
<a class="dropdown-toggle" id="dropdown-{{options.key}}" role="button" data-toggle="dropdown">
<div class="input-group">
<input set-touched="options.formControl" id="{{::id}}" name="{{::id}}" type="text" data-date-time-input="YYYY-MM-DD" class="form-control" data-ng-model="model['date1']"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
</div>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<datetimepicker
data-ng-model="model[options.key]"
data-datetimepicker-config="{ dropdownSelector: '#dropdown-' + options.key, minView: 'day', startView: 'year', modelType: 'YYYY-MM-DDTHH:mm:ssZ'}"/>
</ul>
</div>
</script>
I have updated your JSBin with these changes.
Snippet:
/* global angular */
(function() {
'use strict';
var app = angular.module('formlyExample', ['formly', 'formlyBootstrap', 'ngAnimate', 'ngMessages', 'ui.bootstrap.datetimepicker', 'ui.dateTimeInput'], function config(formlyConfigProvider) {
formlyConfigProvider.setType({
name: 'datepicker',
templateUrl: "custom-template.html",
overwriteOk: true,
wrapper: ['bootstrapHasError'],
defaultOptions: function defaultOptions(options) {
return {
templateOptions: {
validation: {
show: true
}
}
};
}
});
formlyConfigProvider.setWrapper({
name: 'validation',
types: ['input', 'datepicker'],
templateUrl: 'error-messages.html'
});
});
app.run(function run(formlyConfig, formlyValidationMessages) {
formlyConfig.extras.errorExistsAndShouldBeVisibleExpression = 'form.$submitted || fc.$touched || fc[0].$touched';
formlyValidationMessages.addStringMessage('required', 'This field is required');
});
app.directive('setTouched', function MainCtrl() {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return; // do nothing if no ng-model
element.on('blur', function() {
var modelControllers = scope.$eval(attrs.setTouched);
if(angular.isArray(modelControllers)) {
angular.forEach(modelControllers, function(modelCntrl) {
modelCntrl.$setTouched();
});
}
});
}
};
});
app.controller('MainCtrl', function MainCtrl(formlyVersion) {
var vm = this;
vm.onSubmit = onSubmit;
vm.model = {};
vm.options = {};
vm.env = {
angularVersion: angular.version.full,
formlyVersion: formlyVersion
};
vm.fields = [
{
key: 'text',
type: 'input',
templateOptions: {
label: 'Text',
placeholder: 'Write something',
required: true
},
},
{
key: 'moretext',
type: 'input',
templateOptions: {
label: 'More Text',
placeholder: 'Write something else',
},
},
{
key: 'date',
type: 'datepicker',
templateOptions: {
label: 'Date',
placeholder: 'Pick a date',
required: true
},
}
];
vm.originalFields = angular.copy(vm.fields);
// function definition
function onSubmit() {
if (vm.form.$valid) {
vm.options.updateInitialValue();
alert(JSON.stringify(vm.model), null, 2);
}
}
});
})();
body {
margin: 20px
}
.formly-field {
margin-bottom: 30px;
}
.error-messages {
position: relative;
}
.error-messages, .message {
opacity: 1;
transition: .3s linear all;
}
.message {
font-size: .8em;
position: absolute;
width: 100%;
color: #a94442;
margin-top: 4px;
}
.error-messages.ng-enter.ng-enter-active,
.message.ng-enter.ng-enter-active {
opacity: 1;
top: 0;
}
.error-messages.ng-enter,
.message.ng-enter {
opacity: 0;
top: -10px;
}
.error-messages.ng-leave,
.message.ng-leave {
opacity: 1;
top: 0;
}
.error-messages.ng-leave-active,
.message.ng-leave-active {
opacity: 0;
top: -10px;
}
<!DOCTYPE html>
<html>
<head>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<!-- Twitter bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.css" rel="stylesheet">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- apiCheck is used by formly to validate its api -->
<script src="//npmcdn.com/api-check#latest/dist/api-check.js"></script>
<!-- This is the latest version of angular (at the time this template was created) -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<!-- This is the latest version of formly core. -->
<script src="//npmcdn.com/angular-formly#latest/dist/formly.js"></script>
<!-- This is the latest version of formly bootstrap templates -->
<script src="//npmcdn.com/angular-formly-templates-bootstrap#latest/dist/angular-formly-templates-bootstrap.js"></script>
<script src="https://rawgit.com/angular/bower-angular-messages/v1.4.4/angular-messages.js"></script>
<script src="https://rawgit.com/angular/bower-angular-animate/v1.4.4/angular-animate.js"></script>
<!-- Moment -->
<script src="https://cdn.rawgit.com/moment/moment/develop/min/moment-with-locales.min.js"></script>
<!-- Datetime picker -->
<script type="text/javascript" src="https://cdn.rawgit.com/dalelotts/angular-bootstrap-datetimepicker/master/src/js/datetimepicker.js"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/dalelotts/angular-bootstrap-datetimepicker/master/src/js/datetimepicker.templates.js"></script>
<link href="https://cdn.rawgit.com/dalelotts/angular-bootstrap-datetimepicker/master/src/css/datetimepicker.css" rel="stylesheet">
<script type="text/javascript" src="https://cdn.rawgit.com/dalelotts/angular-date-time-input/master/src/dateTimeInput.js"></script>
<title>Angular Formly Example</title>
</head>
<body ng-app="formlyExample" ng-controller="MainCtrl as vm">
<div>
<form ng-submit="vm.onSubmit()" name="vm.form" novalidate>
<formly-form model="vm.model" fields="vm.fields" options="vm.options" form="vm.form">
<button type="submit" class="btn btn-primary submit-button">Submit</button>
<button type="button" class="btn btn-default" ng-click="vm.options.resetModel()">Reset</button>
</formly-form>
</form>
<hr />
<h2>Model</h2>
<pre>{{vm.model | json}}</pre>
<h2>Fields <small>(note, functions are not shown)</small></h2>
<pre>{{vm.originalFields | json}}</pre>
<h2>Form</h2>
<pre>{{vm.form | json}}</pre>
</div>
<!-- Put custom templates here -->
<script type="text/ng-template" id="custom-template.html">
<label class="control-label" for="{{::id}}"
uib-popover="{{options.templateOptions.desc}}"
popover-trigger="mouseenter"
popover-placement="top-left"
popover-popup-delay="500"
popover-append-to-body="true">{{to.label}} {{to.required ? '*' : ''}}</label>
<div class="dropdown">
<a class="dropdown-toggle" id="dropdown-{{options.key}}" role="button" data-toggle="dropdown">
<div class="input-group">
<input set-touched="options.formControl" id="{{::id}}" name="{{::id}}" type="text" data-date-time-input="YYYY-MM-DD" class="form-control" data-ng-model="model['date1']"><span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
</div>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<datetimepicker
data-ng-model="model[options.key]"
data-datetimepicker-config="{ dropdownSelector: '#dropdown-' + options.key, minView: 'day', startView: 'year', modelType: 'YYYY-MM-DDTHH:mm:ssZ'}"/>
</ul>
</div>
</script>
<script type="text/ng-template" id="error-messages.html">
<formly-transclude></formly-transclude>
<div ng-messages="fc.$error" ng-if="form.$submitted || options.formControl.$touched" class="error-messages">
<div ng-message="{{ ::name }}" ng-repeat="(name, message) in ::options.validation.messages" class="message">{{ message(fc.$viewValue, fc.$modelValue, this)}}</div>
</div>
<div ng-messages="fc[0].$error" ng-if="form.$submitted || options.formControl[0].$touched" class="error-messages">
<div ng-message="{{ ::name }}" ng-repeat="(name, message) in ::options.validation.messages" class="message">{{ message(fc[0].$viewValue, fc[0].$modelValue, this)}}</div>
</div>
</script>
</body>
</html>
You should return your validation in the formlyConfigProvider without passing it as the value for templateOptions. Return
validation: {
show: true
}
instead of
templateOptions: {
validation: {
show: true
}
}
Your formlyConfigProvider should look something like this:
formlyConfigProvider.setType({
name: 'datepicker',
templateUrl: "custom-template.html",
overwriteOk: true,
wrapper: ['bootstrapHasError'],
defaultOptions: function defaultOptions(options) {
return {
validation: {
show: true
}
};
}
});
Here is the JSBin for the working code.
I have a modal popup (from bootstrap) that opens and contains a text input with daterangepicker, but the daterangepicker does not work (i see nothing when i click on the textbox) and i see no errors in the console.
Here is the input:
<div id="divChooseDossier" class="modal hide fade" role="dialog" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>#DossierReceipts.ChooseDossier</h3>
</div>
<div class="modal-body">
<input type="text" class="datePicker ui-rangepicker-input search-query input-small"
id="lastModifiedDateFilter" />
</div>
<div class="modal-footer">
#DossierReceipts.Cancel
</div>
Here is the javascript to create the daterangepicker:
$("#lastModifiedDateFilter").daterangepicker({
dateFormat: "yy.mm.dd"
, rangeSplitter: '-'
});
And here is the javascript to open the popup:
$("#divCreateReceipt").modal("show");
Does anyone know why does this not work?
Thanks
UPDATE
Here is the complete code for the popup window:
#{
ViewBag.Title = "Dosare";
}
#using ExpertExecutor.Resources.Dossier
#section leftMenu{
#Html.Partial("_LeftMenu")
}
#Scripts.Render("~/bundles/daterangepicker")
#Scripts.Render("~/bundles/watermark")
#Styles.Render("~/Content/daterangepicker")
<script src="#Url.Content("~/Scripts/jquery.watermark.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("/Scripts/jquery.jqGrid.fluid.js")"></script>
#Html.Hidden("redirectUrl", (string)ViewBag.RedirectUrl)
<div class="form-search form-inline alert alert-info">
<fieldset>
<legend>#Index.FiltersCaption</legend>
<dl>
<dd>
<span>#Index.DossierColumn</span>
<input type="text" id="dossierFilter" class="search-query input-xxlarge" />
</dd>
<dd>
<span>#Index.DossierStatusColumn</span>
#Html.DropDownList("dossierStatusFilter", (List<SelectListItem>)ViewData["DossierStatuses"], new { #class = "input-medium" })
<span>#Index.LastModifiedDateColumn</span>
<input type="text" class="datePicker ui-rangepicker-input search-query input-small"
id="lastModifiedDateFilter" />
<span>#Index.LastOperatorColumn</span>
#Html.DropDownList("lastOperatorFilter", (List<SelectListItem>)ViewData["Users"])
</dd>
<dd>
<input type="button" class="btn btn-info" value="#Index.Search" onclick="applyFilter();"/>
<input type="button" class="btn btn-info" value="#Index.ClearFilter" onclick="clearFilter();" />
</dd>
</dl>
</fieldset>
</div>
<div id="dossiersGridWrapper" class="row-fluid">
<table id="dossiersGrid"></table>
<div id="dossiersGridPager"></div>
</div>
#if (!ViewBag.NoActions)
{
#Index.CreateDossier
}
<script type="text/javascript">
$('#dossierFilter').watermark('#Index.WatermarkSearchDossier');
$.jgrid.defaults.loadtext = '#Index.GridLoading';
var mvcJqGrid = {
customDblClick: "#ViewBag.customDblClick",
actions: {
buttonize: function (cellvalue, options, rowobject) {
return '<a onclick="return mvcJqGrid.actions.edit(\'' + options.rowId + '\')" href="#" title="Editare dosar"><i class="ui-icon ui-icon-pencil" style="display:inline-block"></i></a>' +
'<a onclick="return mvcJqGrid.actions.costs(\'' + options.rowId + '\')" href="#" title="Cheltuieli dosar"><i class="ui-icon ui-icon-cart" style="display:inline-block"></i></a>' +
'<a onclick="return mvcJqGrid.actions.imobil(\'' + options.rowId + '\')" href="#" title="Bunuri imobile"><i class="ui-icon ui-icon-home" style="display:inline-block"></i></a>' +
'<a onclick="return mvcJqGrid.actions.mobil(\'' + options.rowId + '\')" href="#" title="Bunuri mobile"><i class="ui-icon ui-icon-suitcase" style="display:inline-block"></i></a>' +
'<a onclick="return mvcJqGrid.actions.open(\'' + options.rowId + '\')" href="#" title="Open Dossier"><i class="ui-icon ui-icon-folder-open" style="display:inline-block"></i></a>';
},
edit: function (id) {
window.open('#Url.Action("EditDossier", "Dossier")?id=' + id, "_self");
return false;
},
costs: function (id) {
window.open('#Url.Action("OpenRedirect", "Dossier")?idDossier=' + id + '&strUrl=' + encodeURI('#Url.Action("DossierCosts", "Dossier")?id=' + id), "_self");
return false;
},
imobil: function (id) {
window.open('#Url.Action("OpenRedirect", "Dossier")?idDossier=' + id+'&strUrl='+encodeURI('#Url.Action("ImovableList", "Asset")?idDossier=' + id), "_self");
return false;
},
mobil: function (id) {
window.open('#Url.Action("OpenRedirect", "Dossier")?idDossier=' + id + '&strUrl=' + encodeURI('#Url.Action("MovableList", "Asset")?idDossier=' + id), "_self");
return false;
},
open: function (id) {
if (mvcJqGrid.customDblClick.length > 0 && typeof (window[mvcJqGrid.customDblClick]) == "function") {
return window[mvcJqGrid.customDblClick](id);
}
$.getJSON('#Url.Action("OpenDossier", "Dossier")' + "?id=" + id, function (data) {
if (data && data.success) {
var redirectUrl = $("#redirectUrl").val();
if (redirectUrl) {
window.open(redirectUrl, "_self");
} else {
window.location.reload();
}
} else {
alert("#Index.ErrorOpenDossier");
}
});
return false;
}
}
};
$("#dossiersGrid").jqGrid({
url: '#Url.Action("DossiersGridData", "Dossier")',
datatype: 'json',
mtype: 'POST',
colModel: [
{ name: "#Index.CompletedColumn", sortable: false, editable: false, index: "Completed" },
{ name: "#Index.DossierColumn", sortable: true, editable: false, index: "Dossier" },
{ name: "#Index.DossierStatusColumn", sortable: true, editable: false, index: "DossierStatus" },
{ name: "#Index.LastModifiedDateColumn", sortable: true, editable: false, index: "LastModifiedDate" },
{ name: "#Index.LastOperatorColumn", sortable: true, editable: false, index: "LastOperator" },
{ name: "#Index.CreditorsColumn", sortable: false, editable: false, index: "Creditors" },
{ name: "#Index.DebtorsColumn", sortable: false, editable: false, index: "Debtors" },
#if (!ViewBag.NoActions)
{
#:{ name: "#Index.Action", sortable: false, editable: false, index: "Action", formatter: mvcJqGrid.actions.buttonize }
}
],
viewrecords: true,
postData: {
dossierFilter: function () { return $("#dossierFilter").val(); },
dossierStatusFilter: function () { return $("#dossierStatusFilter").val(); },
lastModifiedDateFilter: function () { return $("#lastModifiedDateFilter").val(); },
lastOperatorFilter: function () {
return $("#lastOperatorFilter").val();
}
},
pager: "#dossiersGridPager",
rowNum: 10,
caption: "Lista dosare",
autowidth: true,
rowList: [10, 15, 20, 50],
emptyrecords: 'No record Found',
height: '100%',
ondblClickRow: mvcJqGrid.actions.open
});
$("#lastModifiedDateFilter").daterangepicker({
dateFormat: "yy.mm.dd"
, rangeSplitter: '-'
});
function applyFilter() {
$("#dossiersGrid").trigger("reloadGrid");
}
function clearFilter() {
$('#dossierFilter').val("");
$("#dossierStatusFilter").val("");
$("#lastModifiedDateFilter").val("");
$("#lastOperatorFilter").val("");
$("#dossiersGrid").trigger("reloadGrid");
}
if (leftMenu) {
leftMenu.setContext('dossier-list help-dossier');
}
var resizeDossiersGrid = function () {
$("#dossiersGrid").fluidGrid({ example: "#dossiersGridWrapper", offset: 0 });
};
$(window).on('resize', resizeDossiersGrid);
$("#dossiersGrid").on("jqGridGridComplete", resizeDossiersGrid);
</script>
And here is the complete code for the calling page:
#using ExpertExecutor.DataLayer.Models
#using ExpertExecutor.Resources.Cost
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#section leftMenu{
#Html.Partial("_LeftMenu")
}
#section head
{
#Scripts.Render("~/bundles/jqueryval")
<style type="text/css">
#divChooseDossier {
width: 900px;
}
#divCreateReceipt {
width: 900px;
}
</style>
}
<h2>#ViewBag.Title</h2>
#Html.Hidden("IdDossier", ViewData["IdDossier"])
<table id="dossierReceiptsGrid"></table>
<div id="divCreateReceipt" class="modal hide fade" role="dialog" aria-hidden="true">
</div>
#DossierReceipts.CreateReceipt
<div id="divConfirmDossier" class="modal hide fade" role="dialog" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>#DossierReceipts.ConfirmDossier</h3>
</div>
<div class="modal-body">
<span>#DossierReceipts.ConfirmDossierMessage<strong>#(ViewData["Dossier"] != null ? string.Format("{0}/{1}", ((Dossier)ViewData["Dossier"]).DossierNumber, ((Dossier)ViewData["Dossier"]).DossierYear) : string.Empty)</strong>?</span>
</div>
<div class="modal-footer">
#DossierReceipts.Cancel
<input type="button" class="btn btn-primary" value="#DossierReceipts.ConfirmDossierOk" onclick="confirmDossier();"/>
<input type="button" class="btn" value="#DossierReceipts.SelectDossier" onclick="showChooseDossierModal();"/>
</div>
</div>
<div id="divChooseDossier" class="modal hide fade" role="dialog" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>#DossierReceipts.ChooseDossier</h3>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
#DossierReceipts.Cancel
</div>
</div>
<script type="text/javascript">
leftMenu.setContext('financial help-financial');
$("#dossierReceiptsGrid").jqGrid({
url: '#Url.Action("ReceiptsGridData")',
datatype: "json",
mtype: "POST",
postData: {
idDossier: '#ViewData["IdDossier"]'
},
colNames: ['#DossierReceipts.DossierColumn', '#DossierReceipts.ReceiptDateColumn', '#DossierReceipts.ReceiptValueColumn', '#DossierReceipts.ReceiptCurrencyColumn', '#DossierReceipts.ReceiptColumn'],
colModel: [
{ name: "Dossier", index: "Dossier", sortable: true, editable: false },
{ name: "ReceiptDate", index: "ReceiptDate", sortable: true, editable: false },
{ name: "ReceiptValue", index: "ReceiptValue", sortable: true, editable: false },
{ name: "Currency", index: "Currency", sortable: true, editable: false },
{ name: "Receipt", index: "Receipt", sortable: true, editable: false }
],
viewrecords: true
});
function confirmDossier() {
$("#divConfirmDossier").modal("hide");
$("#divCreateReceipt").modal("show");
}
var reloadDossiersGrid = true;
function showChooseDossierModal() {
$("#divConfirmDossier").modal("hide");
$("#divChooseDossier").modal("show");
if (reloadDossiersGrid) {
reloadDossiersGrid = false;
$.post('#Url.Action("Index", "Dossier")?redirectUrl=&noActions=true&partial=true&customDblClick=chooseDossier', null, function(postResponse) {
$("#divChooseDossier .modal-body").html(postResponse);
$("#divChooseDossier").on("shown", function() {
resizeDossiersGrid();
$("#lastModifiedDateFilter").daterangepicker({
dateFormat: "yy.mm.dd"
, rangeSplitter: '-'
});
});
});
} else {
$("#divChooseDossier").modal("show");
}
}
function chooseDossier(id) {
$("#IdDossier").val(id);
$("#divChooseDossier").modal("hide");
$.get('#Url.Action("CreateReceipt", "Financial")?idDossier=' + id, null, function(getResponse) {
$("#divCreateReceipt").html(getResponse);
$("#divCreateReceipt").modal("show");
});
$.get('#Url.Action("GetDossierDisplayName")?id=' + id, null, function(getResponse) {
$("#divConfirmDossier .modal-body strong").text(getResponse.name);
});
$("#IdDossier").val(id);
}
$(function() {
$("a[href='#divCreateReceipt']").hide();
$("a[href='#divChooseDossier']").hide();
});
function showCreateReceiptOption() {
if ($("#IdDossier").val() && $("#IdDossier").val().length > 0) {
$("#divConfirmDossier").modal("show");
} else {
showChooseDossierModal();
}
}
</script>
Sorry for the long code
Rather than modifying the z-index of elements, you can also set the parentEl option (where the calendar control is created). This will allow the actual daterangepicker object to inherit the z-index of the modal container.
$("#lastModifiedDateFilter").daterangepicker({
parentEl: "#divChooseDossier .modal-body"
})
From the documentation:
parentEl: (string) jQuery selector of the parent element that the date
range picker will be added to, if not provided this will be 'body'
I had a similar problem with datepicker jquery.
When I clicked on the input, nothing happened, no error, nothing.
The problem was that the datepicker was working, but the calendar appeared under the modal, I hacked this with css :
.datepicker{
z-index: 1100 !important;
}
Maybe your problem is similar to the mine ,
Update after 8 years of service....
Look at the better answer at bottom of this answer
You can change z-index in event show.daterangepicker
$('.daterange-single').on('show.daterangepicker', function(e){
var modalZindex = $(e.target).closest('.modal').css('z-index');
$('.daterangepicker').css('z-index', modalZindex+1);
});
I resolve my issues with the couple of :
.datepicker{
z-index: 1100 !important;
}
#ui-datepicker-div {
width: 30% !important;
}
Simply remove tabindex="-1" from your Bootstrap modal.
<div class="modal" tabindex="-1">
On version 0.21.1, insert the below CSS code to effect.
.date-picker-wrapper{
z-index: 1100 !important;
}