Displaying Subnavigation for the Current Page in Orchard 1.6 - asp.net-mvc

I'm building a site in Orchard that will have around 130 pages. We've setup a Navigation menu called "Main Menu" with all of our pages setup in a hierarchy (the example I'm showing is from my sandbox site and not our actual development site).
In my "AsideFirst" zone on our default widget layer, I have added a menu widget that references the above "Main Menu." The start level is set to 0, and the levels to display is set to 0. On the front end, using the shape tracer, I have created an alternate called "Parts.MenuWidget-AsideFirst.cshtml."
The menu renders just fine, however, I'm not sure where to start on modifying the core "Navigation" driver to allow me to display only the pages relative to the page that I'm on. For instance, if I'm on the page "Testing II" I'd like navigation that renders the following HTML:
<ul>
<li>Testing I</li>
<li>
Testing II
<ul>
<li></li>
</ul>
</li>
<li>Testing III
</ul>
The alternate that I'm using has the following code. This works if I'm within any of the top level sections, but not the deeper levels. The following code is not the Orchard way to do things, so I'd like to know how I should achieve this properly through drivers and handlers - especially since this isn't accomplishing what I need it to anyway.
#{
var navTag = Tag(Model, "nav");
navTag.AddCssClass("nav-sidebar");
#navTag.StartElement;
var ulTag = Tag(Model, "ul");
#ulTag.StartElement
// Model is Model.Menu from the layout (Layout.Menu)
var items = getSectionItems((IList<dynamic>)Enumerable.Cast<dynamic>(Model.Menu.Items));
// Add First and Last Classes
if (items.Any())
{
items[0].Classes.Add("first");
items[items.Count - 1].Classes.Add("last");
}
// List Items
foreach (var listModel in items)
{
// Current URL
string requestUrl = getRequestUrl();
string modelUrl = getModelUrl(listModel);
bool showMenu = false;
if (isActivePage(requestUrl, modelUrl))
{
listModel.Classes.Add("active");
showMenu = true;
}
if (showMenu)
{
// Sub Items
var listItems = Enumerable.Cast<dynamic>((System.Collections.IEnumerable)listModel);
if (listItems.Any())
{
listModel.Classes.Add("dropdown");
}
// List Tag
var liTag = Tag(listModel, "li");
#liTag.StartElement;
listModel.Metadata.Alternates.Clear();
listModel.Metadata.Type = "MenuItemLink";
// Top Level Nav Items
string className = System.Text.RegularExpressions.Regex.Replace(listModel.Href, "[^A-Za-z0-9-]", "");
className = className.Length > 0 ? className : "home";
#listModel.Text
if (listItems.Any())
{
<ul>
#DisplayChildren(listModel)
</ul>
}
#liTag.EndElement;
}
}
#ulTag.EndElement;
#navTag.EndElement;
}
#functions{
private IList<dynamic> getSectionItems(IList<dynamic> sectionItems)
{
return sectionItems;
}
private string getRequestUrl()
{
return Request.Path.Replace(Request.ApplicationPath, string.Empty).TrimEnd('/').ToUpperInvariant();
}
private string getModelUrl(dynamic listModel)
{
return listModel.Href.Replace(Request.ApplicationPath, string.Empty).TrimEnd('/').ToUpperInvariant();
}
private bool isActivePage(string requestUrl, string modelUrl)
{
if (requestUrl == modelUrl || (!string.IsNullOrEmpty(modelUrl) && requestUrl.StartsWith(modelUrl + "/")))
{
return true;
}
return false;
}
}

