How to create nested ViewComponents in Monorail and NVelocity? - castle-monorail

I have been asked to update the menu on a website we maintain. The website uses Castle Windors Monorail and NVelocity as the template. The menu is currently rendered using custom made subclasses of ViewComponent, which render li elements. At the moment there is only one (horizontal) level, so the current mechanism is fine.
I have been asked to add drop down menus to some of the existing menus. As this is the first time I have seen Monorail and NVelocity, I'm a little lost.
What currently exists:
<ul>
#component(MenuComponent with "title=Home" "hover=autoselect" "link=/")
#component(MenuComponent with "title=Videos" "hover=autoselect")
#component(MenuComponent with "title=VPS" "hover=autoselect" "link=/vps")
#component(MenuComponent with "title=Add-Ons" "hover=autoselect" "link=/addons")
#component(MenuComponent with "title=Hosting" "hover=autoselect" "link=/hosting")
#component(MenuComponent with "title=Support" "hover=autoselect" "link=/support")
#component(MenuComponent with "title=News" "hover=autoselect" "link=/news")
#component(MenuComponent with "title=Contact Us" "hover=autoselect" "link=/contact-us")
</ul>
Is it possible to have nested MenuComponents (or a new SubMenuComponent) something like:
<ul>
#component(MenuComponent with "title=Home" "hover=autoselect" "link=/")
#component(MenuComponent with "title=Videos" "hover=autoselect")
#blockcomponent(MenuComponent with "title=VPS" "hover=autoselect" "link=/vps")
#component(SubMenuComponent with "title="Plans" "hover=autoselect" "link=/vps/plans")
#component(SubMenuComponent with "title="Operating Systems" "hover=autoselect" "link=/vps/os")
#component(SubMenuComponent with "title="Supported Applications" "hover=autoselect" "link=/vps/apps")
#end
#component(MenuComponent with "title=Add-Ons" "hover=autoselect" "link=/addons")
#component(MenuComponent with "title=Hosting" "hover=autoselect" "link=/hosting")
#component(MenuComponent with "title=Support" "hover=autoselect" "link=/support")
#component(MenuComponent with "title=News" "hover=autoselect" "link=/news")
#component(MenuComponent with "title=Contact Us" "hover=autoselect" "link=/contact-us")
</ul>
I need to draw the sub menu (ul and li elements) inside the overridden Render method on MenuComponent, so using nested ViewComponent derivatives may not work. I would like a method keep the basically declarative method for creating menus, if at all possible.
edit: I can use Context.RenderBody() to render the nested ViewComponent derivitives, but they're being rendered before the parent. I guess the rending of the sub menus need to somehow hook into the same output as the parent?

My original render method looked like
public override void Render()
{
var buffer = new StringBuilder();
var extraClass = _hoverState == MenuHoverState.Selected ? "class=\"Selected\"" : "";
// Menu Item Start
buffer.Append("<li><a href=\"" + ComponentParams["link"] + "\"" + extraClass + ">");
// Menu Text
buffer.Append(ComponentParams["title"]);
// Menu Item End
buffer.Append("</a></li>");
RenderText(buffer.ToString());
}
I needed to hook into the Context.Writer:
public override void Render()
{
var buffer = new StringBuilder();
var extraClass = _hoverState == MenuHoverState.Selected ? "class=\"Selected\"" : "";
// Menu Item Start
buffer.Append("<li><a href=\"" + ComponentParams["link"] + "\"" + extraClass + ">");
// Menu Text
buffer.Append(ComponentParams["title"]);
// Menu Item End
buffer.Append("</a><ul class=\"subMenu\" style=\"display:none;\">");
Context.Writer(buffer.ToString());
Context.RenderBody(Context.Writer);
Contet.Writer("</ul></li>");
}

