Non-Sequential List in Model - c#

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/

Related

How to update List of string

I'm doing a course on PluralSight but the code in the course is not complete and there is a quickedit IActionResult that I can't seem to figure out.
In my View I have the code below:
#model IList<string>
#if (Model.Count > 0)
{
<form asp-action="QuickEdit" method="post">
#for (var i = 0; i < Model.Count; i++)
{
<div class="form-group">
<label>Soup name #(i + 1);</label>
<input id="soupNames" name="soupNames" asp-for="#Model[i]" class="form-control"/>
</div>
}
<button type="submit" class="btn btn-primary">Update</button>
</form>
}
else
{
<h2>No Soups in the system</h2>
}
The Controller:
public IActionResult QuickEdit()
{
var soupNames = _soupRepository.AllSoups.Select(s => s.SoupName).ToList();
return View(soupNames);
}
[HttpPost]
public IActionResult QuickEdit(List<string> soups)
{
var soupNames = _soupRepository.AllSoups.Select(s => s.SoupName).ToList();
for (var i = 0; i < soupNames.Count; i++)
{
soupNames[i] = soups[i];
}
return View(soups);
}
Could someone help me out?
I want the values passed through replace the original values when I click the update button.
this line
<input id="soupNames" name="soupNames" asp-for="#Model[i]" class="form-control"/>
need to change to something like
<input id="#(i)_soups" name="[#(i)].soups" asp-for="#Model[i]" class="form-control"/>
you need to google model binding lists
try , it may show you the raw syntax so you can change to just input
#Html.TextBoxFor(m => #Model[i] )

How to submit multiple identical forms with one button

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>

Populating a VueJS data property through a strongly bound List<T> DOM in a razor view

I am working on Asp.Net Core MVC Web application and using VueJs for front-end rendering/manipulation. Ultimately, I want to implement a Master-Detail in Razor View using VueJS where Detail Rows (Line Items) could be added/removed dynamically but for now, I am just trying to access a List<T> type Domain Object Model in VueJS Data and trying to add/remove rows of data dynamically.
Here is my code:
ViewModel
public class VueJsTestModelDetail
{
public int DetailId { get; set; }
public int Id { get; set; }
[EmailAddress]
public string Email { get; set; }
}
Controller
public IActionResult Vue()
{
VueJsTestModel viewModel = new VueJsTestModel();
viewModel.Id = 1;
viewModel.Name = "Saud Nasir";
viewModel.Designation = "Software Engineer";
for (int i = 0; i < 5; i++)
{
viewModel.VueJsTestModelDetails.Add(new VueJsTestModelDetail { Id = 1, DetailId = i + 1, Email = "xyz_" + (i + 1).ToString() + "#abc.com" });
}
return View(viewModel.VueJsTestModelDetails);
}
Razor View
#model List<Vue.js_Hello_World.Models.VueJsTestModelDetail>
<div id="app">
<div class="container border" style="border: thin;">
<div class="mt-2">
<form asp-action="Vue">
<div class="form-group" style="text-align:right; width:100%">
<input type="submit" value="Save" class="btn btn-primary btn-sm" />
<input v-on:click="View" type="button" value="View" class="btn btn-success btn-sm" />
</div>
<ul>
<li v-for="(detail, index) in DetailData">
<h4>{{detail.DetailID}}</h4>
</li>
</ul>
</form>
</div>
</div>
</div>
VueJS Script
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
DetailData: '#Model',
},
methods: {
View: function () {
alert(this.DetailData);
}
}
});
</script>
I want to populate DetailData with the values that I send from controller but through this code, DetailData is populated with strings that contains the name of the data type. (e.g. "List<Vue.js_Hello_World.Models.VueJSTestModelDetails>"). I want to store actual values in DetailData on page load, for example:
DetailData[0].DetailID = 1,
DetailData[0].Id = 0,
DetailData[0].Email = "xyz_1#abc.com"
What is happening is that outputting #Model is the equivalent of calling ToString() on the Model. Instead what you want to do is output the json serialised version of the object.
Replace the line
DetailData: '#Model',
with the line
DetailData: #Json.Serialize(Model),

How to post dynamic created elements to database MVC c#

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.

Traverse the dom with CsQuery

I'm trying to learn how to use CsQuery to traverse a dom to get specific text.
The html looks like this:
<div class="featured-rows">
<div class="row">
<div class="featured odd" data-genres-filter="MA0000002613">
<div class="album-cover">
<div class="artist">
Half apanese
</div>
<div class="title">
<div class="label"> Joyful Noise </div>
<div class="styles">
<div class="rating allmusic">
<div class="rating average">
<div class="headline-review">
</div>
<div class="featured even" data-genres-filter="MA0000002572, MA0000002613">
</div>
<div class="row">
<div class="row">
<div class="row">
My code attempt looks like this:
public void GetRows()
{
var artistName = string.Empty;
var html = GetHtml("http://www.allmusic.com/newreleases");
var rows = html.Select(".featured-rows");
foreach(var row in rows)
{
var odd = row.Cq().Find(".featured odd");
foreach(var artist in odd)
{
artistName = artist.Cq().Text();
}
}
}
The first select for .featured-row works but then i don't know how to get down to the .artist to get the text.
You should try something similar to this:
var html = GetHtml("http://www.allmusic.com/newreleases");
var query = CQ.Create(html)
var row = query[".artist>a"];
string link = row.Attributes["href"];
string text = row.DefaultValue or row.InnerText or row.Value...
CsQuery is port of JQuery so you can google for JQuery code
UPDATE:
To traverse to get all artists and titles
var rows = query[".featured odd"];
foreach(var row in rows)
{
var artistsLink = row[".artists>a"];
var title = row[".title"];
// here do whatever you need with this
}
List<string> artists = html[".featured .artist a"].Select(dom=>dom.TextContent).ToList();
where html == your CQ object.
var odd = row.Cq().Find(".featured odd");
should be
var odd = row.Cq().Find(".featured.odd");

Categories

Resources