I would like to send a dynamic table back to the controller by using its model.
Following situation. On the view I am creating a table with javascript which allows the users to insert different values. These values are picked from pre-defined comboboxes:
$(document).ready(function () {
$('#btn_insert').on('click', function ()
var table=document.getElementById("table_computerimport");
var table_len=(table.rows.length)-1;
var row = table.insertRow(table_len).outerHTML="<tr id='row"+table_len+"'><td id='computername_row"+table_len+"'>"+computername+"</td><td id='uuid_row"+table_len+"'>"+"HERE UUID"+"</td><td id='collection_row"+table_len+"'>"+" HERE Collection Name"+"</td><td><input type='button' value='Delete' class='delete' onclick='delete_row("+table_len+")'></td></tr>";
});
This is working fine, so that at least the table is created with n entries.
<div>
<button type="button" id="btn_insert" class="btn btn-primary">Insert</button>
<div >
<table class="table" id="table_computerimport">
<thead>
<tr>
<th>Hostname</th>
<th>MacAddress</th>
<th>GUID</th>
</tr>
</thead>
<tr>
</tr>
</table>
</div>
</div>
These entries I would like to pass back to the controller by using its model:
public IEnumerable<ComputerImportModel> TableComputerImport {get; set;}
#model ViewModels.ComputerImportViewModel
So I don’t want to loop though the list of objects and publish them in the view, I would like to create n records in one view at once and pass these back to the controller by using the model TableComputerImport.
I guess in Javascript somehow I have to say something like this: input asp-for="ComputerImport.hostname"
Update,
ok, I think I "solved" it. What I did is that I created an array with javascript out of the table and passed this by ajax to the controller. It was important that the array has the name attributes like the class does so that I can use this object in the constructor as a well defined object.
As I mentioed, I had to use an array which I send by AJAX to the controller. Because of the identical names in the table and in the model, everything goes its correct way:
var singlecomputerimport = [];
var headers = [];
$('#table_computerimport th').each(function(index, item) {
headers[index] = $(item).html();
});
$('#table_computerimport tr').has('td').each(function() {
var arrayItem = {};
$('td', $(this)).each(function(index, item) {
if(headers[index] !== undefined){
//console.log($(item));
//arrayItem[headers[index].toLowerCase()] = $(item).html();
arrayItem[headers[index].toLowerCase()] = $(item).attr('value');
}
});
singlecomputerimport.push(arrayItem);
});
Related
I added a filtering function with Individual column searching (https://datatables.net/examples/api/multi_filter_select.html) to my datatable which is working fine.
This table also has a button to reload table data. The button triggers code like:
table.ajax.url("foo").load();
It updates table data correctly. Now, I want to update searching dropdown box with new column data. I want to empty dropdown box something like select.empty() then fill the box, but not sure how. I think this update process should be written in "rowCallback".
Summary
To rebuild the drop-downs after each ajax call, here is one approach:
Instead of using the DataTables ajax option, you can fetch the data using a jQuery ajax call, outside of DataTables.
Use the jQuery done function populate the table, and re-build the drop-downs after each ajax call.
This approach ensures that the ajax data has been fetched before any additional processing takes place.
Walkthrough
Assume we have a button like this:
<button type="button" onclick="fetchData();">Reload Data</button>
And a HTML table like this:
<table id="example" class="display" style="width:100%">
<tfoot>
<tr>
<th></th>
<th></th>
<th></th> <!-- you may need more footer cells in your table -->
</tr>
</tfoot>
</table>
Here is the related fetchData function, which clears all existing data, then re-populates the table with the newly fetched data:
function fetchData() {
$.ajax({
url: [your url goes here], // your URL here
context: document.body
}).done(function( data ) {
var table = $('#example').DataTable();
table.clear();
table.rows.add(data);
buildSelectLists();
table.draw();
});
}
The function to rebuild the select lists is identical to the logic from the DataTables example solution:
function buildSelectLists() {
$('#example').DataTable().columns().every(function() {
var column = this;
var select = $('<select><option value=""></option></select>')
.appendTo($(column.footer()).empty())
.on('change', function() {
var val = $.fn.dataTable.util.escapeRegex(
$(this).val()
);
column
.search(val ? '^' + val + '$' : '', true, false)
.draw();
});
column.data().unique().sort().each(function(d, j) {
select.append('<option value="' + d + '">' + d + '</option>')
});
});
}
Finally, the DataTable is defined in a "document ready" function:
$(document).ready(function() {
$('#example').DataTable({
// your options here - but no need for the ajax or data options
"initComplete": function() {
fetchData(); // make sure the table contains data, when it is created
}
});
});
Alternatively:
You can achieve a similar result by using the DataTables ajax option which makes use of a function:
Example (taken from the documentation here):
$('#example').dataTable( {
"ajax": function (data, callback, settings) {
callback(
JSON.parse( localStorage.getItem('dataTablesData') )
);
}
} );
I think in this case, it is a bit cleaner to keep the ajax call in its own separate function.
I have an ASP.NET Core 1.1 web application developed with VS.2017 and I decided to put some of the view functionality in a view component (have done others before).
This view component fetches a Dictionary collection of permissions associated to a user ID and displays them in a nice table. When I put it as part of the page (not VC) it works. But when I use it as a view component the component's HTML is never rendered.
I placed a breakpoint in the view component and it triggers, I see the View(granted) return statement return a populated list so up until there execution is as expected.
Then I placed a breakpoint in the ViewComponent's default.cshtml code section at the top the #{ some code here } and that breakpoint triggers as well, so the view component's Default.cshtml file is found. This Default.cshtml has some markup to render a table, therein within the table I have a #foreach() statement and when do a "Run to cursor" to that precise location -the loop that iterates- it triggers as well so it is iterating through the collection.
But after all that the main view looks as if the view component isn't there, none of the HTML found in the Default.cshtml is rendered even though it was found and executed. What am I missing here? so far my impression has been that VS.2017 (with all its updates) is not very stable.
Default.cshtml
#using ACME.AspNetCore.Permissions.Infrastructure.Authorization
#model Dictionary<Permission, string>
#{
ViewBag.UserName = "xxx";
Model.Add(Permission.Permission1, "test");
}
<h1>Component Output</h1>
<div class="well well-lg">
<table class="table table-hover">
<thead>
<tr>
<th>Permission</th>
<th>Description</th>
<th class="text-center">Status</th>
<th class="text-center">Revoke it!</th>
</tr>
</thead>
<tbody>
#foreach (var dictentry in Model)
{
<tr>
<td>#dictentry.Key.ToString()</td>
<td>#dictentry.Value</td>
<td class="text-center"><span class="glyphicon glyphicon-ok" style="color:green;"></span></td>
<td class="text-center"><a asp-action="RevokePermission" asp-route-id="#ViewBag.UserName" asp-route-pid="#dictentry.Key.ToString()"><span class="glyphicon glyphicon-thumbs-down" style="color:red;"></span></a></td>
</tr>
}
</tbody>
<tfoot><p class="alert alert-success"><span class="glyphicon glyphicon-eye-open"></span> Granted permissions</p></tfoot>
</table>
</div>
GrantedPermissionsViewComponent.cs
[ViewComponent(Name = "GrantedPermissions")]
public class GrantedPermissionsViewComponent : ViewComponent {
private readonly ApplicationDbContext _context;
public GrantedPermissionsViewComponent(ApplicationDbContext context) : base()
{
_context = context;
}
public async Task<IViewComponentResult> InvokeAsync(string emailOrUserId)
{
string id;
Guid UID;
if (Guid.TryParse(emailOrUserId, out UID))
{ // THE PARAMETER WAS A GUID, THUS A USER ID FROM IDENTITY DATABASE
id = emailOrUserId;
}
else
{ // THE PARAMETER IS ASSUMED TO BE AN EMAIL/USERNAME FROM WHICH WE CAN DERIVE THE USER ID
id = _context.Users.Where(u => u.Email == emailOrUserId.Trim()).Select(s => s.Id).FirstOrDefault();
}
Dictionary<Permission, string> granted = GetOwnerPermissions(id);
return View(granted);
}
private Dictionary<Permission, string> GetOwnerPermissions(string userId)
{
Dictionary<Permission, string> granted;
granted = _context.UserPermissions.Where(u => u.ApplicationUserId == userId)
.Select(t => new { t.Permission })
.AsEnumerable() // to clients memory
.Select(o => new KeyValuePair<Permission, string>(o.Permission, o.Permission.Description()))
.ToList()
.ToDictionary(x => x.Key, x => x.Value);
return granted;
}
}
So why on earth is it triggering on the component's code as well as on the component's view (default.cshtml) and yet it does not render the HTML code found therein?
Component invokation in the main view:
#{await Component.InvokeAsync<GrantedPermissionsViewComponent>(
new { emailOrUserId = ViewBag.UserName });
}
NOTE
The InvokeAsync is actually executing synchronously (per warning) because I could not find a way to have GetOwnerPermissions to await on anything... But that is not the problem.
The problem lies in how you are invoking the ViewComponent.
If you use #{ ... } it means you want to execute code and not render to output.
If you use parenthesis instead of brackets, the result gets rendered to output. #( ... )
In your case, you don't even need the parenthesis.
Try invoking it has following:
#await Component.InvokeAsync("GrantedPermissions", new { emailOrUserId = ViewBag.UserName })
More info here
try this, your mileage might vary. :)
#if (User.Identity.IsAuthenticated)
{
<div>User: #User.Identity.Name</div>
#(await Component.InvokeAsync("DefaultNavbar"));
#RenderBody()
}
else
{
<div> show public pages</div>
#RenderBody()
}
I have added a button in my view. When this button is clicked partial view is added. In my form I can add as much partial view as I can. When Submitting this form data I am unable to send all the partial view data to controller.
I have made a different model having all the attributes and I have made a list of that model to my main model. Can anyone please give me some trick so that I can send all the partial view content to my controller?
In My View
<div id="CSQGroup">
</div>
<div>
<input type="button" value="Add Field" id="addField" onclick="addFieldss()" />
</div>
function addFieldss()
{
$.ajax({
url: '#Url.Content("~/AdminProduct/GetColorSizeQty")',
type: 'GET',
success:function(result) {
var newDiv = $(document.createElement("div")).attr("id", 'CSQ' + myCounter);
newDiv.html(result);
newDiv.appendTo("#CSQGroup");
myCounter++;
},
error: function(result) {
alert("Failure");
}
});
}
In My controller
public ActionResult GetColorSizeQty()
{
var data = new AdminProductDetailModel();
data.colorList = commonCore.getallTypeofList("color");
data.sizeList = commonCore.getallTypeofList("size");
return PartialView(data);
}
[HttpPost]
public ActionResult AddDetail(AdminProductDetailModel model)
{
....
}
In my Partial View
#model IKLE.Model.ProductModel.AdminProductDetailModel
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategorySizeId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategorySizeId, Model.sizeList, "--Select Size--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategorySizeId)
</div>
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategoryColorId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategoryColorId, Model.colorList, "--Select Color--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategoryColorId)
</div>
<div class="editor-field">
#Html.LabelFor(model => model.productTotalQuantity)
#Html.TextBoxFor(model => model.productTotalQuantity)
#Html.ValidationMessageFor(model => model.productTotalQuantity)
</div>
Your problem is that the partial renders html based on a single AdminProductDetailModel object, yet you are trying to post back a collection. When you dynamically add a new object you continue to add duplicate controls that look like <input name="productTotalQuantity" ..> (this is also creating invalid html because of the duplicate id attributes) where as they need to be <input name="[0].productTotalQuantity" ..>, <input name="[1].productTotalQuantity" ..> etc. in order to bind to a collection on post back.
The DefaultModelBinder required that the indexer for collection items start at zero and be consecutive, or that the form values include a Index=someValue where the indexer is someValue (for example <input name="[ABC].productTotalQuantity" ..><input name="Index" value="ABC">. This is explained in detail in Phil Haack's article Model Binding To A List. Using the Index approach is generally better because it also allows you to delete items from the list (otherwise it would be necessary to rename all existing controls so the indexer is consecutive).
Two possible approaches to your issue.
Option 1
Use the BeginItemCollection helper for your partial view. This helper will render a hidden input for the Index value based on a GUID. You need this in both the partial view and the loop where you render existing items. Your partial would look something like
#model IKLE.Model.ProductModel.AdminProductDetailModel
#using(Html.BeginCollectionItem())
{
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategorySizeId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategorySizeId, Model.sizeList, "--Select Size--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategorySizeId)
</div>
....
}
Option 2
Manually create the html elements representing a new object with a 'fake' indexer, place them in a hidden container, then in the Add button event, clone the html, update the indexers and Index value and append the cloned elements to the DOM. To make sure the html is correct, create one default object in a for loop and inspect the html it generates. An example of this approach is shown in this answer
<div id="newItem" style="display:none">
<div class="editor-field">
<label for="_#__productTotalQuantity">Quantity</label>
<input type="text" id="_#__productTotalQuantity" name="[#].productTotalQuantity" value />
....
</div>
// more properties of your model
</div>
Note the use of a 'fake' indexer to prevent this one being bound on post back ('#' and '%' wont match up so they are ignored by the DefaultModelBinder)
$('#addField').click(function() {
var index = (new Date()).getTime();
var clone = $('#NewItem').clone();
// Update the indexer and Index value of the clone
clone.html($(clone).html().replace(/\[#\]/g, '[' + index + ']'));
clone.html($(clone).html().replace(/"%"/g, '"' + index + '"'));
$('#yourContainer').append(clone.html());
}
The advantage of option 1 is that you are strongly typing the view to your model, but it means making a call to the server each time you add a new item. The advantage of option 2 is that its all done client side, but if you make any changes to you model (e.g. add a validation attribute to a property) then you also need to manually update the html, making maintenance a bit harder.
Finally, if you are using client side validation (jquery-validate-unobtrusive.js), then you need re-parse the validator each time you add new elements to the DOM as explained in this answer.
$('form').data('validator', null);
$.validator.unobtrusive.parse($('form'));
And of course you need to change you POST method to accept a collection
[HttpPost]
public ActionResult AddDetail(IEnumerable<AdminProductDetailModel> model)
{
....
}
On this page, a user fills out a web form, and it gets added to the list when created.
I want to filter the list so a logged in user will only see forms they made themselves.
I have some Razor code that runs a foreach loop through all available items, and I have Javascript that rips the currently logged in user's info.
is it possible to assign this data to a var in the razor code?
ex.
#{var user = getUser(); //want something like this }
#foreach (var item in Model) {
//add check if item.name == user.name here
<tr>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
etc etc
<script>
function getUser() {$.getJSON('GetLoggedUserInfo', function(data) {
return data;
});
</script>
I do not think it is a good idea to have this logic in a view. View should be as 'dumb' as possible.
You could make item filtering at data storage level or at least in controller:
return View(items.Where(x => x.name == user.name))
There is an easier way. You can get current user properties using:
HttpContext.Current.User
I've loaded an ASP.NET MVC viewModel into KnockoutJS using ko.mapping.fromJS(Model).
My viewModel looks something like this:
public IEnumerable<FunkyThing>funkyThings;
public FunkyThing selectedFunkyThing;
I've then got in my HTML View a table which looks something like this
<tbody data-bind="foreach: funkyThings">
<tr>
<td data-bind="text:name"></td>
<td data-bind="text:funkiness"></td>
<td>
</td>
</tr>
</tbody>
and all is good with this table. Clicking the select funky thing link happily calls the selectFunkyThing function:
model.selectFunkyThing= function (funkyThing) {
window.location = '#Url.Action(MVC.FunkyThingController.Index())' + "?funkyThingID=" + funkyThing.funkyThingID();
};
which in turn refreshes the page. The MVC viewmodels is reloaded and selectedFunkyThing is populated with the selected FunkyThing and the knockout view models are then re-read from the MVC viewmodel. So far so good.
I then wanted to update the table to highlight the selected entry.
So I updated the tr line as follows:
<tr data-bind="css:{'selected': $root.isSelected()}">
and created the new knockout function:
model.isSelected = function (funkyThing) {
return funkyThing.funkyThingID== model.selectedFunkyThing.funkyThingID;
};
but... it's not working.
Chrome throws a javascript exception stating that the FunkyThing parameter is undefined.
Technically I figure I could solve it by changing the MVC viewModel to actually set a isSelected on each FunkyThing within the array. However I figure there's got to be away of doing this from Knockout?
You were close! I added the ko.utils.unwrapObservable call because I bet the funkyThingID is an observable and not just a straight property - you did this yourself in your selectFunkyThing function. You could just execute them as well. I like the verbosity of unwrap though.
model.isSelected = function (funkyThing) {
var thisId = ko.utils.unwrapObservable(model.selectedFunkyThing.funkyThingID);
return ko.utils.unwrapObservable(funkyThing.funkyThingID) == thisId;
};
and then in your document you actually have to execute this function when ko parses the binding
<tr data-bind="css:{'selected': $root.isSelected($data)}">
Are those properties not both observables, so you need to reference them as functions? You also need to make your function a computed observable, I think:
model.isSelected = ko.computed(function (funkyThing) {
return funkyThing().funkyThingID() == model.selectedFunkyThing().funkyThingID();
});