I can use Context.RenderBody() to render the nested ViewComponent derivitives
in your Render method override, you could use something like
RenderView("header");
RenderBody();
RenderView("footer");
and maybe use RenderSection could be useful to be able to override some parts from the template you use the component
if(HasSection("header")){
RenderSection("header");
} else {
RenderView("header");
}
it is also possible to itterate and alter the context:
for(var item in this.SubItems){
PropertyBag["item"] = item;
if(HasSection("item")){
RenderSection("item");
} else {
RenderView("item");
}
}
all these solution are fancy, but I generaly prefer having a viewcomponent which takes a purpose specific viewmodel (say HierarchicalMenuViewModel) as a parameter and keep the templating logic simple, it's more easy to use, and output customization happen
at least for simple controls (that would sometime only deserve a macro or partial depending on the viewengine).
In the end, viewcomponent concepts illustrated above are still nice to have when doing control that need more customization. An advice is to take care of documenting the rendering logic or keeping it simple (<= 10 lines in render method)

Related

paginated next button not working in asp.net core

I'm new to asp.net MVC core
trying to build a page where you choose multiple search options
I need a get and post actions for that, the results should be in a partial view that is paginated, the code works fine until I click next or previous, I lose the whole search result object cuz the model doesn't bind them back
[HttpGet]
public ViewResult SearchOutbox(DocumentSearchViewModel doc)
{
var documentSearchViewModel = PopulateDocumentSearchViewModel(doc);//this method fills the dropdowns
return View(documentSearchViewModel);
}
[HttpPost]
public async Task<IActionResult> SearchOutbox(DocumentSearchViewModel doc, int? page)
{
var documentSearchViewModel = PopulateDocumentSearchViewModel(doc);
if (ModelState.IsValid)
{
IQueryable<Document> documents = _documentRepository.SearchDocument(documentSearchViewModel);
documentSearchViewModel.Documents = await PaginatedList<Document>.CreateAsync(documents.AsNoTracking(), page ?? 1, 1);
return View("SearchOutbox",documentSearchViewModel);
}
return View();
}
partial view
....
<a asp-action="SearchOutbox"
asp-route-page="#(Model.PageIndex - 1)"
class="btn btn-default #prevDisabled">
Previous
</a>
<a asp-action="SearchOutbox"
asp-route-page="#(Model.PageIndex + 1)"
class="btn btn-default #nextDisabled">
Next
</a>
main view calling partial
<partial name="_ListDocument" model="#Model.Documents">
Before diving into the details, it is worth to mention that for pagination you can use GET only, no need to use POST.
Back yo your question; if the result set is based on some filters, you have to pass all those filters parameters along with the pagination link.
e.g. if you have a URL like below one you can survive by sending only page parameter to the relevant action:
http://example.com/products/?page=1
But whenever you add some filters to the URL, you have to include all of them in the paging buttons, e.g. in below URL you have to send all paramters after ? to the paging action so it can select the next page from the filtered results using the same filtering options:
http://example.com/products/?page=1&category=mobile&brand=xyz
You can add all parameters manually, or you can use a function that will read the query string values then only increase the page number and generate new URL. Below is a function that do the page number increase and replace it via regex:
Previous
Next
#{
string CreateUrl(int newPage)
{
var index = int.Parse(Request.QueryString["page"].ToString());
var input = Request.QueryString.Value;
var replacement = $"page={index + newPage}";
var pattern = #"page=\d+";
return System.Text.RegularExpressions.Regex.Replace(input, pattern, replacement);
}
}
There is some nuget packages that can handle advanced paging functionalities like LazZiya.TagHelpers, install from nuget :
Install-Package LazZiya.TagHelpers -Version 3.0.2
Add paging tag helper to _ViewImports :
#addTagHelper *, LazZiya.TagHelpers
Then use it where you need a paging control:
<paging
total-records="Model.TotalRecords"
page-no="Model.PageNo"
query-string-key-page-no="page"
query-string-value="#(Request.QueryString.Value)">
</paging>
Notice : in the latest preview version (v3.1.0-preview1) no need to add query-string-value so the tag helper will work like below:
<paging
total-records="Model.TotalRecords"
page-no="Model.PageNo"
query-string-key-page-no="page"
</paging>
See tutorial, live demo and docs

How can to locate descendant elements with selenium web driver API using JavaScript?

