What is the easiest way to create a table where users can edit data directly (excel like) with minimum effort?
There are a lot of libraries and custom control but I am looking for something that is easy to implement and does not require a lot of coding.
In the controller create method for adding where you pass the parameter to with GET:
public bool EditData(int pkId, int statusID, string Comments)
{
// logic to edit
}
In your view add a table of data with your controls add the primary key to the ID of the control so that you can use it in jquery
<table class="table">
<tr>
<th>Status</th>
<th>Comments</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr class="searchable">
<td>
#Html.DropDownList("StatusID" + #Html.DisplayFor(modelItem => item.pkID), new SelectList(ViewBag.Status, "StatusID", "StatusTitle", item.StatusID), htmlAttributes: new { #class = "form-control", style = "width:100px" })
</td>
<td>
<textarea id="Comments_#Html.DisplayFor(modelItem => item.pkID)" rows="5" cols="20">#Html.DisplayFor(modelItem => item.Comments)</textarea>
</td>
<td>
<input type="button" class="btn btn-default" value="save" onclick='updateData("#Html.DisplayFor(modelItem => item.pkID)" )' />
</td>
</tr>
}
</table>
In the javascript add a call to the the edit method
function updateData(pkID) {
// the id of the drop down list is AssetEffectiveness + effectivnessid
var Status = $("#StatusID" + pkID).val();
var Comments= $("#Comments_" + pkID).val();
$.ajax({
url: "YOU_CONTROLLER_URL/EditData?pkId=" + pkID + "&statusID=" + Status + "&Comments=" + Comments,
cache: false
})
.done(function (data) {
if (data == 'True') {
// change bg of the tr
$("#Commects_" + pkID).closest('tr').css('background-color', '#eeffee');
// animate to indicate the save
$("#Commects_" + pkID).closest('tr').fadeOut(500).fadeIn(500);
// return color back after 2 seconds
setTimeout(function () { $("#Commects_" + pkID).closest('tr').css('background-color', ''); }, 1000);
}
else {
alert('not saved');
$("#Commects_" + pkID).closest('tr').css('background-color', '#ffcccc');
}
});
}
Related
So I was trying to follow this guide and the followup article.
But Before I started with the searching, sorting and filtering, I wanted to see if even the pages were working as intended.
Unfortunately they are not and I cant for the life of me figure it out why, I even went so far as to download his working example just to see if it was something with my browser. (To download his working example its at the top of the second article, I cant post more then 2 links)
Since his worked I compared his views, controllers and scripts side by side to mine and from what I can tell they mirror each other.
So I ended up copying my code somewhere else and pasted his into my project, changed the ActionLinks to reflect the naming conventions I used and left out the stuff I havent implemented yet (noted above). And it still do sent work.
When I run them side by side I get no errors in the console, they are loading the same scripts with the exception that I added jquery.unobstrusive-ajax.js as an attempt to correct it from searching for solutions, but it didnt help.
I have no idea what I'm doing wrong here :/
My Manage View - Correlates to his Home Index View
The only thing I really changed here is the action link
#{
ViewBag.Title = "Home Page";
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/bootstrap")
<script src="~/Scripts/ModalDialog.js"></script>
<style>
.testClass {
font-size: xx-large;
text-transform: capitalize;
}
</style>
<title>
Complete example of MVC pagination, filtering and sortig inside patial view with edit in modal dialog
</title>
</head>
<body style="padding-top:0">
<table style="width:100%;" border="1" cellspacing="0" cellpadding="0">
<tr>
<td style="" colspan="2">
<div id="logo" style="height:70px; background-color:rgba(86, 111, 111, 1);font: 1.5em Georgia, Times New Roman, Times, serif;">
Complete example of MVC pagination, filtering and sortig inside patial view with edit in modal dialog
</div>
<div id="navigation" style="background-color:#a4c2c2">
HOME
</div>
</td>
</tr>
<tr style="height:600px">
<td style="width:200px;background-color: #a4c2c2; vertical-align:top; padding-top:10px; padding-left:10px">
<div>
<ul>
<li>
#Html.ActionLink("Manage Assets", "MasterDetail", "Assets", new { }, new { id = "btnCustomers", #class = "btn btn-default btn-xs" })
</li>
</ul>
</div>
</td>
<td>
<div id="contentFrame" style="width:100%; height:600px; padding-top:10px; padding-left:10px" />
</td>
</tr>
</table>
</body>
</html>
<script type="text/javascript">
$(function () {
$.ajaxSetup({cache : false})
$('#btnCustomers').click(function () {
$('#contentFrame').mask("waiting ...");
$('#contentFrame').load(this.href, function (response, status, xhr) {
$('#contentFrame').unmask("waiting ...");
});
return false;
});
});
</script>
My MasterDetail View - Correlates to his Customers Index view
My table is setup different because i havent done everything he has yet
#using PagedList.Mvc
#model PagedList.IPagedList<Furst_Alpha_2._0.Models.Quantities>
#{
Layout = null;
}
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/bootstrap")
<script src="~/Scripts/ModalDialog.js"></script>
<h2>Inventory Management</h2>
<p>
#Html.ActionLink("Create New", "_Create", new { id = -1 }, new { btnName = "btnCreate", #class = "btn btn-default btn-xs" })
</p>
<table class="table">
<tr>
<th>
Category
</th>
<th>
Make
</th>
<th>
Model
</th>
<th>
Type
</th>
<th>
Length
</th>
<th>
Width
</th>
<th>
Height
</th>
<th>
Weight
</th>
<th>
Description
</th>
<th>
Rental Price
</th>
<th>
Number Of Techs
</th>
<th>
Total
</th>
<th>
In User
</th>
<th>
Availability
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Assets.Category.CategoryName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.Make.MakeName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.Model.ModelName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.Type.TypeName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.Length)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.Width)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.Height)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.Weight)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.RentalPrice)
</td>
<td>
#Html.DisplayFor(modelItem => item.Assets.NumTechsReq)
</td>
<td>
#Html.DisplayFor(modelItem => item.total)
</td>
<td>
#Html.DisplayFor(modelItem => item.InUse)
</td>
<td>
#Html.DisplayFor(modelItem => item.Availability)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.QuantityId }, new { btnName = "btnEdit", #class = "btn btn-default btn-xs" })
#Html.ActionLink("Delete", "Delete", new { id = item.QuantityId }, new { btnName = "btnDelete", #class = "btn btn-default btn-xs" })
</td>
</tr>
}
</table>
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of #Model.PageCount
<div id="myPager">
#Html.PagedListPager(Model, page => Url.Action("MasterDetail", new { page, OrderID = ViewBag.OrderID }))
</div>
<script type="text/javascript">
$(function () {
$.ajaxSetup({ cache: false });
setDialogLink($('a[btnName=btnCreate]'), 'Add New Asset', 500, 600, "contentFrame", "/Assets/MasterDetail");
setDialogLink($('a[btnName=btnEdit]'), 'Edit Customer', 500, 600, "contentFrame", "/Customers/Index");
setDialogLink($('a[btnName=btnDetails]'), 'Customer Details', 500, 600, "contentFrame", "/Customers/Index");
$('a[btnName=btnDelete]').click(function (e) {
e.preventDefault();
var confirmResult = confirm("Are you sure?");
if (confirmResult)
{
$('#contentFrame').mask("waiting ...");
$.ajax(
{
url: this.href,
type: 'POST',
data: JSON.stringify({}),
dataType: 'json',
traditional: true,
contentType: "application/json; charset=utf-8",
success:function(data)
{
if (data.success) {
$('#contentFrame').load("/Customers/Index");
}
else {
alert(data.errormessage);
}
$('#contentFrame').unmask("waiting ...");
},
error: function (data) {
alert("An error has occured!!!");
$('#contentFrame').unmask("waiting ...");
}
});
}
})
$("a[btnName=FilterCustomer]").click(function (e) {
e.preventDefault();
var search = $('input[name=search]').val();
this.href = this.href.replace('xyz', search);
$('#contentFrame').mask("waiting ...");
$.ajax({
url: this.href,
type: 'POST',
cache: false,
success: function (result) {
$('#contentFrame').unmask("waiting ...");
$('#contentFrame').html(result);
}
});
});
$(".SortButton").click(function (e) {
e.preventDefault();
$('#contentFrame').mask("waiting ...");
$.ajax({
url: this.href,
type: 'POST',
cache: false,
success: function (result) {
$('#contentFrame').unmask("waiting ...");
$('#contentFrame').html(result);
}
})
});
$('#myPager').on('click', 'a', function (e) {
e.preventDefault();
$('#contentFrame').mask("waiting ...");
$.ajax({
url: this.href,
type: 'GET',
cache: false,
success: function (result) {
$('#contentFrame').unmask("waiting ...");
$('#contentFrame').html(result);
}
});
});
});
</script>
My AssetsController Manage and MasterDetail methods which correlate to his HomeController Index and CustomerController Index methods respectively.
// GET: Assets
public ActionResult Manage()
{
return View();
}
// GET: MasterDetail
public ActionResult MasterDetail(int? page)
{
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>().FindById(User.Identity.GetUserId());
//ApplicationUser user = db.Users.First(u => u.Id == userr.Id);
var assets = db.Quantities.Where(a => a.VendorId == user.VendorId).OrderByDescending(a => a.AssetId);
int pageNumber = page ?? 1;
int pageSize = 3;
return PartialView(assets.ToPagedList(pageNumber, pageSize));
}
I am not too sure about what your problem is, but I THINK there I saw a problem in your code.
in MasterDetail.cshtml try replacing
#Html.PagedListPager(Model, page => Url.Action("_MasterDetail", new { page, OrderID = ViewBag.OrderID }))
with
#Html.PagedListPager(Model, page => Url.Action("MasterDetail", new { page, OrderID = ViewBag.OrderID }))
Url.Action expects an Action name, not a razor view name.
So last night while laying in bed stumped as to whats causing the problem I thought well maybe I should just look up alternatives. I saw some Ajax variations (that youll see commented out) and I ended up on this SO thread which ultimately lead me to a solution.
Below is the end result of my tinkering, I left commented code in from my attempts so you'll see other thigns I've tried. Other than this all I changed in the view was add a reference for a modal wait screen and then changed Html.ActionLink to a button. <input id="btnCustomers" type="button" value="Manage Assets" />
<script type="text/javascript">
$(function () {
$.ajaxSetup({cache : false})
$('#btnCustomers').click(function () {
//$('#contentFrame').mask("waiting ...");
waitingDialog.show("Please wait while we prepare your inventory ...");
$('#contentFrame').load('#Url.Action("MasterDetail","Assets")', function () {
setTimeout(function () {
waitingDialog.hide();
}, 1000);
});
//$.ajax({
// type: 'GET',
// url: '#Url.Content("~/Assets/MasterDetail")',
// data: -1,
// success: function (data) {
// $('#contentFrame').innerHtml = waitingDialog.hide();
//$('#contentFrame').load('#Url.Action("MasterDetail","Assets")');
// }
//})
//$('#contentFrame').load(this.href, function (response, status, xhr) {
// $('#contentFrame').unmask("waiting ...");
//});
return false;
});
});
</script>
I have a table on one of my pages using bootstrap for my responsive design. When on a small screen I want to move one of the columns below the other two. Is this possible?
as you can see there are 5 columns below when on desktop. While on small screens I want the note column to go below the other columns.
#foreach (var item in Model.TicketNotes.OrderBy(m => m.TicketNoteDate).Reverse())
{
<tr>
<td style="text-align: center">
#Html.DisplayFor(modelItem => item.TicketNoteDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.UserNote.FullName)
</td>
<td style="text-align: center" class="hidden-xs">
#if (item.PublicFlag == true)
{
<span>Public</span>
}
else
{
<span>Private</span>
}
</td>
<td>
#Html.DisplayFor(modelItem => item.Note)
</td>
<td>
<div>
#if (item.AttachmentName != null)
{
string path = System.Configuration.ConfigurationManager.AppSettings["MediaFolder"] + "/Tickets/" + ViewBag.ticketNumber + "/" + item.AttachmentName;
if (System.IO.File.Exists(path))
{
#Html.ActionLink(item.AttachmentName, "ViewAttachment", new { controller = "File", filePath = path, fileName = item.AttachmentName }, new { target = "_blank" });
}
}
</div>
</td>
#*<td>
#Html.ActionLink("Edit", "EditTicketNote", "TicketNote", null,
new { id = item.TicketNoteId, ticketNumber = Model.TicketNumber, returnUrl = Model.ReturnUrl, SearchCategory = Model.SearchCategory, SearchStatus = Model.SearchStatus,
SearchTechnician = Model.SearchTechnician, SearchUser = Model.SearchUser, page = ViewBag.page })
</td>*#
</tr>
Background: I'm pretty new to MVC & Knockout.js but I am trying to get up to speed on these technologies. I am using MVC 5 with EF6 and Knockout.JS 3.2.
I have a Detail view that pulls a "VoteAnswer" object using MVC based on the ID passed in the URL:
For example I can go to the url MyDomain/VoteAnswers/Details/1 and it will pull the information from my database correctly (It pulls a VoteAnswer with the ID of 1) and display in my Details view. However I am trying to hook-up my Knockout.js "VoteAnswer" ViewModel to function the same way and am having trouble.
Here is my Details View: (Note the #Html.DisplayFor(model => model.VoteAnswerId) etc works and displays the data from my Database.
#model AM_SPA_TestSite.Models.VoteAnswer
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Details</title>
<script src="~/KnockoutViewModels/VoteAnswers.js"></script>
</head>
<body>
<div>
<h4>VoteAnswer</h4>
<hr />
<table>
<tr>
<td>Id</td>
<td data-bind="text: id"></td>
</tr>
<tr>
<td>Display Text</td>
<td data-bind="text: isActive"></td>
</tr>
<tr>
<td>IsActive</td>
<td data-bind="text: displayText"></td>
</tr>
</table>
<table>
<tr>
<td>Id</td>
<td><input type="text" data-bind="value: id" /></td>
</tr>
<tr>
<td>Display Text</td>
<td><input type="text" data-bind="value: displayText" /></td>
</tr>
<tr>
<td>IsActive</td>
<td><input type="text" data-bind="value: isActive" /></td>
</tr>
</table>
<table>
<tr>
<td>Id</td>
<td>#Html.DisplayFor(model => model.VoteAnswerId)</td>
</tr>
<tr>
<td>Display Text</td>
<td>#Html.DisplayFor(model => model.DisplayText)</td>
</tr>
<tr>
<td>IsActive</td>
<td>#Html.DisplayFor(model => model.IsActive)</td>
</tr>
</table>
</div>
Here is my Knockout.Js ViewModel
// VoteAnswer ViewModel
var VoteAnswerVM = {
id: ko.observable(),
displayText: ko.observable(),
isActive: ko.observable(),
SaveVoteAnswer: function () {
$.ajax({
url: '/VoteAnswers/Create',
type: 'post',
dataType: 'json',
data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
},
error: function (err) {
if (err.responseText == "Creation Failed")
{ window.location.href = '/VoteAnswers/Index/'; }
else {
alert("Status:" + err.responseText);
window.location.href = '/VoteAnswers/Index/';;
}
},
complete: function () {
window.location.href = '/VoteAnswers/Index/';
}
});
}
};
//Go
$(document).ready(function () {
//initialize and create new VoteAnswerVM by URL value here?
ko.applyBindings(VoteAnswerVM);
});
I know what I am missing is initializing the ViewModel with the ID of 1, but I was thinking the MVC model already has the data and the knockout.js SHOULD map to that data without manually initializing by sending a request to the database again. What am I missing? thanks.
EDIT: Added solution below. I'm not sure I am settled on this approach but here it is. Updated the controller to ONLY return a view and not query the DB. (otherwise I would have two database calls for the same data.
// GET: VoteAnswers/Details/5
public ViewResult Details(int? id)
{
return View();
}
Added an API Controller that does query the DB.
// GET: api/VoteAnswers/5
[ResponseType(typeof(VoteAnswer))]
public async Task<IHttpActionResult> GetVoteAnswer(int id)
{
VoteAnswer voteAnswer = await db.VoteAnswers.FindAsync(id);
if (voteAnswer == null)
{
return NotFound();
}
return Ok(voteAnswer);
}
In my View (.cshtml file) I reference my knockout.js ModelView, View is Below:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Details</title>
<script src="~/KnockoutViewModels/VoteAnswers.js"></script>
</head>
<body>
<div>
<h4>VoteAnswer</h4>
<hr />
<table>
<tr>
<td>Id</td>
<td data-bind="text: VoteAnswerId"></td>
</tr>
<tr>
<td>Display Text</td>
<td data-bind="text: IsActive"></td>
</tr>
<tr>
<td>IsActive</td>
<td data-bind="text: DisplayText"></td>
</tr>
</table>
<table>
<tr>
<td>Id</td>
<td><input type="text" data-bind="value: VoteAnswerId" /></td>
</tr>
<tr>
<td>Display Text</td>
<td><input type="text" data-bind="value: DisplayText" /></td>
</tr>
<tr>
<td>IsActive</td>
<td><input type="text" data-bind="value: IsActive" /></td>
</tr>
</table>
</div>
<div id="error"></div>
</body>
</html>
Updated My ViewModel script to access the Database based on the URL ID.
// VoteAnswer ViewModel
var VoteAnswer = function () {
var self = this;
self.VoteAnswerId = ko.observable();
self.DisplayText = ko.observable();
self.IsActive = ko.observable();
self.SaveVoteAnswer = function () {
$.ajax({
url: '/VoteAnswers/Create',
type: 'post',
dataType: 'json',
data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
},
error: function (err) {
if (err.responseText == "Creation Failed")
{ window.location.href = '/VoteAnswers/Index/'; }
else {
alert("Status:" + err.responseText);
window.location.href = '/VoteAnswers/Index/';;
}
},
complete: function () {
window.location.href = '/VoteAnswers/Index/';
}
});
}
self.load = function (id) {
if (id != 0) {
$.ajax({
url: '/api/VoteAnswers/' + id,
type: 'get',
data: ko.toJSON(this),
contentType: 'application/json',
success: function(data) {
self.VoteAnswerId = ko.observable(data.voteAnswerId);
self.DisplayText = ko.observable(data.displayText);
self.IsActive = ko.observable(data.isActive);
ko.applyBindings(self);
},
error: function(err) {
if (err.responseText == "Creation Failed") {
window.location.href = '/VoteAnswers/Index/';
} else {
$("#error").text("Status:" + err.responseText);
//window.location.href = '/VoteAnswers/Index/';;
}
},
complete: function() {
//window.location.href = '/VoteAnswers/Index/';
}
});
} else {
window.location.href = '/VoteAnswers/Index/';
}
}
};
function GetURLParameter() {
var sPageUrl = window.location.href;
var indexOfLastSlash = sPageUrl.lastIndexOf("/");
if (indexOfLastSlash > 0 && sPageUrl.length - 1 != indexOfLastSlash)
return sPageUrl.substring(indexOfLastSlash + 1);
else
return 0;
}
//Go
$(document).ready(function () {
//initialize and create new VoteAnswerVM by URL value here?
var viewModel = new VoteAnswer();
viewModel.load(GetURLParameter());
});
I hope I understood you correctly, if my answer is all wrong for your question let me know where I went wrong.
First thing to realize is, if you bind a KO observable to an input field, knockout will not look at the initial value of that input field and store it in the observable. It will do the opposite: it will look at the current value in the observable and store it in the input field's value. In your case, the observable are initialized without a value, which in JavaScript means the value undefined. So if you bind your observables to the fields you've filled with Razor/MVC viewmodel, you'll immediatly overwrite those values with the empty values stored in your observables.
There IS a way to fill your Knockout model with your data through Razor, but it involves inline JavaScript and is a bad practise for a number of reasons (I will elaborate on this on request).
The best way to do this is to separate your views from your data: Don't inject the MVC viewmodel into the view, but make a separate endpoint that returns JSON and return the data there (this endpoint will receive the ID parameter instead of the view). The JSON endpoint is called from JavaScript and can be used to fill your model with the correct values.
Upsides: separation of concerns, possibility to enable view caching for a more responsive frontend, no need to use razor syntax, or even worse, combine it with inline JS. All your binding of data to the UI will happen through Knockout. I learned this myself because we also started out using razor, but on the long run this solution wasn't feasible for a big project. We never regretted the switch to always getting the data from separate JSON endpoints.
If you are unsure on how to do this I can write some pseudocode to illustrate the idea.
These may be the possible solutions
1.) data_bind is wrongly used
<td><input type="text" data-bind="value: isActive" /></td> // which is wrong
<td><input type="text" data_bind="value: isActive" /></td> //data_bind is wrongly used
2.)
If still problem exists you may try this syntax
#Html.DisplayFor(model => model.IsActive, new { data_bind = "value:IsActive" });
If you find still something missing please provide some detail info .
I am new in knockout and asp.net mvc both.
I am trying to Insert update delete data in database with knockout. My knockout model is
function City(data) {
this.CityId = ko.observable(data.CityId);
this.CityName = ko.observable(data.CityName);
}
function CityViewModel() {
var self = this;
self.Citys = ko.observableArray([]);
self.CityId = ko.observable();
self.CityName = ko.observable();
self.selectedCity = ko.observable();
// self.City = ko.observable();
selectCity = function (item) {
self.selectedCity(item);
}
//load
loadCitys = function () {
$.getJSON("/Admin/GetCitys", {}, function (result) {
var mappedCitys = ko.utils.arrayMap(result.Data, function (item) {
return new City(item);
});
self.Citys([]);
self.Citys.push.apply(self.Citys, mappedCitys);
});
}
//edit
EditCity = function (item) {
//what need to do here
// is it possible to fill the hidden fild and the text box ??
}
//save
SaveCity = function (item) {
City = new City(item);
$.ajax({
type: "POST",
url: "/Admin/SaveCity",
data: ko.toJSON({ City: City }),
contentType: "application/json",
success: function (result) {
if (result.Edit) {
City.CityId = result.Success;
City.CityName = item.CityName;
self.Citys.push(City);
toastr.success('City Information Save Successfully', 'Success');
}
else if (result.Edit == false) {
toastr.success('City Information Update Successfully', 'Success');
}
else {
toastr.error('There is an error please try again later', 'Errror');
}
}
});
}
//delete
DeleteCity = function (City) {
$.ajax("/Admin/DeleteCity", {
data: ko.toJSON({ CityId: City.CityId }),
type: "POST", contentType: "application/json",
success: function (result) {
if (result.Success) {
self.Citys.remove(City);
toastr.success('City Remove Successfully', 'Success');
}
else {
alert("Error..");
}
}
});
}
}
(function () {
ko.applyBindings(new CityViewModel, document.getElementById("Citys"));
loadCitys();
});
And my Html codes are
<table class="table table-striped">
<thead>
<tr>
<th>City Id</th>
<th>City Name</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: $root.Citys">
<tr data-bind="click: selectCity">
<td><span data-bind="text:CityId"></span></td>
<td><span data-bind="text:CityName"></span></td>
<td><button data-bind="click: EditCity" class="btn btn-primary">Edit</button></td>
<td><button data-bind="click: DeleteCity" class="btn btn-danger">Delete</button></td>
</tr>
</tbody>
</table>
<fieldset>
<legend>Add new / Edit City</legend>
<label>City name</label>
<input type="hidden" data-bind="value: CityId" />
<input type="text" data-bind="value: CityName" placeholder="Type city nameā¦">
<button type="submit" data-bind="click: SaveCity" class="btn">Submit</button>
</fieldset>
With this codes I can get data form database display them successfully in my view file,
I delete the data from database, and I also can Insert data to database but here is a problem I can save data only 1st time when I change the textbox value (without page refresh) and try to save city information then it say (in Firebug on my javascript code):
TypeError: City is not a constructor
City = new City(item);
My question is what have I done wrong in this codes, and I am trying to fill the textbox and the hidden field when edit button click, how can I do this?
Thanks in advance.
There are a number of faults with your javascript, including:
The methods on your viewmodel, such as SaveCity, DeleteCity, EditCity are all being defined without the 'this/self' prefixes, therefore they are being added to the global namespace.
In your SaveCity method, your are assigning a new instance of the City class to a variable called 'City', therefore destroying the City class. It will work the first time, but any other attempts to create an instance of a City will yield an exception.
Anyway, this should be a working version of your script and HTML without the ajax stuff. You will need to adapt that yourself. I have also created a working JsFiddle here..
function City(data) {
this.CityId = ko.observable(data.CityId);
this.CityName = ko.observable(data.CityName);
}
function CityViewModel() {
var self = this;
self.Citys = ko.observableArray([]);
self.SelectedCity = ko.observable();
self.EditingCity = ko.observable(new City({CityId: null, CityName: ''}));
self.EditCity = function(city){
self.EditingCity(new City(ko.toJSON(city)));
};
//load
self.loadCitys = function () {
self.Citys().push(new City({CityId: '1245', CityName: 'Los Angeles'}));
self.Citys().push(new City({CityId: '45678', CityName: 'San Diego'}));
};
//save
self.SaveCity = function () {
var city = self.EditingCity();
if(city.CityId()){
var cityIndex;
for(var i = 0; i < self.Citys().length; i++) {
if(self.Citys()[i].CityId() === city.CityId()) {
cityIndex = i;
break;
}
}
self.Citys[cityIndex] = city;
}
else{
city.CityId(Math.floor((Math.random()*1000000)+1));
self.Citys.push(city);
}
self.EditingCity(new City({CityId: null, CityName: ''}));
}
//delete
self.DeleteCity = function (city) {
self.Citys.remove(city);
};
}
var viewModel = new CityViewModel();
viewModel.loadCitys();
ko.applyBindings(viewModel, document.getElementById("Citys"));
HTML
<table class="table table-striped">
<thead>
<tr>
<th>City Id</th>
<th>City Name</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: Citys">
<tr data-bind="click: $root.SelectedCity">
<td><span data-bind="text:CityId"></span></td>
<td><span data-bind="text:CityName"></span></td>
<td><button data-bind="click: $root.EditCity" class="btn btn-primary">Edit</button></td>
<td><button data-bind="click: $root.DeleteCity" class="btn btn-danger">Delete</button></td>
</tr>
</tbody>
</table>
<fieldset data-bind='with:EditingCity'>
<legend>Add new / Edit City</legend>
<label>City name</label>
<input type="hidden" data-bind="value: CityId" />
<input type="text" data-bind="value: CityName" placeholder="Type city name" />
<button type="submit" data-bind="click: $root.SaveCity" class="btn">Submit</button>
</fieldset>
I have a html table generated in my view, does anyone know of any helpers available that I could use so that one of the fields could be edited in-line.
View:
<table>
<caption>Configuration values for current management group</caption>
<thead>
<tr>
<th scope="col">Device Type</th>
<th scope="col">Section</th>
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col">Operation</th>
</tr>
</thead>
<tbody>
#foreach (var param in Model.ParamData)
{
<tr>
<td>#param.DeviceType</td>
<td>#param.Group</td>
<td>#param.Name</td>
<td>#param.Value</td>
<td>#(param.IsMerge ? "Merge" : "Delete")</td>
</tr>
}
</tbody>
</table>
As you can see there is nothing special here, I would like an edit column that would work in a similar way to a web forms gridview. The only field to be edited would be value, and it would always be a textbox.
Im sure people must have done this before but the only example Ive seen on line was for mvc 1.
I could knock something up using jquery but am sure there must be loads of examples already and dont want to re-invent the wheel.
Ive done it myself, if anyones interested:
#foreach (var param in Model.ParamData)
{
<tr>
<td>#param.DeviceType</td>
<td>#param.Group</td>
<td>#param.Name</td>
<td>
<div class="#("ViewValueDiv_" + param.ParamaterValueId)">
#param.Value
</div>
<div class="#("EditValueDiv_" + param.ParamaterValueId)" style="display:none;">
<input type="text" name="#("EditValue_" + param.ParamaterValueId)" value="#param.Value" class="#("Input_" + param.ParamaterValueId)" />
</div>
</td>
<td>#(param.IsMerge ? "Merge" : "Delete")</td>
<td>
<div class="#("EditButtonDiv_" + param.ParamaterValueId)">
<input type="button" value="Edit" class="EditButton" Id="#param.ParamaterValueId" />
</div>
<div class="#("UpdateCancelButtonDiv_" + param.ParamaterValueId)" style="display:none;">
<input type="button" value="Update" class="UpdateButton" id="#("U" + param.ParamaterValueId)" />
<input type="button" value="Cancel" class="CancelButton" id="#("C" + param.ParamaterValueId)" />
</div>
</td>
</tr>
}
$(document).ready(function () {
$(".EditButton").click(function () {
var id = $(this).attr('id');
$(".ViewValueDiv_" + id).hide();
$(".EditValueDiv_" + id).show();
$(".EditButtonDiv_" + id).hide();
$(".UpdateCancelButtonDiv_" + id).show();
oldvalue = $(".Input_" + id).val();
});
$(".CancelButton").click(function () {
var id = $(this).attr('id').substr($(this).attr('id').indexOf("C") + 1);
$(".ViewValueDiv_" + id).show();
$(".EditValueDiv_" + id).hide();
$(".EditButtonDiv_" + id).show();
$(".UpdateCancelButtonDiv_" + id).hide();
$(".Input_" + id).val(oldvalue);
});
$(".UpdateButton").click(function () {
var id = $(this).attr('id').substr($(this).attr('id').indexOf("U") + 1);
NewValue = $(".Input_" + id).val();
if (NewValue) {
$.ajax({
url: "/Terminals_configuration/UpdateConfigValue",
data: { valueId: id, newValue: NewValue },
dataType: "json",
type: "POST",
error: function () {
alert("An error occurred.");
},
success: function (data) {
$(".ViewValueDiv_" + id).show();
$(".EditValueDiv_" + id).hide();
$(".EditButtonDiv_" + id).show();
$(".UpdateCancelButtonDiv_" + id).hide();
$(".ViewValueDiv_" + id).html(NewValue);
}
});
} else {
alert("You didn't supply a new value");
}
});
});