I'm working on a website where the user should be able to fill out a dynamic expanding form.
I would add a row with greyed out fields and when the user gives focus to one of those field the following javascript would add the line
<tr class="unSelected">
<input name="id[]">
<input name="blabla[]">
</tr>
$('.unSelected').focusin(function () {
if ($(this).hasClass('unSelected')) {
var row = $(this).clone(true);
$(this).after(row);
$(this).removeClass('unSelected');
}
});
but how would one do this using razor and asp.net, as the objects wont be autogenerated then?
In ASP.NET MVC, if you have a model class like:
public class PageModel
{
public Collection<RowItem> RowItems { get; set; }
}
public class RowItem
{
public int Id {get;set;}
public string MoreFields { get; set; }
}
And your javascript adds rows like so:
<script type="text/javascript">
var currentRowIndex = 0;
$(document).ready(function () {
SetFocusEventForInputs('unSelected0');
});
function SetFocusEventForInputs(className) {
var inputSelector = '.' + className;
$(inputSelector).focusin(function () {
AddNewRowTo(this);
$(inputSelector).unbind('focusin').removeClass(className);
});
}
function AddNewRowTo(sendingInputField) {
currentRowIndex++;
var className = 'unSelected' + currentRowIndex;
var collectionNamePrefix = 'RowItems[' + currentRowIndex + '].';
var idField = $('<input/>').attr('name', collectionNamePrefix + 'Id').attr('type', 'text').attr('class', className);
var moreFields = $('<input/>').attr('name', collectionNamePrefix + 'MoreFields').attr('type', 'text').attr('class', className);
var cell = $('<td></td>').append(idField).append(moreFields);
var row = $('<tr></tr>').append(cell);
$(sendingInputField).parents("tr").after(row);
SetFocusEventForInputs(className);
}
</script>
<table>
<tr>
<td>
<input name="RowItems[0].Id" type="text" class="unSelected0" />
<input name="RowItems[0].MoreFields" type="text" class="unSelected0" />
</td>
</tr>
</table>
The default model binder in MVC should resolve it just fine when it gets posted
[HttpPost]
public ActionResult YourPostActionHere(PageModel model)
{
var count = model.RowItems.Count();
etc...
}
You can do it the same way, because in the code example above, you are using jQuery which is also supported (of course) with ASP.NET MVC.
Perhaps I don't understand you, but I don't see any PHP code in the code example above.
what you want to do is a client side script that is not depend on PHP or Asp.Net so it does not matter what your code is written by. this should work in Asp.Net mvc too.
if you want to collect the new control data to use it in server side you can collect it by JavaScript and assign it in a hidden field that can be accessed in server side. you can use one hidden field by save the values in one string and separated by any delimiter.
Are you possibly just missing the script tags? Like the others said, javascript is platform independent.
<tr class="unSelected">
<input name="id[]">
<input name="blabla[]">
</tr>
<script src="/Scripts/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$().ready(function () {
$('.unSelected').focusin(function () {
if ($(this).hasClass('unSelected')) {
var row = $(this).clone(true);
$(this).after(row);
$(this).removeClass('unSelected');
}
});
});
</script>
Related
My issue is very similar to my previous post:
How to bind a property to a dynamic list of objects
However I am trying to do the same thing that solved this problem in my previous post with a more complex control, an input file selector. Due to this, my question is very similar, however in this case I am guessing the fix is slightly different since the previous solution did not work. Anyhow here goes:
I am using the .net core 2.2 framework and am having trouble finding out how I can bind a list of IFormFile to a razor page. On the razor page I have a button to add a new file input to the screen. This button executes a jquery click event that alters the html to add a new input button of type file without refreshing the page. What I am looking to do with this, is that when I add a new button it binds the selected file to the List object. I can then process this list of items when I post the form.
My Razor Page cs looks something like this:
public class CreateModel : PageModel
{
#region Variables
private readonly MyContext _myContext;
#endregion
#region Properties
[BindProperty]
public List<IFormFile> Files { get; set; }
#endregion
public CreateModel(MyContext myContext)
{
_myContext = myContext;
}
public async Task<IActionResult> OnGetAsync()
{
#region Create instance of a FormFile for testing purposes
FormFile file;
using (var stream = System.IO.File.OpenRead("/test.txt"))
{
file = new FormFile(stream, 0, stream.Length, stream.Name, Path.GetFileName(stream.Name))
{
Headers = new HeaderDictionary(),
ContentType = "text/css",
};
}
Files.Add(file);
#endregion
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
return Page();
}
}
The Razor Page cshtml looks something like this:
...
<div id="file-container">
#for (var i = 0; i < Model.Files.Count(); i++)
{
<div class="myfile">
<label class="control-label">Upload file</label>
<input asp-for="Files[i]" type="file" />
</div>
}
</div>
<div class="item-add">
<a id="add-file" class="link-button"><img class="add-file" src="#Url.Content("~/images/ic_add.png")" />Add File</a>
</div>
and finally here is my jquery code:
$("#add-file").click(function () {
var nextId = $(".file").length;
var rowHtml = '<div class="file">' +
'<label class="control-label">Upload file</label>' +
'<input id="Files_' + nextId + '_" name="Files[' + nextId + ']" type="file" />' +
'</div>';
$("#file-container").append(rowHtml);
});
Finally, when I post the form that contains this code, I want to be able to access the values input into the dynamically created html from my binded property.
If there is anything that is not understood please let me know and I will try clarifying.
So Apparently when you work with input files it appears that you don't use the [i] part as in normal cases. Below is the code that changed:
<div id="file-container">
#for (var i = 0; i < Model.Files.Count(); i++)
{
<div class="myfile">
<label class="control-label">Upload file</label>
<input asp-for="Files" type="file" />
</div>
}
</div>
and in jquery:
$("#add-file").click(function () {
var nextId = $(".file").length;
var rowHtml = '<div class="file">' +
'<label class="control-label">Upload file</label>' +
'<input id="Files" name="Files" type="file" />' +
'</div>';
$("#file-container").append(rowHtml);
});
Is there any way to get the name of View that called method in controller and save it for example in some custom variable inside that controller's method?
For example:
I have one View that uses Ajax to get to InfinateScroll method in controller:
<div class="container-post">
<div id="postListDiv">
#{Html.RenderAction("PostList", "Posts", new { Model = Model });}
</div>
<div id="loadingDiv" style="text-align: center; display: none; margin-bottom: 20px;">
<img alt="Loading" src="#Url.Content("~/images/ajax-loader.gif")" />
</div>
</div>
<script src="#Url.Content("~/Scripts/jquery-1.10.2.min.js")"></script>
<script type="text/javascript">
var BlockNumber = 2;
var NoMoreData = false;
var inProgress = false;
$(window).scroll(function () {
if ($(window).scrollTop() == $(document).height() - $(window).height() && !NoMoreData && !inProgress) {
inProgress = true;
$("#loadingDiv").show();
$.post("#Url.Action("InfinateScroll", "Posts")", { "BlockNumber": BlockNumber },
function (data) {
BlockNumber = BlockNumber + 1;
NoMoreData = data.NoMoreData;
$("#postListDiv").append(data.HTMLString);
$("#loadingDiv").hide();
inProgress = false;
});
}
});
</script>
I use this View on two pages. In one case I'm using it to show only posts from specific user (user who is logged in), and on the other view I'm showing posts from all users in database(similar to Facebook wall where you can see only your post, and NewsFeed where you can not only your's but also posts from your frineds).
For some reason I would like to know which page was active when call for InfinateScroll method was made.
This is the method where I would like to make some differences between those two pages so I can do some check out's later.
[HttpPost]
public ActionResult InfinateScroll(int BlockNumber)
{
int BlockSize = 5;
var posts = PostManager.GetPosts(BlockNumber, BlockSize);
JsonModel jsonModel = new JsonModel();
jsonModel.NoMoreData = posts.Count < BlockSize;
jsonModel.HTMLString = RenderPartialViewToString("PostList", posts);
return Json(jsonModel);
}
This method gets posts using helper method GetPosts and it's used for showing more posts on scroll.
You can get the name of the current View from inside the view using the following:
#Path.GetFileNameWithoutExtension(Server.MapPath(VirtualPath))
Source: How to get the current view name in asp.net MVC 3?
so you could add this as a routevalue into your #Url.Action like so:
#Url.Action(
"InfinateScroll",
"Posts",
new{callingView=Path.GetFileNameWithoutExtension(Server.MapPath(VirtualPath))})
Then you could add a parameter to your controller method
public ActionResult InfinateScroll(int BlockNumber, string callingView)
You can create a hidden variable in the html like this -
<input type="hidden" id="pageName" value="myPage1" />
Add an extra parameter to your Action -
public ActionResult InfiniteScroll(int BlockNumber, int pageName)
And then, in your jquery code, when you post, send in pageName as well.
$.post("#Url.Action("InfinateScroll", "Posts")", { "BlockNumber": BlockNumber, "pageName": $('#pageName').val() },
Hope this helps.
In one case I'm using it to show only posts from specific user... and
on the other view I'm showing posts from all users in database...
Putting your desired logic on the view is unsafe, especially if showing data is user-based or user-specific. However, if you insists on having the logic on the view then you should pass along another variable to the controller like so:
$.post("#Url.Action("InfinateScroll", "Posts")",
{ "BlockNumber": BlockNumber, "UserId": userId },
// rest of your code goes here...
});
You then should have another parameter in your controller:
[HttpPost]
public ActionResult InfinateScroll(int BlockNumber, int userId)
{
//filter your data based on the "userId" parameter
}
But like I mentioned this is unsafe because someone can easily pass in a valid "userId" and get to the data when you don't want them to. So the safest (or safer) way is to have the "filtering logic" in your controller like so:
[HttpPost]
public ActionResult InfinateScroll(int BlockNumber)
{
// a context based logic
var userId = GetLoggedInUserId();
// that method could return null or zero
// and depending on how you approach it
//filter your data based on the "userId"
}
I have the following idea that i am trying to implement
#foreach (var item in Model)
{
<div>User: #item.Name<br />
Scores: #item.scores<br />
#Html.TextBox("lastvisit");
#Html.ActionLink("Update item", "updateMyItem", new { name = item.Name, lastvisit=????? })
</div>
}
I have seen this SO question Pass text in query string, but that is not what i want..
so my question is ..
in the above code how can I replace the (?????) with the value of the textbox(lastvisit)
and send the value as a querysting in the URL of the action link ??
Notice that I opted not to use a webform for my own reason and I know how to do it with webform.submit(), but my main concern is how to extract the value of #HTMLhelper.textbox()..
:)
Something like this might help. For this to work you need to render unique IDS for the links and textboxes.
Here is an example
Action method with a simple model
public ActionResult Index(int? id)
{
List<MyModel> mod = new List<MyModel>() {
new MyModel { SelectedValue = 1 } ,
new MyModel {SelectedValue = 2},
new MyModel {SelectedValue = 3}
};
return View(mod);
}
And this is the view with the script.
#model List<MVC3Stack.Models.MyModel>
#{
ViewBag.Title = "Home Page";
var i = 1;
}
<h2>#ViewBag.Message</h2>
<script type="text/javascript">
$(document).ready(function () {
var lastVisits = $("input[id*='lastVisit']");
$(lastVisits).each(function () {
var i = this.id.substring(this.id.length - 1);
var link = $("[id='testLink" + i + "']");
if (link) {
var _href = $(link).attr("href");
$(link).attr("href", _href + "&lastvisit=" + $(this).val());
}
});
});
</script>
#foreach (var item in Model)
{
#Html.TextBox("lastVisit" + i, item.SelectedValue )
#Html.ActionLink("TestLink", "Index", "Home", new { id = "testLink" + i });
<br />
i++;
}
<input type="button" value="GetFile" id="getFile" />
here is a snapshot with the changed link
Hope this helps.
EDIT
My bad. Here is the update javascript which can do the trick.
$(document).ready(function () {
var lastVisits = $("input[id*='lastVisit']");
$(lastVisits).each(function () {
$(this).change(function () {
var i = this.id.substring(this.id.length - 1);
var link = $("[id='testLink" + i + "']");
if (link) {
var _href = $(link).attr("href");
$(link).attr("href", _href + "?lastvisit=" + $(this).val());
}
});
});
});
Ok Nilesh I will answer my own question.. but I will cheat from your solution lol cuz it is inspiring .. thanx in advance
<script type="text/javascript">
$(document).ready(function () {
var myMainPath = "updateMyItem";
$("a").each(function(){
var name =$(this).parent("div").child("#itemName").val();
var visit = $(this).parent("div").child("#lastvisit").val();
$(this).attr('href', myMainPath +'?name=' + name + '&lastVisit='+ visit);
});
});
</script>
#foreach (var item in Model)
{
<div>User: <span id="itemName">#item.Name</span><br />
Scores: #item.scores<br />
#Html.TextBox("lastvisit", new { id="lastvisit"});
Update item
</div>
}
you see it can be done by javascript , but i was mistaken to think that you can manipulate it via Razor on the server ..
I know this post is old, but i just started learning MVC thanks to the asp.net/mvc/ website and i faced a similar problem during the tutorial. My Index action expects 2 parameters which define sorting and filtering (through the macthing of a substring) of a set of record displayed in the view. My problem is that i can't sort a filtered subset, since the view is called but no parameter for filtering is passed once i activate the sorting clicking on the link of the header.
#* Index.cshtml *#
#using (Html.BeginForm())
{
<p>
Find by name: #Html.TextBox("SearchString")
<input type="submit" value="Search" />
</p>
}
. . .
<!-- header -->
<table><tr><th>
#Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm })
</th>
. . .
//controller.cs
public ActionResult Index(string sortOrder, string searchString){...}
I thought i needed to access the TextBox, but apparently i just need to use the provided ViewBag object as already seen in this example!
#* Index.cshtml *#
#using (Html.BeginForm())
{
<p>
Find by name: #Html.TextBox("SearchString")
<input type="submit" value="Search" />
</p>
}
. . .
<!-- header -->
<table><tr><th>
#Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm, searchString = ViewBag.SearchString })
</th>
. . .
//controller.cs
public ActionResult Index(string sortOrder, string searchString)
{
ViewBag.SearchString = searchString;
. . .
}
Maybe a similar behaviour could have been used for solving the problem that originated this post, i don't know.
I have a datatable, on that datatable i set a Html.ActionLink. When I click that action link, I want to send an id of the item to a javascript function and have a new datatable appear below with all of its content that belongs to the selected item in the datatable above. So for example if I click a students name in a table, I want all the students Grades and Test to appear below in a separate datatable. I've never worked with javascript much so I'm not sure how I can do this. If someone can please point me in the right direction or give some tips I'd appreciate it.
original first datatable:
#foreach (var item in ((List<Epic>) ViewData["selectedestimate"]))
{
<tr>
<td>
#* #Html.ActionLink(#item.Name, "action", "controller", new {id = item})*#
#item.Name
</td>
Javascript to call:
<script type="text/javascript">
function StoryClick(story) {
$.get("#Url.Action("action", "controller")", function (response) {
$('#stories').accordion({ collapsible: true });
});
}
</script>
ActionController:
public List<EpicDetails> getEpicDetails(int id)
{
return eRepository.getItemsById(id).tolist();
}
Or do I need an ActionResult?
public Actionresult Details(int id)
{
}
I realize that I'm not even close right now, but its just b/c I'm not sure what steps to take to do this.
Eventually I would make a accordion and put the table in the accordion.
In situations like this I like to actually keep the <a> the ActionLink generates, and just add JavaScript to enhance the behavior of the link. So your view wouldn't really change (I did add a class so that we can bind an event handler to it later):
#Html.ActionLink(#item.Name, "action", "controller", new {id = item, #class = "item-link" })
Then write some jQuery (it looks like you already have a dependency on jQuery. If not, I can revise the answer to use vanilla JavaScript) to bind an event handler to links with class item-link:
<script type="text/javascript">
$(document).ready(function () {
$("a.item-link").click(function (event) {
event.preventDefault(); // Stop the browser from redirecting as it normally would
$.get(this.href, function (response) {
// Do whatever you want with the data.
});
});
});
</script>
And, yes, your action method in the controller should return an ActionResult. It's hard for me to say what type of ActionResult you should return without actually knowing what type of data you want to consume on the client, but if you wanted to inject HTML onto the page, you could write something like this:
public ActionResult Details(int id)
{
var itemDetails = /* Get details about the item */;
return PartialView("Details", itemDetails);
}
Then in your JavaScript you would write:
$("a.item-link").click(function (event) {
event.preventDefault(); // Stop the browser from redirecting as it normally would
$.get(this.href, function (response) {
$("element_to_populate").html(response);
});
});
Where element_to_populate would be a selector that points to where you want to inject the HTML.
I would highly recommend using javascript templating (I prefer handlebars.js) on the client side and returning your student data as a JsonResult. This will keep your bandwidth usage to a minimum.
But, because you seem more comfortable with razor, you could use that for all your templates, return plain html from your controller/view, and then use this javascript instead
<script type="text/javascript">
$(function() {
$("a.item-link").click(function (event) {
event.preventDefault(); // Stop the browser from redirecting as it normally would
$("#gradesContainer").load(this.href, function (response) {
//Do whatever you want, but load will already have filled up
//#gradesContainer with the html returned from your grades view
});
});
});
</script>
In your main page, below the student list, you would just need to add
<div id="gradesContainer"></div>
Your other controller would look like this
public ActionResult TestGrades(int id) {
var model = getTestGradesModel(id);
return View(model);
}
If you were returning JSON for client-side javascript templating it would look like
public ActionResult TestGrades(int id) {
var model = getTestGradesModel(id);
return new JsonResult() {Data = model}; //no view here!
}
Is there a way to add a Html.ActionLink through javascript?
For instance, I have this Edit function in my controller:
public ViewResult Edit(int companyID)
{
....
}
And I'd like to do something like this in javascript:
var id = $("#hdnID").val();
$("#editLink").html(<%: Html.ActionLink("Edit", "Edit", new { id }) %>);
A bit of a crude example, but it's basically what I'd like to do. Is it at all possible?
The id is a client script. You cannot mix server side script with client script. I am afraid that you are trying to submit HTML forms with action links instead of using submit buttons which is very bad. I see that you fetch the value of an input field with $("#hdnID").val() and then try to assign it to some action link and send to the server whereas if you used a simple submit button you wouldn't even need javascript. Your code would simply be:
<% using (Html.BeginForm("Edit", "Home")) { %>
<%: Html.HiddenFor(x => x.HdnId) %>
<input type="submit" value="Edit" />
<% } %>
Also it is clear that if you are using a hidden field it's because the user cannot change the value so an even simpler solution would be to directly generate the link you need:
<%: Html.ActionLink("Edit", "Edit", new { id = Model.SomeId }) %>
I haven't found a really good way yet. What I usually do is something like this:
var id = $("#hdnID").val();
var link = '<%: Html.ActionLink("Edit", "Edit", new { id = -999 }) %>';
$("#editLink").html(link.replace('-999', id));
The key is to select a value that id would never have in reality or exist otherwise in the link.
I found a handy way out of this problem thinking slighly out of the box. The reason I use ActionLink is really for an easy way to handle the routing. Simply supply Controller and action name and the helper generates the correct url. To get around this in JavaScript I first created an HtmlHelperExtender using the UrlHelper to resolve the url in proper context.
namespace System.Web.Mvc.Html
{
public static class HtmlHelperExtensions
{
public static string ResolveUrl(this HtmlHelper html, string url)
{
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
return urlHelper.Content(url);
}
}
}
Now in JavaScript it's easy enough to get the proper Url
$(document).ready(function () {
var action = '<%= Html.ResolveUrl("~/Controller/JsonAction") %>';
// JSON controller call for model data
$.getJSON(action, null, function (models) {
// Do something with models
action = '<%= Html.ResolveUrl("~/Controller/Details/") %>';
for (var i = 0; i < models.length; i++) {
$('#modelList').append(
'<tr><td>' + models[i].Title + '</td></tr>');
}
});
});
This is how I did it. You can use javascript replace.
var ul = document.createElement('ul');
if (data.EvidenceList != null) {
for (var i = 0; i < data.EvidenceList.length; i++) {
var li = document.createElement("li");
var evidenceId = data.EvidenceList[i].EvidenceId;
var evidenceName = data.EvidenceList[i].Name;
var candidateProgrammeComponentId = data.CandidateProgrammeComponentId;
var str1 = '#Html.ActionLink("dummyEvidenceText", "DownloadFile", new { candidateProgrammeComponentId = "dummyCandidateProgrammeComponentId", evidenceId = "dummyEvidenceId", evidenceName = "dummyEvidenceName" })';
var str2 = str1.replace('dummyEvidenceName', evidenceName);
var str3 = str2.replace('dummyCandidateProgrammeComponentId', candidateProgrammeComponentId);
var str4 = str3.replace('dummyEvidenceId', evidenceId);
var str5 = str4.replace('dummyEvidenceText', evidenceName);
li.innerHTML = li.innerHTML +str5 ;
ul.appendChild(li);
}
}
var element = document.getElementById('evidenceList_' + data.guidId);
$('#evidenceList_' + data.guidId).empty();
document.getElementById('fileUploadFreeStructure_' + data.guidId).value = '';
$('#freeTextArea_' + data.guidId).val('');
element.appendChild(ul);
The server side code (the C#) is ran on the server, and the result is sent to the client, where the client then executes the JavaScript. So as weird as it is, you have two different code environments bumping into each other but can't interact with each other very well.
I usually do something like this, although I'm open to better ways:
function SetUrl(id) {
var url = '<%: Html.ActionLink("Bar", "Foo") %>' + '?id=' + id;
return url;
}
This takes advantage of the fact that
/Foo/Bar/{id} is usually equivalent to /Foo/Bar?id={id}, depending on how your routes are set up.