How to I render out two paginated lists on my Home Page?
I am willing to do what it takes - If it means tailoring my code to fit a solution or tailoring an existing solution to fit my code.
I have successfully rendered one paginated list to the home page using a partial view.
Take a look at my code:
Views
..Home > Index.cshtml
#foreach (var m in Model.First)
{
Html.RenderPartial("FirstSummary", m);
}
<div class="pager">
#Html.PageLinks(Model.PagingInfo, x => Url.Action("Index", new { page = x }))
</div>
Views
..Shared > FirstSummary.cshtml
#model MovinMyStuff.Domain.Entities.First
#{
if (#Model.IsActive)
{
<div class="first-list-item">
<ul>
<li>
<span class="first-name">
#Model.Property.ToString()
#Model.Property.ToString()
#Model.Property.ToString() -
#Model.Property.ToString()
#Model.Property.ToString()
#Model.Property.ToString()
</span>
</li>
<li>
#Html.ActionLink("Details", "Details", "First", new { area = "", id = #Model.FirstId }, new { #class = "button" })
</li>
</ul>
</div>
}
}
Controllers > HomeController.cs
public ViewResult Index(int page = 1)
{
FirstListViewModel viewModel = new FirstListViewModel
{
First = repository.First
.OrderByDescending(m => m.FirstId)
.Skip((page - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = repository.First.Count()
}
};
return View(viewModel);
}
Models > FirstListViewModel.cs
using System.Collections.Generic;
using MovinMyStuff.Domain.Entities;
namespace MovinMyStuff.WebUI.Models
{
public class FirstListViewModel
{
public IEnumerable<First> Firsts { get; set; }
public PagingInfo PagingInfo { get; set; }
}
}
ViewModels > FirstAndSecond.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MovinMyStuff.WebUI.Models;
namespace MovinMyStuff.WebUI.ViewModels
namespace MovinMyStuff.WebUI.ViewModels
{
public class MovesAndMovers
{
public MovesListViewModel movesList { get; set; }
public MoversListViewModel moversList { get; set; }
public MovesAndMovers()
{
movesList = new MovesListViewModel();
moversList = new MoversListViewModel();
}
}
}
I FIGURED IT OUT! This was quite a beast of a task...I have spent nearly 10 hours trying to solve this so please stay tuned as I WILL GIVE WHO EVER WANTS THIS the answer. Now all I have to do is prevent the refresh from sending me back to the top of the page. Yikes! That will hopefully not be so bad... ( :
Related
I am using Paging in my View and originally my #model was of type PagedList.IPagedList but I was having issues posting back the IPagedList interface back to controller, therefore, I found a post that gave a hint on how to deconstruct my IPagedList so I could present it in the View and be able to post back to controller for further processing as well. However, I ran into the following issue - I want to display only 50 records on each page. The search criteria I am testing returns 13000 records. I am expecting only 50 records on first page with page numbers 1,2,3 at bottom. I see the page numbers as expected at the bottom but all 13000 records get displayed on every page. I have debugged the code and found out that StaticPagedList function is returning 13000 records instead of 50 so I applied Skip and Take on the object before returning it to the View but the issue is still occurring. My code:
View Models -
public class PagedClientViewModel
{
public int? Page { get; set; }
public IEnumerable<SelectTimeTrackerEditorViewModel> Clients { get; set; }
public IPagedList PagingMetaData { get; set; }
}
public class SelectTimeTrackerEditorViewModel
{
public bool Selected { get; set; }
public int SID { get; set; }
public string PerNo { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
View -
#model EmergencyResponseTimeEntry.Models.PagedClientViewModel
#using PagedList;
#using PagedList.Mvc;
#foreach (var item in Model.Clients)
{
<tr>
<td>
#Html.CheckBoxFor(modelItem => item.Selected)
</td>
<td>
#Html.DisplayFor(modelItem => item.PerNo)
</td>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
</tr>
}
Page #(Model.PagingMetaData.PageCount < Model.PagingMetaData.PageNumber ? 0 : Model.PagingMetaData.PageNumber) of #Model.PagingMetaData.PageCount
#Html.PagedListPager(new StaticPagedList<EmergencyResponseTimeEntry.Models.SelectTimeTrackerEditorViewModel>(Model.Clients, Model.PagingMetaData), page => Url.Action("Index", new { page }), new PagedListRenderOptions { DisplayEllipsesWhenNotShowingAllPageNumbers = false})
Controller -
var list = (from c in timeTrackers
select new SelectTimeTrackerEditorViewModel
{
Selected = false,
SID = c.SID,
PerNo = c.PerNo,
FirstName = c.FirstName,
LastName = c.LastName
});
var pagedClientViewModel = new PagedClientViewModel
{
Clients = list
};
int pageSize = 50;
int pageNumber = (page ?? 1);
int totalClients = list.Count();
// List of current page of 50 records (hits database again, pulls only 50 records, though)
var timeTrackerList = list.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList();
pagedClientViewModel.PagingMetaData = new StaticPagedList<SelectTimeTrackerEditorViewModel>
(timeTrackerList, pageNumber, pageSize, totalClients).GetMetaData();
return View(pagedClientViewModel);
I got it working after co-worker pointed out that I was displaying all records in my view - Model.Clients. So in my controller, I changed this
var pagedClientViewModel = new PagedClientViewModel
{
Clients = list
};
to this -
var pagedClientViewModel = new PagedClientViewModel
{
Clients = list.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList()
};
Now paging is working as expected!
can someone help please, dont know what I am doing wrong:
my viewmodel:
public class ScormModuleViewModel
{
public IEnumerable<ScormModuleInfo> ScormModuleInfo { get; set; }
public int CurrentPage { get; set; }
public int PageSize { get; set; }
public double Total { get; set; }
public int RecordsPerPage { get; set; }
}
then my controller:
public ActionResult Index(int? page)
{
List<ScormModuleInfo> modules = new List<ScormModuleInfo>();
foreach (var directory in Directory.EnumerateDirectories(scormRootDir))
{
ScormModuleInfo module = new ScormModuleInfo();
module.ScormModule = ZincService.ScormService.GetScormModule(scormTitle, scormDirectory);
if (module.ScormModule != null)
module.Installed = true;
else
{
module.Installed = false;
module.ScormModule = new ScormModule();
module.ScormModule.Directory = scormDirectory;
module.ScormModule.Title = scormTitle;
}
module.ScormModule.EntryPointRef = scormEntryPointRef;
module.ScormModule.IdentifierRef = scormIdentifierRef;
module.ScormModule.LaunchHeight = scormLaunchHeight;
module.ScormModule.LaunchWidth = scormLaunchWidth;
module.ScormModule.LaunchResize = scormLaunchResize;
module.ScormModule.RelativeHtmlPath = scormRelativeHtmlPath;
module.ScormModule.SchemaVersion = scormSchemaVersion;
if (module.Installed)
{
// TODO: Save changes to module
//ZincService.ScormService.UpdateScormModuleSettings(
}
modules.Add(module);
noOfScormPackages++ ;
}
}
ScormModuleViewModel model = new ScormModuleViewModel();
model.ScormModuleInfo = modules;
model.CurrentPage = page.GetValueOrDefault(1);
model.PageSize = PageSizeSettings.ScormPackages;
model.Total = noOfScormPackages;
// Now iterate over modules and read imsmanifest.xml file for details of entry point and resources required.
return View(model);
}
and my view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<List<Zinc.Web.Areas.Admin.ViewModels.Scorm.ScormModuleViewModel>>" %>
foreach (var module in Model)
{ %>
<li>
<div class="col1">
<a href='javascript:LoadTrainingModuleWithDedicatedAPIObj("<%= module.ScormModuleInfo.r .sc.ScormModule.RelativeHtmlPath %>/<%= module.ScormModule.EntryPointRef %>", "<%: module.ScormModule.Title %>", "<%: module.ScormModule.LaunchWidth %>", "<%: module.ScormModule.LaunchHeight %>");'>
<% if (module.ScormModule.Title.Length < 70)
{ %>
<%: module.ScormModule.Title%>
<% }
else
{ %>
<%: module.ScormModule.Title.Substring(0, 70)%>....
<% } %>
</a>
</div>
i get redlines under all the module. properties?
i dont understand, what am i doing wrong?
thanks
Model is your ViewModel. Model.ScormModuleInfo is the IEnumerable of ScormModuleInfo type.
foreach (var module in Model.ScormModuleInfo)
In your controller you are passing ScormModuleViewModel to view not List<ScormModuleViewModel>
So Replace
System.Web.Mvc.ViewPage<List<Zinc.Web.Areas.Admin.ViewModels.Scorm.ScormModuleViewModel>>
To:
System.Web.Mvc.ViewPage<Zinc.Web.Areas.Admin.ViewModels.Scorm.ScormModuleViewModel>
I have a main view using a ViewModel. Inside the ViewModel I do this (Edited to show complete ViewModel):
namespace MyNameSpace.ViewModels
{
public class MyViewModel
{
public ModelOne ModelOne { get; set; }
public ModelTwo ModelTwo { get; set; }
}
}
On my main view I do this (EDIT: Added #Html.Hidden code):
#using MyNameSpace.ViewModels
#using MyNameSpace.Models
#model MyViewModel
...
#using (Html.BeginFormAntiForgeryPost())
{
#Html.Hidden("myData", new MvcSerializer()
.Serialize(Model, SerializationMode.Signed))
....
<div>
#{Html.RenderPartial("_MyCheckBox",
Model.ModelTwo, new ViewDataDictionary());}
</div>
}
....
My partial view is:
#using MyNameSpace.Models
#model ModelTwo
<div>
<fieldset>
<div class="editor-label">
#Html.LabelFor(x => x.MyCheckBox)</div>
<div class="editor-field">
<select multiple="multiple" id="#Html.FieldIdFor(x =>
x.MyCheckBox)" name="#Html.FieldNameFor(x =>
x.MyCheckBox)">
#foreach (MyEnum item in Enum.GetValues(typeof(MyEnum)))
{
var selected = Model.MyCheckBox.Contains(item); //ERROR HERE
<option value="#((int)item)" #if (selected)
{<text>selected="selected"</text>}>
#T(item.ToString())
</option>
}
</select>
</div>
</fieldset>
</div>
I am getting the Object reference not set to an instance ... error and am not sure how to correct it.
Originally, I had that <fieldset> inside my main view and was getting that error. I thought it was because of the two models and that's why I placed it in a partial view. But only to discover I am running into the same problem.
I am not setting the MyCheckBox in my partial view on the line var selected = Model.MyCheckBox.Contains(item); properly.
Any thoughts?
EDIT (Adding MyCheckBox code)
Here is MyCheckBox inside ModelOne.cs:
[Mandatory(ErrorMessage = "Please select at least one option")]
[Display(Name = "Please select one ore more options")]
[MySelector]
public virtual int MyCheckBox { get; set; }
And here it is inside ModelTwo.cs:
private IList<MyEnum> _myEnum;
public IList<MyEnum> MyCheckBox
{
get
{
if (_myEnum== null)
{
_myEnum= new List<MyEnum>();
foreach (MyEnumitem in Enum.GetValues(typeof(MyEnum)))
{
if (((MyEnum)Record.MyCheckBox& item) == item)
_myEnum.Add(item);
}
}
return _myEnum;
}
set
{
_myEnum= value;
Record.MyCheckBox= 0;
foreach (var item in value)
{
Record.MyCheckBox|= (int)item;
}
}
}
Please note, I am using Orchard (hence the Record) which, in turn, uses NHibernate. I don't believe that is relevant, but I could be wrong. The MyCheckBox code is using [Flags] attribute of enum and storing the value as an int in the DB (hence the proxy). Here is what the enum would look like:
MyEnum.cs:
[Flags]
public enum MyEnum
{
[Display(Name="Name 1")]
Enum1 = 1,
[Display(Name="Name 2")]
Enum2 = 2,
[Display(Name="Name 3")]
Enum3 = 4,
[Display(Name="Name 4")]
Enum4 = 8,
[Display(Name="Name 5")]
Enum5 = 16
}
MyController.cs
private MyViewModel myData;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["myData"];
if (serialized != null) //Form was posted containing serialized data
{
myData= (MyViewModel)new MvcSerializer().Deserialize
(serialized, SerializationMode.Signed);
TryUpdateModel(myData);
}
else
myData= (MyViewModel)TempData["myData"] ?? new MyViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["myData"] = myData;
}
Then in the particular Action within *MyController.cs:
public ActionResult Step5(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("Step4");
else if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("Confirm");
else
return View(myData);
}
I have a ViewModel wrapping two complex types:
public class EditProductViewModel
{
public ProductData ProductData { get; set; }
public FridgeContent FridgeContent { get; set; }
}
and this view:
#model EditProductViewModel
#using (Html.BeginForm("Edit", "ProductData", FormMethod.Post))
{
#Html.EditorForModel()
[...]
}
ProductData and FridgeContent contain POCO properties with DataAnnotations like this:
public class FridgeContentMetadata : DatabaseEntityMetadataBase
{
[Required]
[HiddenInput(DisplayValue = false)]
public int ProductDataId { get; set; }
[Required]
[UIHint("StringReadOnly")]
public int ScaleId { get; set; }
[Required]
[UIHint("StringReadOnly")]
[Range(0.01, float.MaxValue, ErrorMessage = "The weight of a product must be positive.")]
public float Weight { get; set; }
[...]
}
I want to edit both ProductData and FridgeContent in the EditProductView using the appropriate data annotations from those classes and the EditorForModel() method (I don't want to generate the templates myself). I therefore created the templates ProductData.cshtml and FridgeContent.cshtml in /Views/Shared/EditorTemplates/:
#model FridgeContent
#Html.EditorForModel()
Unfortunately, the view for EditProductViewModel is empty (no errors raised). If I use EditorForModel for either FridgeContent or ProductData alone, it's working fine. I also tried adding [UIHInt("..")] annotations to EditProductViewModel but that doesn't make a difference.
What am I missing?
#model EditProductViewModel
#using (Html.BeginForm("Edit", "ProductData", FormMethod.Post))
{
#Html.EditorFor(o=> o.ProductData )
#Html.EditorFor(o=> o.FridgeContent )
}
or create an edit template for you ViewModel containing these two lines
#Html.EditorFor(o=> o.ProductData )
#Html.EditorFor(o=> o.FridgeContent )
UPADTE:
Oh got it finally because the rendering engine will not go more that one step in object hierarchy, you can find it in asp.net mvc code also.
Check the MVC 3.0 Source Code Here:
There is a file named DefaultEditorTemplates.cs which contains this method:
internal static string ObjectTemplate(HtmlHelper html, TemplateHelpers.TemplateHelperDelegate templateHelper) {
ViewDataDictionary viewData = html.ViewContext.ViewData;
TemplateInfo templateInfo = viewData.TemplateInfo;
ModelMetadata modelMetadata = viewData.ModelMetadata;
StringBuilder builder = new StringBuilder();
if (templateInfo.TemplateDepth > 1) { // DDB #224751
return modelMetadata.Model == null ? modelMetadata.NullDisplayText : modelMetadata.SimpleDisplayText;
}
foreach (ModelMetadata propertyMetadata in modelMetadata.Properties.Where(pm => ShouldShow(pm, templateInfo))) {
if (!propertyMetadata.HideSurroundingHtml) {
string label = LabelExtensions.LabelHelper(html, propertyMetadata, propertyMetadata.PropertyName).ToHtmlString();
if (!String.IsNullOrEmpty(label)) {
builder.AppendFormat(CultureInfo.InvariantCulture, "<div class=\"editor-label\">{0}</div>\r\n", label);
}
builder.Append("<div class=\"editor-field\">");
}
builder.Append(templateHelper(html, propertyMetadata, propertyMetadata.PropertyName, null /* templateName */, DataBoundControlMode.Edit, null /* additionalViewData */));
if (!propertyMetadata.HideSurroundingHtml) {
builder.Append(" ");
builder.Append(html.ValidationMessage(propertyMetadata.PropertyName));
builder.Append("</div>\r\n");
}
}
return builder.ToString();
}
which clearly states that if the TemplateDepth > 1 just render a simple text.
As the above answer shows, this problem seems related to the framework limiting the depth of nesting it will consider.
One way to work around the problem is to use your own editor template. Create the partial view, Object.cshtml, in Views/Shared/EditorTemplates. Here's an example template taken from here:
#{
Func<ModelMetadata, bool> ShouldShow = metadata =>
metadata.ShowForEdit && !ViewData.TemplateInfo.Visited(metadata);
}
#if (ViewData.TemplateInfo.TemplateDepth > 5) {
if (Model == null) {
#ViewData.ModelMetadata.NullDisplayText
} else {
#ViewData.ModelMetadata.SimpleDisplayText
}
} else {
foreach (var prop in ViewData.ModelMetadata.Properties.Where(ShouldShow)) {
if (prop.HideSurroundingHtml) {
#Html.Editor(prop.PropertyName)
} else {
if (string.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())==false) {
<div class="editor-label">
#Html.Label(prop.PropertyName)
</div>
}
<div class="editor-field">
#Html.Editor(prop.PropertyName)
#Html.ValidationMessage(prop.PropertyName)
</div>
}
}
}
In the above example, you can set the maximum nesting depth by changing the 5 constant.
I'm getting back to an MVC3 project after a 3 month hiatus. I need to display a drop down list that pulls from Database A, but saves to Database B. The property I need to persist is the NAICS/SIC code. Right now I just provide the user a text box to key in freeform text. So, I have the mechanics of that down. But instead it should provide only a valid list of codes from a source database.
The tricky thing to is I'm using a custom model binder to generate my ViewModels on the fly, so I don't have a distinct .cshtml file to customize.
[Serializable]
public class Step4ViewModel : IStepViewModel
{
public Step4ViewModel()
{
}
//load naics codes from somewhere
[Display(Name = "Describe the nature of your business.")]
public String NatureOfBusiness { get; set; }
[Display(Name="NAICS/SIC CODE")]
public String BusinessTypeCode { get; set; }
Tricky ViewModel
#using Microsoft.Web.Mvc;
#using Tangible.Models;
#model Tangible.Models.WizardViewModel
#{
var currentStep = Model.Steps[Model.CurrentStepIndex];
var progress = ((Double)(Model.CurrentStepIndex) / Model.Steps.Count) * 100;
}
<script type="text/javascript">
$(function () {
$("#progressbar").progressbar({
value: #progress
});
});
</script>
<div id="progressbar" style="height:20px;">
<span style="position:absolute;line-height:1.2em; margin-left:10px;">Step #(Model.CurrentStepIndex + 1) out of #Model.Steps.Count</span>
</div>
#Html.ValidationSummary()
#using (Html.BeginForm())
{
#Html.Serialize("wizard", Model)
#Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType())
#Html.EditorFor(x => currentStep, null, "")
if (Model.CurrentStepIndex > 0)
{
<input type="submit" value="Previous" name="prev" />
}
if (Model.CurrentStepIndex < Model.Steps.Count - 1)
{
<input type="submit" value="Save & Continue" name="next" />
}
else
{
<input type="submit" value="Finish" name="finish" />
}
#*<input type="submit" value="Save" name="Save" />*#
}
Controller
[HttpPost]
public ActionResult Index([Deserialize] WizardViewModel wizard, IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
//Always save.
var obj = new dr405();
//wire up to domain model;
foreach (var s in wizard.Steps)
{
Mapper.Map(s,obj,s.GetType(), typeof(dr405));
}
using (var service = new DR405Service())
{
//Do something with a service here.
service.Save(db, obj);
}
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
return View("Upload", obj);
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
return View(wizard);
}
WizardViewModel
[Serializable]
public class WizardViewModel
{
public String AccountNumber { get; set; }
public int CurrentStepIndex { get; set; }
public Boolean IsInitialized { get { return _isInitialized; } }
public IList<IStepViewModel> Steps { get; set; }
private Boolean _isInitialized = false;
public void Initialize()
{
try
{
Steps = typeof(IStepViewModel)
.Assembly.GetTypes().Where(t => !t.IsAbstract && typeof(IStepViewModel).IsAssignableFrom(t)).Select(t => (IStepViewModel)Activator.CreateInstance(t)).ToList();
_isInitialized = true;
//rewrite this. get the profile and wire them up or something.
this.AccountNumber = Tangible.Profiles.DR405Profile.CurrentUser.TangiblePropertyId;
}
catch (Exception e)
{
_isInitialized = false;
}
}
}
You can specify a template for a specific property on your view model by adding the UIHint attribute to the field. Since your view calls EditorFor on the model it will use the template you specified with UIHint.
BusinessTypeDropdown.ascx - (placed in Views/Shared/EditorTemplates
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<% var businessTypes = ViewData["businessTypes"] as IEnumerable<string>; %>
<%= Html.DropDownListFor(m => m , new SelectList(businessTypes, Model))%>
In your View Model
[Serializable]
public class Step4ViewModel : IStepViewModel
{
public Step4ViewModel()
{
}
//load naics codes from somewhere
[Display(Name = "Describe the nature of your business.")]
public String NatureOfBusiness { get; set; }
[Display(Name="NAICS/SIC CODE")][UIHint("BusinessTypeDropdown")]
public String BusinessTypeCode { get; set; }
Then in your controller just set ViewData["businessTypes"] to your list of business types.
Without understanding your "tricky" view model code, it will be hard to make helpful suggestions.
However, there shouldn't be much problem here. You need to somehow create your dropdown list in yoru view, and populate it from data passed from your controller.
All the work happens in your controller. Populate your list or IEnumerable or whatever data source from your first database, then in your post handler save the selection it to your second database (the second part should not be much different from what you already have).