Using path parameters in <view-id> with PrettyFaces - prettyfaces

Many pages in a typical JSF applications are dynamic, meaning that there is a template view that would be used to render every object of a given type. For these pages PrettyFaces rewriting solution works great and effortless. An example is a web application that displays a product, basing on its id, or other unique field. There is typically one view related to such a display, say product.xhtml, and one view parameter, holding the unique field of a product, say name.
With a simple setting we get all requests like /product.xhtml?name=exact-product-name rewritten as, for example, /products/exact-product-name:
The URL mapping:
<url-mapping id="viewProduct">
<pattern value="/products/#{ name : productBean.name }" />
<view-id value="/store/product.xhtml" />
<action> #{ productBean.loadData } </action>
</url-mapping>
The view:
<f:metadata>
<f:viewParam id="name" name="name" required="true" />
</f:metadata>
The model:
public class ProductBean implements Serializable {
private ProductService productService;
private String name;
private Product product;
public String loadData() {
if(!((name == null) || (name.equals(""))) {
Product product = productService.findByName(name);
this.product = product;
return null;
}
return "error";
}
}
However, there are also many pages with static data, that are not templated in a way described above, using view parameters. These pages simply display what was put in them. For example, there may be many articles that were created as separate views (like /pages/articles/article1.xhtml and so on). Using PrettyFaces we would need to create as many URL mapping as the number of such pages. But, in fact this behavior can also be templated in one URL mapping. Unfortunately, this is not supported in current PrettyFaces release.
The proposed enhancement of the PrettyFaces framework is the following:
<url-mapping id="viewArticle">
<pattern value="/articles/#{ articleName }" />
<view-id value="/store/#{ articleName }.xhtml" />
</url-mapping>
or, using an ArticleBean (containing, for example, two fields: articleName and articleId, where name is defined in setter of id field as a unique value):
<url-mapping id="viewArticle">
<pattern value="/articles/#{ articleId : articleBean.articleId }" />
<view-id value="/store/#{ articleBean.articleName }.xhtml" />
</url-mapping>
or using other predefined dependence based on an EL-expression, which is in turn based on a unique correspondence.
I want to emphasize that this is not going to be a DynaView because there is no uncertainty in the view-id: there is a one-to-one correspondence between a <pattern> and a <view-id>.
What do you think about implementing this feature in PrettyFaces?

I think Stackoverflow is not the right place to discuss proposals for PrettyFaces. You should have a look at the PrettyFaces Support Forum for that.
There are some options for you to implement something like this. IMHO you could try to do this view DynaView. Even if there is a one-to-one relationship between pattern and view-Id like your wrote. However dynaview has some problems especially with outbound rewriting.
But you should have a look at Rewrite, which is the successor of PrettyFaces. With Rewrite it is very simple to implement such a requirement:
.addRule(Join.path("/articles/{articleName}").to("/store/{articleName}.xhtml"))
Have a look at the configuration examples of Rewrite.

As far as the setup of pretty-config.xml doesn’t currently support this feature, there are some workarounds to achieve this functionality. I will describe them below.
A dummy view with <f:event> that handles navigation to the final pages based on a view parameter in a dummy bean.
URL mapping:
<url-mapping id="viewArticle">
<pattern value="/articles/#{ articleName : articleBean.articleName }" />
<view-id value="/handle-article-redirection.xhtml" />
</url-mapping>
View handle-article-redirection.xhtml:
<f:metadata>
<f:viewParam id="articleName" name="articleName" required="true" />
<f:event type="preRenderView" listener="#{articleBean.handleRedirect}" />
</f:metadata>
Model:
public class ArticleBean {
private ArticleService articleService;
private String articleName;
private String articleUrl;
public void handleRedirect() {
if(!((articleName == null) || (articleName.equals(""))) {
String url = articleName;
//String url = articleService.getUrlForArticleName(articleName);
//articleUrl = url;
FacesContext.getCurrentInstance().getExternalContext().redirect("/" + url + ".xhtml");
return null;
}
FacesContext.getCurrentInstance().getExternalContext().redirect("/home.xhtml");
}
}
A meaningful view with a dynamic <ui:include> that imports the necessary page content as a snippet, basing on the bean value / view parameter.
URL mapping:
<url-mapping id="viewArticle">
<pattern value="/articles/#{ articleName : articleBean.articleName }" />
<view-id value="/article.xhtml" />
</url-mapping>
View article.xhtml:
<f:metadata>
<f:viewParam id="articleName" name="articleName" required="true" />
</f:metadata>
<h:head></h:head>
<h:body>
<ui:include src="/#{articleBean.articleUrl}.xhtml" />
</h:body>
Model:
public class ArticleBean {
private ArticleService articleService;
private String articleName;
private String articleUrl;
public void setArticleName(String articleName) {
this.articleName = articleName;
if((!(articleName == null)) || (articleName.equals("")) {
articleUrl = articleName;
//articleUrl = articleService.getUrlForArticleName(articleName);
} else {
articleUrl = null;
}
}
}
A DynaView URL mapping with a method that returns a proper outcome.
URL mapping:
<url-mapping id="viewArticle">
<pattern value="/articles/#{ articleName : articleBean.articleName }" />
<view-id value="#{articleBean.getViewPath}" />
</url-mapping>
No extra view needed.
Model:
public class ArticleBean {
private ArticleService articleService;
private String articleName;
private String articleUrl;
public String getViewPath() {
this.articleName = articleName;
if(!((articleName == null) || (articleName.equals(""))) {
articleUrl = articleName;
//articleUrl = articleService.getUrlForArticleName(articleName);
return articleUrl;
}
return "error";
}
}
A template view that loads the page data from the database, hence there will be no separate views for those pages.
URL mapping:
<url-mapping id="viewArticle">
<pattern value="/articles/#{ articleName : articleBean.articleName }" />
<view-id value="/article.xhtml" />
<action> #{ articleBean.loadData } </action>
</url-mapping>
View article.xhtml:
<f:metadata>
<f:viewParam id="articleName" name="articleName" required="true" />
</f:metadata>
<h:head></h:head>
<h:body>
<h:panelGroup>
#{articleBean.content}
<h:panelGroup>
</h:body>
Model:
public class ArticleBean {
private ArticleService articleService;
private String articleName;
private String articleUrl;
private String content;
public void loadData() {
if(!((articleName == null) || (articleName.equals(""))) {
articleUrl = articleName;
//articleUrl = articleService.getUrlForArticleName(articleName);
content = articleService.getContentForArticleName(articleName);
} else {
articleUrl = null;
content = null;
}
}
}
Write a custom WebFilter or a NavigationHandler.
What is the best alternative, well, it depends. All of them have their pros and cons.

Related

Binding a List in a ObservableCollection in XAML - Specified cast not valid exception

I have an ObservableCollection<List<Model>> Data in my ViewModel.
In my Page I need a CarouselView, in which each ItemTemplate shows the data of the Data list in a ListView.
Currently, I am doing that in that way:
<CarouselView ItemsSource="{Binding Data}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
...
<ListView ItemsSource="{Binding .}">
...
</ListView>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
In the way I am doing that I get a "Specified cast not valid" exception, in which I see the following additional information:
{System.InvalidCastException: Specified cast is not valid.
at (wrapper castclass) System.Object.__castclass_with_cache(object,intptr,intptr)
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].ActivateContent (System.Int32 index, System.Object item) [0x00032]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].CreateContent (System.Int32 index, System.Object item, System.Boolean insert) [0x00000]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].GetOrCreateContent (System.Int32 index, System.Object item) [0x00023]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].get_Item (System.Int32 index) [0x0000e]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Platform.iOS.ListViewRenderer+ListViewDataSource.GetCellForPath (Foundation.NSIndexPath indexPath) [0x00007]
in D:\a\_work\1\s\Xamarin.Forms.Platform.iOS\Renderers\ListViewRenderer.cs:1397
at Xamarin.Forms.Platform.iOS.ListViewRenderer+ListViewDataSource.GetCell (UIKit.UITableView tableView, Foundation.NSIndexPath indexPath) [0x00021]
in D:\a\_work\1\s\Xamarin.Forms.Platform.iOS\Renderers\ListViewRenderer.cs:1105
at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr)
at UIKit.UIApplication.Main (System.String[] args, System.Type principalClass, System.Type delegateClass) [0x0003b]
in /Users/builder/azdo/_work/1/s/xamarin-macios/src/UIKit/UIApplication.cs:85
at App.iOS.Application.Main (System.String[] args) [0x00001]
in <Path>\Main.cs:18 }
The Model holds only string values, so the exception cannot come from this.
I'm not sure why you're getting that specific exception. I couldn't get the ListView inside of a CarouselView to work either.
However, it works when you use a bindable StackLayout instead of a ListView. My guess is that the bindable StackLayout doesn't support scrolling and thus doesn't fight with the CarouselView but I don't know.
MainPage, MainViewModel and items
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Xamarin.Forms;
namespace App1
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}
public class MainViewModel
{
public ObservableCollection<Item> Data { get; }
public MainViewModel()
{
Data = new ObservableCollection<Item>(GenerateItems());
}
private IEnumerable<Item> GenerateItems()
{
return Enumerable.Range(1, 10)
.Select(a => new Item
{
ItemTitle = $"Item {a}",
SubItems = Enumerable.Range(1, 10).Select(b => new SubItem { SubItemTitle = $"SubItem {b}" }).ToList()
});
}
}
public class Item
{
public string ItemTitle { get; set; }
public List<SubItem> SubItems { get; set; }
}
public class SubItem
{
public string SubItemTitle { get; set; }
}
}
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="App1.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App1">
<ContentPage.BindingContext>
<local:MainViewModel />
</ContentPage.BindingContext>
<CarouselView ItemsSource="{Binding Data}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding ItemTitle}" />
<StackLayout BindableLayout.ItemsSource="{Binding SubItems}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding SubItemTitle}" />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</ContentPage>
Result:

