Dynamically add/remove table rows in a wizard - asp.net

I'm currently using this Wizard in my application.
In one of my steps, I need to add and remove items from a table. Add functions works fine. But I can't make the Delete function work. I've searched SO and other resources online, can't find a solution.
This is what I've so far:
Wizard Step 2 Table:
<table id="LibList" class="table table-responsive table-striped">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Types)
</th>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Teams)
</th>
<th>
<label>Delete</label>
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Step3.Liabilities) {
Html.RenderPartial("_LibListItem", item);
}
</tbody>
</table>
Partial View:
<tr>
<td>
#Html.DropDownListFor(model => model.Type, Model.Types, new { #class = "selectpicker form-control", id = string.Format("{0}_{1}", "TYPE", Model.ID) })
</td>
<td>
#Html.TextBoxFor(model => model.Name, new { #class = "form-control", id = string.Format("{0}_{1}", "NAME", Model.ID) })
</td>
<td>
#Html.TextBoxFor(model => model.Teams, new { #class = "form-control", #type = "currency", id = string.Format("{0}_{1}", "TERMS", Model.ID) })
</td>
<td>
#Ajax.ActionLink("Delete", "Delete", "QuestionWizard", new { id = Model.ID },
new AjaxOptions() {
HttpMethod = "Delete",
Confirm = "Are you sure you want to delete this record?",
OnComplete = "function() { $(this).parent().parent().remove() }"
},
new { #class = "btn btn-primary" })
</td>
Add and Delete Action:
public ActionResult AddRow() {
LiabilityModel lib = new LiabilityModel();
try {
if (LiabilitySessionState.Count == 0) {
lib.ID = 1;
} else {
lib.ID = LiabilitySessionState.LastOrDefault().ID + 1;
}
LiabilitySessionState.Add(lib);
return PartialView("_LibListItem", lib);
} catch (Exception ex) {
System.Web.HttpContext.Current.Application.Add("LASTERROR", ex);
return RedirectToAction("Index", "Error");
}
}
public void Delete(int Id) {
try {
if (LiabilitySessionState.Count > 0) {
LiabilitySessionState.RemoveAll(item => item.ID == Id);
}
} catch (Exception ex) {
System.Web.HttpContext.Current.Application.Add("LASTERROR", ex);
RedirectToAction("Index", "Error");
}
}
public List<LiabilityModel> LiabilitySessionState {
get {
if (Session["LiabilityModel"] == null) {
return LiabilitySessionState = new List<LiabilityModel>();
} else {
return Session["LiabilityModel"] as List<LiabilityModel>;
}
}
set {
Session["LiabilityModel"] = value;
}
}
Script:
<script type="text/javascript">
$(document).ready(function () {
$('.add-button').click(function () {
var action = "/QuestionWizard/AddRow";
$.ajax({
url: action,
cache: false,
success: function (result) {
$("#LibList tbody").append($(result));
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
}
});
})
$(".remove-button").click(function () {
$(this).parents().parent().remove();
return false;
});
});
Right now, the Delete Action returns a blank page, because I'm not returning a view. But, I'm not sure what view to return.
Please help!
Thanks in advance.

Firstly your Delete() method is changing data so it needs to be a POST, not a GET (which #Ajax.ActionLink() is). Next, your 'delete link' does not have class="remove-button" so $(".remove-button").click(function () { wont do anything. Since you already using jquery ajax methods to add items (refer notes below), there seems no point using the Ajax helpers anyway (your just adding extra overhead by loading the jquery.unobtrusive-ajax.js file).
Change the last <td> element of the partial to
<td>
<button type="button" class="remove-button" data-id=#Model.ID>Delete</button>
</td>
Then in the main view, use the following script (note you need event delegation so you can remove dynamically added items)
var url = '#Url.Action("Delete", "YourControllerName")';
$('#LibList').on('click', .remove-button, function() {
var row = $(this).closest('tr');
var id = $(this).data('ID');
if (id) { // assumes property ID is nullable, otherwise may need to test if id != 0
$.post(url, {ID: id }, function(response) {
if(response) {
row.remove();
} else {
// Oops - display message?
}
});
} else {
// its a new row so it has not been saved yet - just delete the row
row.remove();
}
});
and the controller method should look like
[HttpPost]
public JsonResult Delete(int ID)
{
// delete the item
return Json(true); // or if an exception occurred, return(null) to indicate failure
}
Side notes:
Both your AddRow() and Delete() methods include a return
RedirectToAction() statement. You are making ajax calls which stay
on the same page so RedirectToAction() is pointless (it will never
be executed)
If you believe the "Add functions works fine", then you must have
some unnecessary hacks in order to make this work (your generating
duplicate name attributes for the controls (without indexers)
which will not bind to your collection of Liabilities). You need
to generate existing items in a for loop and include a hidden
input for an Index property, and use a client side template to
generate new items; or you need to use the BeginCollectionItem
helper if using a partial view. This
answer
shows how to use both techniques.
You don't appear (and you should not need) to access the id
attributes of the controls in your table. Instead of using id = string.Format("{0}_{1}", "NAME", Model.ID), just use id="" which
will render the element without and id attribute.

Related

Angular - Change BackGround Color of Row in Table After click the Buttom

I want change the background color of the selected row after click the button
I tried but change the color of all rows.
This is a similar code.
HTML
<tr *ngFor="let data of (datas$ | async) | filter:authService.filter | paginate: config | orderBy: key : reverse" [ngClass]="{'data-selected':isSelected}">
<td>{{data.id}}</td>
<td>{{data.text}}</td>
<td>
<a class="mr-3" (click)="delete(data.id)"><i class="fa fa-remove"></i>
Remove
</a>
</td>
</tr>
TS
delete(postId) {
this.isSelected=true;
const ans = confirm('TEXT TEXT '+dataid);
if (ans) {
this.dataService.deleteData(postId).subscribe((data) => {
if(data) {
this.showSuccessDelete();
} else {
this.showError();
}
this.isSelected=false;
this.loadDatas();
});
}
}
CSS
.data-selected{
background-color: #e9eaea;
}
Thanks so much
You can add an attribute to your component class and call it selectedRow, which will get the data.id.
selectedRow: number;
delete(postId,numeroDeposito) {
this.selectedRow = postId;
const ans = confirm('TEXT TEXT '+dataid);
if (ans) {
this.dataService.deleteData(postId).subscribe((data) => {
if(data) {
this.showSuccessDelete();
} else {
this.showError();
}
this.isSelected=false;
this.loadDatas();
});
}
}
then in the tr tag use [ngClass]="{'data-selected': selectedRow === data.id}".
You can also take the following approach:
Working Demo
[class.data-selected]="data.isSelected"
and On click event.
(click)="data.isSelected = true";
Rather than declaring a separate isSelected variable. you should introduce is as a property of data object. Ex:
{
id: 1,
name: 'AA',
isSelected: false
}
Then on click you can toggle it when delete function is called with data.id as parameter.
deleteMe(id: string) {
this.data.map(x => x.isSelected = false);
this.data.find(x => x.id === id).isSelected = true;
}
And in html
<tr *ngFor="let item of data" [ngClass]="{selected: item.isSelected}">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td><button (click)="deleteMe(item.id)">delete me</button></td>
</tr>
Here is a minimum example.
You can use conditional styling to achieve this.
<div [className]="( checkSelectedRow(i) ? 'example-class' : 'other-class'"></div>
In Typescript file:
checkSelectedRow(rowNumber) : boolean {
for(int j=0; j<rows.length; j++){
if(j==i){
rows[i]["selected"]=true; return true;
};
}
return false;
}
this can be solve by create an array of selected rows like this
deleteList = []
then when you click remove push the id of the item in the list
delete(id) {
this.deleteList.push(id)
}
then at the template use ngClass directive to set the class if the item id include in the list
<tr *ngFor="let data of list" [ngClass]="{'data-selected':deleteList.includes(data.id)}">
....
</tr>
demo 🚀🚀

Post data from aurelia form into asp.net controller

I can read data and display it on aurelia-SPA like this:
In my ASP.NET controller I have:
[HttpGet("[action]")]
public IEnumerable<Team> Teams()
{
var Teams = _teamRepository.GetAllTeams().OrderBy(p => p.Name);
return Teams;
}
On aurelia-page typescript file I can then read data like this:
#inject(HttpClient)
export class Fetchdata {
public Teams: Teams[] | undefined;
constructor(http: HttpClient) {
http.fetch('api/SampleData/Teams')
.then(result => result.json() as Promise<Teams[]>)
.then(data => {
this.Teams = data;
});
}
And then in aurelia-page html file I can show the data like this:
<template>
<h1>Teams in database</h1>
<p if.bind="!Teams"><em>Loading...</em></p>
<table if.bind="Teams" class="table">
<thead>
<tr>
<th>TeamName</th>
</tr>
</thead>
<tbody>
<tr repeat.for="team of Teams">
<td>${ team.name }</td>
</tr>
</tbody>
</table>
</template>
This works fine. Now I cannot find any example on how to create a simple form and post data from it to the ASP.NET controller. For example If my aurelia html would contain a form like this:
<form role="form" submit.delegate="postdata()">
<label for="name">Teamn Name</label>
<input type="text" value.bind="name" placeholder="Name">
<label for="teamurl">TeamUrl</label>
<input type="text" value.bind="teamurl" placeholder="Teamurl">
<button type="submit">Add Team to DB</button>
</form>
In your view model which corresponds to the view with the form you have in your question - you would need to implement your postdata() method. Given that the view model has HttpClient injected and assigned to the property called http and properties called name and teamurl declared on this same view model and there is a property called postPath value of which is the url for your post endpoint - postdata method would look something like this:
public async postdata(): Promise<bool> {
if (!this.name || !this.teamurl) {
return; // no valid data. abort.
}
// given that there is a type called Team and it has a constructor which
// takes corresponding parameters - init up a new instance of Team with
// current name and teamurl values
let payload = new Team(this.name, this.teamurl);
try {
await this.http.fetch(postPath, {
method: "post",
body: JSON.stringify(payload),
}).then(response => response.json());
// return true if no error, there might be a need for more complicated logic here
// depending on what your post endpoint returns
return true;
} catch(e: Exception) {
console.log("Error: ", e.Message); // handle the error logging however you need.
return false;
}
}

How to fetch data from parse.com and render it to table using AngularJs

I am trying to fetch data stored in parse.com collection. I am using Parse Javascript SDK to call the service asynchronously as following:
ctrl.factory('TLDs', function($q){
var query = new Parse.Query(Fahras)// Fahras is the Parse Object initialized earlier in code
query.equalTo("type", "Domain")
var myCollection = query.collection()
return {
fetchDomains: function(){
var defer = $q.defer();
myCollection.fetch({
success : function(results) {
defer.resolve(results.modles);
console.info(results.models)
},
error : function(aError) {
defer.reject(aError);
}
});
console.info(defer.promise)
return defer.promise;
}
}
}) // end of factory topDomains
I have a simple table to show the fetched data
<div id="showdomainshere"> {{domains}}</div>
<table id="domains_table" class="table table-hover">
<thead>
<tr>
<th>Domain</th>
<th>Code</th>
<th>Subjects</th>
<th>Instances</th>
</tr>
</thead>
<tbody id="table_body">
<form id="edit_row" class="form-inline">
<tr ng-repeat="item in domains">
<td><span>{{item.attributes.arTitle}}</span>
</td>
<td><span>{{item.attributes.domainCode}}</span>
</td>
<td><span>{{subclasses}}</span>
</td>
<td><span>{{instances}}</span>
</td>
</tr>
</form>
</tbody>
</table>
</div> <!-- end of main div -->
And hereunder the controller I ma using to render the view:
ctrl.controller('Home', ['$scope','TLDs',function($scope, TLDs) {
$scope.domains = TLDs.fetchDomains()
}])
Using console.info I can see that the result is fetched and I can go through the array of returned models as expected. Problem is that $scope.domains never been updated and as a result the table never been rendered
Fortunately I am able to figure it out.
The controller should be like:
ctrl.controller('Home', ['$scope','TLDs',function($scope, TLDs) {
TLDs.fetchDomains().then(function(data){
$scope.domains = data
})
}
While the factory itself should be like:
ctrl.factory('TLDs', function($q){
var query = new Parse.Query(Fahras)
query.equalTo("type", "Domain")
var myCollection = query.collection()
return {
fetchDomains: function(){
var defer = $q.defer();
myCollection.fetch({
success : function(results) {
defer.resolve(results.models)
return results.models
},
error : function(aError) {
defer.reject(aError)
}
})
return defer.promise;
}
} }) // end of factory

How to conditionally set attribute on HTML-element in Meteor.js/Handlebars.js?

The code below shows one row from a table, and the JavaScript-code to handle the template. The code works, but it sets the disabled-attribute on all the buttons in the table. I only want it for the one button-element that is pushed.
Question: What is the best way to conditionally set the correct element as disabled in Meteor.js?
In my HTML-file:
<template name="userRow">
<tr>
<td>{{ username }}</td>
<td>
<select class="newRole">
{{{optionsSelected globalRoles profile.role }}}
</select>
</td>
<td>
Disabled: {{disabledAttr}}
{{#isolate}}
<button type="button" {{disabledAttr}} class="btn btn-xs btn-primary saveUser">Save</button>
{{/isolate}}
</td>
<td><button type="button" class="btn btn-xs btn-danger deleteUser">Delete</button></td>
</tr>
And in my .js-file:
var newUserRole;
var savedDep = new Deps.Dependency;
var saved;
var disabledDep = new Deps.Dependency;
var disabledAttr = "";
Template.userRow.saved = function () {
savedDep.depend();
return saved;
};
Template.userRow.disabledAttr = function () {
disabledDep.depend();
return disabledAttr;
};
Template.userRow.events({
'change .newRole' : function (event) {
newUserRole = event.target.value;
},
'click .saveUser' : function (event) {
disabledAttr = "disabled";
disabledDep.changed();
Meteor.call('updateUser',
{
userId: this._id,
role: newUserRole
},
function (error, result) {
if (error) {
saved = "NOT saved, try again!";
} else {
saved = "Saved!";
savedDep.changed();
};
});
return false;
}
});
To answer your question:
All of your rows are using the same Dependency object, so when one row changes the object, all the other rows respond.
To fix this you can create a new Dependency object for each row.
For example:
Template.userRow.created = function () {
this.disabledDep = new Deps.Dependency;
};
And update all of your code to use the template's disabledDep instead of the 'global' one. That should solve your problem.
But let's talk about your goal here:
It looks like you want to render your rows differently while saving, until they're confirmed server side.
A cleaner way to do this is to take advantage of Meteor's isSimulation method. For example:
// Put this in a file that will be loaded on both the client and server
Meteor.methods({
add_item: function (name) {
Items.insert({name: name,
unconfirmed: this.isSimulation});
}
});
This example is using inserts, but you can use the same technique for updates.
Now each document in your collection will have an 'unconfirmed' field, which you can use to change your view:
Template.userRow.disabledAttr = function () {
return this.unconfirmed ? "disabled" : "";
};

Passing a List of Radio buttons objects to my Action Method

I am working on a security metrix page similar to the below:-
currently i have the following code inside my view:-
#foreach(var item in Model.PermisionLevel.OrderByDescending(a=>a.PermisionSize)){
<th>
</th>}
</tr>
#{
int i =0 ;
foreach (var item2 in Model.TechnologyType.OrderBy(a=>a.Name)) {
<tr>
<td class="f">
#item2.Name
</td>
#foreach (var item3 in Model.PermisionLevel.OrderByDescending(a=>a.PermisionSize))
{
<td class="f">
#Html.RadioButton(item2.Name, item3.PermisionLevelID)
#Html.Hidden("AssetTypeID" , item2.AssetTypeID)
#Html.Hidden("PermisionLevelID",item3.PermisionLevelID)
#Html.Hidden("SecurityRoleID",Model.SecurityRole.SecurityRoleID)
</td>
}
</tr>
}
}
and the following ActionMethod:-
[HttpPost]
public ActionResult AssignPermisionLevel(ICollection<SecurityroleTypePermision> list)
{
foreach (var c in list)
{
repository.InsertOrUpdateSecurityroleTypePermisions(c);
}
repository.Save();
return RedirectToAction("Index");
}
But i am not sure about how i can pass the associated hidden field values only if the related radio button was checked. currently if i submit the view , the action method will raise a null exception?
Can anyone advice on how to fix this ?
each radio group has a name, which looks like your item2.Name.
// see if this group is selected
var radWebSite = $('[name="SomeName"]:checked');
if (radWebSite.length > 0) {
// this group has a selection, get the value
var WebSiteVal = radWebSite.val();
}
if you get all of your answers into variables or some object, you can pass that down to your action method, here is a suggestion
$('#SaveClick').click(function() {
var SavePermissions = {};
SavePermissions.WebSite = WebSiteVal // this is the variable from above
// .... do this for each radio group
var DTO = { 'DTO': SavePermissions };
$.ajax({
type: "POST",
contentType: 'application/json;charset=utf-8',
url: "/ControllerName/ActionMethodName",
dataType: "json",
data: JSON.stringify(DTO),
success: function (data) {
// do success stuff
},
error: function(data){
// do error stuff
},
complete: function () {
//do final stuff
}
});
});
Then in your action method create a class that has a property for each item you put in SavePermissions on the javascript, probably should even call it SavePermissions
public ActionResult AssignPermisionLevel(SavePermissions DTO){
// save stuff
}
*Edit: *
I didn't think about this before, but we have a reference to Newtonsoft.Json in our application, you'll probably need that to do this...

Resources