Dynamic row add & delete in html table in Blazor web assembly - javascript

I am developing a blazor webassembly app where i have this feature to add or delete html row. Can we do it easily in Blazor? or we have to go for javascript? Thanks in advance
I am looking for some output like this or anything similar to my requirement. Any link to such solution also should be helpful. Just the example image:

Something like this?
<table style="width:100%">
<tr>
<th>Name</th>
<th>Value</th>
<th>Command</th>
</tr>
#foreach(var model in models)
{
<tr>
<td>#model.Name</td>
<td>#model.Value</td>
<td>
<button #onclick="() => models.Remove(model)">
X
</button>
</td>
</tr>
}
</table>
<button #onclick="#(() => models.Add(new Model(){Name = nameTextField, Value = Int32.Parse(valueTextField)}))">
New value
</button>
<div>
Name: <input #bind="#nameTextField" #oninput="(e)=> { nameTextField = e.Value ==null? string.Empty:(string)e.Value; }" />
</div>
<div>
Value: <input type="number" #bind="#valueTextField" #oninput="(e)=> { valueTextField = e.Value ==null? string.Empty:(string)e.Value; }" />
</div>
#code {
string nameTextField = "";
string valueTextField = "";
List<Model> models = new()
{
new Model(){Name="Row1",Value = 1},
new Model(){Name="Row2",Value = 2}
};
}
Model.cs:
public class Model
{
public string Name {get;set;}
public int Value {get;set;}
}
Working demo.

Related

Index error in my code controller?

When I run my project index, it shows this error. I have googled it but I have not found a proper solution for this error. So, please someone help me.
The Error message:
"The parameters dictionary contains a null entry for parameter
'chapterIdS' of non-nullable type 'System.Int32' for method
'System.Web.Mvc.ActionResult Index(Int32)' in
'opr.Controllers.QuizeController'. An optional parameter must be a
reference type, a nullable type, or be declared as an optional
parameter.
Parameter name: parameters"
This is my Index code:
#model List<opr.Data.Chapter>
#{
ViewBag.Title = "Index";
}
<h4>Select Chapter</h4>
<div class="container">
<div class="alert-info form-control m-auto w-75 custom-form col-6">
#using (Html.BeginForm("Index", "Quize", FormMethod.Get))
{
<h4>Quizes</h4>
<hr />
foreach (var std in Model)
{
<div class="row">
<div class="col-4"></div>
<div class="col-4">
#Html.RadioButton("searchBy",#std.Chapter_Name, true)
<text>#std.Chapter_Name</text>
</div>
<div class="col-4"></div>
</div>
<hr />
<h3>Questions</h3>
<hr />
<table>
<tbody>
#foreach (var quesion in std.C_QuestionTable)
{
<tr>
<td>
<h5>#quesion.QuestionText</h5>
</td>
</tr>
foreach (var answer in quesion.C_AnswerTable)
{
<tr>
<td>#answer.Options</td>
</tr>
}
}
</tbody>
</table>
}
<input type="submit" value="Create Question" />
}
</div>
</div>
this my controller
public class QuizeController : Controller
{
examsEntities db = new examsEntities();
public ActionResult Index(int chapterIdS)
{
List<C_QuestionTable> ques = new List<C_QuestionTable>();
ViewBag.ques = db.C_QuestionTable.Where(w => w.Id == chapterIdS).ToList();
List<Chapter> model = new List<Chapter>();
model = db.Chapters.Where(w=>w.Id==chapterIdS).ToList();
return View(model);
}
}
Default route expects optional parameter named id:
public ActionResult Index(int id)
{
...
}
Therefore, if your url looks like .../Quize/Index/1 you need to call this parameter id, or register your own route for this controller.
Try with this...
public ActionResult Index(int? chapterIdS)
{
List<C_QuestionTable> ques = new List<C_QuestionTable>();
ViewBag.ques = db.C_QuestionTable.Where(w => w.Id == chapterIdS).ToList();
List<Chapter> model = new List<Chapter>();
model = db.Chapters.Where(w=>w.Id==chapterIdS).ToList();
return View(model);
}
Now parameter will accept the null value.

How can i pass list of complex object to controller from my view(jsp)

