Refreshing ViewModel and view after AJAX call - javascript

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/

Related

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

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.

Angularjs - Attempted to trust a non-string value in a content requiring a string: Context: html - while inserting data

I'm trying to insert HTML content dynamically based on the ITEMS available in DB and need to save back to DB again on click of each item's save button which was added dynamically as below.
Controller.js:
function getItemCommentItems() {
$http({
method: 'GET',
url: 'http://xxx/api/ItemComments/GetItemCommentItems',
params: { Area_Id: 'L4' },
headers: { 'Content-Type': 'application/json; charset=utf-8', 'dataType': 'json' }
}).then(function successCallback(response) {
// $scope.itemDtls = [];
$scope.itemDtls = response.data;
displayItems($scope.itemDtls);
}, function errorCallback(response) { });
}
function displayItems(itemData)
{
// alert(itemData.length); Length: 2
for (i = 0; i <= itemData.length; i++) {
Title = '<table><tr><td><label for="ITEM_NAME">Item: </label>{{' & itemData[i].ITEM_NAME & '}}</td></tr ><tr><td><input type="text" id="inpPriority" value="{{ ' & itemData[i].PRIORITY & ' }}" /></td></tr> <tr> <td><input type="text" id="inpComment" value="{{ ' & itemData[i].COMMENT & '}}" /></td></tr><tr> <td><input type="button" ng-click="onSave()" value="Save {{ ' & itemData[i].ITEM_ID & '}}" /></td></tr ></table >';
// Title = $sce.trustAsHtml(itemData[i]); ----> Error here Attempted to trust a non-string value in a content requiring a string: Context: html
$scope.divHtmlVar = $sce.trustAsHtml(Title); ----> Error here Attempted to trust a non-string value in a content requiring a string: Context: html.
}
}
.HTML:
<tr> <td> <div ng-bind-html="divHtml"></div> </td> </tr>
Class details:
public string ITEM_ID { get; set; }
public string ITEM_NAME { get; set; }
public string COMMENT { get; set; }
public string PRIORITY { get; set; }
public string ITEM_ID { get; set; }
Error msg: Error: [$sce:itype] Attempted to trust a non-string value in a content requiring a string: Context: html
Can some body help me here in fixing this issue or is there a better way to do this whole way of insert and save dynamically?
Why are you using the ng-bind-html in the first place? It would be much better if you had your elements described in a template. Then you don't need to use $sce at all. Something like that I suppose:
<table ng-repeat="item in itemDtls">
<tr>
<td>
<label for="ITEM_NAME">Item: </label>{{item.ITEM_NAME}}
</td>
</tr>
<tr>
<td>
<input type="text" id="inpPriority" value="{{item.PRIORITY}}" />
</td>
</tr>
<tr>
<td>
<input type="text" id="inpComment" value="{{item.COMMENT}}" />
</td>
</tr>
<tr>
<td>
<input type="button" ng-click="onSave()" value="Save {{item.ITEM_ID}}" />
</td>
</tr>
</table>

Ajax Response Laravel to object

I created an ajax request to display results from my table eloquent query who depends one a select box "poule".
Everything is working but when I run the ajax request by selecting a poule_id from the select box I need to display the json result. I would like to display the result as my foreach loop in the table ($equipes as $equipe) because as you can see I display value from models in relation.
UPDATED:
My model Equipe:
class Equipe extends Model
{
public function structure()
{
return $this->belongsTo('App\Structure');
}
My model CompetEquipe (i use it to display my foreach)
class CompetEquipe extends Model
{
public function equipe(){
return $this->belongsTo('App\Equipe' , 'equipe_id');
}
Like this i can access to the tables in relations in my foreach
<tr>
<td>
{{$equipe->equipe->structure->nom_structure}}
</td>
<td>
{{$equipe->equipe->lb_equipe}}
</td>
<td>{!! Form::text('nb_bonus') !!}</td>
</tr>
Actually with this way I can only display equipe_id but I would like to display the object to access to the other models in relation and display the result as my foreach in the table like:
#foreach($equipes as $equipe)
<tr>
<td>
{{$equipe->equipe->structure->nom_structure}}
</td>
<td>
{{$equipe->equipe->lb_equipe}}
</td>
<td>{!! Form::text('nb_bonus') !!}</td>
</tr>
#endforeach
Hope someone understood what I want to do. thanks a lot in advance friends
here my JSON RESULT : {"equipes":[{"equipe_id":1,"poule_id":1}]}
My select filter search:
<select id="poule">
#foreach($select_poules as $select_poule)
<option value="{{$select_poule->id}}">{{$select_poule->lb_poule}}</option>
#endforeach
</select>
My table:
<table id="equipes" class="table table-striped">
<thead>
<tr>
<th>Club</th>
<th>Nom de l'équipe</th>
<th>Bonus(+/-)</th>
</tr>
</thead>
<tbody>
#foreach($equipes as $equipe)
<tr>
<td>
{{$equipe->equipe->structure->nom_structure}}
</td>
<td>
{{$equipe->equipe->lb_equipe}}
</td>
<td>{!! Form::text('nb_bonus') !!}</td>
</tr>
#endforeach
</tbody>
</table>
My controller:
public function searchEquipes(Request $request)
{
$equipes = [];
if($request->has('poule_id')){
$equipes = EquipePoule::where('poule_id',$request->poule_id)
->get();
}
return response()->json(['equipes' => $equipes]);
}
My script:
<script>
$(document).on('change', '#poule', function() {
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
$.ajax({
type: 'GET',
dataType: "json",
url : '/licences/public/search/equipes',
data : {
poule_id : $('#poule').val()
},
success:function(data){
$('#equipes').empty();
for (var i = 0; i < data.equipes.length; i++) {
$('#equipes').append('<tr><td>'+data.equipes[i].equipe_id+'</td></‌​tr>')
}
},
timeout:10000
});
});
</script>
To replace the content of your table with the response of an AJAX request you jQuery's replaceWith.
You need to change your jQuery success function slightly.
success: function(data) {
//Build the row data as you wish to display it
var rowData = ""
$.each(team["equipes"][0], function(i, value) {
var rowData += $("#equipes").append("<td>"+value+"</td>");
})
$("#equipes").replaceWith("<tr>"+rowData+"</tr>
$("#equipes").append("</tr>");
}
This will replace your initial table data with that of your select.

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.

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