How to embed language in Sitecore-uCommerce nice URLs?

I am using the default configuration of uCommerce and see that uCommerce nice URLs are not language aware: http://sitename/catalogname/productname/c-XX/p-YY.
What should I do to have language in those URLs like this: http://sitename/en/catalogname/productname/c-XX/p-YY ?
Here is the configuration:
<linkManager defaultProvider="sitecore">
<providers>
<clear />
<add name="sitecore" type="Sitecore.Links.LinkProvider, Sitecore.Kernel" addAspxExtension="false" alwaysIncludeServerUrl="false" encodeNames="true" languageEmbedding="always" languageLocation="filePath" lowercaseUrls="true" shortenUrls="true" useDisplayName="true" />
</providers>
</linkManager>
Here is how I use it:
public WebshopProduct Map(UCommerceProduct uProduct)
{
ProductCatalog catalog = CatalogLibrary.GetCatalog(25);
IUrlService urlService = ObjectFactory.Instance.Resolve<IUrlService>();
...
var url = urlService.GetUrl(catalog, uProduct) // this returns "/catalogname/productname/c-XX/p-YY"
//And I would like to have "/en/catalogname/productname/c-XX/p-YY"
}
Adding language to URL depends on how you are rendered links. If you don't pass specific parameters than Sitecore (and uCommerce as part of Sitecore) uses LinkManager configuration sitecore>linkManager>providers: languageEmbedding adn languageLocation attributes. You should have languageEmbedding="always" and languageLocation="filePath"
P.S.
But, be aware if you using their demo or something based on their demo(e.g. from certification courses): they uses regular ASP.Net MVC(not Sitecore MVC). And links are not rendered via LinkManager and you should put language to URL by youself. Register routed with language code that is embedded to them.
Here is what I have come up with:
public static class TemplateIDs
{
// sitecore/ucommerce item's template id
public static ID UCommerce => new ID("{AABC1CFA-9CDB-4AE5-8257-799D84A8EE23}");
}
public static class ItemExtensions
{
public static bool IsUCommerceItem(this Item item)
{
var items = item.Axes.GetAncestors();
return items.Any(x => x.TemplateID.Equals(TemplateIDs.UCommerce));
}
}
public static string GetItemUrlByLanguage(Sitecore.Globalization.Language language)
{
if (Context.Item.IsUCommerceItem() && SiteContext.Current.CatalogContext.CurrentProduct != null && SiteContext.Current.CatalogContext.CurrentProduct.Guid == Context.Item.ID.Guid)
{
ProductCatalog catalog = CatalogLibrary.GetCatalog(25);
IUrlService urlService = ObjectFactory.Instance.Resolve<IUrlService>();
var url = "/" + language.CultureInfo.TwoLetterISOLanguageName + urlService.GetUrl(catalog, SiteContext.Current.CatalogContext.CurrentProduct);
return url;
}
else
{
//Normal URL creation
using (new LanguageSwitcher(language))
{
var options = new UrlOptions
{
AlwaysIncludeServerUrl = true,
LanguageEmbedding = LanguageEmbedding.Always,
LowercaseUrls = true
};
var url = LinkManager.GetItemUrl(Context.Item, options);
url = StringUtil.EnsurePostfix('/', url).ToLower();
return url;
}
}
}