Updated
I've solved this using the following code. I was close, but I wish I still had a better solution. It solves this for up to 4 levels deep.
#{
var navTag = Tag(Model, "nav");
navTag.AddCssClass("nav-sidebar");
#navTag.StartElement;
var ulTag = Tag(Model, "ul");
#ulTag.StartElement
// Model is Model.Menu from the layout (Layout.Menu)
string requestUrl = getRequestUrl();
var menus = (IList<dynamic>)Enumerable.Cast<dynamic>(Model.Menu.Items);
var items = getSectionItems(requestUrl, menus);
// List Items
foreach (var listModel in items)
{
// Sub Items
var listItems = Enumerable.Cast<dynamic>((System.Collections.IEnumerable)listModel);
if (listItems.Any())
{
listModel.Classes.Add("nav-section");
}
// List Tag
var liTag = Tag(listModel, "li");
#liTag.StartElement;
listModel.Metadata.Alternates.Clear();
listModel.Metadata.Type = "MenuItemLink";
// Top Level Nav Items
string className = System.Text.RegularExpressions.Regex.Replace(listModel.Href, "[^A-Za-z0-9-]", "");
className = className.Length > 0 ? className : "home";
#listModel.Text
if (listItems.Any())
{
<ul>
#DisplayChildren(listModel)
</ul>
}
#liTag.EndElement;
}
#ulTag.EndElement;
#navTag.EndElement;
}
#functions{
private IList<dynamic> getSectionItems(string requestUrl, IList<dynamic> sectionItems1)
{
foreach (var sectionItem1 in sectionItems1)
{
if (getModelUrl(sectionItem1) == requestUrl)
return sectionItem1;
foreach (var sectionItem2 in sectionItem1)
{
if (getModelUrl(sectionItem2) == requestUrl)
return sectionItem2;
foreach (var sectionItem3 in sectionItem2)
{
if (getModelUrl(sectionItem3) == requestUrl)
return sectionItem3;
foreach (var sectionItem4 in sectionItem3)
{
if (getModelUrl(sectionItem4) == requestUrl)
return sectionItem4;
}
}
}
}
return sectionItems1;
}
private string getRequestUrl()
{
return Request.Path.Replace(Request.ApplicationPath, string.Empty).TrimEnd('/').ToUpperInvariant();
}
private string getModelUrl(dynamic listModel)
{
return listModel.Href.Replace(Request.ApplicationPath, string.Empty).TrimEnd('/').ToUpperInvariant();
}
private bool isActivePage(string requestUrl, string modelUrl)
{
if (requestUrl == modelUrl || (!string.IsNullOrEmpty(modelUrl) && requestUrl.StartsWith(modelUrl + "/")))
{
return true;
}
return false;
}
}

Related

How can I handle error as Value cannot be null. Parameter name: source

My below view code in View is below
#{
ViewBag.Title = "Student Dashboard";
var StudentRequestTimedt = ViewBag.StudentRequestTime as DataTable;
if (StudentRequestTimedt != null)
{
var StudentRequestTime = StudentRequestTimedt.AsEnumerable().Select(t => new
{
StudentRequestId = t.Field<int>("StudentRequestId"),
FromTime = t.Field<string>("FromTime"),
ToTime = t.Field<string>("ToTime"),
}).ToList();
}
else
{ var StudentRequestTime = ""; }
}
if (StudentRequestTime != "")
{
var StudentRequestTimecount = StudentRequestTime.Where(d => d.StudentRequestId == StudentRequestId).ToList();
}
On writting this I am getting error as StudentRequestTime doesnot exist in the current context.
This issue comes in case I am returning ViewBag.StudentRequestTime as null from controller side
My controller side code is as
if (GetData.Tables[1].Rows.Count > 0 && GetData.Tables[0].Rows.Count > 0)
{
ViewBag.StudentRequestTime = GetData.Tables[1];
}
else
{
ViewBag.StudentRequestTime = null;
}
return View();
Please Also review this below image, Here I am getting data in multiple viewbag in this case how can I manage? var StudentRequestTime is null or empty
How can I handle this issue ?
Updated code resolved my issue
Old code of controller
if (GetData.Tables[1].Rows.Count > 0 && GetData.Tables[0].Rows.Count > 0)
{
ViewBag.StudentRequestTime = GetData.Tables[1];
}
else
{
ViewBag.StudentRequestTime = null;
}
return View();
New Code of controller
ViewBag.StudentRequestTime = GetData.Tables[1];
On ViewSide
Consider this example:
if (eyeColor == EyeColor.Green)
{
// greenEyeColorFound has been declared *in this if statement*,
// so it only exists *within this if statement*
var greenEyeColorFound = true;
}
// this will fail. greenEyeColorFound was declared *in the first if statement*,
// how can the if statement below be aware of it's existence?
if (greenEyeColorFound == true)
{
Debug.WriteLine("Found a person with green eyes!");
}
greenEyeColorFound is locally scoped to the first if statement. Only code within that if statement can be aware of it's existence.
To get my example to work, greenEyeColorFound should be accessible by both ifs, which can be achieved by placing it's declaration outside of both ifs:
// this is now declared *outside* of the two if statements,
// so both are now aware of it and can access it's value.
var greenEyeColorFound = false;
if (eyeColor == EyeColor.Green)
{
greenEyeColorFound = true;
}
// presto, this now works
if (greenEyeColorFound == true)
{
Debug.WriteLine("Found a person with green eyes!");
}
This is the exact issue you are having with StudentRequestTime. Declare it once outside of the ifs, then just set it's value in your if/else statements.
Since we're at it, I wouldn't use ViewBag at all, let alone have it carry DataTables over to the Razor side. I would use viewmodels (read "Accessing Your Model's Data from a Controller" over at Microsoft ASP.NET MVC docs to see how this works, in particular section "Strongly Typed Models and the #model Keyword") which are much cleaner and maintainable.
You can easily refactor your existing code to use viewmodels using the steps below:
1) Create a class, let's name it StudentRequestTimeViewModel:
public class StudentRequestTimeViewModel
{
public int StudentRequestId { get; set; }
public string FromTime { get; set; }
public string ToTime { get; set; }
}
2) In your controller, populate a List<StudentRequestTimeViewModel>:
var studentRequestTimes = new List<StudentRequestTimeViewModel>();
if (GetData.Tables[1].Rows.Count > 0 && GetData.Tables[0].Rows.Count > 0)
{
// populate studentRequestTimes here
}
// return the view, passing in studentRequestTimes as our model
return View(studentRequestTimes);
3) Your Razor then becomes:
/* your model is declared as "#model",
but is accessed as "Model". */
#model List<StudentRequestTimeViewModel>
#if (Model != null && Model.Count > 0)
{
/* your List<StudentRequestTimeViewModel> Model is not null or empty */
foreach(var studentRequestTime in Model)
{
<p>Student with ID #studentRequestTime.StudentRequestId is here.</p>
}
}
else
{
/* your List<StudentRequestTimeViewModel> Model is null or empty */
}

