ASP.Net Core MVC - Bind to List of Strings With Ability To Add/Delete - asp.net-core

I want to bind to list of strings with ability to add new item or delete existing, similar to the UI in Azure DevOps -> Process -> Edit Work Item type... it has an a add text box/button at the top, the list of items, and the ability to delete each item with an X.
I tried to look at the html code but it seems dynamically created with javascript. Any existing solution so i don't need to reinvent the wheel? Thank you

According to your description, when the cursor focuses on the input text element, it will show the select items, and might be it implement the autocomplete function.
To achieve this effect, first, you should use JQuery Ajax to get the selectable items from the controller, then, you could attach the focusin, input and keydown events on the input text element, in these events, you could according to the entered value to add html elements, in order to show/hide the select item.
For the delete icon on each select option, you could use the Font Awesome element, and attach the click event to achieve the delete action.
Finally, to achieve the add new value feature, you could use the Bootstrap Modal to show a popup modal and insert the new value.
More detail steps, you could check the following sample code:
Controller:
//main page
public IActionResult SelectIndex()
{
return View();
}
//this method is used to get the select items.
[HttpGet]
public IActionResult GetSelectItems()
{
//initial data. You could query database and get the real data.
List<string> list = new List<string>() { "Angola", "Anguilla", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Cambodia", "Cameroon", "Canada", "Dominica", "Dominican Republic", "Ecuador", "Egypt" };
return Json(list);
}
View page (SelectIndex.cshtml):
CSS style:
<style>
* {
box-sizing: border-box;
}
body {
font: 16px Arial;
}
/*the container must be positioned relative:*/
.autocomplete {
position: relative;
display: inline-block;
}
input {
border: 1px solid transparent;
background-color: #f1f1f1;
padding: 10px;
font-size: 16px;
}
input[type=text] {
background-color: #f1f1f1;
width: 100%;
}
input[type=submit] {
background-color: DodgerBlue;
color: #fff;
cursor: pointer;
}
.autocomplete-items {
position: absolute;
border: 1px solid #d4d4d4;
border-bottom: none;
border-top: none;
z-index: 99;
/*position the autocomplete items to be the same width as the container:*/
top: 100%;
left: 0;
right: 0;
}
.autocomplete-items div {
padding: 10px;
cursor: pointer;
background-color: #fff;
border-bottom: 1px solid #d4d4d4;
}
/*when hovering an item:*/
.autocomplete-items div:hover {
background-color: #e9e9e9;
}
/*when navigating through the items using the arrow keys:*/
.autocomplete-active {
background-color: DodgerBlue !important;
color: #ffffff;
}
</style>
main html resource:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<!--Make sure the form has the autocomplete function switched off:-->
<form autocomplete="off">
<div class="autocomplete" style="width:300px;">
<input id="myInput" type="text" name="myCountry" placeholder="Country">
</div>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#myModal">
<i class="fa fa-plus"></i> Add Value
</button>
<!-- The Modal -->
<div class="modal" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">Add a New Value</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<!-- Modal body -->
<div class="modal-body">
<input id="inputNewValue" type="text" placeholder="Enter a new Country">
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="btnAddNew" data-dismiss="modal">Submit</button>
</div>
</div>
</div>
</div>
</form>
JavaScript script:
<script>
function autocomplete(inp, arr) {
/*the autocomplete function takes two arguments,
the text field element and an array of possible autocompleted values:*/
var currentFocus;
/*execute a function when focus on the text field*/
inp.addEventListener("focusin", function () {
var a, b, i, val = this.value;
/*close any already open lists of autocompleted values*/
closeAllLists();
if (!val) { arr = countries.slice(0, 5); }
/*create a DIV element that will contain the items (values):*/
a = document.createElement("DIV");
a.setAttribute("id", this.id + "autocomplete-list");
a.setAttribute("class", "autocomplete-items");
/*append the DIV element as a child of the autocomplete container:*/
this.parentNode.appendChild(a);
/*for each item in the array...*/
for (i = 0; i < arr.length; i++) {
/*check if the item starts with the same letters as the text field value:*/
if (arr[i].substr(0, val.length).toUpperCase() == val.toUpperCase()) {
/*create a DIV element for each matching element:*/
b = document.createElement("DIV");
/*make the matching letters bold:*/
b.innerHTML = "<strong>" + arr[i].substr(0, val.length) + "</strong>";
b.innerHTML += arr[i].substr(val.length);
/*insert a input field that will hold the current array item's value:*/
b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>";
b.innerHTML += "<span style='font-size: 1em; color: Tomato; float: right'><i class='fa fa-times'></i></span>"
/*execute a function when someone clicks on the item value (DIV element):*/
b.addEventListener("click", function (e) {
/*insert the value for the autocomplete text field:*/
inp.value = this.getElementsByTagName("input")[0].value;
/*close the list of autocompleted values,
(or any other open lists of autocompleted values:*/
closeAllLists();
});
b.getElementsByTagName("span")[0].addEventListener("click", function (e) {
e.stopPropagation();
var deletevalue = this.parentElement.getElementsByTagName("input")[0].value;
//delete item from the data array.
const index = countries.indexOf(deletevalue);
if (index > -1) {
countries.splice(index, 1);
}
//remove item from current
document.getElementsByClassName("autocomplete-items")[0].removeChild(this.parentElement);
//closeAllLists(e.target);
autocomplete(document.getElementById("myInput"), countries.sort());
});
a.appendChild(b);
}
}
});
/*execute a function when someone writes in the text field:*/
inp.addEventListener("input", function (e) {
var a, b, i, val = this.value;
/*close any already open lists of autocompleted values*/
closeAllLists();
if (!val) {return false;}
currentFocus = -1;
/*create a DIV element that will contain the items (values):*/
a = document.createElement("DIV");
a.setAttribute("id", this.id + "autocomplete-list");
a.setAttribute("class", "autocomplete-items");
/*append the DIV element as a child of the autocomplete container:*/
this.parentNode.appendChild(a);
/*for each item in the array...*/
for (i = 0; i < arr.length; i++) {
/*check if the item starts with the same letters as the text field value:*/
if (arr[i].substr(0, val.length).toUpperCase() == val.toUpperCase()) {
/*create a DIV element for each matching element:*/
b = document.createElement("DIV");
/*make the matching letters bold:*/
b.innerHTML = "<strong>" + arr[i].substr(0, val.length) + "</strong>";
b.innerHTML += arr[i].substr(val.length);
/*insert a input field that will hold the current array item's value:*/
b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>";
b.innerHTML += "<span style='font-size: 1em; color: Tomato; float: right'><i class='fa fa-times'></i></span>"
/*execute a function when someone clicks on the item value (DIV element):*/
b.addEventListener("click", function (e) {
/*insert the value for the autocomplete text field:*/
inp.value = this.getElementsByTagName("input")[0].value;
/*close the list of autocompleted values,
(or any other open lists of autocompleted values:*/
closeAllLists();
});
b.getElementsByTagName("span")[0].addEventListener("click", function (e) {
e.stopPropagation();
var deletevalue = this.parentElement.getElementsByTagName("input")[0].value;
//delete item from the data array.
const index = countries.indexOf(deletevalue);
if (index > -1) {
countries.splice(index, 1);
}
//remove item from current
document.getElementsByClassName("autocomplete-items")[0].removeChild(this.parentElement);
//closeAllLists(e.target);
autocomplete(document.getElementById("myInput"), countries.sort());
});
a.appendChild(b);
}
}
});
/*execute a function presses a key on the keyboard:*/
inp.addEventListener("keydown", function (e) {
var x = document.getElementById(this.id + "autocomplete-list");
if (x) x = x.getElementsByTagName("div");
if (e.keyCode == 40) {
/*If the arrow DOWN key is pressed,
increase the currentFocus variable:*/
currentFocus++;
/*and and make the current item more visible:*/
addActive(x);
} else if (e.keyCode == 38) { //up
/*If the arrow UP key is pressed,
decrease the currentFocus variable:*/
currentFocus--;
/*and and make the current item more visible:*/
addActive(x);
} else if (e.keyCode == 13) {
/*If the ENTER key is pressed, prevent the form from being submitted,*/
e.preventDefault();
if (currentFocus > -1) {
/*and simulate a click on the "active" item:*/
if (x) x[currentFocus].click();
}
}
});
function addActive(x) {
/*a function to classify an item as "active":*/
if (!x) return false;
/*start by removing the "active" class on all items:*/
removeActive(x);
if (currentFocus >= x.length) currentFocus = 0;
if (currentFocus < 0) currentFocus = (x.length - 1);
/*add class "autocomplete-active":*/
x[currentFocus].classList.add("autocomplete-active");
}
function removeActive(x) {
/*a function to remove the "active" class from all autocomplete items:*/
for (var i = 0; i < x.length; i++) {
x[i].classList.remove("autocomplete-active");
}
}
function closeAllLists(elmnt) {
/*close all autocomplete lists in the document,
except the one passed as an argument:*/
var x = document.getElementsByClassName("autocomplete-items");
for (var i = 0; i < x.length; i++) {
if (elmnt != x[i] && elmnt != inp) {
x[i].parentNode.removeChild(x[i]);
}
}
}
/*execute a function when someone clicks in the document:*/
document.addEventListener("click", function (e) {
closeAllLists(e.target);
});
}
/*An array containing all the country names in the world:*/
// var countries = ["Angola", "Anguilla", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Cambodia", "Cameroon", "Canada", "Dominica", "Dominican Republic", "Ecuador", "Egypt"];
var countries;
$.get("/Home/GetSelectItems", function (response) {
countries = response;
/*initiate the autocomplete function on the "myInput" element, and pass along the countries array as possible autocomplete values:*/
autocomplete(document.getElementById("myInput"), countries.sort());
})
document.getElementById("btnAddNew").addEventListener("click", function (e) {
var newvalue = document.getElementById("inputNewValue").value;
var itemindex = countries.indexOf(newvalue);
if (itemindex == -1) {
countries.push(newvalue);
}
autocomplete(document.getElementById("myInput"), countries.sort());
});
</script>
The result like this (when focus on the input element, it will show the Top 5 items):

Related

Unable to login to PayPal account from PayPal button

I have a paypal Smart Button on my WordPress site:
https://baranovich.org/pay-for-auction-purchase/
The code is from PayPal and I edited it a little because the input fields were not displaying properly.
When I use the button, everything seems fine, but then I can't pay by logging into another account.
It just doesn't log in. It doesn't give me an error either.
After I enter the email it goes back to here but I haven't logged in...
I tied clearing the cache, and used different browsers and PCs.
I checked out the answer https://stackoverflow.com/questions/53840252/unable-to-login-to-paypal-using-paypal-payment-button-localhost
But I don't understand why I'm even using the sandbox PayPal feature. I wanted a real button, not a sandbox test button...
How do I put a real PayPal button?
Here is the code I am using:
<style>
input {
border-top-style: hidden;
border-right-style: hidden;
border-left-style: hidden;
border-bottom-style: groove;
background-color: #eee;
}
</style>
<div id="smart-button-container">
<div style="text-align: center">
<label for="description">First and Last Name </label>
<span style="display:inline-block; width:10px;"></span>
<input type="text" name="descriptionInput" id="description" maxlength="127" value=""></div>
<p id="descriptionError" style="visibility: hidden; color:red; text-align: center;">Please enter a First and Last Name</p>
<div style="text-align: center"><label for="amount">Amount to Pay </label>
<span style="display:inline-block; width:10px;"></span>
<input name="amountInput" type="number" id="amount" value="" ><span> USD</span></div>
<p id="priceLabelError" style="visibility: hidden; color:red; text-align: center;">Please enter the full amount owed</p>
<div id="invoiceidDiv" style="text-align: center; display: none;"><label for="invoiceid">Auction Lot Number </label>
<span style="display:inline-block; width:10px;"></span>
<input name="invoiceid" maxlength="127" type="text" id="invoiceid" value="" ></div>
<p id="invoiceidError" style="visibility: hidden; color:red; text-align: center;">Please enter the Lot number</p>
<div style="text-align: center; margin-top: 0.625rem;" id="paypal-button-container"></div>
</div>
<script src="https://www.paypal.com/sdk/js?client-id=sb&enable-funding=venmo&currency=USD" data-sdk-integration-source="button-factory"></script>
<script>
function initPayPalButton() {
var description = document.querySelector('#smart-button-container #description');
var amount = document.querySelector('#smart-button-container #amount');
var descriptionError = document.querySelector('#smart-button-container #descriptionError');
var priceError = document.querySelector('#smart-button-container #priceLabelError');
var invoiceid = document.querySelector('#smart-button-container #invoiceid');
var invoiceidError = document.querySelector('#smart-button-container #invoiceidError');
var invoiceidDiv = document.querySelector('#smart-button-container #invoiceidDiv');
var elArr = [description, amount];
if (invoiceidDiv.firstChild.innerHTML.length > 1) {
invoiceidDiv.style.display = "block";
}
var purchase_units = [];
purchase_units[0] = {};
purchase_units[0].amount = {};
function validate(event) {
return event.value.length > 0;
}
paypal.Buttons({
style: {
color: 'gold',
shape: 'pill',
label: 'paypal',
layout: 'horizontal',
},
onInit: function (data, actions) {
actions.disable();
if(invoiceidDiv.style.display === "block") {
elArr.push(invoiceid);
}
elArr.forEach(function (item) {
item.addEventListener('keyup', function (event) {
var result = elArr.every(validate);
if (result) {
actions.enable();
} else {
actions.disable();
}
});
});
},
onClick: function () {
if (description.value.length < 1) {
descriptionError.style.visibility = "visible";
} else {
descriptionError.style.visibility = "hidden";
}
if (amount.value.length < 1) {
priceError.style.visibility = "visible";
} else {
priceError.style.visibility = "hidden";
}
if (invoiceid.value.length < 1 && invoiceidDiv.style.display === "block") {
invoiceidError.style.visibility = "visible";
} else {
invoiceidError.style.visibility = "hidden";
}
purchase_units[0].description = description.value;
purchase_units[0].amount.value = amount.value;
if(invoiceid.value !== '') {
purchase_units[0].invoice_id = invoiceid.value;
}
},
createOrder: function (data, actions) {
return actions.order.create({
purchase_units: purchase_units,
});
},
onApprove: function (data, actions) {
return actions.order.capture().then(function (orderData) {
// Full available details
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
// Show a success message within this page, e.g.
const element = document.getElementById('paypal-button-container');
element.innerHTML = '';
element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Or go to another URL: actions.redirect('thank_you.html');
});
},
onError: function (err) {
console.log(err);
}
}).render('#paypal-button-container');
}
initPayPalButton();
</script>
Sandbox buttons can only be logged into with a sandbox payer account from the developer dashboard.
Since you want a live button, you'll need to regenerate it when logged into your live PayPal account that you want to receive the payments. Alternatively, manually change the code that says client-id=sb to use a live client ID from a live REST App, which are created in the developer dashboard -> My Apps & Credentials.

How can I disalbe all button&input templately in blazor server-side?

Now I have a CSS3 animation(not a full-screen animation) to show on my page.
When the animation is playing, I wanna disable all the button&input.
After the animation is finished, then enable all the button&input.
How can I achieve this? I don't want to add a boolean "IsEnabled" in each void to achieve this.
Thank you.
You could listener the animationstart and animationend event. Then, in the animationstart event, disable the button by using the "disabled" property. In the animationend event, enable the buttons.
Sample code as below:
<style>
#myDIV {
margin: 25px;
width: 550px;
height: 100px;
background: orange;
position: relative;
font-size: 20px;
}
/* Chrome, Safari, Opera */
##-webkit-keyframes mymove {
from {
top: 0px;
}
to {
top: 200px;
}
}
##keyframes mymove {
from {
top: 0px;
}
to {
top: 200px;
}
}
</style>
<div id="myDIV" onclick="myFunction()">Click me to start the animation.</div>
<div class="btn_group">
<input type="button" value="Submite" onclick="alert('click')" class="btn btn-info" />
<input type="button" value="Crete" onclick="alert('crete')" class="btn btn-light" />
</div>
<script>
var x = document.getElementById("myDIV");
// Start the animation with JavaScript
function myFunction() {
x.style.WebkitAnimation = "mymove 4s 1"; // Code for Chrome, Safari and Opera
x.style.animation = "mymove 4s 1"; // Standard syntax
}
// Code for Chrome, Safari and Opera
x.addEventListener("webkitAnimationStart", myStartFunction);
x.addEventListener("webkitAnimationEnd", myEndFunction);
// Standard syntax
x.addEventListener("animationstart", myStartFunction);
x.addEventListener("animationend", myEndFunction);
function myStartFunction() {
this.innerHTML = "animationstart event occured - The animation has started";
this.style.backgroundColor = "pink";
//find the elements and disable them.
var elems = document.getElementsByClassName("btn");
for (var i = 0; i < elems.length; i++) {
elems[i].disabled = true
}
}
function myEndFunction() {
this.innerHTML = "animationend event occured - The animation has completed";
this.style.backgroundColor = "lightgray";
//find the elements and enable them.
var elems = document.getElementsByClassName("btn");
for (var i = 0; i < elems.length; i++) {
elems[i].disabled = false;
}
}
</script>
The output like this:
You can use javascript for this purpose.
In js you can get all elements by class and add disabled attribute to them:
document.getElementsByClassName("buttons-to-disable").disabled = true;
and to enable them:
document.getElementsByClassName("buttons-to-disable").disabled = false;
To call the js, you can use IJsRuntime

