Dynamic breadcrumbs with primefaces - jsf-2

I would like to add to my web app a dynamic breadcrumb using the Primefaces component. I've created a model to push items on the breadcrumb so that when one of its links is followed, the trailing links are removed. This works in most scenarios, but sometime the bradcrumb doesn't behave the way I expect. Basically, in order to track the landing page, I've added a preRenderView listener on each navigable page and implemented the model update logic in a session scoped bean.
<f:event type="preRenderView" listener="#{bcb.onRenderView}" />
<f:attribute name="pageName" value="ThisPage" />
The listener receives the page name as an attribute and obtains the complete URL (including query string) from the external context; these information, along with a unique id created from the UIViewRoot, are used to build a BreadCrumbItem that is pushed on the model:
public void onRenderView(ComponentSystemEvent evt) {
UIViewRoot root = (UIViewRoot)evt.getSource();
final String reqUrl = FacesUtils.getFullRequestURL();
String pageName = (String) evt.getComponent().getAttributes().get("pageName");
if(pageName != null) {
model.push(new BreadCrumbItem(root.createUniqueId(), pageName, reqUrl));
} else {
model.reset();
}
}
The push() and reset() methods of the model are implemented as follows:
/**
* When a link is pushed on the bread crumb, the existing items are analyzed
* and if one is found to be equal to the pushed one, the link is not added
* and all the subsequent links are removed from the list.
*
* #param link
* the link to be added to the bread crumb
*/
public void push(BreadCrumbItem link) {
boolean found = removeTrailing(link);
if(!found) {
addMenuItem(link);
}
}
/**
* Reset the model to its initial state. Only the home link is retained.
*/
public void reset() {
BreadCrumbItem home = new BreadCrumbItem();
removeTrailing(home);
}
Is this approach feasible? Can you suggest some better way to track page navigation without the need to leverage a life cycle listener? Thanks a lot for your help.

I have implemented my own one for my web app, in my case I didn't use the p:breadCrumb component because it's implemented using buttons.
Basically, I have an #SessionScoped bean which contains a stack (navigation stack), storing all the url's you have in the breadcrumb and the params for each of them. The view (xhtml) part is composed by p:button elements, which have the outcome of the stack's stored urls.
When you navigate to an url, corresponding bean's f:event type="preRenderView" is called (as the way you're doing it) and the bean takes the params from the url, after that it establishes itself into the stack (not the bean itself, cause it's #ViewScoped and going to be destroyed, just the url and params).
In case you click on a back button in the breadcrum, you send an additional param which indicates the index of the button. Based on that index, the destination bean knows that you're trying to recover such view, so asks the navigation stack for that view params and navigation stack removes navegables which are after it.
It took me a while, but it's fully functional. Good luck.
Edit
Be careful when using session scope for saving the current navigation state. It'll have influence in all the opened tabs, so probably it's not what you want, unless you expect your end user to use your application in one tab only. Anyway, the general usability guidelines say you should use categories instead of navigation paths for your breadcrumbs (HTTP is stateless and the history itself is kept by the browser). So a dynamic breadcrumb doesn't make sense anymore, at least if you're using different urls for your views.

Related

JSF Page Navigation+Data flow

I am new to JSF and started learning and have worked on struts. Just wanted to understand how navigation works in JSF2.0. I understand in JSF2.0 navigation rules can be done in faces-config.xml. But I would like to understand how it works in real world.
Suppose I have a screen to enter search criteria and the next screen should dispaly the search results.Where do I process the result set <h:commandButton value="Show Results"
action="#{simpleController.doNavigation}"/>and my doNavigation() does the job of querying the database and fetch the results, how do I carry it next screen. Do I need to hold the value in SESSION
Just assign the data as a property of the current bean instance.
public String doNavigation() {
results = searchService.find(query);
return "nextpage";
}
By default, navigation doesn't fire a new request, it just sets the target page to the current response. In the target page, you can just use #{simpleController.results}.
Unrelated to the concrete problem, this is a poor practice (bad for SEO and UX). Rather show the results in the very same page and don't perform navigation on postbacks.
See also:
How to navigate in JSF? How to make URL reflect current page (and not previous one)
Problems with navigation cases from an embedded JSF 2.0 application

Difference in navigation by action="xyz" and action="#{bean.returnXyz}"

How is navigation from a Facelet page
<p:commandLink action="xyz.xhtml">
or a backing bean
<p:commandLink action="#{bean.redirect}">
public class Bean{
public String redirect(){
.....
return "xyz.xhtml";
}
}
different from each other?
How is navigation from a xhtml page or a backing bean different from each other.
There's no difference. The both examples invoke a POST request and instructs JSF to render the view associated with the given outcome. The backing bean method has the only advantage that it allows you to perform some business logic beforehand or even control the outcome value programmatically.
However, if you don't have any business logic at all and solely want to have an idempotent link to another page, then using a command link is actually a bad practice. Using POST for page-to-page navigation is not user nor SEO friendly. The target page is not bookmarkable (the URL remains the one of the page where the POST form was been submitted to) nor searchbot-crawlable (it is using JavaScript to submit a hidden form).
You should instead use a normal link.
<h:link outcome="xyz.xhtml">
This generates a SEO-friendly <a> element with the full URL in its href and ends up in an user-friendly bookmarkable URL.
See also:
When should I use h:outputLink instead of h:commandLink?
How to navigate in JSF? How to make URL reflect current page (and not previous one)
Check out the documentation of p:commandLink here, which says the following for action attribute:
A method expression or a string outcome to process when command is
executed.
Now, as action="xyz.xhtml" returns String xyz.xhtml you're redirected accordingly and for action="#{bean.redirect}" which again returns xyz.xhtml you are again redirected according to the returned String.

jsf clearing the form

I need help. I'm new to JSF and im using JSF2 and richfaces in my project.
I want to clear the form for which I'm using <f:ajax render="#form"/> in refresh button. I have an ADD button on that screen which adds one record and I hit refresh then it's going to the default page. But when I once again go to enter a record then those values which I entered earlier remain in the form fields.
Can anyone please help me out with this issue?
Assuming that you mean the browser's refresh button when you say "I hit refresh", then that can happen if you've incorrectly placed the bean holding view scoped data in the session scope. You're then basically reusing the very same bean as the form is previously been submitted to. Putting the bean in the view scope instead of the session scope should fix this problem. The view scope ends when you navigate to a different page or fires a new request (as by hitting browser's refresh button).
See also:
How to choose the right bean scope?
Update if you're due to bad design restricted to using session scope, then you might want to hack this around by a
<f:event type="preRenderView" listener="#{sessionScopedBeanWhichShouldActuallyBeViewScoped.resetModel}" />
with
public void resetModel() {
if (!FacesContext.getCurrentInstance().isPostback()) {
model = null;
}
}
This will clear the model on every GET request. However, regardless of this hack, you'll still run into serious problems when the enduser opens the same view in a different browser tab/window within the same session.
The right solution is to put the bean in the view scope instead of the session scope, as said earlier.