I need to pass a list of object (that contains reference of itself) in my controller from my view. I have gone through so many links but did not get satisfactory solution.
Below is my scenario and code snippet.
I have UI with list of questions and answers. Each question can have sub-questions as well which will be child of main question.
I want to pass complete data as List of Questions to my controller.
My UI is like :
https://i.stack.imgur.com/yA8hN.png
Models are:
Question model:
public class Question {
private String question;
private String question_type;
private List<String> answers;
private List<Question> subQuestions;
.. getter and setter and constructors...
}
public class SurveyData {
private List<Question> data = new ArrayList<Question>();
... getter and setter and constructors...
}
Controller :
#RequestMapping(value = "save", method = RequestMethod.POST)
public ModelAndView saveData( HttpServletRequest request, HttpServletResponse response, #ModelAttribute("surveyData") SurveyData data) {
// code to save the data....
}
In my jsp I have a javascript method which is fetching all the data and saving in in an hidden input value.
JSP/View:
<form id="surveyForm" modelAttribute="surveyData" method="POST">
<table style="height: 200px;" border="0" width="70" cellspacing="0">
<tbody>
<tr style="height: 50px;">
<td>
<h2>
<span style="color: #00ccff;"> ADD NEW CALL </span>
</h2>
</td>
</tr>
<tr style="height: 30px;">
<td>
<div id="question-container">
<p style="padding-left: 1000px;">
<span style="color: #999999;"><a style="color: #999999;"
title="+ ADD NEW QUESTION" href="javascript:void(0);"
onclick="javascript:addNewQuestion();">+ ADD NEW QUESTION</a></span>
</p>
</div>
</td>
</tr>
<tr style="height: 70px;">
<td>
<input type="hidden" id="surveyData" name="surveyData" value="" />
<input type = "submit" value = "Save" onclick="javascript:saveData();"/>
<input type = "submit" value = "Cancel" onclick="javascript:cancel();"/>
</td>
</tr>
</tbody>
</table>
</form>
<script>
function saveData() {
var surveyData = {};
var questions = [];
// code to fetch the data and save it in questions array
.
.
.
surveyData.data = questions;
$("#surveyData").val(surveyData);
$("#surveyForm").submit();
}
</script>
I am not getting any data in modelAttribuite parameter.
How can I pass this data to my controller?
Thanks in advance!!
You can use nested property for list items, which is documented at Spring MVC Document.
In your case, you can generate question and sub-question in JSP via a JSTL forEach iteration with proper item index. To add a new item, use JavaScript to add a new set of input elements with a new index.
Besides, you can construct your surveyData in JavaScript and convert to proper http request that Spring can handle:
var surveyData = {
data: [{
question: "question1",
question_type: "type1",
answers: [
"answer1",
"answer2"
],
subQuestions: []
}, {
question: "question1",
question_type: "type1",
answers: [
"answer1",
"answer2"
],
subQuestions: []
}]
}
var mapData = function (data, prefix) {
if (Array.isArray(data)) {
// map array data
return data
.map((e, i) => mapData(e, prefix + '[' + i + ']'))
.reduce((a, b) => a.concat(b), []);
} else if ((typeof data === "object") && data !== null) {
// map object data
return Object.getOwnPropertyNames(data)
.map(k => mapData(data[k], prefix + '.' + k))
.reduce((a, b) => a.concat(b), []);
}
return [{
key: prefix,
value: data
}]
}
var surveyDataInputs = mapData(surveyData, "surveyData");
// remove old nodes
document.querySelectorAll('input[type=hidden][name^=surveyData]').forEach(i => i.remove());
// add new nodes
var surveyForm = document.querySelector("#surveyForm");
surveyDataInputs.map(d => {
var input = document.createElement('input');
input.setAttribute("type", "hidden");
input.setAttribute("name", d.key);
input.setAttribute("value", d.value);
surveyForm.appendChild();
})
$("#surveyData").val(surveyData);
$("#surveyForm").submit();

Knockout - Instead of the data-bind value, javascript is displayed