How to pass function to html

I'm new to vue js, so I have simple function to hide the progress bar created in methods, but doesn't seem to work, I'm wondering if I need to add event or bind it, I think it's something simple, but I can't figure it out.
methods: {
hideProgressBar: function() {
const hideProgress = document.querySelector(".progress-bar");
if (hideProgress) {
hideProgress.classList.add(hide);
} else {
hideProgress.classList.remove(hide);
}
}
}
.progress-bar {
height: 1rem;
color: #fff;
background-color: #f5a623;
margin-top: 5px;
}
.hide.progress-bar {
display: none;
}
<div class="progress-bar" role="progressbar"></div>
If you want to invoke the method when the page is loaded, add the following created option after your methods option
created: function(){
this.hideProgressBar()
}
otherwise if you want to invoke the method based on an event then you would need to add your event.
If you're using vue.js you'd want to use the inbuilt directives as much as you can.
This means you can avoid the whole hideProgressBar method.
<button #click="hideProgressBar = !hideProgressBar">Try it</button>
<div class="progress-bar" v-if="!hideProgressBar">
Progress bar div
</div>
And you script would have a data prop that would help you toggle the hide/show of the progress bar
data () {
return {
hideProgressBar: false
}
}
just try like this:
methods: {
hideProgressBar: function() {
var element = document.getElementsByClassName("progress-bar")[0];
if (element.classList.contains('hide')) {
element.classList.remove('hide');
} else {
element.classList.add('hide');
}
}
}
<div class="progress-bar">
Progress bar 1 div
</div>
<div class="progress-bar hide">
Progress bar 2 div
</div>
I have used two progress bar for demonstration. Initially,
The first progress bar doesn't contain the hide class so hide class will be added.
Then the second progress already has hide class so it will be removed
DEMO:
//hides first progress bar by adding hide class.
var element = document.getElementsByClassName("progress-bar")[0];
if (element.classList.contains('hide')) {
element.classList.remove('hide');
} else {
element.classList.add('hide');
}
//display second progress bar by remove hide class
var element = document.getElementsByClassName("progress-bar")[1];
if (element.classList.contains('hide')) {
element.classList.remove('hide');
} else {
element.classList.add('hide');
}
.progress-bar {
height: 1rem;
color: #fff;
background-color: #f5a623;
margin-top: 5px;
}
.hide.progress-bar {
display: none;
}
<div class="progress-bar">
Progress bar 1 div
</div>
<div class="progress-bar hide">
Progress bar 2 div
</div>

