I have an ASP.NET MVC 4 application. There is a razor view in my application that works with List type as a model.
#model List<MeetingLog.Models.UserModel>
#{
Layout = null;
}
.
.
.
I am iterating through Model variable like this:
#foreach (var item in Model)
{
<tr>
<td>
#item.Name
</td>
<td>
#item.Surname
</td>
<td>
#Html.ActionLink("Click", "SavePerson", "Meeting", new {model = item, type = "coordinator"}, null)
#Html.ActionLink("Click", "SavePerson", "Meeting", new {model = item, type = "participant"}, null)
</td>
</tr>
}
I need to pass two variables to SavePerson action with action links. First one is current UserModel, and second one is string variable named type. But in my action first parameter comes through as null. But string parameter comes correctly. How can I achieve this?
I use ajax calls for this
$('.btnSave').on('click', function(){
$.ajax({
url: '#(Url.Action("SavePerson", "Meeting"))',
type: 'post',
data: {
Value1: 'Value1',
Value2: 'Value2'
},
success: function (result) {
alert('Save Successful');
}
});
});
and put the call in a button click or a link click if you want with href = # hopefully this helps.
You can not pass instances complex types through querystring. This is the way to use it:
#Html.ActionLink("Click", "SavePerson", "Meeting", new {x = item.x, y = item.y, ..., type = "participant"}, null)
You actually can, it's just a little weird, when you are coding the passed values (new{})
what you have to do is pass it as a new object that you are constructing so it ends up being:
#Html.ActionLink("Link Name", "LinkActionTarget", new Object{model = item, type ='Coordinator'}
Where Object is the name of your object, and model and type are attributes of that object
Related
I would like to send a dynamic table back to the controller by using its model.
Following situation. On the view I am creating a table with javascript which allows the users to insert different values. These values are picked from pre-defined comboboxes:
$(document).ready(function () {
$('#btn_insert').on('click', function ()
var table=document.getElementById("table_computerimport");
var table_len=(table.rows.length)-1;
var row = table.insertRow(table_len).outerHTML="<tr id='row"+table_len+"'><td id='computername_row"+table_len+"'>"+computername+"</td><td id='uuid_row"+table_len+"'>"+"HERE UUID"+"</td><td id='collection_row"+table_len+"'>"+" HERE Collection Name"+"</td><td><input type='button' value='Delete' class='delete' onclick='delete_row("+table_len+")'></td></tr>";
});
This is working fine, so that at least the table is created with n entries.
<div>
<button type="button" id="btn_insert" class="btn btn-primary">Insert</button>
<div >
<table class="table" id="table_computerimport">
<thead>
<tr>
<th>Hostname</th>
<th>MacAddress</th>
<th>GUID</th>
</tr>
</thead>
<tr>
</tr>
</table>
</div>
</div>
These entries I would like to pass back to the controller by using its model:
public IEnumerable<ComputerImportModel> TableComputerImport {get; set;}
#model ViewModels.ComputerImportViewModel
So I don’t want to loop though the list of objects and publish them in the view, I would like to create n records in one view at once and pass these back to the controller by using the model TableComputerImport.
I guess in Javascript somehow I have to say something like this: input asp-for="ComputerImport.hostname"
Update,
ok, I think I "solved" it. What I did is that I created an array with javascript out of the table and passed this by ajax to the controller. It was important that the array has the name attributes like the class does so that I can use this object in the constructor as a well defined object.
As I mentioed, I had to use an array which I send by AJAX to the controller. Because of the identical names in the table and in the model, everything goes its correct way:
var singlecomputerimport = [];
var headers = [];
$('#table_computerimport th').each(function(index, item) {
headers[index] = $(item).html();
});
$('#table_computerimport tr').has('td').each(function() {
var arrayItem = {};
$('td', $(this)).each(function(index, item) {
if(headers[index] !== undefined){
//console.log($(item));
//arrayItem[headers[index].toLowerCase()] = $(item).html();
arrayItem[headers[index].toLowerCase()] = $(item).attr('value');
}
});
singlecomputerimport.push(arrayItem);
});
I have been able to bring back the data from the card table I want based on the following SQL Query in my controller:
SQLstatement = string.Format("select * from cards, cardcollections where isowned = 1 and cards.cardid = cardcollections.cardid and collectionid = {0}", v.CollectionID);
var CardList = db.Cards.SqlQuery(SQLstatement);
return View(CardList.ToPagedList(pageNumber, pageSize));
If I reference just the Cards model it shows the card listing without the extra data I want. I want to be able to display one column from card collections table (NumberOfCopies) in the same view as though it were included in the first table. If I run the query in SQL Server Management Studio, it brings back both tables worth of data by appending the columns of cardcollections to the cards table.
I made a view but cannot get it to pass correctly. I get this error:
The model item passed into the dictionary is of type 'PagedList.PagedList`1[MTG.Models.Card]', but this dictionary requires a model item of type 'PagedList.IPagedList`1[MTG.Models.ViewCardCollectionView]'.
I understand that I am not passing the ViewCardCollectionView because of the way I have the CardList variable set. I don't know how to do the query I want while using the correct ViewModel.
#model IPagedList<MTG.Models.ViewCardCollectionView>
#foreach (var card in Model) {
<tr>
<td>
#Html.ActionLink("Edit", "Edit", new { id=card.Cards.CardID }) |
#Html.ActionLink("Details", "Details", new { id=card.Cards.CardID }) |
#Html.ActionLink("Delete", "Delete", new { id=card.Cards.CardID }) |
#Html.DisplayFor(modelItem => card.CardCollections.NumberofCopies)
</td>
<td>
<b>#Html.DisplayFor(modelItem => card.Cards.Title)</b><br />
#Html.DisplayFor(modelItem => card.Cards.MainType.Title) - #Html.DisplayFor(modelItem => card.Cards.SubType.Title) #Html.DisplayFor(modelItem => card.Cards.AdditionalType)<br />
AND SO ON...
My ViewModel is:
public class ViewCardCollectionView
{
public Card Cards { get; set; }
public CardCollection CardCollections { get; set; }
I have tried many variations on querying in the controller and trying to bring the viewmodel back in the return View() but to no avail. Any advice would be greatly appreciated. :)
Honestly, the easiest way to handle this would be to familiarize yourself with the Entity Framework and start using that. You're not only writing a lot of excess code you don't have to, but you're opening up your application to SQL Injection attacks by manually building SQL statements based on user input (I'm not sure where v.CollectionID comes from but I would assume somewhere in the app there's a security hole even if it's not right there).
I was able to get this to work by changing the controller to the following:
var viewModel = from c in db.Cards
join j in db.CardCollections on c.CardID equals j.CardID
where (j.IsOwned == true) && (j.CollectionID == v.CollectionID)
select new ViewCardCollectionView { Cards = c, CardCollections = j };
return View(viewModel);
I'm trying to create a single page editor for data records using ASP.NET MVC 4 and KnockoutJS. It is fairly straightforward with a table showing the records and a form to edit individual records.
When clicking 'Edit' to edit a record the form updates and the data is persisted to the database without problem. There are two issues after this:
The record being edited does not update in the table after saving (i.e. the observables do not update)
The controls containing the record being edited do not clear after saving.
I have no idea how to solve (1). For (2) is there some way of writing a generic extension method or function to clear ANY form after Knockout has finished with it. I could do it with JQuery reasonably easily but I may be missing something that Knockout can do already.
The code for the page is as below:
#model IEnumerable<SiteDto>
#{
ViewBag.Title = "Index";
}
<h2>Sites</h2>
<table>
<caption>Sites</caption>
<thead>
<tr>
<th>Name</th>
<th>Link</th>
<th>Url</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: sites">
<tr>
<td><span data-bind="text: id"></span></td>
<td><span data-bind="text: name"></span></td>
<td><span data-bind="text: url"></span></td>
<td><button data-bind="click: $parent.selectItem">Edit</button></td>
</tr>
</tbody>
</table>
<div data-bind="with: selectedItem">
<table>
<caption data-bind="text: name"></caption>
<tbody>
<tr>
<td><input data-bind="value: id" /></td>
</tr>
<tr>
<td><input data-bind="value: url" /></td>
</tr>
<tr>
<td><input data-bind="value: name" /></td>
</tr>
</tbody>
</table>
<button data-bind="click: save">Save</button>
</div>
<script type="text/javascript">
function viewModel() {
var sites = ko.observableArray(#Html.Raw(Model.ToJson()));
var selectedItem = ko.observable();
selectItem = function (s) {
selectedItem(s);
};
save = function () {
alert(ko.toJSON(selectedItem));
$.ajax({
url: "/Home/Save",
type: "POST",
data: ko.toJSON(selectedItem),
contentType: "application/json",
dataType: "json",
success: function(result) {
alert(result);
},
error: function() {
alert("fail");
}
});
};
return {
sites: sites,
selectedItem: selectedItem,
selectItem: selectItem,
save: save
}
}
ko.applyBindings(viewModel);
</script>
I'll answer your points one at a time, since they are not really related.
1) The issue here is, you take your ASP.NET MVC model, and put it in an observableArray. The thing is, an observableArray will update the UI if items are added, deleted or swapped around, but it will not notify the UI of changes to a single item. So even though you're really editing the row correctly, the UI will never know. The ideal solution would be to not simple inject your MVC model into an observableArray, but to map the model to a datastructure where the editable properties of an item (id, url, name) are made observable. Untested demonstration code:
var rawSites = #Html.Raw(Model.ToJson()),
sites = ko.observableArray(rawSites.map(function (rawSite) {
return {
id: ko.observable(rawSite.id),
url: ko.observable(rawSite.url),
name: ko.observable(rawSite.name)
};
}));
Edit: My original answer suggested a second approach that 'hacked' a UI update by removing the edited item from the observableArray and re-adding it. #Tomalak made a better suggestion in the comments: use valueHasMutated() on the item instead. The result is the same but it's much less hacky. Note that the above solution is in my opinion still preferable because it will perform better (less UI reflow necessary), and it is more robust when you later add more functionality to this code.
2) Depends a bit on what you want. Do you want the edit-form to stay visible or to disappear? You're already using a with: selectedItem binding which makes the disappear-behavior very easy: Just call selectItem(null) from your save success-callback. If you want the form to stay visible all the time, and just clear the fields, I guess the following approach would work:
function viewModel() {
var sites = ko.observableArray(#Html.Raw(Model.ToJson()));
var originalItem = null;
var selectedItem = {
id: ko.observable(),
url: ko.observable(),
name: ko.observable()
};
var selectItem = function (s) {
// This function now copies the properties instead of using the item itself
selectedItem.id(ko.unwrap(s.id));
selectedItem.url(ko.unwrap(s.url));
selectedItem.name(ko.unwrap(s.name));
// Get a reference to s so we can update it when we are done editing
originalItem = s;
};
var resetSelectedItem = function () {
// Clear the form and reset the reference we held earlier
selectItem({
id: null,
url: null,
name: null
});
originalItem = null;
};
save = function () {
alert(ko.toJSON(selectedItem));
$.ajax({
url: "/Home/Save",
type: "POST",
data: ko.toJSON(selectedItem),
contentType: "application/json",
dataType: "json",
success: function(result) {
alert(result);
// Done editing: update the item we were editing
originalItem.id(selectedItem.id());
originalItem.url(selectedItem.url());
originalItem.name(selectedItem.name());
// Clear the form
resetSelectedItem();
},
error: function() {
alert("fail");
// Clear the form
resetSelectedItem();
}
});
};
return {
sites: sites,
selectedItem: selectedItem,
selectItem: selectItem,
save: save
}
}
I need to have multiple radio button groups in my form like this:
I know it's simply done by specifying the same "name" html attribute for each group.
HOWEVER
MVC doesn't let you specify your own name attribute when using html helper like this:
#Html.RadioButtonFor(i => item.id, item.SelectedID, new { Name = item.OptServiceCatId })
Because it looks at each tag's "name" attribute (not "id") to map/bind the form to the model which the controller receives, etc.
Some said that specifying each with the same "GroupName" attribute will solve the problem, but it didn't work either.
So, is there any way which works ?
EDIT:
Here's my view (simplified):
#model Service_Provider.ViewModels.SelectOptServicesForSubServiceViewModel
#foreach (var cat in Model.OptServices)
{
//A piece of code & html here
#foreach (var item in cat.OptItems.Where(i => i.MultiSelect == false))
{
#Html.RadioButtonFor(i => item.id, item.SelectedID, new { GroupName = item.OptServiceCatId })
<br />
}
}
NOTE:
My model is a List<OptServices>:
public List<OptServices> Cats {get; set;}
And OptServices has a List of OptItems inside:
public class OptServices
{
//a few things
public List<OptItems> Items {get; set;}
}
all you need is to tie the group to a different item in your model
#Html.RadioButtonFor(x => x.Field1, "Milk")
#Html.RadioButtonFor(x => x.Field1, "Butter")
#Html.RadioButtonFor(x => x.Field2, "Water")
#Html.RadioButtonFor(x => x.Field2, "Beer")
Ok here's how I fixed this
My model is a list of categories. Each category contains a list of its subcategories.
with this in mind, every time in the foreach loop, each RadioButton will have its category's ID (which is unique) as its name attribue.
And I also used Html.RadioButton instead of Html.RadioButtonFor.
Here's the final 'working' pseudo-code:
#foreach (var cat in Model.Categories)
{
//A piece of code & html here
#foreach (var item in cat.SubCategories)
{
#Html.RadioButton(item.CategoryID.ToString(), item.ID)
}
}
The result is:
<input name="127" type="radio" value="110">
Please note that I HAVE NOT put all these radio button groups inside a form. And I don't know if this solution will still work properly in a form.
Thanks to all of the people who helped me solve this ;)
I fixed a similar issue building a RadioButtonFor with pairs of text/value from a SelectList. I used a ViewBag to send the SelectList to the View, but you can use data from model too. My web application is a Blog and I have to build a RadioButton with some types of articles when he is writing a new post.
The code below was simplyfied.
List<SelectListItem> items = new List<SelectListItem>();
Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add("Texto", "1");
dictionary.Add("Foto", "2");
dictionary.Add("Vídeo", "3");
foreach (KeyValuePair<string, string> pair in objBLL.GetTiposPost())
{
items.Add(new SelectListItem() { Text = pair.Key, Value = pair.Value, Selected = false });
}
ViewBag.TiposPost = new SelectList(items, "Value", "Text");
In the View, I used a foreach to build a radiobutton.
<div class="form-group">
<div class="col-sm-10">
#foreach (var item in (SelectList)ViewBag.TiposPost)
{
#Html.RadioButtonFor(model => model.IDTipoPost, item.Value, false)
<label class="control-label">#item.Text</label>
}
</div>
</div>
Notice that I used RadioButtonFor in order to catch the option value selected by user, in the Controler, after submit the form. I also had to put the item.Text outside the RadioButtonFor in order to show the text options.
Hope it's useful!
I was able to use the name attribute that you described in your example for the loop I am working on and it worked, perhaps because I created unique ids? I'm still considering whether I should switch to an editor template instead as mentioned in the links in another answer.
#Html.RadioButtonFor(modelItem => item.Answers.AnswerYesNo, "true", new {Name = item.Description.QuestionId, id = string.Format("CBY{0}", item.Description.QuestionId), onclick = "setDescriptionVisibility(this)" }) Yes
#Html.RadioButtonFor(modelItem => item.Answers.AnswerYesNo, "false", new { Name = item.Description.QuestionId, id = string.Format("CBN{0}", item.Description.QuestionId), onclick = "setDescriptionVisibility(this)" } ) No
You can use Dictonary to map
Assume Milk,Butter,Chesse are group A (ListA)
Water,Beer,Wine are group B
Dictonary<string,List<string>>) dataMap;
dataMap.add("A",ListA);
dataMap.add("B",ListB);
At View , you can foreach Keys in dataMap and process your action
On this page, a user fills out a web form, and it gets added to the list when created.
I want to filter the list so a logged in user will only see forms they made themselves.
I have some Razor code that runs a foreach loop through all available items, and I have Javascript that rips the currently logged in user's info.
is it possible to assign this data to a var in the razor code?
ex.
#{var user = getUser(); //want something like this }
#foreach (var item in Model) {
//add check if item.name == user.name here
<tr>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
etc etc
<script>
function getUser() {$.getJSON('GetLoggedUserInfo', function(data) {
return data;
});
</script>
I do not think it is a good idea to have this logic in a view. View should be as 'dumb' as possible.
You could make item filtering at data storage level or at least in controller:
return View(items.Where(x => x.name == user.name))
There is an easier way. You can get current user properties using:
HttpContext.Current.User