I created a ASP.Net MVC 5 project and used Knockout.js library.
I have a View called Statement which basically shows the a table with a couple of Transaction items.
My complete Statement.cshtml is as follow:
#using Newtonsoft.Json;
#model IEnumerable<ATMMVCLearning.Models.Transaction>
#{
ViewBag.Title = "Statement";
}
<h2>Statement</h2>
<table class="table table-striped table-bordered">
<thead>
<tr>
<td><strong>Transaction ID</strong></td>
<td><strong>Amount</strong></td>
</tr>
</thead>
<tbody data-bind="foreach:currentTransactions">
<tr>
<td data-bind="text:Id"></td>
<td data-bind="text:formattedPrice"></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<span data-bind="click:previousPage" class="glyphicon glyphicon-circle-arrow-left"
style="cursor:pointer;"></span>
<span data-bind="text:currentPage"></span>
<span data-bind="click:nextPage"class="glyphicon glyphicon-circle-arrow-right"
style="cursor:pointer;"></span>
</td>
</tr>
</tfoot>
</table>
<script src="~/Scripts/knockout-3.4.0.js"></script>
<script>
function formattedPrice(amount) {
var price = amount.toFixed(2);
return price;
}
function StatementViewModel() {
var self = this;
//properties
//note that there is a ko.observableArray for making bindings for array
self.transactions = #Html.Raw(JsonConvert.SerializeObject(Model, new JsonSerializerSettings {
ReferenceLoopHandling = ReferenceLoopHandling.Ignore}));
//TODO: embed transactions from server as JSON array
self.pageSize = 5; //number of transactions to display per page
self.currentPage = ko.observable(1); //the first observable. If the page changes, then the grid changes
self.currentTransactions = ko.computed(function () {
var startIndex = (self.currentPage() - 1) * self.pageSize; //because currentPage is an observable, we get the value by calling it like a function
var endIndex = startIndex + self.pageSize;
return self.transactions.slice(startIndex, endIndex);
});
//methods to move the page forward and backward
self.nextPage = function () {
self.currentPage(self.currentPage() + 1);
};
self.previousPage = function () {
self.currentPage(self.currentPage() - 1);
};
};
ko.applyBindings(new StatementViewModel()); //note this apply bindings, used extensively in KnockOut
</script>
As you can see in the <tbody> I have two <td> elements which have data-bind attribute:
<tbody data-bind="foreach:currentTransactions">
<tr>
<td data-bind="text:Id"></td>
<td data-bind="text:formattedPrice"></td>
</tr>
</tbody>
And the formattedPrice can be referred to the script section below:
function formattedPrice(amount) {
var price = amount.toFixed(2);
return price;
}
Now, I expect the resulting View when it is rendered should show a table with 5 transactions each page, where each table row shows an Id as well as its transaction amount. I.e. something like:
1 100.00
2 150.00
3 -40.00
4 111.11
5 787.33
However, when I render the page, I got the following result:
Instead of Id and amount, I got Id and javascript.
Any idea?
Update:
The Transaction class is as follow:
public class Transaction {
public int Id { get; set; } //this is internally used, not need to have anything
[Required]
[DataType(DataType.Currency)]
public decimal Amount { get; set; }
[Required]
public int CheckingAccountId{ get; set; }
public virtual CheckingAccount CheckingAccount { get; set; } //this is to force the entity framework to recognize this as a foreign key
}
Since formattedPrice is not part of your view-model, Knockout won't automatically unwrap it, nor will it pass it the amount argument.
Try this instead:
<td data-bind="text: formattedPrice(Amount)"></td>
Price probably needs to be computed field and you need to bind to price (I think). It's been a while since I did Knockoutjs.

Refreshing ViewModel and view after AJAX call

