ASP.NET MVC: How to send the ids of the DOM Body elements on the client's browser TO the controller when navigating from the view - asp.net

I am working on an ASP.NET MVC app (ASP.NET NOT ASP.NET Core).
When a View is rendered, the user can click on some buttons on the page to collapse or show divs associated with each button. The div changes its class depending on whether it is collapsed or shown. I am using bootstrap attributes for this, and it works fine.
Now I have a "Save" button on the page. When the user clicks on this button, I need to retrieve the ids and classes of the divs, and pass them TO the Controller (in an array/collection/dictionary whatever).
Is there a way/method in ASP.NET to send to the Controller the attributes (ids, classes, etc) of the DOM elements on the client's browser ?
Thanks

If you want to send some attributes of DOM to Controller, I have a way.
HTML:
<div id="demo-1" class="chosendiv other-className" data-code ="abc">Lorem Ipsum</div>
<div id="demo-2" class="chosendiv other-className" data-code ="xyz">Lorem Ipsum</div>
<div id="demo-3" class="other-className" data-code ="mnt">Lorem Ipsum</div>
<button id="btn-save" onclick="Save()">SAVE</button>
Javascript
<script>
function Save(){
var cds = document.getElementsByClassName('chosendiv');
var finder = [];
if(cds != null){
for(i = 0; i< cds.length; i++){
finder.push({
ID: cds[i].getAttribute('id'),
ClassName: cds[i].getAttribute('class'),
Code: cds[i].getAttribute('data-code')
})
}
}
//
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
//
$.ajax({
url: '/Home/YourAction',
type: 'GET', //<---- you can use POST method.
data:{
myDiv: JSON.stringify(finder)
},
success: function(response){
// Your code
}
})
}
</script>
Your Controller
public class HomeController: Controller
{
public HomeController(){}
[HttpGet]
public void YourAction(string myDiv)
{
//A lot of ways for converting string to Object, such as: creating new class for model, ...
// I use Dictionary Class
List<Dictionary<string, string>> temp = new List<Dictionary<string, string>>();
if(!string.IsNullOrEmpty(myDiv))
{
try
{
temp = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(myDiv);
}
catch { // Do something if it catches error. }
}
// Get a element (at index) from temp if temp.Count()>0
// var id = temp.ElementAt(index)["ID"];
// var className = temp.ElementAt(index)["ClassName"];
// var code = temp.ElementAt(index)["Code"];
//
//Your code
//
}
//......
}
It would be great if my answer could solve your problem.

Based on the answer provided by #Gia Khang
I made few changes in order to avoid the issue of the length of the URL exceeding the maximum limit.
Instead of adding the element's classes to an array using JS, I add them to a string :
function Save() {
var cds = document.getElementsByClassName('chosendiv');
// I use as string instead of an array
var finder = "";
if(cds != null){
for(i = 0; i< cds.length; i++){
finder = finder + "id=" + cds[i].getAttribute('id') + "class=" + cds[i].getAttribute('class') + "data-code=" +cds[i].getAttribute('data-code')
}
}
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
var myURL = "/{Controller}/{Action}"
$.ajax({
url: myURL,
type: "POST",
data: { ids:finder },
success: function (response) {
}
})
}
In the Controller Action I add a parameter named "ids" (this must be the same name as the identifier of the data object in the post request)and I extract the id, class, and data value from the ids string by a method in one of my Models classes (sorry I work with VB.NET not with C# and it will take me a lot of time to convert the code to C#. I use the Split method in VB to split the ids string several times: a first one by using "id=" as delimiter, then spiting each element in the resulting array by the second delimiter "class=", etc. I add the resulting elements to a collection)
The Controller Action looks like this:
public class HomeController: Controller
{
public HomeController(){}
[HttpPost]
public void YourAction(string ids)
{
Models.myClass.splitStringMethod(ids)
Return View()
}
}

Related

Double Clicking on row and getting respective row values

I want to double click on any row to getting respective row values. It is an .Razor page.
First of all, I would create a component that will inherit from QuickGrid, so you can manage it easier in the future.
// CustomGrid.razor.cs
[CascadingTypeParameter( nameof(TGridItem) )]
public partial class CustomGrid<TGridItem> : QuickGrid<TGridItem>, IAsyncDisposable
{
[Inject]
private IJSRuntime JS { get; set; } // inject service in order to use JS interop
// sometimes it is mandatory to override base class parameters by your own
}
// CustomGrid.razor
#using Microsoft.AspNetCore.Components.QuickGrid // move it into the _Imports.razor
#typeparam TGridItem // QuickGrid is a generic-typed component
#inherits QuickGrid<TGridItem> // show inheritance
<div #ref="#_gridRef"> // HTML reference of current element
<QuickGrid TGridItem="TGridItem"
Items="#Items"
ItemsProvider="#ItemsProvider"
ChildContent="#ChildContent"
Class="#Class"
// more parameters... >
</QuickGrid>
</div>
Since there is no built-in functionality for adding your custom logic into QuickGrid, you will need to use some JavaScript interopability. Read about it more in docs here and here.
We need to declare some local variables in our CustomGrid.razor.cs:
private string? _rowValue; // specifies row value of currently double clicked row
private ElementReference _gridRef; // HTML element reference object that will be passed to JS function
private IJSObjectReference? _module; // JS module, a file that contains our JS functions
private DotNetObjectReference<CustomGrid<TGridItem>>? _objRef; // .NET object reference that will be passed to JS function in order to use its C# methods
And override some of the component lifecycle methods:
protected override void OnInitialized()
{
_objRef = DotNetObjectReference.Create( this ); // creates .NET object reference of current component instance
}
protected override async Task OnAfterRenderAsync( bool firstRender )
{
if( firstRender )
{
_module = await JS.InvokeAsync<IJSObjectReference>( "import", "./js/customGrid.js" ); // creates a reference of our JS module
if( _module is not null )
{
await _module.InvokeVoidAsync( "initialize", _gridRef, _objRef ); // calls our JS function and passes some arguments
}
}
}
Now, you need to create a JS module and a functions that will add desired logic for you on the first render of the CustomGrid component, like this:
// wwwroot/js/customGrid.js
export function initialize(customGrid, dotNetObj) {
if (customGrid) { // check if custom grid element exists
var rowValue;
const rows = customGrid.querySelectorAll('tbody > tr'); // get all rows except the header row
for (let i = 0; i < rows.length; i++) {
rows[i].addEventListener('dblclick', (e) => { // add event listener to current row in the loop
rowValue = e.path[1].innerText; // get innerText of current row in the loop
console.log(rowValue)
updateCurrentRowValue(rowValue, dotNetObj); // function that will return the current row value and refresh the UI
});
}
}
}
function updateCurrentRowValue(rowValue, dotNetObj) {
dotNetObj.invokeMethodAsync("UpdateCurrentRowValue", rowValue); // C# method
}
We're almost done here! If you would try to perform double click on the row, you would see an error in the console stating that CustomGrid does not contain a public method called UpdateCurrentRowValue. Let's add it like this:
[JSInvokable]
public void UpdateCurrentRowValue( string rowValue )
{
_rowValue = rowValue; // assign received rowValue from the JS function to our local _rowValue variable
StateHasChanged(); // force UI refresh
}
Now, all you need to do is to display your _rowValue:
// CustomGrid.razor
<div #ref="#_gridRef">
<QuickGrid TGridItem="TGridItem" . . . /> // collapsed for brevity
<p>Current Row Value: #_rowValue</p>
</div>
You will also need to Dispose your newly created objects of _module and _objRef using IAsyncDisposable.DisposeAsync method:
// CustomGrid.razor.cs
async ValueTask IAsyncDisposable.DisposeAsync()
{
if( _module is not null )
{
await _module.DisposeAsync();
}
_objRef?.Dispose();
}
Usage:
<CustomGrid Items="#people">
<PropertyColumn Property="#(p => p.PersonId)" Sortable="true" />
<PropertyColumn Property="#(p => p.Name)" Sortable="true" />
<PropertyColumn Property="#(p => p.BirthDate)" Format="yyyy-MM-dd" Sortable="true" />
</CustomGrid>
That should work. If you will need any help -- don't hesitate to ask!
Remarks:
This is a basic implementation of your request. It doesn't support scenarios when there are more than 1 grid on the page. It might work, but will be buggy, I guess. For that, you will need to add some more code in JS and CustomGrid code-behind. I didn't add it because it would be too much code in one answer (quite a lot of code came out here anyway).
UPD-1:
Removed custom [Parameter]s to override QuickGrid's ones and added a comment.

How to send an array using Url.Action

I have an array of integers called data which I would like to send from my View to a specific controller, I could see that i can send integers and strings and it works with the code that I have so far, but when I try to send an array I can get the data correctly.
This is the code that I have in my view, it is something simple just to be in perspective.
function SeeStation() {
var data = [];
var i = 0;
$("input:checkbox:checked").each(function () {
data[i] = $(this).val();
});
window.location.href = "#Url.Action("ExportData", "Dispatch")?id=" + data;
}
and this is the code in the controller. I know it doesn't make much sense but so far I am focused on correctly obtaining the array by parameter.
public ActionResult ExportData(int[] id)
{
var data = cn.ESTACIONDESPACHOes.ToList();
return View(data);
}
In my array data I store something like this [1,2,3] and I would like to get something similar in the controller array id.
It will not bind like that.
To get the id array in your action you need to have the link at the end like this: *Dispatch/ExportData?id=1&id=2&id=3*
Your "#Url.Action("ExportData", "Dispatch")?id=" + data; will not generate that (data will give the numbers separated with commas).
You can just build the query string when you enumerate the checkboxes.
function SeeStation() {
var data = '';
$("input:checkbox:checked").each(function () {
data += 'id='$(this).val() + '&';
});
window.location.href = "#Url.Action("ExportData", "Dispatch")?" + data;
}
You will have a "&" in the end. You can easily remove it, but it will not affect anything.
There may be better ways to do this though, but I just used your function.
try
#Url.Action("ExportData", "Dispatch", new { id= [1,2,3] })
Store the Values in the Hidden Fields
#Html.HiddenFor(m => m.Ids, new { #Value = [1,2,3] })
Then Using the Ajax Get Method Pass the Hidden fields
In the Controller Method Convert the sting to array using string extension method
function SeeStation() {
var data = [];
var i = 0;
$("input:checkbox:checked").each(function () {
data[i] = $(this).val();
});
location.href = '#Url.Action("ExportData", "Dispatch")?id=' + data;
}
Please remove window keyword.

Get ControollerContext.RouteValues from POST action

I created partial view and I call it like this:
#{Html.RenderAction("_SearchPartial", "Search", new { RenderVillaOption=true,RenderHotelOption=true,Frame=true,RenderAccommodationOption=true,RenderRentACarOption=true});}
I pass some values because I want them to be seen near the declaration od partial view and I controller I can get this values which is fine.
Than I post to some action inside SearchController. I would like to get this "Options" (route values I specified inside partial view declaration. How to achive that?
This is action method:
public string SearchAndRedirect([FromBody] SearchAPIModel searchModel)
{
var redirectUrl = "";
var c = ControllerContext;
switch (searchModel.ProductType)
Get them as parameters inside your ActionMethod:
public ActionResult Search(bool RenderVillaOption, bool RenderHotelOption, bool Framem ... )
{
}
As an alternative to #vortex answer, considering your comment you can use Request.Params:
public ActionResult Search(...)
{
var renderVillaOption = Request.Params["RenderVillaOption"];
}

HTML.DropDownList values from multiple sources?

In ASP.NET MVC, is it possible to fill the list of values of a Html.DropDownList from multiple data sources along with multiple manually entered values?
Basically, I envision it being formated like the below using something along the lines of OPTGROUP:
**Group 1**
Manual Item 1
Manual Item 2
**Group 2**
DS1 Item 1
DS1 Item 2
**Group 3**
DS2 Item 1
DS2 Item 2
I've thought about using a view on the DB and getting the data from that, however, I've not really the faintest how to lay it out like above using helpers and to pass the data to it from multiple sources.
Thanks for any help in advance.
As always start with a model (actually start with a unit test but no time for this here):
public class MyModel
{
public string SelectedItem { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
Then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var items1 = new[]
{
new { Value = "1", Text = "Manual Item 1" },
new { Value = "2", Text = "Manual Item 2" },
};
// TODO: Go fetch those from your repo1
var items2 = new[]
{
new { Value = "3", Text = "DS1 Item 1" },
new { Value = "4", Text = "DS1 Item 2" },
};
// TODO: Go fetch those from your repo2
var items3 = new[]
{
new { Value = "5", Text = "DS2 Item 1" },
new { Value = "6", Text = "DS2 Item 2" },
};
var items = items1.Concat(items2).Concat(items3);
var model = new MyModel
{
Items = new SelectList(items, "Value", "Text")
};
return View(model);
}
}
And finally a strongly typed view to the model:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyApp.Models.MyModel>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<%= Html.DropDownListFor(x => x.SelectedItem, Model.Items) %>
</asp:Content>
You will probably define an intermediary type to avoid the anonymous types that I've used for brevity.
Remark: If your original question was about using an OPTGROUP then ignore my answer and make your intention clear so that you can get a more adapted answer.
It seems it would be easier for you to write your own helper. The basic syntax to do that is this:
// The class can be named anything, but must be static and accessible
public static class HtmlHelperExtensions
{
// The method name is what you want to call on Html,
// in this case Html.CoolSelectList(arguments...)
//
// The method has to be static, and the first argument should be of the type
// you're extending (in this case HtmlHelper, which is the type of the
// Html property on your view). The first argument must be prefixed with the
// "this" keyword, to indicate it's an extension method.
//
// All the following arguments will be arguments that you supply when calling
public static string CoolSelectList(this HtmlHelper helper,
IEnumerable<IEnumerable<CoolThingThatWeMakeAListOf>> groups)
{
// I chose an argument of type IEnumerable<IEnumerable<T>>, since that
// allows you to create each group of item on its own (i.e. get them from
// various data sources) and then add all of them to a list of groups
// that you supply as argument. It is then easy to keep track of which
// items belong to which groups, etc.
// Returned from the extension method is a string you have built, that
// constitutes the html you want to output on your view. I usually use
// the TagBuilder class to build the html.
return "this is what will be on the page";
}
}
Many solutions exist for you problem. One would be the one that Tomas described, another is a Controller Action that returns PartialView, which contains the code to render the input and option tags, another solution would be to have the Controller Action populate the ViewData with a SelectList or have the SelectList as a strong type for your View/ViewUserControl (Partial).

asp.net mvc - how to update dropdown list in tinyMCE

Scenario: I have a standard dropdown list and when the value in that dropdownlist changes I want to update another dropdownlist that exists in a tinyMCE control.
Currently it does what I want when I open the page (i.e. the first time)...
function changeParent() {
}
tinymce.create('tinymce.plugins.MoePlugin', {
createControl: function(n, cm) {
switch (n) {
case 'mylistbox':
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts',
onselect: function(v) {
tinyMCE.execCommand("mceInsertContent",false,v);
}
});
<% foreach (var insert in (ViewData["Inserts"] as List<String>)) { %> // This is .NET
yourobject = '<%= insert %>'; // This is JS AND .NET
mlb.add(yourobject, yourobject); // This is JavaScript
<% } %>
// Return the new listbox instance
return mlb;
}
return null;
}
});
<%= Html.DropDownList(Model.Record[184].ModelEntity.ModelEntityId.ToString(), ViewData["Containers"] as SelectList, new { onchange = "changeParent(); return false;" })%>
I am thinking the way to accomplish this (in the ChangeParentFunction) is to call a controller action to get a new list, then grab the 'mylistbox' object and reassign it, but am unsure how to put it all together.
As far as updating the TinyMCE listbox goes, you can try using a tinymce.ui.NativeListBox instead of the standard tinymce.ui.ListBox. You can do this by setting the last argument to cm.createListBox to tinymce.ui.NativeListBox. This way, you'll have a regular old <select> that you can update as you normally would.
The downside is that it looks like you'll need to manually hook up your own onchange listener since NativeListBox maintains its own list of items internally.
EDIT:
I played around a bit with this last night and here's what I've come up with.
First, here's how to use a native list box and wire up our own onChange handler, the TinyMCE way:
// Create a NativeListBox so we can easily modify the contents of the list.
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
As far as updating the list box at runtime, your idea of calling a controller action to get the new items is sound; I'm not familiar with ASP.NET, so I can't really help you there.
The ID of the <select> that TinyMCE creates takes the form editorId_controlId, where in your case controlId is "mylistbox". Firebug in Firefox is the easiest way to find the ID of the <select> :)
Here's the test button I added to my page to check if the above code was working:
<script type="text/javascript">
function doFoo() {
// Change "myEditor" below to the ID of your TinyMCE instance.
var insertsElem = document.getElementById("myEditor_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
optElem.value = "1";
optElem.text = "Foo";
insertsElem.add(optElem, null);
optElem = document.createElement("option");
optElem.value = "2";
optElem.text = "Bar";
insertsElem.add(optElem, null);
}
</script>
<button onclick="doFoo();">FOO</button>
Hope this helps, or at least gets you started.
Step 1 - Provide a JsonResult in your controller
public JsonResult GetInserts(int containerId)
{
//some code to get list of inserts here
List<string> somedata = doSomeStuff();
return Json(somedata);
}
Step 2 - Create javascript function to get Json results
function getInserts() {
var params = {};
params.containerId = $("#184").val();
$.getJSON("GetInserts", params, updateInserts);
};
updateInserts = function(data) {
var insertsElem = document.getElementById("183_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
for (var item in data) {
optElem = document.createElement("option");
optElem.value = item;
optElem.text = data[item];
try {
insertsElem.add(optElem, null); // standards compliant browsers
}
catch(ex) {
insertsElem.add(optElem, item+1); // IE only (second paramater is the items position in the list)
}
}
};
Step 3 - Create NativeListBox (code above provided by ZoogieZork above)
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
//populate inserts on listbox create
getInserts();

Resources