keeping track of a series of simple multiple choice web form answers

This is the code I'm trying to use, which seems logical. But doesn't seem to be working.
MyAsFileName.prototype.getTotalScore = function() {
var totalScore = 0;
for (var i = 0; i < allQuestions.length; i++) {
totalScore += allQuestions[i].getCalculatedScore();
if (currentModule.allQuestions[i].parent.questionCorrect == true) {
knowledgePoints++;
} else {
knowledgePoints--;
}
}
debugLog("Total score: " + totalScore);
debugLog(knowledgePoints);
return totalScore;
}
I have allQuestions defined as below:
var allQuestions = Array();
I have knowledgePoints defined as:
this.knowledgePoints = 10;
I have questionCorrect defined as:
this.questionCorrect = false;
Second fresh attempt made with new class as answer below suggested (commented out for now until I figure out how to get working):
// package
// {
/*public class Quiz {
//public
var knowledgePoints: int = 10;
//public
var allQuestions: Array = new Array;
//public
var questionCorrect: Boolean = false;
//public
function getTotalScore(): int {
var totalScore: int = 0;
for (var i = 0; i < allQuestions.length; i++) {
totalScore += allQuestions[i].getCalculatedScore();
if (currentModule.allQuestions[i].parent.questionCorrect) {
knowledgePoints++;
} else {
knowledgePoints--;
}
}
debugLog("Total score: " + totalScore);
debugLog(knowledgePoints);
return totalScore;
}
}*/
//}
This code above outputs two errors in flash console:
Error 1. Attribute used outside of class.
Error 2. 'Int' could not be loaded.
It's a weird (and actually non-AS3 way) way to do this. Instead of creating a unnamed closure which refers weird variables from who-knows where, you should make it a normal AS3 class, something like that (in a file named Quiz.as):
package
{
public class Quiz
{
public var knowledgePoints:int = 10;
public var allQuestions:Array = new Array;
public var questionCorrect:Boolean = false;
public function getTotalScore():int
{
var totalScore:int = 0;
// Your code does not explain how you will that Array.
// It is initially an empty Array of length 0.
for (var i = 0; i < allQuestions.length; i++)
{
totalScore += allQuestions[i].getCalculatedScore();
if (currentModule.allQuestions[i].parent.questionCorrect)
{
knowledgePoints++;
}
else
{
knowledgePoints--;
}
}
// Not sure what it is.
debugLog("Total score: " + totalScore);
debugLog(knowledgePoints);
return totalScore;
}
}
}

What kind of object has to be passed for JsonResult in MVC.Net