I'm new in Selenium web driver and I facing some problems trying to locate DOM elements.
Let's say I have a bunch of <div class="column">...</div>, and inside them, I have a bunch of <div class="text">...</div>.
My question is: What is the better way to get a specific descendant and click it?
Below my code
var driver = new webdriver.Builder()
.forBrowser('chrome')
.build();
driver.get('http://www.localhost:4000/');
var columns = [];
driver.findElements(By.css('.column')).then(function(list) {
columns = list.slice();
columns[1].findElements(By.css('.text')).then(function(textList) {
textList[0].click();
});
});
You could combine the selectors and do:
driver.findElements(By.css('.column .text'))
which would locate all the elements with class text inside elements with class .column.
And, you could use the nth-child(), nth-of-type() or other pseudo classes to get to the elements by index inside the selectors, for instance:
driver.findElements(By.css('.column:nth-of-type(1) .text:nth-of-type(2)'))

MVC4 C# - How to submit list of object that are being displayed to the user?

I'm working on an MVC4 C# project in VS2010.
I would like to allow the user to upload the contents of a .csv file to a database but there is a requirement to first echo the contents of the file to screen (as a final visual check) before submitting. What would be the best approach of submitting to the database as I am struggling to find a way of persisting the complex object in the view?
Here is the view where I am using a form to allow the user to upload the csv file:
#model IEnumerable<MyNamespace.Models.MyModel>
#{
ViewBag.Title = "Upload";
WebGrid grid = new WebGrid(Model, rowsPerPage: 5);
}
<h2>Upload</h2>
<form action="" method="post" enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<input type="submit" />
</form>
<h2>Grid</h2>
#grid.GetHtml(
//Displaying Grid here)
<p>
#Html.ActionLink("Submit", "Insert")
</p>
Here is the action in the controller that processes the csv file:
[HttpPost]
public ActionResult Upload(HttpPostedFileBase file)
{
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data"), fileName);
file.SaveAs(path);
//Stream reader will read test.csv file in current folder
StreamReader sr = new StreamReader(path);
//Csv reader reads the stream
CsvReader csvread = new CsvReader(sr);
List<MyModel> listMyModele = new List<MyModel>(); // creating list of model.
csvread.Configuration.RegisterClassMap<MyModelMap>(); // use mapping specified.
listMyModel = csvread.GetRecords<MyModel>().ToList();
sr.Close();
//return View();
return View(listMyModel);
}
Up until this point everything is simple, I can upload the csv to the controller, read using CsvHelper, produce a list of MyModel objects and display in the view within a grid. To reiterate my initial question, is it now possible to submit the complex object (the list of MyModel) from the view as I can't figure out a way of making it available to an action within the controller.
Thank you.
Yes it's possible, It's "easier" if you have a Model with the IEnumerable in it so you can use the naming convention like this:
Property[index].ItemProperty
for every Html input/select field.
If you want to keep the IEnumerable as Model I think the naming convention is something like this:
ItemProperty[index]
So translated in code:
#Html.TextBoxFor(t => t.Property, new { name = "Property[" + i + "]" })
where i comes from a for loop to render all items or something like that.
I have already done it but I can't find the code at the moment. KendoUI uses this scheme for its multirows edit in the grid component.
You can check their POST AJAX requests for the right naming convention.
EDIT 1:
Otherwise you can think about store the model somewhere temporarily and retrieve it every time and updating with user inputs. It's a little more expensive but probably easier to write. Something like an updated csv file or a temporary db table.

MVC Partial view with controller, ajax - how do I ge the partial controller to get data?

