I have an MVC project where I'm using jquery DataTables 1.10.2. Within my project's pages I often have multiple places where I need to reuse a partial view, which contains a self-contained jquery DataTable instance, initialization, and handlers. As the partial view is being built I uniquely name each DT instance using a GUID, including all other buttons, etc, so each should be able to exist in its own world, not caring about those around it. Or so I thought... I've read quite a bit on this issue and I can't seem to determine why this is happening. From what I'm doing I don't believe that I'm attempting to change/reinitialize an existing DT instance. When I only have a single one of these DT partial views everything is great. Any thoughts?
I have 3 of these partial views that must reside on the page, and I always get this kinda message:
Also, only the last instance actually shows any records, 3x what it's supposed to display, and all the others are just blank (not even the no data message).
1. Here's how I create my raw HTML table within my partial view, where I have a unique identifier for the table:
2. Here's an example how the calling view requests the partial view in question:
I pass a unique identifier (GUID), along with my data via a ViewModel into the partial view. All standard MVC kinda stuff, and is working fine.
3. Here's my partial view initialization of the DataTable with razor injected into the javascript creating a unique HTML table ID for each DT to use, along with a unique DT global object variable (c#unique), which when populated will look something like this: c6e201ac10b4a4a6a987878c7b2390fa4. I shouldn't need to reinitialize anything, despite DT telling me. Each version of the DataTable partial view should have all its variables (c#unique, rows#unique, etc.) to be unique. The existingData variable is set here, which is passed in via the ViewModel:
c#(unique) = $('##(unique)phones').DataTable(
{
"data" : existingData
, "responsive": true
, "searching" : false
, "paging": false
, "order": 0
, "createdRow" : function (row, data, index){
$(row).attr("data-id", index);
rows#(unique)++;
}
, "columns": [
{ "data": "Id"
, "visible" : false
}
, { "data": "PhoneTypeID", "title": "Phone Type",
render : function (data, type, row, meta) {
// Renders combination of select element, with exisitng value correctly selected and validation control
var $clone = $select.clone();
$clone.attr('name', 'Phones[' + rows#(unique) + '].PhoneTypeID' ); // changing the phone collection name for serialization
$clone.find('option[value="' + data + '"]').attr('selected','selected'); // attempting to match existing data
$clone.attr('data-val', 'true' ); // adding validation
$clone.attr('data-val-required', 'Phone Type Required' ); // adding error message
var validation = ' <div><span class="field-validation-valid text-danger" data-valmsg-for="Phones[' + rows#(unique) + '].PhoneTypeID" data-valmsg-replace="true" </span></div>';
var selectctl = $clone.wrap('<div></div>').parent().html();
// combines both the select control and the validation span element
return selectctl.concat(validation);
}}
, { "data": "PhoneNumber", "title": "Phone Number",
render : function (data, type, row) {
// Renders combination of phone number text box, with exisitng value correctly selected and validation control
var phoneDetail = '<div><input class="form-group" name="Phones[' + rows#(unique) + '].PhoneNumber" placeholder="Number" type="tel" pattern="\d{3}[\-]\d{3}[\-]\d{4}" value="' + data + '"'
+ ' data-val="true" data-val-required="Phone Required" />'
+ ' <input type="hidden" name="Phones[' + rows#(unique) + '].Id" value="' + row["Id"] + '" />'
+ ' <span class="field-validation-valid text-danger" data-valmsg-for="Phones[' + rows#(unique) + '].PhoneNumber" data-valmsg-replace="true" /></div>';
return phoneDetail;
}}
, { "data" : "Id",
render : function (data,type,row,meta){
var deleteBtn = '<a class="btn btn-warning removeSelected" href="#">Delete</a>';
return deleteBtn;
}
}
]
});
I'm rendering some columns to have a select element (PhoneTypeId), a text box (PhoneNumber) and a button for deleting. The select element is setup using some code that creates a $select element (not included here) that's then cloned within the rendering column to match any existing data. All my element names use this notation (SomeName[index].PropertyName) so the serializer will understand when the page is posted to my collections, etc.
Here's a working example of what it looks like, including when the row is selected and displays the delete button. Not fully styled yet, as I can't seem to have more than 1 on a page! Any help is appreciated!
The code itself wasn't the problem. To keep my doc.ready event clean and tidy I used a call to the method LoadDataTable(). When two or more of these partial views were loaded and doc.ready was finally called, there were multiple LoadDataTable methods, each pointing to an already existing instance of a DataTable. This is why I kept getting that initialize error. My Solution: Either create a dynamically named LoadDataTable method (using razor), or just put everything directly inside the partial's doc.ready method. Working fine now!
What I could see is that you weren't trying to destroy an existing table. Looking at the following in your post:
c#(unique) = $('##(unique)phones').DataTable(
{
"data" : existingData
, "responsive": true
, "searching" : false
, "paging": false
, "order": 0
, "createdRow" : function (row, data, index){
$(row).attr("data-id", index);
rows#(unique)++;
}
, "columns": [
{ "data": "Id"
, "visible" : false
}
, { "data": "PhoneTypeID", "title": "Phone Type",
render : function (data, type, row, meta) {
// Renders combination of select element, with exisitng value correctly selected and validation control
var $clone = $select.clone();
$clone.attr('name', 'Phones[' + rows#(unique) + '].PhoneTypeID' ); // changing the phone collection name for serialization
$clone.find('option[value="' + data + '"]').attr('selected','selected'); // attempting to match existing data
$clone.attr('data-val', 'true' ); // adding validation
$clone.attr('data-val-required', 'Phone Type Required' ); // adding error message
var validation = ' <div><span class="field-validation-valid text-danger" data-valmsg-for="Phones[' + rows#(unique) + '].PhoneTypeID" data-valmsg-replace="true" </span></div>';
var selectctl = $clone.wrap('<div></div>').parent().html();
// combines both the select control and the validation span element
return selectctl.concat(validation);
}}
, { "data": "PhoneNumber", "title": "Phone Number",
render : function (data, type, row) {
// Renders combination of phone number text box, with exisitng value correctly selected and validation control
var phoneDetail = '<div><input class="form-group" name="Phones[' + rows#(unique) + '].PhoneNumber" placeholder="Number" type="tel" pattern="\d{3}[\-]\d{3}[\-]\d{4}" value="' + data + '"'
+ ' data-val="true" data-val-required="Phone Required" />'
+ ' <input type="hidden" name="Phones[' + rows#(unique) + '].Id" value="' + row["Id"] + '" />'
+ ' <span class="field-validation-valid text-danger" data-valmsg-for="Phones[' + rows#(unique) + '].PhoneNumber" data-valmsg-replace="true" /></div>';
return phoneDetail;
}}
, { "data" : "Id",
render : function (data,type,row,meta){
var deleteBtn = '<a class="btn btn-warning removeSelected" href="#">Delete</a>';
return deleteBtn;
}
}
]
});
You need to add "destroy", true. Let it look like the following:
c#(unique) = $('##(unique)phones').DataTable(
{
"destroy" : true
"data" : existingData
, "responsive": true
, "searching" : false
, "paging": false
, "order": 0
, "createdRow" : function (row, data, index){
$(row).attr("data-id", index);
rows#(unique)++;
}
, "columns": [
{ "data": "Id"
, "visible" : false
}
, { "data": "PhoneTypeID", "title": "Phone Type",
render : function (data, type, row, meta) {
// Renders combination of select element, with exisitng value correctly selected and validation control
var $clone = $select.clone();
$clone.attr('name', 'Phones[' + rows#(unique) + '].PhoneTypeID' ); // changing the phone collection name for serialization
$clone.find('option[value="' + data + '"]').attr('selected','selected'); // attempting to match existing data
$clone.attr('data-val', 'true' ); // adding validation
$clone.attr('data-val-required', 'Phone Type Required' ); // adding error message
var validation = ' <div><span class="field-validation-valid text-danger" data-valmsg-for="Phones[' + rows#(unique) + '].PhoneTypeID" data-valmsg-replace="true" </span></div>';
var selectctl = $clone.wrap('<div></div>').parent().html();
// combines both the select control and the validation span element
return selectctl.concat(validation);
}}
, { "data": "PhoneNumber", "title": "Phone Number",
render : function (data, type, row) {
// Renders combination of phone number text box, with exisitng value correctly selected and validation control
var phoneDetail = '<div><input class="form-group" name="Phones[' + rows#(unique) + '].PhoneNumber" placeholder="Number" type="tel" pattern="\d{3}[\-]\d{3}[\-]\d{4}" value="' + data + '"'
+ ' data-val="true" data-val-required="Phone Required" />'
+ ' <input type="hidden" name="Phones[' + rows#(unique) + '].Id" value="' + row["Id"] + '" />'
+ ' <span class="field-validation-valid text-danger" data-valmsg-for="Phones[' + rows#(unique) + '].PhoneNumber" data-valmsg-replace="true" /></div>';
return phoneDetail;
}}
, { "data" : "Id",
render : function (data,type,row,meta){
var deleteBtn = '<a class="btn btn-warning removeSelected" href="#">Delete</a>';
return deleteBtn;
}
}
]
});
Related
I am using Datatables and trying to populate the datatable through an ajax call.
In datatable I have added a textbox functionality so users can change the value of final marks. Once the user has changed the value of final marks I want to use a onblur function to save that new final marks value in database. So I have to pass rosterID and new final marks to the onblur function. But I don't know how to pass 2 parameters in this. I have decided to pass it an object named as "obj". But it is not passing at all to the function and formal parameter for the function "obj1" seems undefined.
Here is my code.
// piece of code from my datatables AJAX call.
{ data: 'SrNo', title: "Sr No" }, // 0
{ data: 'RosterID', title: "Roster ID", visible: false }, // 1
{ data: 'RollNo', title: "Roll No" }, // 2
{ data: 'StudentName', title: "Student Name" }, // 3
{
data: 'FinalScore', title: "Final Score", //4
render: function (data, type, full, row) {
var obj = {
ros: full.RosterID,
final: full.FinalScore
}
return '<input class="form-control" id="DTFinalaTermMarks"
name="DTFinalaTermMarks" type="text" onblur="updateFinalTermScore('+obj+');"
value = ' + data + ' >';
}
},
{ data: 'WeightedScore', title: "Weighted Score"}, //5
// function with formal parameter
function updateFinalTermScore(obj1) {
var roterID = obj1.ros;
alert("hi");
}
```
kindly help.
The object you need to use is this.
updateFinalTermScore(this)
In the context of an event such as onblur, created in a column renderer, it would look like the following:
{
title: "Salary",
data: "salary",
render: function (data, type, row, meta) {
return '<input class="form-control" id="DTFinalaTermMarks" ' +
'name="DTFinalaTermMarks" type="text" ' +
'onblur="updateFinalTermScore(this, ' +
'\'' + row.RosterID + '\'' + ');" ' +
'value = ' + data + ' >';
}
}
The return statement uses string concatenation to create the required HTML.
The this object represents the context of the onblur event - the node for which the event happened. In this case, that is the relevant <input> element.
You need to use this approach so that you can access the value of the <input> element - which may have been updated by the user before the onblur event.
The resulting HTML created by the DataTables render function is:
<input class="form-control"
id="DTFinalaTermMarks"
name="DTFinalaTermMarks"
type="text"
onblur="updateFinalTermScore(this, '123');" value="456">
Here, 123 is the roster ID. I assume this is a string value, which is why I use '\'' to surround that value in single quotes.
When the onblur event happens, the updateFinalTermScore can access the node represented by this and extract the user-provided value using the jQuery val() function:
function updateFinalTermScore(node, salary) {
console.log( $( node ).val() );
console.log( RosterID );
}
I am trying to create a row of 3 form fields dynamically when the user clicks on the ADD button / plus symbol .
Javascript code used to create the form elements is below :
<script type="text/javascript">
var field_counter = 1;
var field_limit = 5;
species = document.getElementById('species');
breed = document.getElementById('breed');
function addInput(divName){
if (field_counter == field_limit) {
alert("You have reached the limit of adding " + field_counter + " inputs");
}
else {
var dynamic_species = 'species'+field_counter; //dynamic id for species
var dynamic_breed = 'breed'+field_counter; //dynamic id for breed
var dynamic_quantity = 'quantity'+field_counter; //dynammic id for quantity
var dynamicForm = '<div class="form-group"><div class="col-md-4"><label for="'+dynamic_species+'">Animal Type</label><select class="form-control input-sm '+dynamic_species+'" id="'+dynamic_species+'"><option></option></select></div>';
dynamicForm+='<div class="form-group"><div class="col-md-4"><label for="'+dynamic_breed+'">Breed</label><select class="form-control input-sm '+dynamic_breed+'" id="'+dynamic_breed+'"><option></option></select></div>';
dynamicForm+='<div class="form-group"><div class="col-md-2"><label for="'+dynamic_quantity+'">Quantity</label><input type="number" name="quantity_export" id="'+dynamic_quantity+'" class="form-control input-sm '+dynamic_quantity+'" /></div>';
var newdiv = document.createElement('div');
newdiv.innerHTML = dynamicForm;
document.getElementById(divName).appendChild(newdiv);
document.getElementById(dynamic_species).innerHTML = species.innerHTML;
document.getElementById(dynamic_breed).innerHTML = breed.innerHTML;
field_counter++;
}
}
</script>
<div class="col-md-2" >
<label for=""> </label>
<i onclick="addInput('dynamicInput');" style="cursor: pointer; border: none;" class="fa fa-plus form-control input-sm">Add</i>
</div>
Using the above code am creating the form fields "Animal Type , Breed and Quantity ", all together in a row as shown in the image . Maximum number of rows that can be added is limited to the value of the variable "field_limit".
The value of the drop downs are initially populated from the parent drop down using the code :
species = document.getElementById('species');
breed = document.getElementById('breed');
document.getElementById(dynamic_species).innerHTML = species.innerHTML;
document.getElementById(dynamic_breed).innerHTML = breed.innerHTML;
Question : How can I select the dynamically generated ID of the new form fields .
Here is the script am using to select the first row of form fields which is in the HTML when the page loads for the first time :
$("#species").change(function(){
$('#breed').empty();
//alert($(this).val());
var param = {'id':$(this).val()};
$.ajax({
type : 'POST',
url : '<?php echo base_url();?>select_breed',
dataType : 'json',
data: param,
success : function(data)
{
var select = $('#breed');
select.empty().append(data);
},
error : function(XMLHttpRequest, textStatus, errorThrown)
{
alert(XMLHttpRequest.responseText);
}
});
});
The second row of form fields are created dynamically with the following ID's
First Field : Animal Type : ID="species1"
Second Field : Breed : ID="breed1"
Third Field : Quantity : ID="quantity1"
I am not able to select the dynamically generated form fields using the jquery selector :- eg: $("#species1").change(function(){}); , it is not working .
What I am trying to do ?
I need to get the value of these fields using its ID attribute. Any help would be highly appreciated . Thank you .
Use event delegation for dynamic generated content like so :
// change `document` to top level parent that existed on page or
// parent container
$(document).on("change", "#species1", function(){...});
Thats because they do not exists yet when binding to it's change event.
You could add the event listener in the add_input function, or use a more abstract event handler:
$("form").on("change", ".species", function () {
var id = $(this).attr("data-id");
...
});
This will require you to drop the ID's and use class attributes instead. Which is the way to go by my opinion.
You can attach the ID's using $(speciesElement).attr("data-id", id).
I am building a custom Dropzone.js: http://www.dropzonejs.com/ layout. The upload is working well. I am wanting to save additional data in the form that the Dropzone is in for a specific post.
I need to index the array so that all the data is posted is relevant in the array.
The 'previewTemplate' allows for strings only - no function.
eg: lead_image[ INDEX HERE ][filename]
uploader.dropzone({
url: "/admin/upload",
acceptedFiles: 'image/*',
thumbnailWidth: 80,
thumbnailHeight: 80,
parallelUploads: 20,
autoProcessQueue: true, // Make sure the files aren't queued until manually added
clickable: ".fileinput-button", // Define the element that should be used as click trigger to select files.
previewsContainer: "#previews", // Define the container to display the previews
init: function() {
this.on("addedfile", function(file) {
var index = $('li.image').length;
});
},
previewTemplate: '<li class="image row dd-item">' +
'<div class="col-sm-1 dd-handle">' +
'<span class="preview">' +
'<img data-dz-thumbnail />' +
'</span>' +
'</div>' +
'<div class="col-sm-8">' +
'<p><span class="name" data-dz-name></span> | <span class="size" data-dz-size></span></p>' +
'<input type="hidden" class="form-control" name="lead_image[ INDEX HERE ][filename]" data-dz-name/>' +
'<input type="text" class="form-control" name="lead_image[ INDEX HERE ][title]" value="" placeholder="Title" />' +
'<input type="text" class="form-control" name="lead_image[ INDEX HERE ][alt]" value="" placeholder="Alt Tag" />' +
'<input type="text" class="form-control" name="lead_image[ INDEX HERE ][caption]" value="" placeholder="Caption" />' +
'<input type="text" class="form-control" name="lead_image[ INDEX HERE ][sort]" value="" placeholder="Sort Order" />' +
'<strong class="error text-danger" data-dz-errormessage></strong>' +
'</div>' +
'<div class="col-sm-2">' +
'<button data-dz-remove class="btn btn-danger delete"><i class="glyphicon glyphicon-trash"></i><span>Delete</span></button>' +
'</div>' +
'</li>',
});
I am having difficulty passing the template the index of the current item as these items are passed through later.
Has anyone dealt with this or can see a solution? I am currently trying to inject the file name as the index as a solution, but this isn't the best way to go in my mind.
Thanks in advance for taking the time to help.
bI sorted this in the end.
init: function() {
this.on("success", function(file, responseText) {
console.log(responseText);
// Create the hidden fields
// Created_at
file.createdat = Dropzone.createElement("<input type=\"hidden\" class=\"form-control input-sm\" name=\"" + this.options.inputName + "[" + responseText.id + "][created_at]\" value=\"" + responseText.created_at + "\" />");
file.previewElement.appendChild(file.createdat);
}
}
On the init function, you are basically waiting to hear back from Dropzone of the successful uploaded. So, depending on your server side implementation, you can pass back any data you want about the file. In my case, I stored it in the DB and returned the row's info.
From there, to save that information in in the current post, I just created some hidden fields to store the data and then repeated the above for each hidden field I wanted. You can of course add other non-hidden fields for things like alt tags, titles or anything you like.
The index I was after in the the responseText: this.options.inputName + "[" + responseText.id + "][created_at]
Hope it helps.
As a side note, you can also do the same thing when loading files that have been stored on the server that you want to retrieve for this specific post. Just Google mockfile and dropzone and you should find a million results helping you. Its the same principle.
My current page looks like this WITHOUT the input box. The problem I'm having is with the input. I'm going to have multiple controls with the same id name, and I want the value to be different for each row. What should I do to automate this? If I hardcoded it, I would still get the same ID tag on each loop iteration. My goal is to be able to add and delete entire target groups at the end
Target Name: text <--- what it looks like now
Command Line: [ input box ] <----- desired output
Build Env: [ input box ]
Rel Out: [ input box ]
Rel Log: [ input box ]
Dep: [ input box ]
my JS looks like:
for (var i = 0; i < records.length; i++) {
var row = $('<tr></tr>');
row.append($('<tr></tr>').text("Target Name: " + records[i].TargetName));
row.append($('<tr></tr>').text("Command Line: " + records[i].CommandLine));
row.append($('<tr></tr>').text("Build Environment: " + records[i].BuildEnvTypeName));
row.append($('<tr></tr>').text("Relative Output Path: " + records[i].RelativeOutputPath));
row.append($('<tr></tr>').text("Relative Log Path: " + records[i].RelativeLogPath));
row.append($('<tr></tr>').text("Dependencies: " + records[i].Dependencies));
$('#AddTargetTable').append(row);
}
the input box part I want to append for each part (this is specific for the target name one):
<div class="control-group">
<div class="controls">
<input type="text" id="target-name" value="<% =TargetName %>" />
</div>
</div>
if you pass a unique identifier along with your record data you can add that identifier to each row. You don't need element ID's for tasks like this.
/* add identifier to start of row */
var row = $('<tr data-id="'+records[i].id +'"></tr>');
Then when you interact with a row you can pull the data-id using jQuery.data() method.
As for the input's you need to use html() instead of text() for cell data.
A simple template function will help keep the clutter down
function inputTemplate( value, type){
return ' <div class="control-group">'+
'<div class="controls">'+
'<input type="text" value="'+value+'" data-type="'+type+'" />'+
'</div>'+
'</div>';
}
Adding cells:
row.append($('<td>').html("Command Line: " + inputTemplate( records[i].CommandLine, 'Command')));
Now add a change handler for the input's. Will use similar row traversal approach for delete row.
$('.controls input').change(function(){
var rowId = $(this).closest('tr').data('id');
var newValue=$(this).val();
var fieldType= $(this).data('type')
alert('new value for '+ fieldType +' in row ' + rowId +' is ' + newValue );
updateServer( rowId, fieldType, newValue);
})
I have a simple ui which has a link that says "add item". When this is clicked, a pair of input boxes appears below it. If it is clicked again, yet another pair appears. I'm trying to think of the best way to generate these elements and turn it into some sort of json array of key value pairs (the first input element in each pair being the key and the second input element being the value).
Right now I just have a counter and I generate the ids using it, such as (in the click event of the "add item" link):
$('#features').append('<input id="feature-name-' + self.featureCount + '" type="text" name="asdf" /><a class="delete-feature" data-id="' + self.featureCount + '">Delete</a><input id="feature-description-' + self.featureCount + '" type="text" name="asdf" />');
I don't know what to use as the "name" attributes in order to make it easy to create a json array from them.
you can do something like this without using id attributes.
$('#features').append('<div><input type="text" />
<a class="delete-feature" data-id="' + self.featureCount + '">Delete</a><input type="text" /></div>');
And your javascript,
var yourArray=[];
$('#yourButton').click(function(){
$('#features div').each(function(){
var div=$(this);
var k=$('input:first',div).val();
var v=$('input:first',div).next().val();
yourArray.push({key:k, value: v});
});
});
It doesn't matter what you use for a name attribute, so long as there name and description names are different. Let's say that these elements are all appended to a form with the id myform. Give each pair its own wrapper object. Here, I've used a div, but a fieldset is equally appropriate.
$('#features').append(
'<div class="feature-div">
'<input id="feature-name-' + self.featureCount + '" type="text" name="asdf" />' +
'<a class="delete-feature" data-id="' + self.featureCount + '">Delete</a>' +
'<input id="featurena-description-' + self.featureCount + '" type="text" name="asdf" />' +
'</div>');
Now, it's possible to extract each pair sensibly:
var myarray = [];
$('#myform .feature-div').each(function(i, v) {
myarray.push([
$('input[name=name]', v).val(), $('input[name=description]', v).val()]);
});
Or however you want the data to be presented.