So I'm passing a custom class to my controller and it seems that the JsonResult is not properly passed.
What bothers me is that (also the fullcalendar wont read the json) the console.log which I have in my view prints the path to the function (wtf?) instead of what Json shoul return
This is my code:
public JsonResult GetCalendarEvents()
{
var eventList = BusinessLayer.Event.getAllEvents();
return Json(eventList.ToArray(), JsonRequestBehavior.AllowGet);
}
What kind of object has to be passed for this to work?
My evenList is of type List<Event> from here:
public static String ListToString(List<Event> evs)
{
String ret = "";
foreach (var ev in evs)
{
ret += ev.ToString() + "\n";
}
return ret;
}
public static List<Event> getAllEvents()
{
List<DataLayer.Event> dbEvents = DataApi.db.Event.ToList();
List<Event> returnEvents = new List<Event>();
foreach (DataLayer.Event oneEvent in dbEvents)
{
Event newEvent = new Event
{
ID = oneEvent.IDEvent,
userID = oneEvent.UserID,
projectID = oneEvent.ProjectID,
jobtypeID = oneEvent.JobTypeID,
taskID = oneEvent.TaskID,
ticketID = oneEvent.TicketID,
loccoID = oneEvent.LoccoID,
startTime = oneEvent.StartTime,
endTime = oneEvent.EndTime,
shiftFrom = oneEvent.ShiftFrom,
shiftTo = oneEvent.ShiftTo,
description = oneEvent.Description,
billable = oneEvent.Billable
};
returnEvents.Add(newEvent);
}
return returnEvents;
}
I tried displaying the events in fullcalendar:
$('#calendar').fullCalendar({
header: {
left: 'title',
center: '',
right: 'prev,next today basicDay,basicWeek,month',
},
//events: "/Calendar/GetEvents/", // not implemented
events: "#Url.Action("GetCalendarEvents/")",
and outputing the result to console:
console.log("#Url.Action("GetCalendarEvents/")");
but I get:
VM84 Index:83 /Calendar/GetCalendarEvents/
fullcalendar.min.js:6 Uncaught TypeError: Cannot read property 'hasTime' of undefined
It looks like you're missing some required fields. If you look at the documentation, title, start are required. Try setting these in the class to start with and build from that...
public static List<Event> getAllEvents()
{
List<DataLayer.Event> dbEvents = DataApi.db.Event.ToList();
List<Event> returnEvents = new List<Event>();
foreach (DataLayer.Event oneEvent in dbEvents)
{
Event newEvent = new Event
{
start = oneEvent.StartTime,
title = oneEvent.Description // you may need to add this to your Event class.
};
returnEvents.Add(newEvent);
}
return returnEvents;
}
Also, instead of using console to log the Json, use Fiddler or Chrome Advanced Tools

System.data.entity.validation.DbEntityValdationException Error Come when save the data

This my Controller
public JsonResult SaveBillingSystemParameters(BillingSystemParameters model)
{
var id = -1;
//Initialize the newId variable
var userId = Helpers.GetLoggedInUserId();
var currentDate = Helpers.GetInvariantCultureDateTime();
var defaultCorporateId = Helpers.GetSysAdminCorporateID();
//Check if Model is not null
if (model != null)
{
using (var bal = new BillingSystemParametersBal())
{
model.CorporateId = defaultCorporateId;
if (model.Id > 0)
{
model.ModifiedBy = userId;
model.ModifiedDate = currentDate;
}
else
{
model.CreatedBy = userId;
model.CreatedDate = currentDate;
model.CorporateId = Helpers.GetSysAdminCorporateID();
}
//Call the AddBillingSystemParameters Method to Add / Update current BillingSystemParameters
id = bal.SaveBillingSystemParameters(model);
}
}
return Json(id);
}
//Bal class
public int SaveBillingSystemParameters(BillingSystemParameters model)
{
using (var rep = UnitOfWork.BillingSystemParametersRepository)
{
if (model.Id > 0)
{
var current = rep.GetSingle(model.Id);
model.CreatedBy = current.CreatedBy;
model.CreatedDate = current.CreatedDate;
rep.UpdateEntity(model, model.Id);
}
else
rep.Create(model);
return model.Id;
}
}
I am getting following error
do what exception message says, inspect EntityValidationErrror
this exception usually means you're trying to break some constraint like varchar filed width
if you browse through this param you'll get more details on type of constrain SaveChanges() tried to brake

Unit testing a controller that depends on a session variable

I have a controller that depends on a Session variable. In order to unit test this controller, I came up with the following solution. It works but I'm wondering if there is a better/cleaner way. Thanks
Controller
public JsonResult UpdateStatus(ImageUpdateStatus imageUpdateStatus, SessionStateItemCollection sessionItems = null)
{
var data = new object();
string status = null;
ImageInfo imageInfo = new ImageInfo();
IImageInfoServices svcImageInfo = new ImageInfoServicesRepository();
imageInfo = svcImageInfo.GetImageByImageId(imageUpdateStatus.ImageId);
IDeviceControlServices svcDevice = new DeviceControlServicesRespository();
IPVSCommandServices svcPVSCmds = new PVSCommandServicesRespository();
if (imageUpdateStatus.Task == "prep")
{
List<UpdateReasonForm> updateReasonForms;
if (sessionItems != null)
{
updateReasonForms = sessionItems["UpdateReasonForms"] as List<UpdateReasonForm>;
}
else
{
updateReasonForms = Session["UpdateReasonForms"] as List<UpdateReasonForm>;
}
foreach (var item in updateReasonForms)
{
if (item.ImageId == imageInfo.ImageId)
{
status = svcPVSCmds.PrepImage(imageInfo, item.NewVersion);
}
}
data = new
{
status
};
}
if (imageUpdateStatus.Task == "boot")
{
status = svcDevice.Boot(imageInfo.ImageId);
data = new
{
status
};
}
return this.Json(data, JsonRequestBehavior.AllowGet);
}
Unit Test
[TestMethod()]
public void UpdateStatusTest()
{
BuildController target = new BuildController(); // TODO: Initialize to an appropriate value
ImageUpdateStatus imageUpdateStatus = new ImageUpdateStatus(); // TODO: Initialize to an appropriate value
imageUpdateStatus.ImageId = 3;
imageUpdateStatus.Task = "prep";
UpdateReasonForm updateReasonForm = new UpdateReasonForm();
updateReasonForm.ImageId = 3;
updateReasonForm.NewVersion = "TestThis";
List<UpdateReasonForm> updateReasonForms = new List<UpdateReasonForm>();
updateReasonForms.Add(updateReasonForm);
var sessionItems = new SessionStateItemCollection();
sessionItems["UpdateReasonForms"] = updateReasonForms;
JsonResult actual;
actual = target.UpdateStatus(imageUpdateStatus, sessionItems);
}
Instead of passing in the session values as a parameter you can mock the session state like here:
How do you mock the session object collection using Moq
You have a dependency on Session. You could move your code into a testable method where you inject the dependency at the method level. It looks like you are on this path I would just abstract the code into its own method allowing you to test the functionality regardless of the whether the data comes from session or not.
public JsonResult UpdateStatusDependencyInjection(ImageUpdateStatus imageUpdateStatus, Dictionary<string, object> sessionValues)
{
var data = new object();
string status = null;
ImageInfo imageInfo = new ImageInfo();
IImageInfoServices svcImageInfo = new ImageInfoServicesRepository();
imageInfo = svcImageInfo.GetImageByImageId(imageUpdateStatus.ImageId);
IDeviceControlServices svcDevice = new DeviceControlServicesRespository();
IPVSCommandServices svcPVSCmds = new PVSCommandServicesRespository();
if (imageUpdateStatus.Task == "prep")
{
List<UpdateReasonForm> updateReasonForms;
if (sessionItems != null)
{
updateReasonForms = sessionItems["UpdateReasonForms"] as List<UpdateReasonForm>;
}
else
{
updateReasonForms = Session["UpdateReasonForms"] as List<UpdateReasonForm>;
}
foreach (var item in updateReasonForms)
{
if (item.ImageId == imageInfo.ImageId)
{
status = svcPVSCmds.PrepImage(imageInfo, item.NewVersion);
}
}
data = new
{
status
};
}
if (imageUpdateStatus.Task == "boot")
{
status = svcDevice.Boot(imageInfo.ImageId);
data = new
{
status
};
}
return this.Json(data, JsonRequestBehavior.AllowGet);
}
http://codingsmith.co.za/a-better-way-of-working-with-httpcontext-session-in-mvc/
This is my implementation of an interface wrapper for Session.
Its currently in production and works fine, its injected into my controllers, but I can use one of the other implementations manually when testing

Resources