I'm learning MVC and am stumped by this. I'm trying to factor some common code that gets data and displays it in a WebGrid into a partial view that i can use on multiple pages.
My home controller Index method just does a return View(). The Home view looks like this:
#using (Ajax.BeginForm("SearchAction", "Search",
new AjaxOptions { UpdateTargetId = "data-grid", HttpMethod = "Post" }))
{
#Html.TextBoxFor(model => model.name)
<input type="submit" value="Search" />
}
#{
<div id="data-grid">
#Html.Partial("SearchResults", Model)
</div>
}
I'm trying to use Ajax to avoid losing my search form data when clicking a WebGrid pager link, which are rendered as normal links.
My SearchController looks like this:
public ActionResult SearchAction(string name)
{
return RedirectToAction("SearchResults", new { name = name });
}
public ActionResult SearchResults(string name)
{
//does database query and sticks results in the viewbag
//filter on optional name parameter
VieweBag.Members = MyQueryResults;
return PartialView();
}
My SearchResults shared view, data is passed in via ViewBag.Members:
#{
var grid = new WebGrid(null, rowsPerPage: ViewBag.Pagesize);
grid.Bind(ViewBag.Members);
#grid.GetHtml(// etc. etc.)
}
The results I'm getting is that the ViewBag.Pagesize and ViewBag.Members binding fails since there is no data in the viewbag. Obviously, my partial controller is not being called to do the initial query and put stuff in the ViewBag when the home page is first loaded. How do I make that happen?
The other weird thing is that if I just copy the database query code into my home controller (where it originally was) to force the original query, then if I put some text into the search field and do a search, the partial view renders by itself on a new page. Why is that happening, I thought it would only render as part of my home page.
I've cobbled this partial view together from various answers/places and have no doubt gotten something horribly wrong :\
The partial page won't pass through a controller, but simply render the view directly. If you want to pass view data to the partial view, there is an overloaded function that takes a viewdata dictionary. I'm sorry I can't be more detailed, but I'm on my mobile (waiting for my son to fall asleep in the other room) :)
Update:
If you want to trigger a GET action for your partial view, you can use Html.Action. Here are some useful links:
MSDN RenderAction
Difference between RenderPartial and RenderAction
Further, it would probably make sense for you to move your form tags into your partial view, but those are details for when you clean up the code.
Jonass is right, the ViewBag only propagates between the Controller and the View.
One thing you can do is make the model of the partial view be the same as the type of data you're putting into the ViewBag.
So if for example your MyQueryResults is of type:
IEnumerable<Result>
In your partial view you'd add
#Model IEnumerable<Result>
And then in the main view, you'd pass it through the Render method:
#Html.Partial("SearchResults", ViewBag.Members);
You'll need to tweak this a bit to make sure it's the right type, but this should do the trick.
Good luck!

Two-Way binding in Windows 8 HTML / JavaScript Metro Apps

I'm trying to achieve a two way binding between an input field and a field in my JavaScript ViewModel. The binding has been wired up declarativly. Unfortunately the changes I do in the UI aren't reflected in my ViewModel.
My Code looks like that (written out of my head as I don't have the code here)
View:
<form data-win-bind="onsubmit: onCalculate">
<div class="field">
Product Name:
<input type ="number" data-win-bind="text:value1"/>
</div>
<div class="field">
Product Price:
<input type ="number" data-win-bind="text:value2"/>
</div>
<div class="field">
Result
<br />
<span data-win-bind="innerText: result" />
</div>
</form>
JavaScript
var model= WinJS.Class.define(
function() {
this.onCalculate = calculate.bind(this);
this.value1 = 0;
this.value2 = 0;
this.result = 0;
},{
value1: 0,
value2: 0,
result: 0
calculate: function() {
this.result = this.value1 + this.value2;
return false;
}
}, {});
// Make the model Observable
var viewModel = WinJS.Binding.as(new model());
WinJS.Binding.processAll(null, viewModel);
When I apply the binding, the ui shows my initial values. The form submition is correctly wired with the calculate function. The values of value1 and value2 however aren't updated with the users input.
What I'm trying to achive is to keep my JavaScript unaware of the underlying view. So I don't want to wire up change events for the html input fields in JavaScript.
Is there any way to achive this with pure WinJS? All samples I've found so far only do a one-way binding and use event listeners to update the ViewModel with changes from the UI.
WinJS only supports one-way binding for Win8. It is necessary to wire up listeners for change events in the UI elements, hence the nature of the samples you've seen. In other words, the implementation of WinJS.Binding's declarative processing doesn't define nor handle any kind of two-way syntax.
It would be possible, however, to extend WinJS yourself to provide such support. Since WinJS.Binding is just a namespace, you can add your own methods to it using WinJS.Namespace.define (repeated calls to this are additive). You could add a function like processAll which would also look for another data-* attribute of your own that specified the UI element and the applicable change events/properties. In processing of that you would wire up a generic event handler to do the binding. Since you have the WinJS sources (look under "References" in Visual Studio), you can see how WinJS.Binding.processAll is implemented as a model.
Then, of course, you'd have a great piece of code to share :)
This article provides a nice solution:
http://www.expressionblend.com/articles/2012/11/04/two-way-binding-in-winjs/