I'm trying to create a table with child rows (always one child per row) acting as details section. In this details section users will be able to see a log history, and will also have the ability to input a specific log. Upon inputting a new log and clicking on the "Add" button, the log history should update and show the newly added event.
I have the following AJAX call that will be used to add a log and should refresh the details section, triggered after clicking on the "Add" button mentioned above:
$('#addLog').click(function () {
formData = {
logType: logType.value, // Parameter to add a new log
logComments: logComments.value, // Parameter to add a new log
agent: agent.value // Parameter to add a new log
}
$.ajax({
url: '#Url.Action("AddLog", "AgentUser")',
type: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(formData),
cache: false,
success: function (data) {
// Here I should refresh the the details section
// and clear the logType and logCommands inputs
}
});
});
In my controller:
[HttpPost]
public ActionResult AddLog(string logType, string logComments, string agent, AgentUserValidatePhoneIndexViewModel vm)
{
DoDbStuff();
// Here I need to update the view model and view without having to
// refresh the page, so that it shows the recently added event.
return View(vm);
}
My ViewModel:
public class AgentUserValidatePhoneIndexViewModel
{
public IEnumerable<AgentUserWithoutValidPhone> AgentUserWithoutValidPhoneList { get; set; }
}
My Model:
public class AgentUserWithoutValidPhone
{
private string phone;
private DateTime creationDate;
public string Agent { get; set; }
public string Phone
{
get
{
return phone;
}
set
{
phone = PhoneNumberUtil.GetInstance().Parse("+" + value, String.Empty).NationalNumber.ToString();
}
}
public DateTime CreationDate
{
get
{
return creationDate;
}
set
{
creationDate = value;
TimeSpan timeSpan = (DateTime.Now) - creationDate;
TimeGoneBy = (timeSpan.Days != 0 ? timeSpan.Days + "d " : String.Empty) + timeSpan.Hours + "h";
}
}
public string TimeGoneBy { get; set; }
public DateTime LastLogEventDate { get; set; }
public LogEventTypePhone LastLogEvent { get; set; }
public IEnumerable<AgentUsersLog> EventList { get; set; }
}
My view:
#foreach (var agentUser in Model.AgentUserWithoutValidPhoneList)
{
<tr data-toggle="collapse" data-target="#details" class="accordion-toggle">
<td>
<button class="btn btn-default btn-sm"><span class="glyphicon glyphicon-collapse-down"></span></button>
</td>
<td>
#agentUser.Agent
</td>
<td>
#agentUser.Phone
</td>
<td>
#agentUser.CreationDate
</td>
<td>
#agentUser.TimeGoneBy
</td>
<td>
#agentUser.LastLogEventDate
</td>
<td>
#agentUser.LastLogEvent.GetDescription()
</td>
</tr>
<tr>
<td colspan="12" class="hiddenRow" id="">
<div class="accordian-body collapse" id="details">
<table class="table table-striped">
<thead>
<tr>
<input type="hidden" id="agent" value='#agentUser.Agent'>
<td>
#Html.DropDownList("LogEventTypePhone", EnumHelper.GetSelectList(typeof(Enums.LogEventTypePhone)), "Select log event",
new
{
id = "logType",
#class = "form-control"
})
</td>
<td colspan="2">
<input type="text" class="form-control" placeholder="Comments" id="logComments">
</td>
<td>
<a href="#" class="btn btn-default btn-sm" id="addLog">
<i class="glyphicon glyphicon-plus"></i>
</a>
</td>
</tr>
<tr>
<th>Event date</th>
<th>Event type</th>
<th>Comments</th>
<th>User</th>
</tr>
</thead>
<tbody>
#foreach (var e in agentUser.EventList)
{
<tr>
<td>#e.Date</td>
<td>#(((Enums.LogEventTypePhone)e.Subtype).GetDescription())</td>
<td>#e.Comments</td>
<td>#e.AspNetUsers.UserName</td>
</tr>
}
</tbody>
</table>
</div>
</td>
</tr>
}
How do I pass my ViewModel into my controller action, together with the parameters? Right now it's empty by the time I get to the action. I need to pass it into the action, interact with the DB, update the ViewModel, return to the View and have it updated with the current ViewModel.
I've never done what I'm trying to do here and I'm confused about it. Not sure if it's even possible, or maybe I should use several ViewModels.
There is no need to pass the view model to the controller and back again (it would just unnecessarily degrade performance). If your just wanting to add a new row based on the values you post to your controller method, then create a anonymous object (or a new instance of AgentUsersLog) containing the values to be shown in the new row, return it as json and update the DOM by adding a new <tr> element.
There are a few other issues with you code including the fact your creating invalid html (duplicate id attributes) in your foreach loops. Remove the id attributes and use class names instead in conjunction with relative selectors (the code you have shown will only ever handle the .click() event of the first link with id="addLog"). You view code should be
#foreach (var agentUser in Model.AgentUserWithoutValidPhoneList)
{
<tr data-toggle="collapse" data-target=".details" class="accordion-toggle">
....
</tr>
<tr>
<td colspan="12" class="hiddenRow">
<div class="accordian-body collapse details"> // use class name
<table class="table table-striped">
<thead>
<tr>
<td>
<input type="hidden" class="agent" value='#agentUser.Agent'> // must be inside a td element
#Html.DropDownList("LogEventTypePhone", EnumHelper.GetSelectList(typeof(Enums.LogEventTypePhone)), "Select log event", new
{
id = "", // remove id
#class = "form-control logType" // add class name
})
</td>
<td colspan="2">
<input type="text" class="form-control logComments" placeholder="Comments"> // use class name
</td>
<td>
<a href="#" class="btn btn-default btn-sm addLog"> // use class name
<i class="glyphicon glyphicon-plus"></i>
</a>
</td>
</tr>
<tr>
....
</tr>
</thead>
<tbody>
#foreach (var e in agentUser.EventList)
{
....
}
</tbody>
</table>
</div>
</td>
</tr>
}
And the script becomes
var url = '#Url.Action("AddLog", "AgentUser")';
$('.addLog').click(function () {
var table = $(this).closest('table');
var logType = table.find('.logType').val();
var logComments = table.find('.logComments').val();
var agent = table.find('.agent').val();
$.post(url, { logType: logType, logComments: logComments, agent: agent }, function(data) {
var row = $('<tr></tr>');
row.append($('<td></td>').text(data.Date));
.... // add other cells for data.Subtype, data.Comments and data.UserName
table.children('tbody').append(row);
});
});
Then in the controller
[HttpPost]
public JsonResult AddLog(string logType, string logComments, string agent)
{
DoDbStuff();
// Build the data to return
var data = new
{
Date = .... ,
Subtype = .... ,
Comments = ..... ,
UserName = ....
};
return Json(data);
}
You can acheive this by creating child object. Lets assume the model "AgentUserValidatePhoneIndexViewModel" has the below properties
Phone (int)
AgentDetail (string)
then generate formData as follows
formData = {
logType: logType.value, // Parameter to add a new log
logComments: logComments.value, // Parameter to add a new log
agent: agent.value // Parameter to add a new log
vm : { // Parameter to add values to view model
phone : answer.value,
agentDetail : agentDetail.value
}
}
Check this post to know how to render partial views
This post explains https://www.simple-talk.com/dotnet/asp.net/revisiting-partial-view-rendering-in-asp.net-mvc/