.NET MVC: How to implement different page appearance per user?

I am running out of ideas here. Maybe you can advice me what pattern or method(s) to use.
User should be able to log in and change the appearance only for his/her profile.
The difference (AFAIK) with personalization is that personalized layout are seen only for the editor (him-/herself).
The difference between skinning, I guess, is that Skins are predefined but users should be able to change the settings themselves.
I need to be able to display the customized layout to everyone who visit author`s page.
The good solution would be to keep the layout info in a DB table. Also it should be cached I guess to take load off the DB and used in CSS.
Thanks
Edit:
OK I have done some research now. Came up with this kind of idea.
In a View get a userId (Guid type) from a DB and set it to the ViewData:
ViewData["userId"] = profile.userId;
That View uses the following MasterPage called 'Profile.Master' and links to the dynamic CSS file:
<link href="<%= Url.Action("Style", "Profile",
ViewData["userId"]) %>" rel="stylesheet" type="text/css" />
</head>
In the ProfileController get the CSS data from DB and return it to the dynamic CSS View:
public ActionResult Style(Guid userId)
{
var styles = (from s in Db.UserStyleSet.OfType<UserStyle>()
where s.aspnet_Users.UserId == userId
select s);
return View("Style", styles);
}
The problem is that the UserId is never passed to the dynamic CSS link:
The parameters dictionary contains a null entry for parameter 'userId' of non-nullable type 'System.Guid' for method 'System.Web.Mvc.ActionResult Style(System.Guid)' in 'Project.Controllers.ProfileController'.
Any advice is welcome, thank you.
Very neat layout customization features you can find in Kona project developed by Rob Conery. When you run source code which you can find here, you will see layout management UI which allows you to change the position of each component on the screen.
The approach used there is as follows:
When page is rendered our customized view engine check which master page should present (this way we are able to switch themes based on current settings)
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
ViewEngineResult result = null;
var request = controllerContext.RequestContext;
if (controllerContext.Controller.GetType().BaseType == typeof(KonaController)) {
var orchardController = controllerContext.Controller as KonaController;
string template = orchardController.ThemeName;
View engine uses master page and renders view which was defined by specific controller action resolved using route tables. For instance, we typed main url of the site which pointed to Home Controller, Index method. This method returned Index.aspx view which was rendered by View engine.
While view engine is rendering the Index.aspx page it launches helper methods like
<%this.RenderWidgets("sidebar1"); %>.
This method is truely responsible for rendering specific widdgets per each div in the aspx page. This way, if your user changes the layout of the widgets they will be correctly presented on the screen.
public static void RenderWidgets(this ViewPage pg, Kona.Infrastructure.Page page, bool useEditor, string zone) {
if (page != null) {
foreach (IWidget widget in page.Widgets.Where(x => x.Zone.Equals(zone, StringComparison.InvariantCultureIgnoreCase))) {
string viewName = useEditor ? widget.EditorName : widget.ViewName;
if (widget.ViewName != null) {
if (widget.IsTyped) {
var typedWidget = widget as Widget<IList<Product>>;
pg.Html.RenderPartial(viewName, typedWidget);
} else {
pg.Html.RenderPartial(viewName, widget);
}
} else if (!string.IsNullOrEmpty(widget.Title)) {
pg.Html.RenderPartial("TitleAndText", widget);
} else {
pg.Html.RenderPartial("TextOnly", widget);
}
}
}
}
How user is able to change the layout? Kona has very neat javascript which is used together with Ajax and user simply drag&drop widgets from one panel to another to reorder the layout.
You could use a CMS framework. See this question for suggestions
You could dynamically build a CSS file and save the css name in the user's db entry.
How much customisation do you need? Storing an entire css in the database 1 style at a time seems a little overkill, are you sure your users really need / want that level of customisation?
Wouldn't it be simpler to present a list of themes, allow the user to select the one they want and then store that information with the user profile so that when you retrieve the profile details you also retrieve the theme. This information can then be used to select the appropriate master as well as passed to the view to render the correct stylesheet(s).
If you really want to allow extreme customisation down to the individual style level, I would use a default css and then when the user customises their layout, copy the default and alter as necessary, creating a custom css for the user. Each time the user updates their profile layout, simply update the css file with the changes. To get around css caching, record an incrementing version number for each change and append that to the end of the url for the css e.g. <link rel="stylesheet" href="user001.css?v=2>.

ASP.net MVC - request-scoped global variable

I have a value which I want to be vaild during a single request. I am not using Session, as this would make the value global for the entire navigation session.
So I have put thie value in a static field of a class. Great, but then I discovered that such fields are even more global, that is, they stay set for the entire application! This means that there could be random interaction among navigation sessions.
So the question is: is there a safe place I can put a global variable, which will be
global throughout the request
reset after the request is completed
not affected by any other request, either of the same user or by other users
Thanks
Palantir
EDIT
I'll elaborate. I have a piece of code in my master page, which I need to hide on certain conditions, of which I am aware in the controller only. I thought about setting a static variable in the controller, which then would be queried by the master page, but now I see there could be a better way...
Use HttpContext.Items - a per-request cache store. Check out this article on 4guysfromrolla for more details.
It should work fine in ASP.NET MVC. You may wish to derive your master page from a base class (either via code-behind or using the Inherits directive) and have a protected method on the base class that inspects HttpContext.Items and returns, e.g. true/false depending whether you want to display the conditional code.
TempData lasts until the next request as already noted.
But there are also two other dictionaries scoped to the single request.
{Controller,ViewPage}.ViewData
Context.Items
To communicate from controller to (master) page ViewData is probably the better choice.
Two approaches come to mind:
Create a base controller where you set this variable, and then have all your controllers inherit from that.
Use TempData - the problem here being that it sticks around for the next request. But maybe knowing that, you can work around it by using a GUID key to determine that you are, in fact, getting a new value when you need it.
I would probably go with 1).
The common way to access data in a MasterPage that is set in Controller (Action) is via ViewData["TheDataKey"] = "SomeValue".
This is relatively easy and there are a couple of ways that you can do it - depending on how your site works.
I'm interpreting your request as that you want a property or variable that exists for the duration of the request and is visible to the controller, model and master.
A static property is visible to the current application in ASP this means a load of users connecting at once, but not necessarily all of them. IIS will spawn new ASP applications as it needs to.
So the ways you can do this:
You can have a custom base class for your master page or a code-behind page (as all the WebForms stuff still works)
You can have a custom base class for your controllers.
You can get to one from the other, so:
void Page_Init( object sender, EventArgs e )
{
var ctrl = this.ViewContext.Controller as MyBaseController;
if ( ctrl != null )
{
MyLocalProp = ctrl.PropOnMyController;
}
}
This will then be available in the controller and the master page on a per Request basis.
Did you look into the tempData that is attached to the controller class? it is a simple dictionary that preserves it's value through one single request.That would meant that your data can only be accessed in the controller but that should not be a problem.
public class MyController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult MyAction(string id)
{
this.TempData["Message"] = "YourData";
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MyAction(string Id)
{
var myData = this.TempData["Message"];
}
}
This works for me. I use it only to display warning messages and stuff like that.

Resources