Power user access to Sitecore Recycle Bin

Does anyone know of a solution to allow users in a certain role to view all items in the Sitecore Recycle Bin?
Currently, only admins can see all deleted items. Users can only see items they have deleted.
There isn't a way out of the box, the SqlArchive.GetEntries checks against user.IsAdministrator to show all entries in the archive.
You would need to implement a custom Archive provider and override the GetEntries method to work from a role.
Example:
public class CustomSqlArchive : SqlArchive
{
public CustomSqlArchive(string name, Database database)
: base(name, database)
{
}
protected override IEnumerable<ArchiveEntry> GetEntries(User user, int pageIndex, int pageSize, ID archivalId)
{
Assert.IsNotNull(archivalId, "archivalId");
var arrayList = new ArrayList(new[] { "archiveName", this.Name });
var str1 = "SELECT * FROM \r\n (SELECT {0}Archive{1}.{0}ArchivalId{1}, {0}Archive{1}.{0}ItemId{1}, {0}ParentId{1}, {0}Name{1}, {0}OriginalLocation{1}, \r\n {0}ArchiveDate{1}, {0}ArchivedBy{1}, ROW_NUMBER() OVER(ORDER BY {0}ArchiveDate{1} DESC, {0}ArchivalId{1}) as {0}RowNumber{1}\r\n FROM {0}Archive{1} \r\n WHERE {0}ArchiveName{1} = {2}archiveName{3}";
var showAllItems = user.IsInRole("Super User Role") || user.IsAdministrator;
if (user != null && !showAllItems)
{
str1 = str1 + " AND {0}ArchivalId{1} IN (SELECT {0}ArchivalId{1}\r\n FROM {0}ArchivedVersions{1} WHERE {0}ArchivedBy{1} = {2}archivedBy{3}) ";
arrayList.AddRange(new[] { "archivedBy", user.Name });
}
if (archivalId != ID.Null)
{
str1 = str1 + " AND {0}ArchivalId{1} = {2}archivalId{3}";
arrayList.Add("archivalId");
arrayList.Add(archivalId);
}
var str2 = str1 + ") {0}ArchiveWithRowNumbers{1}";
if (pageSize != int.MaxValue)
{
str2 = str2 + " WHERE {0}RowNumber{1} BETWEEN {2}firstRow{3} AND {2}lastRow{3}";
var num1 = (pageIndex * pageSize) + 1;
int num2 = pageSize == int.MaxValue ? int.MaxValue : (pageIndex + 1) * pageSize;
arrayList.AddRange(new[] { "firstRow", num1.ToString(), "lastRow", num2.ToString() });
}
return this.GetEntries(str2 + " ORDER BY {0}ArchiveDate{1} DESC, {0}ArchivalId{1}", arrayList.ToArray());
}
}
You would then need to add your custom provider to the config:
<archives defaultProvider="custom" enabled="true">
<providers>
<clear />
<add name="custom" type="Sitecore.Data.Archiving.SqlArchiveProvider, Sitecore.Kernel" database="*" />
<add name="sql" type="Sitecore.Data.Archiving.SqlArchiveProvider, Sitecore.Kernel" database="*" />
<add name="switcher" type="Sitecore.Data.Archiving.SwitchingArchiveProvider, Sitecore.Kernel" />
</providers>
</archives>
Then add a role called Super User Role and put any users you want to have that access as members.
** note - code is untested **
Below is a similar approach to Richard's answer, but instead of copying all of the logic within GetEntries(), it spoofs the admin user. You will also need to implement a SqlArchiveProvider in addition to the CustomSqlArchive itself.
SQL Archive
public class CustomSqlArchive : SqlArchive
{
private const string PowerUserRole = #"sitecore\Power User";
private const string AdminUser = #"sitecore\Admin";
public AvidSqlArchive(string name, Database database) : base(name, database) { }
protected override IEnumerable<ArchiveEntry> GetEntries(User user, int pageIndex, int pageSize, ID archivalId)
{
if (user != null && Role.Exists(PowerUserRole) && user.IsInRole(PowerUserRole))
{
User admin = User.FromName(AdminUser, true);
return base.GetEntries(admin, pageIndex, pageSize, archivalId);
}
return base.GetEntries(user, pageIndex, pageSize, archivalId);
}
}
SQL Archive Provider
public class CustomSqlArchiveProvider : SqlArchiveProvider
{
protected override Sitecore.Data.Archiving.Archive GetArchive(XmlNode configNode, Database database)
{
string attribute = XmlUtil.GetAttribute("name", configNode);
return !string.IsNullOrEmpty(attribute) ?
new CustomSqlArchive(attribute, database) :
null;
}
}
Configuration Patch
<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<!-- Use custom archive that allows users in the "Power User" role to see other user's items by spoofing the admin user -->
<archives defaultProvider="sql" enabled="true">
<patch:attribute name="defaultProvider">custom</patch:attribute>
<providers>
<clear />
<add name="custom" type="Example.CustomSqlArchiveProvider, Example" database="*" />
<add name="sql" type="Sitecore.Data.Archiving.SqlArchiveProvider, Sitecore.Kernel" database="*" />
<add name="switcher" type="Sitecore.Data.Archiving.SwitchingArchiveProvider, Sitecore.Kernel" />
</providers>
</archives>
</sitecore>
</configuration>

