Good day, i have dynamically created form elements with jquery that looks like this:
<label for="fname" class="fname_label col-sm-2 control-label">First Name</label>
<div class="col-sm-4">
<input type="text" class="fname_input form-control" id="fname" placeholder="First Name" name="firstnames">
</div>
<label for="uemail" class="uemail_label col-sm-2 control-label">Email</label>
<div class="col-sm-4">
<input type="text" class="uemail_input form-control" id="uemail" placeholder="Email" name="emailaddresses">
A user can create multiple elements like this with same name and ids. I mean if a user should click on add more, the same elements are created with the same name and id with jquery. A user can create multiple elements say 10 or more. My question is how can i post or insert the values of the dynamically create elements to the database. i am using c# in MVC. Thank You.
Had a bit of time and put this together. I created a JavaScript namespace to hold my functions, data etc; kept the jQuery part separate for the event (submit and add rows) management. You could easily add capability to delete new entry groups (row) as well, just need ONE to stay as I used .clone() to get that new row.
Sample markup using some bootstrap stuff (not required for the functional part). Note I used jQuery for the ajax stuff, you would not have to but it made the sample a bit smaller perhaps.
<div class="container">
<form id="myform">
<div class="inputs-holder">
<fieldset class="entry-group">
<legend class="col-form-legend col-xm-2">
one input
</legend>
<div class="form-group row">
<label class="col-xs-2 col-form-label col-form-label-sm">Name</label>
<div class="col-xs-7">
<input required="true" class="form-control form-control-xs name-field" type="text" />
</div>
</div>
<div class="form-group row">
<label class="col-xs-2 col-form-label col-form-label-sm">Email</label>
<div class="col-xs-7">
<input required="true" class="form-control form-control-xs email-field" type="email" placeholder="enter email" value="testme#example.com" />
</div>
</div>
</fieldset>
</div>
<div class="form-group row">
<div class="offset-xs-2 col-xs-5">
<button id="submitme" type="submit" class="btn btn-primary btn-xs">Submit Me</button>
</div>
<div class="offset-xs-2 col-xs-5">
<button id="addnewgroup" type="button" class="btn btn-xs">Add new group</button>
</div>
</div>
</form>
</div>
<div id="results">
Entries
</div>
Some script to process and push data via ajax to server:
/* Latest compiled and minified JavaScript included as External Resource */
var myApp = myApp || {};
myApp.arrayObj = {
// some stuff clipped out not used here...
// use to lookup duplicates
lookup: function(myArray, searchTerm, property, firstOnly) {
var found = [];
var i = myArray.length;
while (i--) {
if (myArray[i][property] === searchTerm) {
found.push(myArray[i]);
if (firstOnly) break; //if only the first
}
}
return found;
},
// could be used to validate duplicates for example
lookupAll: function(myArray, property, searchTerm) {
return this.lookup(myArray, searchTerm, property, false);
}
};
myApp.data = {
entries: [],
saveUrl: "/Home/SaveEmails" this COULD be from server/MVC
};
myApp.func = {
addEmailRow: function(myArray, item, allowdups, uniquekey) {
// matches the POCO object class names
var entry = {
"name": item.name,
"email": item.email
};
if (allowdups || (!allowdups && !myApp.arrayObj.lookup(myArray, entry[uniquekey], uniquekey, true).length)) {
myArray.push(entry);
} else if (allowdups && myApp.arrayObj.lookup(myArray, entry[uniquekey], uniquekey, true).length) {
myApp.data.entries[uniquekey] = item[uniquekey];
} else if (allowdups && !myApp.arrayObj.lookup(myArray, entry[uniquekey], uniquekey, true).length) {
myArray.push(entry);
}
},
// just something to show what is entered/being submitted
showEntries: function(list) {
$.each(list, function(index, value) {
$('#results').append("<div>" + value.name + " " + value.email + "</div>");
});
},
// the important part
saveEntries: function(list) {
var entries = JSON.stringify({
'Entries': list
});
$.ajax({
contentType: 'application/json;',
dataType: 'json',
type: 'POST',
url: myApp.data.saveUrl,
data: entries
}).done(function() {
$('#results').html('"SaveEmails()" successfully called.');
})
.fail(function(response) {
$('#results').html(response);
});
}
};
$(document).ready(function() {
// add new "group" row
$('#addnewgroup').on('click', function() {
var holder = $('.inputs-holder');
// clone that first row
var newRow = holder.find('.entry-group').eq(0).clone();
// clear any values entered and append it
newRow.find('input').val("");
newRow.appendTo(holder);
});
// a bit verbose for clarity here
$('#myform').on('submit', function(e) {
e.preventDefault();
e.stopPropagation();
// clear entries
myApp.data.entries = [];
var allowdups = false,
uniquekey = "name";
var holder = $('.inputs-holder');
// get entries
holder.find('.entry-group').each(function() {
var email = $(this).find('.email-field').val();
var name = $(this).find('.name-field').val();
var item = {
"email": email,
"name": name
};
myApp.func.addEmailRow(myApp.data.entries, item, allowdups, uniquekey);
});
$('#results').html("<div>Results:</div>");
myApp.func.showEntries(myApp.data.entries);
myApp.func.saveEntries(myApp.data.entries);
// supress default submit form
return false;
});
});
Now the server side:
/* MVC for this: */
// POCO object: - reference this whereever you put it.
public class EmailEntry
{
public String name { get; set; }
public String email { get; set; }
}
// controller/ method: used IEnumerable instead of List as simpler
public class HomeController : Controller
{
[HttpPost]
public void SaveEmails(IEnumerable<EmailEntry> entries)
{
// do stuff with email entries here...validate/save for example
}
}
ALL this is untested and my contain small errors but I believe it to be pretty bug free.
Related
I'm currently building and application in ASP.NET Core MVC and I have ran into a problem which I cannot solve.
I have a form for something and that form should contain multiple identical fields which are added dynamically (1-10). I have managed to do that by creating a ViewComponent which contains those form fields and I make an Ajax call to invoke the view component into a tab if a user chooses to add another segment of those fields.
function CallViewComponent(num_tabs) {
var data = { id: num_tabs };
$.ajax({
type: 'POST',
url: '/Create/CreateActivityForm',
cache: false,
data: data
}).done(function (result) {
var container = "#activity-" + num_tabs;
$(container).html(result);
});
}
The problem arises because each of those fields in that view component shares a name with the other fields so each time I invoke another view component the radio buttons are shared between all identical fields.
Here is a snippet of the ViewComponent:
#model CreateActivityViewModel
<div class="activity-tab">
<div class="form-group">
<label asp-for="La.OrdinalNumber">Redni broj aktivnosti</label><br />
<select asp-for="La.OrdinalNumber" class="ordinals" style="width:50%">
#foreach (var on in Model.OrdinalNumbers)
{
<option value="#on.Value">#on.Text</option>
}
</select>
</div>
<div class="form-group">
<label asp-for="La.Latype">Tip aktivnosti</label><br />
<select asp-for="La.Latype" class="activity-type" style="width:50%">
#foreach (var lt in Model.LaTypes)
{
<option value="#lt">#lt.LatypeName</option>
}
</select>
</div>
<div class="form-group">
<label asp-for="La.Laname">Naziv aktivnosti</label>
<input asp-for="La.Laname" type="text" name="La.Laname" placeholder="Unesite naziv aktivnosti" class="f1-activity-name form-control" id="f1-activity-name">
</div>
Here is my controller which returns the ViewComponent:
[HttpPost]
public IActionResult CreateActivityForm(int id)
{
return ViewComponent("ActivityTab", id);
}
Here is the Invoke method from the ViewComponent:
public IViewComponentResult Invoke(int id)
{
var latypes = _laTypeRepository.GetAllLaType.ToList();
var ordinals = new List<SelectListItem>();
var laperformances = _laPerformanceRepository.GetAllLaPerformance.ToList();
var teachingAids = _teachingAidRepository.GetAllTeachingAid.ToList();
var strategyMethods = _strategyMethodRepository.GetAllStrategyMethod.ToList();
var laCollaboration = _laCollaborationRepository.GetAllLaCollaboration.ToList();
for (int i = 1; i <= 100; i++)
{
ordinals.Add(new SelectListItem($"{ i }. aktivnost", i.ToString()));
}
return View( new CreateActivityViewModel
{
FormId = id,
LaTypes = latypes,
OrdinalNumbers = ordinals,
LaPerformances = laperformances,
StrategyMethods = strategyMethods,
Lacollaborations = laCollaboration,
TeachingAids = teachingAids,
TeachingAidUser = new List<TeachingAid>(),
TeachingAidStudent = new List<TeachingAid>()
});
}
And finally this is where the ViewComponent gets invoked. It is inside another form because I need to submit the main form and all the ViewComponents at once:
<fieldset>
<h4>Aktivnosti</h4>
<!-- Activity Tabs -->
<div id='activity-tabs'>
<!-- Activity Links -->
<ol id="#activity-links">
<li><a href='#activity-1'>#1</a></li>
<li id="add-activity"><button type="button" id='add-activity'><i class="fa fa-plus"></i></button></li>
</ol>
<!-- Activity Content -->
<div id='activity-1'>
<h3>Aktivnost #1</h3>
#await Component.InvokeAsync("ActivityTab")
</div>
</div>
<!-- Navigation Buttons -->
<div class="f1-buttons">
<button type="button" class="btn btn-previous">Prethodna</button>
<button type="submit" class="btn btn-submit">Kreiraj scenarij</button>
</div>
</fieldset>
My question is how do I separate those identical forms and be able to submit them and store every single one of those forms into an array of objects which I can then store into a database.
I am open to all ideas and will change the entire code if necessary.
Thank you!
If you have an array of objects you need to render the components using a FOR loop rather than a FOR-EACH. I like to push common code into a shared view but you can code direct in the view. You will need to set your asp-for attributes in order to bind values to the model
#for (int index = 0; index < Model.Resources.Count; index++)
{
<partial name="_ResourceHidden" for="#Model.Resources[index]" />
Direct render
#for (int index = 0; index < Model.Resources.Count; index++)
{
<tr>
<td>
#Model.Resources[index].ResourceName
</td>
I have been following the answers on here but can't seem to get it to work. I think it's firing my function and calling my controller but it isn't rendering my partial view. Any help would be awesome.
Controller
public ActionResult Detail(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
User_Accounts user_accounts = db.User_Accounts.Find(id);
if (user_accounts == null)
{
return HttpNotFound();
}
return PartialView("_Detail", user_accounts);
}
HTML
<h2>Index</h2>
<div class="container left">
<div class="panel-default panelbox" style="position:static">
#*<p>
#Html.ActionLink("Create New", "Create")*#
#using (Html.BeginForm("Index", "Users", FormMethod.Get))
{
<p>
Type: #Html.DropDownList("userType", "All")
</p>
<p>
Last Name: #Html.TextBox("SearchString")
</p>
}
</div>
<div class="panel panel-default left">
<div class="panel-heading">
<label style="text-align:center">
User
</label>
</div>
<div class="table-responsive">
<table id="UserTable" class="table-bordered table leftPanel table-condensed">
#foreach (var item in Model)
{
<tr>
<td>
<button data-url='#Html.Action("Detail", "Users", new { id = item.user_id_IN })' id="js-reload-details">#Html.DisplayFor(modelItem => item.DisplayName)</button>
#*#Html.ActionLink(item.DisplayName, "Detail", new { id = item.user_id_IN }, new { onclick = "renderPartial();" })*#
</td>
</tr>
}
</table>
</div>
</div>
</div>
<div>
<label>Details</label>
<div id="detailsDiv"></div>
</div>
Script
<script>
$('.js-reload-details').click(function (evt) {
var $detailDiv = $('#detailsDiv'),
url = $(this).data('url');
$.get(url, function (data) {
$detailsDiv.replaceWith(data);
});
});
</script>
Let me know if you need anything else.
You cant use data-url='#Html.Action("Detail", "Users", new { id = item.user_id_IN })' in your button to generate a url. #Html.Action() is a method which calls you controller. What would be happening is that for each item in your model you would be hitting the Detail method of UsersController (performance must of been awful if you had a lot of items :) ).
Since you appear to need only the one url (/Users/Detail) I suggest you just store the ID in data to minimize the html generated. As noted in the other answers you also need to use a class name for the button to prevent invalid html, and I also suggest using type="button" because the default (depending on the browser) may be "submit" (you don't have a form so does not matter in this case, but its good practice to get into). There is also no real need to use #Html.DisplayFor() unless your using a custom DisplayTemplate or have a [DisplayFormat] attribute on the property.
Change the html to
<button type="button" data-id="#item.user_id_IN" class="js-reload-details">#item.DisplayName</button>
and the script to
var url = '#Url.Action("Detail", "Users");
$('.js-reload-details').click(function() {
$.get(url, { id: $(this).data('id') }, function (data) {
$('#detailsDiv').html(data);
});
});
Note you do not want to use replaceWith() in your case. .replaceWith() would replace the actual div <div id="detailsDiv"></div> with the html your method returned, so the next time a user clicked on this or any other button, the method would be called, but <div id="detailsDiv"></div> no longer exists and nothing would happen.
$('#detailsDiv').html('Hello world');
renders
<div id="detailsDiv">Hello world</div>
but
$('#detailsDiv').replaceWith('Hello world');
renders
Hello world
The id of your button id="js-reload-details"
Mistake this code is repeated in a foreach loop. which will cause multiple id's of the same name on your HTML page.
Your click event is on : '.js-reload-details'. which is a class:
so make your code like this:
#foreach (var item in Model)
{
<tr>
<td>
<button data-url='#Html.Action("Detail", "Users", new { id = item.user_id_IN })' class="js-reload-details">
#Html.DisplayFor(modelItem => item.DisplayName)
</button>
</td>
</tr>
}
One error I noticed in your jQuery is that you have $detailsDiv.replaceWith(data);
It should be $detailDiv according to your code: var detailDiv = $('#detailsDiv'); instead of $detailsDiv
<script>
$(document).ready(function(){
$('.js-reload-details').click(function (evt) {
evt.stopPropagation();
var detailDiv = $('#detailsDiv');
// TRY using the attr function:
var url = $(this).attr("data-url");
$.get(url, function (data) {
detailDiv.html(data);
});
});
});
</script>
UPDATE:
<script>
$(document).ready(function(){
$('.js-reload-details').click(function (evt) {
evt.stopPropagation();
var detailDiv = $('#detailsDiv');
// TRY using the attr function:
var url = $(this).attr("data-url");
$.get(url).success(function(result) {
detailDiv.html(result);
});
});
</script>
It's a good practice we use unique id's for our HTML elements. Since the following statement is going to be executed mulitple times
<button data-url='#Html.Action("Detail", "Users", new { id = item.user_id_IN })' id="js-reload-details">#Html.DisplayFor(modelItem => item.DisplayName)</button>
You will have multiple buttons with the same id. Instead of doing so, you could use a class.
<button data-url='#Html.Action("Detail", "Users", new { id = item.user_id_IN })' #class="js-reload-details">#Html.DisplayFor(modelItem => item.DisplayName)</button>
Then you have to correct your script:
// Now we bound the click event in all the elements that contain
// the .js-reload-details class
$('.js-reload-details').click(function (evt) {
var $detailDiv = $('#detailsDiv');
// Here was your the error
var url = $(this).attr("data-url");
$.get(url, function (data) {
$detailsDiv.replaceWith(data);
});
});
I have a list of Guitar objects in my view model.
public List<Guitar> Guitars { get; set; }
The user is able to create these by clicking a button (Thanks to JQuery clone()). I noticed if they remove the 1st list item ([0]) The model returns a null list or if they remove something in the middle of the list like [1], the model only returns item [0] in the list.
I see in the raw request that all of the items exist so I guess I have 2 choices - Maybe someone has a different approach?
1. Operate on the raw Request array in the controller like this:
[HttpPost]
public ActionResult Index(CustomerViewModel customer)
{
var guitars = new List<Guitar>();
var listValues = new List<string>();
var numGuitars = 0;
//Loop through all Request keys in the POST
foreach (string key in Request.Form.AllKeys)
{
//Save any that are part of the Guitar object
if (key.StartsWith("Guitars["))
{
listValues.Add(key);
}
}
//Guitar object has 3 properties so divide by 3 to get total object count
numGuitars = (int)Math.Ceiling(listValues.Count / 3.0);
for (int i = 0; i < numGuitars; i++)
{
var guitarMake = Request["Guitars[" + i + "].Make"];
var guitarModel = Request["Guitars[" + i + "].Model"];
var guitarProductonYear = Request["Guitars[" + i + "].ProductionYear"];
if (!String.IsNullOrEmpty(guitarMake) &&
!String.IsNullOrEmpty(guitarModel) &&
!String.IsNullOrEmpty(guitarProductonYear))
{
var g = new Guitar
{
Make = guitarMake,
Model = guitarModel,
ProductionYear = Int32.Parse(guitarProductonYear)
};
guitars.Add(g);
}
}
2. When a user deletes an item, use JQuery to reassign list indices so we are sequential.
3. Anything else?
Form HTML
<div id="guitars_1" style="display: block;">
<input type="text" value="" name="Guitars[0].Make" id="Guitars_0__Make" placeholder="Make">
<input type="text" value="" name="Guitars[0].Model" id="Guitars_0__Model" placeholder="Model">
</div>
<div id="guitars_2" style="display: block;">
<input type="text" value="" name="Guitars[1].Make" id="Guitars_1__Make" placeholder="Make">
<input type="text" value="" name="Guitars[1].Model" id="Guitars_1__Model" placeholder="Model">
</div>
<div id="guitars_3" style="display: block;">
<input type="text" value="" name="Guitars[2].Make" id="Guitars_2__Make" placeholder="Make">
<input type="text" value="" name="Guitars[2].Model" id="Guitars_2__Model" placeholder="Model">
</div>
<!-- Start Add Guitar Row Template -->
<div style="display:none">
<div id="guitarsTemplate">
<div class="formColumn1"><label>Guitar</label></div>
<div class="formColumn2">#Html.TextBoxFor(model => model.Guitars[0].Make, new { Placeholder = "Make" })
<div class="messageBottom">
#Html.ValidationMessageFor(model => model.Guitars[0].Make)
</div>
</div>
<div class="formColumn3">#Html.TextBoxFor(model => model.Guitars[0].Model, new { Placeholder = "Model" })
<div class="messageBottom">
#Html.ValidationMessageFor(model => model.Guitars[0].Model)
</div>
</div>
<div class="formColumn4">#Html.TextBoxFor(model => model.Guitars[0].ProductionYear, new { Placeholder = "Production Year" })
<div class="messageBottom">
#Html.ValidationMessageFor(model => model.Guitars[0].ProductionYear)
</div><a class="icon delete">Delete</a>
</div>
</div>
</div>
<!-- End Add Guitar Row Template -->
JS that clones and deletes items
$(document).ready(function() {
var uniqueId = 1;
var ctr = 0;
$(function() {
$('.js-add-guitar-hyperlink').click(function() {
var copy = $("#guitarssTemplate").clone(true).appendTo("#addGuitarSection").hide().fadeIn('slow');
var guitarDivId = 'guitars_' + uniqueId;
var copyText = copy.html();
copyText = copyText.replace(/Guitars\[0\]/g, 'Guitars[' + ctr + ']');
copyText = copyText.replace('Guitars_0', 'Guitars_' + ctr);
copy.html(copyText);
$('#guitarsTemplate').attr('id', guitarDivId);
var deleteLink = copy.find("a.icon.delete");
deleteLink.on('click', function() {
copy.fadeOut(300, function() { $(this).remove(); }); //fade out the removal
});
$('#' + cosponsorDivId).find('input').each(function() {
//$(this).attr('id', $(this).attr('id') + '_' + uniqueId);
// $(this).attr('name', $(this).attr('name') + '_' + uniqueId);
});
uniqueId++;
ctr++;
});
});
});
For this kind of dynamic list management in MVC, you could do worse than take a look at the BeginCollectionItem HtmlHelper:
https://www.nuget.org/packages/BeginCollectionItem/
https://github.com/danludwig/BeginCollectionItem
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
I am very new to both JQuery and Asp.net MVC 3 (C#), so I apologize if this is trivial. I have an MVC partial view (Index.cshtml) that has a list of tasks. These tasks are contained within indivudal divs that I have in a list style layout. I have a button called "add task" that opens a dialog. This dialog will save the added task to the database via an AJAX Json call to the controller.
This is where I am having trouble - after the dialog closes I would like the list of tasks to reload with the task i just added. I have found examples where the entire page is reloaded, and I found examples where the controller is supposed to return a rendered view. My problem is that the dialog is being opened from the partial I want to reload. Is there a way to accomplish what I am trying to do.
Index.cshtml
#model IEnumerable<TaskManagementApplication.Models.Project>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div id="ProjectAccordionWrapper">
#foreach (var item in Model)
{
<div class="ProjectWrapper">
<h3>#item.Name</h3>
<div class="column">
<button class="createTaskButton" id="#item.ProjectID">Create New Task</button>
#foreach(var task in item.Tasks) {
var buttonClass = "taskID" + task.TaskID;
<div class="portlet">
<div class="portlet-header">#task.TaskName</div>
<div class="portlet-content">#task.TaskDescription</div>
<button class="editTaskButton" id="#task.TaskID">Edit Task</button>
</div>
}
</div>
</div>
}
</div>
<div id="dialog-form" title="Create new user">
<p class="validateTips">All form fields are required.</p>
<form>
<fieldset>
<label for="TaskName">Task Name</label>
<input type="text" name="TaskName" id="name" class="text ui-widget-content ui-corner-all" />
<label for="TaskDescription">Task Description</label>
<input type="text" name="TaskDescription" id="description" value="" class="text ui-widget-content ui-corner-all" />
<input type="hidden" name="TaskID" id="ID" />
<input type="hidden" name="ProjectID" id="ProjectID" />
</fieldset>
</form>
</div>
Partial Javascript
function GetTask(id) {
if (id.length > 0) {
$.ajax({
url: '/Project/GetTaskFromID',
type: "POST",
data: { "id": id },
success: PopulateDialogFields,
error: HandleError
});
}
}
function PopulateDialogFields(data) {
$("#name").val(data.TaskName);
$("#description").val(data.TaskDescription);
$("#ID").val(data.TaskID);
}
function HandleError(data) {
alert(data.error);
var foo = data;
}
function SaveTask() {
var taskName = $("#name").val();
var taskDescription = $("#description").val();
var id = $("#ID").val();
var projectID = $("#ProjectID").val();
if (id.length > 0) {
$.ajax({
url: '/Project/SaveTask',
type: "POST",
data: { "taskName": taskName, "taskDescription": taskDescription, "taskID": id }
});
}
else {
$.ajax({
url: '/Project/SaveTask',
type: "POST",
data: { "taskName": taskName, "taskDescription": taskDescription, "projectID": projectID }
});
}
}
$("#dialog-form").dialog({
autoOpen: false,
height: 300,
width: 350,
modal: true,
buttons: {
"OK": function () {
SaveTask();
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
},
close: function () {
allFields.val("").removeClass("ui-state-error");
window.location.reload(true);
},
open: function () {
var id = $(this).data("id");
var projectID = $(this).data("projectID");
$("#ProjectID").val(projectID);
var button = $("#" + id);
GetTask(id);
}
});
$(".editTaskButton")
.button()
.click(function () {
$("#dialog-form").data('id', this.id).dialog("open");
});
$(".createTaskButton")
.button()
.click(function () {
$("#dialog-form").data('projectID', this.id).dialog("open");
});
I am relatively new to jQuery and ASP.NET MVC as well, however, here's what first comes to mind.
In order to maintain the AJAX-y aspect of the page, I suggest that you create a method that handles a POST which returns a JSON formatted set of TaskManagementApplication.Models.Project. This method can optionally return filtered results.
The markup would look like this,
<div id="ProjectAccordionWrapper">
<div id="ProjectWrapperTemplate" class="ProjectWrapper" style="display: none;">
<h3 id="itemName"></h3>
<div class="column">
<button class="createTaskButton" id="itemProjectID">Create New Task</button>
<div id="portletTemplate" class="portlet">
<div class="portlet-header" id="taskName"></div>
<div class="portlet-content" id="taskDescription"></div>
<button class="editTaskButton" id="taskID">Edit Task</button>
</div>
</div>
</div>
</div>
Next, you would have jQuery clone the ProjectWrapperTemplate element, and set all of the corresponding fields.
$(function () {
$.ajax({
url: '/Project/GetTasks',
type: "POST",
data: { }
}).done(function (data) {
data.forEach(function (element) {
AppendProjectWrapper(element);
});
});
function AppendProjectWrapper(data) {
var projectAccordionWrapper = $('#ProjectAccordionWrapper');
var projectWrapper = $('#ProjectWrapperTemplate').clone(true, true);
projectWrapper.id = nothing; // remove the id, so as to not have duplicates
projectWrapper.style.display = nothing; // remove the style "display: none"
var itemName = projectWrapper.children('#itemName'); // h3
itemName.id = nothing;
itemName.text(data.ItemName);
var itemProjectID = projectWrapper.children('#itemProjectID'); // button Create New Task
itemProjectID.id = data.ItemProjectID;
var portletTemplate = projectWrapper.children('#portletTemplate'); // div
data.Tasks.forEach(function (element) {
var portlet = portletTemplate.clone();
portlet.id = nothing;
var taskName = portlet.children('#taskName');
taskName.id = nothing;
taskName.text(element.TaskName);
var taskDescription = portlet.children('#taskDescription');
taskDescription.id = nothing;
taskDescription.text(element.TaskDescription);
var editTaskButton = portlet.children('#taskID');
editTaskButton.id = element.TaskID;
portlet.appendTo(projectWrapper);
});
portletTemplate.remove(); // remove the portlet template element
projectWrapper.appendTo(projectAccordionWrapper);
}
}
Finally, have '/Project/SaveTask' return a JSON formatted TaskManagementApplication.Models.Project of the currently saved task.
$.ajax({
url: '/Project/SaveTask',
type: "POST",
data: { "taskName": taskName, "taskDescription": taskDescription, "taskID": id }
}).done(function (data) {
AppendProjectWrapper(data);
});
The return data for '/Project/GetTasks' should look as follows:
[
{
ItemName: '#item.Name',
ItemProjectID: '#item.ProjectID',
Tasks: [
TaskName: '#task.TaskName',
TaskDescription: '#task.TaskDescription',
TaskID: '#task.TaskID'
]
}
]
The return data from '/Project/SaveTask' should follow the same format, except or the outer-most array.
Please note that a lot of this code is untested.
It may be easiest to refactor the list into another action+view. Then, you can call this in both the original Index.cshtml view, and via the .load() method in jQuery. So, assuming this:
Projects controller
[HttpGet]
[ChildActionOnly]
public ActionResult Tasks(int id)
{
// create the appropriate model object as an IEnumerable of your Task type
return View(model);
}
Tasks.cshtml
#foreach(var task in Model) {
var buttonClass = "taskID" + task.TaskID;
<div class="portlet">
<div class="portlet-header">#task.TaskName</div>
<div class="portlet-content">#task.TaskDescription</div>
<button class="editTaskButton" id="#task.TaskID">Edit Task</button>
</div>
}
You would adjust Index.cshtml like so:
#model IEnumerable<TaskManagementApplication.Models.Project>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div id="ProjectAccordionWrapper">
#foreach (var item in Model)
{
<div class="ProjectWrapper">
<h3>#item.Name</h3>
<div class="column">
<button class="createTaskButton" id="#item.ProjectID">Create New Task</button>
<div id="tasks-#item.ProjectID">
#Html.Action("Tasks", "Project", new { id = item.ProjectID })
</div>
</div>
</div>
}
</div>
//... the rest of the view
And finally,
// this should happen inside the callback of your .ajax() method
$('#tasks-'+projectID).load('/project/tasks/'+ projectID);
I've seen a couple of similar questions here but I couldn't fix this problem. It seems not be a bid deal because eventually it works, but trying to display a hidden div (imitating a popup) using Ajax when clicking a button if I debug I can see the controller in the method (ConfirmMaxBid) is called twice (even more I feel sometimes even more times, and mixing the flow through the code, i.e going back and forth).
Here's the view
#model UI.Models.BidOnAuctionViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Content/js/BidAndMaxBidConfirmation.js")" type="text/javascript"></script>
#{
var disableButton = Model.AuctionOpen ? "" : "bid-btn-disabled";
}
#using (Html.BeginForm("ConfirmMaxBid", "Lot"))
{
<fieldset>
<div class="lotdetail-maxbid-row">
<label>Your Max Bid</label>
#*<input id="Amount" class="radius2 text-box single-line valid" type="text" name="Amount" data-val-required="The Amount field is required." data-val-number="The field Amount must be a number." data-val="true">*#
#if (Model.AuctionOpen)
{
#Html.TextBoxFor(model => model.Amount, new {#class = "radius2 text-box single-line valid"})
}
else
{
#Html.TextBoxFor(model => model.Amount, new {#class = "radius2 text-box single-line valid", #disabled = "disabled"})
}
</div>
<input type="submit" id="autobidButton" value="Increase Max bid" class="btn btn-level2 btn-maxbid #disableButton" />
</fieldset>
<div id="autoBidConfirmation" class="lotdetail-modal" style="display: none;">
#Html.HiddenFor(model => model.AuctionId)
#Html.HiddenFor(model => model.AutobidAmount)
#Html.HiddenFor(model => model.AuctionEventId)
<h4 id="confirmationMessage"></h4>
<span id="returnedCorrectedAmount" class="lotdetail-modal-msg"></span>
<input name="button" value="Place bid" type="submit" class="btn btn-level2" />
<input id="cancelAutoBid" name="button" value="Cancel" type="submit" class="btn btn-level2 btn-cancel" />
</div>
}
And here's the script's function called when document.ready
$('#autobidButton').click(function (e) {
e.preventDefault();
var model =
{
AuctionEventId: $('#AuctionEventId').val(),
AuctionId: $('#AuctionId').val(),
Amount: $('#Amount').val()
};
$.ajax({
url: "/Lot/ConfirmMaxBid",
dataType: 'json',
type: "POST",
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(model),
success: function (data) {
var amount = "$" + data.AutobidAmount;
$('#returnedCorrectedAmount').html(amount);
$('#AutobidAmount').val(data.AutobidAmount);
$('#confirmationMessage').text(data.AutobidConfirmationMessage);
$('#autoBidConfirmation').show();
}
});
});
And finally this is the method (the part is called twice at least, to avoid this question be too long)
public ActionResult ConfirmMaxBid(string button, BidOnAuctionViewModel model)
{
if(Request.IsAjaxRequest())
{
var confirmationMessage = "Confirm your bid";
var correctedAmount = model.Amount;
if (!CheckMaxBidFitsIncrementTier(model.Amount, model.AuctionEventId))
{
confirmationMessage = "This bid can't be placed, do you want to place the following instead?";
correctedAmount = CalculateNextBidAmountForWrongMaxAmount(model.Amount, model.AuctionEventId);
}
return Json(new
{
AutobidAmount = correctedAmount,
model.AuctionId,
model.AuctionEventId,
AutobidConfirmationMessage = confirmationMessage
});
}
if (button == "Place bid")
{
//Here's the code to do the stuff when confirming in the partial view
Any idea why this happens?
Thanks in advance
Update
After placing some alerts (quite primitive way of debugging, I guess) I see that the method is called again just after the last line of the success function (after $('#autoBidConfirmation').show();)
Your document is probably ready multiple times after asyncronous post-backs.
Try something like:
$(document).ready(OnDocumentReady);
function OnDocumentReady()
{
$("#autobidButton")
.unbind("click", AutoBidFunction)
.click(AutoBidFunction);
}
function AutoBidFunction(e)
{
// Code Here
}