Submit multiple radio button values with one button ASP.NET MVC

There are three groups of radio buttons on my page and I want to submit each of their values with one button.
<table>
<tr>
<td>
#Html.RadioButton("rating1", "yes", true) Yes
#Html.RadioButton("rating1", "no", false) No
#Html.RadioButton("rating1", "maybe", false) Maybe
</td>
</tr>
<tr>
<td>
#Html.RadioButton("rating2", "yes", true) Yes
#Html.RadioButton("rating2", "no", false) No
#Html.RadioButton("rating2", "maybe", false) Maybe
</td>
</tr>
<tr>
<td>
#Html.RadioButton("rating3", "yes", true) Yes
#Html.RadioButton("rating3", "no", false) No
#Html.RadioButton("rating3", "maybe", false) Maybe
</td>
</tr>
</table>
I want to send a dictionary as a parameter to the controller action so I can retrieve the values for each rating.
The controller looks like this:
public ActionResult Rate(Guid uniqueId, Dictionary<Guid, string> ratings)
{
[...]
}
I have tried:
<input type="submit" value="Send Ratings" onclick="#Url.Action("Rate", "Controller", new {new Dictionary<string,string> {{"rating1", "yes"}, {"rating2", "no"}, {"rating3", "maybe"}})"></input>
but passing a Dictionary as a RouteValue like that is not allowed.
How do I send all 3 radio button [name,value] pairs to the action with one button /submit? Also, should all three groups be in the same form or separate forms?
I am open to using javascript, but would prefer using Razor HTML helpers.
Thanks
Model
public class ExampleViewModel
{
public ExampleViewModel()
{
Answers = new Dictionary<string, string>();
Questions = new List<KeyValuePair<string, List<string>>>();
}
public Dictionary<string, string> Answers { get; set; }
public List<KeyValuePair<string, List<string>>> Questions { get; set; }
public ExampleViewModel Add(string key, string[] value)
{
Questions.Add(new KeyValuePair<string, List<string>>(key, value.ToList()));
return this;
}
}
Controller
[HttpGet]
public ActionResult Index()
{
ExampleViewModel model = new ExampleViewModel();
model.Add("rating1",new[] { "Yes" ,"No", "Maybe"});
model.Add("rating2", new[] { "Yes", "No", "Maybe" });
model.Add("rating3", new[] { "Yes", "No", "Maybe" });
return View(model);
}
[HttpPost]
public ActionResult Index(ExampleViewModel model)
{
//model.Answers is the dictionary of the values submitted
string s = model.Answers.Count.ToString();
return View();
}
View
#model ExampleViewModel
#using (Html.BeginForm())
{
<table class="table table-bordered table-striped">
#for(int i=0;i<Model.Questions.Count;i++)
{
var question = Model.Questions[i];
<tr>
<td>
#foreach (var answer in question.Value)
{
<input type="hidden" name="Model.Answers[#question.Key].Key" value="#question.Key" />
<input type="hidden" name="Model.Answers.Index" value="#question.Key" />
#Html.RadioButton("Model.Answers[" + question.Key+"].Value", answer, false) #answer
}
</td>
</tr>
}
<tr>
<td>
<input type="submit" class="btn btn-primary" value="Submit" />
</td>
</tr>
</table>
}
model.Answers will hold the dictionary containing the submitted values
You can use FormCollection for gating your radio button value on your post.
you have to simply write var variable = f["rating1"]; in your post method and u get your selected radio button value on post method,
public ActionResult Rate(Guid uniqueId,FormCollection f)
{
var variable1 = f["rating1"];
var variable2 = f["rating2"];
var variable3 = f["rating3"];
[...]
}

Categories

Resources