Google Glass Live Card not inserting

Glass GDK here. Trying to insert a livecard using remote views from service. I'm launching service via voice invocation. The voice command works, however it appears my service is not starting(no entries in log). Service is in android manifest. Below is code:
public class PatientLiveCardService extends Service {
private static final String LIVE_CARD_ID = "timer";
#Override
public void onCreate() {
Log.warn("oncreate");
super.onCreate();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
publishCard(this);
return START_STICKY;
}
#Override
public void onDestroy() {
unpublishCard(this);
super.onDestroy();
}
private void publishCard(Context context) {
Log.info("inserting live card");
if (mLiveCard == null) {
String cardId = "my_card";
TimelineManager tm = TimelineManager.from(context);
mLiveCard = tm.getLiveCard(cardId);
mLiveCard.setViews(new RemoteViews(context.getPackageName(),
R.layout.activity_vitals));
Intent intent = new Intent(context, MyActivity.class);
mLiveCard.setAction(PendingIntent
.getActivity(context, 0, intent, 0));
mLiveCard.publish();
} else {
// Card is already published.
return;
}
}
private void unpublishCard(Context context) {
if (mLiveCard != null) {
mLiveCard.unpublish();
mLiveCard = null;
}
}
}
Here is AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET" >
</uses-permission>
<uses-permission android:name="android.permission.RECORD_AUDIO" >
</uses-permission>
<application
android:name="com.myApp"
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name="com.myApp.MyActivity"
android:label="#string/app_name"
android:screenOrientation="landscape" >
</activity>
<service android:name="com.myApp.services.MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
</intent-filter>
<meta-data
android:name="com.google.android.glass.VoiceTrigger"
android:resource="#xml/voice_trigger_get_patient" />
</service>
</application>
This is a bug with XE11: the service is not started after the speech recognizer is complete.
As a workaround, you can have your voice trigger start an Activity which:
Processes the recognized speech in onResume.
Once the speech is processed, starts your Service with startService.
Calls finish to jump to the published LiveCard.

