Post data from aurelia form into asp.net controller - asp.net

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;
}
}

Related

Editing a viewmodel's member via button without submit

I'm using Asp Net Core 3.1 and am working on developing admin controls to approve and delete submitted images that are awaiting approval. The functionality that I am developing and am stuck on is as follows: I have created a grid of images waiting approval (using a loop in razor) and would like to click a button to "approve" that image via the logic I have written in my controller. How would I pass that data to the controller without refreshing the page?
View Model
public class ImageManagerViewModel
{
public List<ListingImages> Images;
public List<Tuple<long, string>> ListingNames;
}
Controller
public class AdminController : Controller
{
public ActionResult ApproveImage(int listingID, long imageID, bool isFeatured)
{
....
}
}
Client-side
#foreach (ListingImages row in Model.Images)
{
....
<div class="d-flex card-footer">
<a a class="btn btn-success btn-space"
href="#Url.Action("ApproveImage", "Admin", new { listingID = row.ListingId, imageID = row.ImageId, isFeatured = false})" role="button">Approve</a>
}
As VDWWD described, you wanna use ajax to achieve this behavior.
I made a quick sample for your code (I didn't have the ability to test it atm though).
Your loop (you can also use hidden input fields to track the ids of every single item):
#foreach (ListingImages row in Model.Images)
{
...
<span class="imageId">#(row.ImageId)</span>
<span class="listingId">#(row.ListingId)</span>
<input type="button" class="btn btn-success approveBtn">Approve</button>
}
JQuery code in the script section:
<script>
$(document).on("click",
".approveBtn",
function () {
var imageId = $(this).closest(".imageId").text();
var listingId = $(this).closest(".listingId").text();
$.ajax({
url: "/Admin/ApproveImage",
type: "POST",
data: {
__RequestVerificationToken: $('input[name=__RequestVerificationToken]').val(),
listingID : listingId,
imageID: imageId,
isFeatured: false
},
timeout: 5000,
success: function(results) {
// result action
},
contentType: "application/x-www-form-urlencoded; charset=utf-8"
})
.fail(function (xhr, textStatus, errorThrown) {
// error handling
});
});
</script>
Hints:
If you use one, include the antiforgery token in the request as shown in the sample.
You can also send the payload as JSON. You then need to edit the content type and use JSON.stringify in the data section to convert the payload.

How to set data properties to value returned from Vuex getter

I have a Vue component that will display data depending on what the user previously clicked on in a previous component. When they originally click, I set the 'current' index. Then when they get to the next page, I have a getter that will look in the data array and return the 'current' data.
The component they are redirected to is a form that they can edit. I would like to be able to have the 'current' data be pre-populated. NOT as a placeholder but as the actual value so that they can edit it directly.
The issue is I don't know how to set the values returned from the getter to the data function values so that they can be bound with v-model.
HTML
<input type="text" class="form-control" id="nickname1" v-model="name" name="name">
<input type="text" class="form-control" id="address1" placeholder="" v-model="address" name="address" >
<input type="text" class="form-control" id="city1" placeholder="" v-model="city" name="city" >
VUE
data: function() {
return {
name: this.currentArea.title,
address: this.currentArea.address,
city: this.currentArea.city,
state: this.currentArea.state
}
},
computed: {
currentArea() { return this.$store.getters.currentArea }
}
*this.currentArea.title and currentArea.title do not work.
*if I bind the placeholder, the data displays correctly, so the getter function is returning currentArea correctly
The data method is only fired once during initialization, before the computed properties are set up. So, referencing currentArea from within the data method won't work as it will be undefined at the time of execution.
If this.$store.getters.currentArea isn't expected to change in the lifespan of this component, it would be simplest to just set the data properties once in the mounted hook:
data() {
return {
name: '',
address: '',
city: '',
state: ''
}
},
mounted() {
let area = this.$store.getters.currentArea;
this.name = area.name;
this.address = area.address;
this.city = area.city;
this.state = area.state;
}
Or, if you know that the data properties for this component will always be the same as the currentArea, you could simply return this.$store.getters.currentArea in the data method directly:
data() {
return this.$store.getters.currentArea;
}
#thanksd: thank you for your answer.
I am working on a scenario where the state is stored in vuex, temporarily sent incomplete to the component and then updated through a fetch.
And it should be editable in a form.
My solution was to export part of the state with a getter in vuex:
getters: {
getItemDetail: (state, getters) => (id) => {
let item = state.openedItems.items.find(i => i.id === id);
return item ? item.data : null;
}
}
using it in the component by combining data, computed and watch properties (and deep cloning the object with the help of lodash):
data () {
return {
itemDetail: null
};
},
computed: {
itemData () {
return this.$store.getters.getItemDetail(this.item.id);
}
},
watch: {
itemData (n, o) {
this.itemDetail = _.cloneDeep(n);
}
}
Finally I bind the input to "itemDetail" (using an elemen switch, in my example):
<el-switch v-model="itemDetail.property"></el-switch>
To me (but I am quite new to Vue), it seems a good and working compromise.

Dynamically add/remove table rows in a wizard

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.

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

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