Vue JS code is not updating inside a loop

I'm stuck with a problem for a couple of days now and I've no idea what might be the solution. I've tried a couple, nothing worked.
I'm trying to enlarge a span text to fit the parent container width.
I've tried this.$forceUpdate();, didn't work.
I've also tried to pause the loop, but I found out later that that's not really possible in JS.
<template>
<span
ref="textRowRef"
v-bind:style="{fontSize: fontSize + 'px'}" >
{{ textRow }}</span>
</template>
// this is the VueJS code
var textRow = this.$refs.textRowRef;
var parentRow = textRow.parentElement;
for(var i = 0; i < 10; i++) {
this.fontSize += 10;
console.log(`Text Row in loop: ${textRow.clientWidth}`);
textRow = this.$refs.textRowRef;
}
console.log(`Text Row: ${textRow.clientWidth}`);
console.log(`Parent Row: ${parentRow.clientWidth}`);
Results in the console:
10 Text Row in loop: 48
Text Row: 48
Parent Row: 378
Here's my attempt.
I set fontSize to 72px then gradually reduce it by 1px until the text fits within the box. For such a simple example the performance is fine but I suspect that in the context of a larger page reducing it by 1px each time might prove too slow.
Each time the fontSize changes it triggers a re-render. The updated hook then measures the text to decide whether it needs to be shrunk even further.
If the text changes it resets the size to 72px and starts the process over again. I've not made any attempt to track the size of the parent element but it would also need to perform a reset if the width of that element changed.
I've added whitespace: nowrap to the <span> to ensure that its width will overflow the parent element. Otherwise the text would just wrap when the width reaches the edge of the parent element.
const MAX_FONT_SIZE = 72
new Vue({
el: '#app',
data () {
return {
fontSize: MAX_FONT_SIZE,
textRow: 'Initial value for the text'
}
},
watch: {
textRow: 'resetSize'
},
mounted () {
this.refreshSize()
},
updated () {
this.refreshSize()
},
methods: {
async refreshSize () {
await this.$nextTick()
const textRow = this.$refs.textRowRef
const parentRow = textRow.parentElement
if (textRow.offsetWidth > parentRow.clientWidth) {
this.fontSize = Math.max(8, this.fontSize - 1)
}
},
resetSize () {
if (this.fontSize === MAX_FONT_SIZE) {
this.refreshSize()
} else {
this.fontSize = MAX_FONT_SIZE
}
}
}
})
#app {
background: #ccc;
border: 1px solid #000;
margin: 20px;
width: 300px;
}
.text-span {
white-space: nowrap;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<span
ref="textRowRef"
class="text-span"
:style="{fontSize: fontSize + 'px'}"
>
{{ textRow }}
</span>
<br>
Edit: <input v-model="textRow">
</div>

Row Nested WebGrid with Expand/Collapse MVC4

I want a row nested webgrid. something like the one in the below link for normal gridview.
http://csharpdotnetfreak.blogspot.com/2012/06/nested-gridview-example-in-aspnet.html
I was able to find only column nested webgrids like the following:
formatting in razor nested webgrid
http://www.dreamincode.net/forums/topic/229962-mvc-3-webgrid-inside-a-webgrid/
is there any solution for this??
Now in the MVC framework, we have full control over HTML.So, we can create a view for show nested tabular data very easily. Visit How to Create Nested WebGrid with Expand/Collapse in ASP.NET MVC4 for the complete guide.
Suppose we have an OrderMaster and OrderDetails data and need to show order in a table and when click on the row will show order details...
Step 1. Create a ViewModel
public class OrderVM
{
public OrderMaster order { get; set; }
public List<OrderDetail> orderDetails { get; set; }
}
Step 2 : Write action for get View
public ActionResult List()
{
List<OrderVM> allOrder = new List<OrderVM>();
// here MyDatabaseEntities is our data context
using (MyDatabaseEntities dc = new MyDatabaseEntities())
{
var o = dc.OrderMasters.OrderByDescending(a => a.OrderID);
foreach (var i in o)
{
var od = dc.OrderDetails.Where(a => a.OrderID.Equals(i.OrderID)).ToList();
allOrder.Add(new OrderVM { order= i, orderDetails = od });
}
}
return View(allOrder);
}
Step 3: Create view with css and js code for collapse and expend
#model IEnumerable<MVCNestedWebgrid.ViewModel.OrderVM>
#{
ViewBag.Title = "Order List";
WebGrid grid = new WebGrid(source: Model, canSort: false);
}
<style>
/*Here I will write some css for looks good*/
th, td {
padding:5px;
}
th
{
background-color:rgb(248, 248, 248);
}
#gridT, #gridT tr {
border:1px solid #0D857B;
}
#subT,#subT tr {
border:1px solid #f3f3f3;
}
#subT {
margin:0px 0px 0px 10px;
padding:5px;
width:95%;
}
#subT th {
font-size:12px;
}
.hoverEff {
cursor:pointer;
}
.hoverEff:hover {
background-color:rgb(248, 242, 242);
}
.expand {
background-image: url(/Images/pm.png);
background-position-x: -22px;
background-repeat:no-repeat;
}
.collapse {
background-image: url(/Images/pm.png);
background-position-x: -2px;
background-repeat:no-repeat;
}
</style>
#model IEnumerable<MVCNestedWebgrid.ViewModel.OrderVM>
#{
ViewBag.Title = "Order List";
WebGrid grid = new WebGrid(source: Model, canSort: false);
}
<style>
/*Here I will write some css for looks good*/
th, td {
padding:5px;
}
th
{
background-color:rgb(248, 248, 248);
}
#gridT, #gridT tr {
border:1px solid #0D857B;
}
#subT,#subT tr {
border:1px solid #f3f3f3;
}
#subT {
margin:0px 0px 0px 10px;
padding:5px;
width:95%;
}
#subT th {
font-size:12px;
}
.hoverEff {
cursor:pointer;
}
.hoverEff:hover {
background-color:rgb(248, 242, 242);
}
.expand {
background-image: url(/Images/pm.png);
background-position-x: -22px;
background-repeat:no-repeat;
}
.collapse {
background-image: url(/Images/pm.png);
background-position-x: -2px;
background-repeat:no-repeat;
}
</style>
<div id="main" style="padding:25px; background-color:white;">
#grid.GetHtml(
htmlAttributes: new {id="gridT", width="700px" },
columns:grid.Columns(
grid.Column("order.OrderID","Order ID"),
grid.Column(header:"Order Date",format:(item)=> string.Format("{0:dd-MM-yyyy}",item.order.OrderDate)),
grid.Column("order.CustomerName","Customer Name"),
grid.Column("order.CustomerAddress","Address"),
grid.Column(format:(item)=>{
WebGrid subGrid = new WebGrid(source: item.orderDetails);
return subGrid.GetHtml(
htmlAttributes: new { id="subT" },
columns:subGrid.Columns(
subGrid.Column("Product","Product"),
subGrid.Column("Quantity", "Quantity"),
subGrid.Column("Rate", "Rate"),
subGrid.Column("Amount", "Amount")
)
);
})
)
)
</div>
#* Here I will add some jquery code for make this nested grid collapsible *#
#section Scripts{
<script>
$(document).ready(function () {
var size = $("#main #gridT > thead > tr >th").size(); // get total column
$("#main #gridT > thead > tr >th").last().remove(); // remove last column
$("#main #gridT > thead > tr").prepend("<th></th>"); // add one column at first for collapsible column
$("#main #gridT > tbody > tr").each(function (i, el) {
$(this).prepend(
$("<td></td>")
.addClass("expand")
.addClass("hoverEff")
.attr('title',"click for show/hide")
);
//Now get sub table from last column and add this to the next new added row
var table = $("table", this).parent().html();
//add new row with this subtable
$(this).after("<tr><td></td><td style='padding:5px; margin:0px;' colspan='" + (size - 1) + "'>" + table + "</td></tr>");
$("table", this).parent().remove();
// ADD CLICK EVENT FOR MAKE COLLAPSIBLE
$(".hoverEff", this).live("click", function () {
$(this).parent().closest("tr").next().slideToggle(100);
$(this).toggleClass("expand collapse");
});
});
//by default make all subgrid in collapse mode
$("#main #gridT > tbody > tr td.expand").each(function (i, el) {
$(this).toggleClass("expand collapse");
$(this).parent().closest("tr").next().slideToggle(100);
});
});
</script>
}