JSF: Adding a String to List

I have an JSF 2.0 application that has a bean that holds a list of Strings.
I want to add the String from an <h:inputText>/> to my List and display my list.
The following code just put references in my List. So every element from my List is set to the last input.
#ManagedBean
#ApplicationScoped
public class Bean {
private String name;
private ArrayList<String> test = new ArrayList<String>();
public Bean() {
}
public Bean(String name) {
this.name = name;
}
public String addtoList(String _name){
test.add(_name);
return "./index.xhtml";
}
/***************GETTER/SETTER/HASHCODE/EQUALS**************************/
...
}
here a part of my index.xhtml:
<h:inputText id="name"
value="#{bean.name}"
required="true">
</h:inputText>
<h:commandButton value="Post"
action="#{bean.addtoList(name)}"/>
<br/>
<h:dataTable var="bean"
value="#{bean.test}">
<h:column>
<h:outputText value="#{bean.name}"/>
</h:column>
</h:dataTable>
Try this:
public String addtoList() { // no parameter
test.add(this.name); // add value of bean's property
return "./index.xhtml";
}
and in the facelet:
<h:commandButton
value="Post"
action="#{bean.addtoList}"/> <!-- no parameter passed -->
The point is to have the addToList method without parameters and the string you add to the list should be the value of the name property of the backing bean.
And also, in the datatable, do not name the var the same as the backing bean. It's confusing and potentially leads to bugs. Use something like this:
<h:dataTable
var="it"
value="#{bean.test}">
<h:column>
<h:outputText value="#{it}" />
</h:column>
</h:dataTable>
<h:dataTable var="beany"
value="#{bean.test}">
<h:column>
<h:outputText value="#{beany}"/>
</h:column>
</h:dataTable>
the prob was that var="bean" is the same name of my class bean
better should be another name for var