I'm trying to use LibVLCSharp with a playlist ( or that's the plan) and am testing it out with a basic loop between two different videos. I'm using this in WPF. The UI has a button to start the first video playing. If I don't loop and click the button each time, the next video will play as expected. Let it loop and threading error occurs on second video. I have checked out some of the other posts here on SO - How to achieve looping playback with Libvlcsharp and the various links within there, but I'm missing something. I'm hoping someone has a suggestion - or two! Thanks.
XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpf="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF"
xmlns:local="clr-namespace:testVLC"
xmlns:Properties="clr-namespace:testVLC.Properties" x:Class="testVLC.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".2*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width=".2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height=".25*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height=".25*"/>
<RowDefinition Height=".25*"/>
</Grid.RowDefinitions>
<wpf:VideoView x:Name="VLC_player" Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="0" Grid.RowSpan="3" Background="Black" Loaded="VLC_player_Loaded"/>
<Button x:Name="go_Btn" Content="Press to view video" Grid.Column="2" HorizontalAlignment="Right" Grid.Row="3" Padding="4,1" Margin="0,0,1,0" Click="Button_click"/>
<TextBox x:Name="countTBox" Grid.Column="2" HorizontalAlignment="Left" Grid.Row="3" Width="30" VerticalContentAlignment="Stretch"/>
</Grid>
</Window>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using LibVLCSharp.Shared;
using MediaPlayer = LibVLCSharp.Shared.MediaPlayer;
namespace testVLC
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
LibVLC _libVLC;
MediaPlayer media_player;
bool init_flag = false;
bool Web_flag = false;
int count = 0;
public MainWindow()
{
InitializeComponent();
}
private async void Media_play (string URI)
{
if (init_flag)
{
if(Web_flag)
{
_libVLC = new LibVLC("--verbose=2");
Media media = new Media(_libVLC, (URI), FromType.FromLocation);
await media.Parse(MediaParseOptions.ParseNetwork);
media_player = new MediaPlayer(media.SubItems.First());
VLC_player.MediaPlayer = media_player;
media_player.EndReached += Video_Ended;
media_player.Play();
//MessageBox.Show(Thread.CurrentThread.Name);
}
else
{
_libVLC = new LibVLC();
media_player = new MediaPlayer(_libVLC);
VLC_player.MediaPlayer = media_player;
media_player.EndReached += Video_Ended;
media_player.Play(new Media(_libVLC, URI));
}
}
}
private void Button_click(object sender, RoutedEventArgs e)
{
count++;
this.Dispatcher.Invoke((Action)(() =>
{
countTBox.Text = count.ToString();
}));
Main_List();
}
private async void Video_Ended(object sender, EventArgs e)
{
//ThreadPool.QueueUserWorkItem(_ => media_player.Stop());//doesn't work
await Task.Run(() => media_player.Stop());
Web_flag = false;
//Button_click(this, null);//Works fine when commented out and the "Press to view video" button is clicked each time. Not so much when looping.
}
private void Main_List()
{
if (count%2 == 0)//even
{
Media_play("C:\\Users\\echo_\\Downloads\\videoplayback (1).mp4");
}
else//odd
{
Web_flag = true;
Media_play("https://youtu.be/UK4t59mhIhs");
}
}
private void VLC_player_Loaded(object sender, RoutedEventArgs e)
{
init_flag = true;
Core.Initialize();
}
}
}
Don't try to call Stop at the VideoEnded event, I think there is a known issue in this area in libvlc 3 that hangs the program.
Call .Play with the new media instead.
Also, I wouldn't recommend to use two different libvlc instances if you can avoid it. 1 LibVlc, and 1 Media Player, but multiple Play() on the same MediaPlayer would be the norm. You can pass different options as media options if you need to have different options (though some options are not available at the media level, like the verbosity option (in which case, I'd register a log callback and filter there))
Related
I followed the example - https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/hybridwebview/ to create a hybrid web view. It works fine in Andriod project but I have issues with the iOS project. In the OnElementChanged override, the Element property is always Xamarin.Forms.Element type which is the base type and not my generic view type (TElement). I have rechecked my code multiple times but cannot find any fixes. If I download the original code and run it, it works fine. The issue is only in my project.
Can anybody give me pointers as to why I might not be getting the generic TElement type. In the original project , the TElement type is HybridWebView. In mine I have a different name but with almost all the same properties and methods.
Below are some of my code. It is really easy to replicate the issue. Just create a new Xamarin.Forms solution and add my code. You will see the issue on this lie - var ele = Element; //ELEMENT HERE IS NOt SBMHybridWebView
The custom control in PCL
public class SBMHybridWebView : Xamarin.Forms.View
{
}
The iOS Renderer (off-course I have added the ExportRenderer)
public class SBMHybridWebViewRenderer : ViewRenderer<SBMHybridWebView, WKWebView>, IWKScriptMessageHandler
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
WKUserContentController userController;
protected override void OnElementChanged(ElementChangedEventArgs<SBMHybridWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
userController = new WKUserContentController();
var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript(script);
userController.AddScriptMessageHandler(this, "invokeAction");
var config = new WKWebViewConfiguration { UserContentController = userController };
var webView = new WKWebView(Frame, config);
SetNativeControl(webView);
}
if (e.OldElement != null)
{
//Clean up code
}
if (e.NewElement != null)
{
var ele = Element; //ELEMENT HERE IS NOt SBMHybridWebView
}
}
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
//Element.InvokeAction(message.Body.ToString());
}
}
The XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:localcontrol="clr-namespace:App2.Control;assembly=App2"
x:Class="App2.View.Page1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<localcontrol:SBMHybridWebView Grid.Row="0" />
</Grid>
</ContentPage>
I found the cause which I cannot explain. In my iOS project, I had my SBMHybridWebViewRenderer in Renderer folder. Somehowm iOS does not like the renderer to be in a folder. Moving it to the root of the project (but keeping the same namespace) solved the issue. WHY? I dont know. I would have expected it to work regardless of where I place the file.
I thought I will post this just incase someone else has the same issue.
Goal: I want to enrich a predefined component with my own behavior. This is typically the case with list, tables and trees, implementing my actions like "delete", "add before", "add after", "move up",... (with text field this seems to be simple...)
I thought there must be a way to attach key listeners at the component itself (assumed that there's something like a "focus"), e.g. if i have two trees on a page pressing "Ctrl+" will add one time an A to treeA via listenerA and the other a B to treeB via listenerB.
Adding an ajax listener at a tree node or the tree itself does not work. So it seems to be necessary (see two answers below) to catch key globally and "dispatch" them myself properly. At least with one tree this should work without hassle.
According to the answers below this can only be done using JavaScript or using a non standard JSF tag.
As i am concerned with JSF question at most 2 times a year, i think someone more involved can give insight in best practice on this twilight zone between JSF and JavaScript.
In this snippet i want to create a new child item when "+" is pressed.
<h:form>
<p:tree id="document" value="#{demo.root}" var="node"
selectionMode="single" selection="#{demo.selection}">
<p:treeNode>
<h:outputText value="#{node.label}" />
</p:treeNode>
</p:tree>
</h:form>
The tag
<f:ajax event="keypress" listener="#{demo.doTest}" />
is not accepted in "treeNode" and "tree" and has no function in "form".
= EDIT
As can be seen in the answers, this concrete scenario is supported by simply using <p:hotkey>. This solution has 2 drawbacks, its Primefaces bound and it fails if we add input components like this
<h:form>
<p:tree id="document" value="#{demo.root}" var="node"
selectionMode="single" selection="#{demo.selection}">
<p:treeNode>
<p:inputText value="#{node.label}" />
</p:treeNode>
</p:tree>
</h:form>
What is the best practice to implement such things? At least, is it possible in plain JSF at all? If i only use plain JSF, what would be the least ugly idiom.
= EDIT
I want to point to a short history of findings, given as an answer below, to give more detail on the problem behind this question
This implementation enables navigation and add/remove as well.
IMHO it has the best functionality/effort ratio.
I don't know what you mean with standard JSF tag or plain JSF, but in this example there isn't a single line of JavaScript.
Note that p:hotkey component behavior is global. Non-input components like p:tree cannot have owned key listeners since they can't be "focused" (or at least by default behavior), just like you pointed.
However, here it is:
<h:form>
<p:hotkey bind="left" actionListener="#{testBean.onLeft}" process="#form" update="target" />
<p:hotkey bind="right" actionListener="#{testBean.onRight}" process="#form" update="target" />
<p:hotkey bind="up" actionListener="#{testBean.onUp}" process="#form" update="target" />
<p:hotkey bind="down" actionListener="#{testBean.onDown}" process="#form" update="target" />
<p:hotkey bind="ctrl+a" actionListener="#{testBean.onAdd}" process="#form" update="target" />
<p:hotkey bind="ctrl+d" actionListener="#{testBean.onDelete}" process="#form" update="target" />
<h:panelGroup id="target">
<p:tree value="#{testBean.root}" var="data" selectionMode="single"
selection="#{testBean.selection}" dynamic="true">
<p:treeNode expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed">
<h:outputText value="#{data}" />
</p:treeNode>
</p:tree>
<br />
<h3>current selection: #{testBean.selection.data}</h3>
</h:panelGroup>
</h:form>
and this is the managed bean:
#ManagedBean
#ViewScoped
public class TestBean implements Serializable
{
private static final long serialVersionUID = 1L;
private DefaultTreeNode root;
private TreeNode selection;
#PostConstruct
public void init()
{
root = new DefaultTreeNode("node");
root.setSelectable(false);
DefaultTreeNode node_0 = new DefaultTreeNode("node_0");
DefaultTreeNode node_1 = new DefaultTreeNode("node_1");
DefaultTreeNode node_0_0 = new DefaultTreeNode("node_0_0");
DefaultTreeNode node_0_1 = new DefaultTreeNode("node_0_1");
DefaultTreeNode node_1_0 = new DefaultTreeNode("node_1_0");
DefaultTreeNode node_1_1 = new DefaultTreeNode("node_1_1");
node_0.setParent(root);
root.getChildren().add(node_0);
node_1.setParent(root);
root.getChildren().add(node_1);
node_0_0.setParent(node_0);
node_0.getChildren().add(node_0_0);
node_0_1.setParent(node_0);
node_0.getChildren().add(node_0_1);
node_1_0.setParent(node_1);
node_1.getChildren().add(node_1_0);
node_1_1.setParent(node_1);
node_1.getChildren().add(node_1_1);
selection = node_0;
node_0.setSelected(true);
}
private void initSelection()
{
List<TreeNode> children = root.getChildren();
if(!children.isEmpty())
{
selection = children.get(0);
selection.setSelected(true);
}
}
public void onLeft()
{
if(selection == null)
{
initSelection();
return;
}
if(selection.isExpanded())
{
selection.setExpanded(false);
return;
}
TreeNode parent = selection.getParent();
if(parent != null && !parent.equals(root))
{
selection.setSelected(false);
selection = parent;
selection.setSelected(true);
}
}
public void onRight()
{
if(selection == null)
{
initSelection();
return;
}
if(selection.isLeaf())
{
return;
}
if(!selection.isExpanded())
{
selection.setExpanded(true);
return;
}
List<TreeNode> children = selection.getChildren();
if(!children.isEmpty())
{
selection.setSelected(false);
selection = children.get(0);
selection.setSelected(true);
}
}
public void onUp()
{
if(selection == null)
{
initSelection();
return;
}
TreeNode prev = findPrev(selection);
if(prev != null)
{
selection.setSelected(false);
selection = prev;
selection.setSelected(true);
}
}
public void onDown()
{
if(selection == null)
{
initSelection();
return;
}
if(selection.isExpanded())
{
List<TreeNode> children = selection.getChildren();
if(!children.isEmpty())
{
selection.setSelected(false);
selection = children.get(0);
selection.setSelected(true);
return;
}
}
TreeNode next = findNext(selection);
if(next != null)
{
selection.setSelected(false);
selection = next;
selection.setSelected(true);
}
}
public void onAdd()
{
if(selection == null)
{
selection = root;
}
TreeNode node = createNode();
node.setParent(selection);
selection.getChildren().add(node);
selection.setExpanded(true);
selection.setSelected(false);
selection = node;
selection.setSelected(true);
}
public void onDelete()
{
if(selection == null)
{
return;
}
TreeNode parent = selection.getParent();
parent.getChildren().remove(selection);
if(!parent.equals(root))
{
selection = parent;
selection.setSelected(true);
if(selection.isLeaf())
{
selection.setExpanded(false);
}
}
else
{
selection = null;
}
}
// create the new node the way you like, this is an example
private TreeNode createNode()
{
int prog = 0;
TreeNode lastNode = Iterables.getLast(selection.getChildren(), null);
if(lastNode != null)
{
prog = NumberUtils.toInt(StringUtils.substringAfterLast(String.valueOf(lastNode.getData()), "_"), -1) + 1;
}
return new DefaultTreeNode(selection.getData() + "_" + prog);
}
private TreeNode findNext(TreeNode node)
{
TreeNode parent = node.getParent();
if(parent == null)
{
return null;
}
List<TreeNode> brothers = parent.getChildren();
int index = brothers.indexOf(node);
if(index < brothers.size() - 1)
{
return brothers.get(index + 1);
}
return findNext(parent);
}
private TreeNode findPrev(TreeNode node)
{
TreeNode parent = node.getParent();
if(parent == null)
{
return null;
}
List<TreeNode> brothers = parent.getChildren();
int index = brothers.indexOf(node);
if(index > 0)
{
return findLastUnexpanded(brothers.get(index - 1));
}
if(!parent.equals(root))
{
return parent;
}
return null;
}
private TreeNode findLastUnexpanded(TreeNode node)
{
if(!node.isExpanded())
{
return node;
}
List<TreeNode> children = node.getChildren();
if(children.isEmpty())
{
return node;
}
return findLastUnexpanded(Iterables.getLast(children));
}
public TreeNode getRoot()
{
return root;
}
public TreeNode getSelection()
{
return selection;
}
public void setSelection(TreeNode selection)
{
this.selection = selection;
}
}
UPDATE
Maybe I found an interesting solution to attach key bindings to single DOM elements:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:cc="http://xmlns.jcp.org/jsf/composite" xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions" xmlns:p="http://primefaces.org/ui"
xmlns:o="http://omnifaces.org/ui" xmlns:of="http://omnifaces.org/functions"
xmlns:s="http://shapeitalia.com/jsf2" xmlns:sc="http://xmlns.jcp.org/jsf/composite/shape"
xmlns:e="http://java.sun.com/jsf/composite/cc" xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">
<h:head>
<title>test hotkey</title>
</h:head>
<h:body>
<h:form>
<h:panelGroup id="container1">
<s:hotkey bind="left" actionListener="#{testBean.onLeft}" update="container1" />
<s:hotkey bind="right" actionListener="#{testBean.onRight}" update="container1" />
<s:hotkey bind="up" actionListener="#{testBean.onUp}" update="container1" />
<s:hotkey bind="down" actionListener="#{testBean.onDown}" update="container1" />
<s:hotkey bind="ctrl+a" actionListener="#{testBean.onAdd}" update="container1" />
<s:hotkey bind="ctrl+d" actionListener="#{testBean.onDelete}" update="container1" />
<p:tree value="#{testBean.root}" var="data" selectionMode="single"
selection="#{testBean.selection}" dynamic="true" pt:tabindex="1">
<p:treeNode expandedIcon="ui-icon-folder-open"
collapsedIcon="ui-icon-folder-collapsed">
<h:outputText value="#{data}" />
</p:treeNode>
</p:tree>
<br />
<h3>current selection: #{testBean.selection.data}</h3>
</h:panelGroup>
</h:form>
</h:body>
</html>
three important things:
h:panelGroup attribute id is required, otherwise it is not rendered as DOM element. style, styleClass, and other render-enable attributes can be used with or instead.
Note that pt:tabindex=1 on p:tree: it is required to enable "focus". pt is the namespace used for "passthrough" attributes and only works in JSF 2.2.
I had to customize HotkeyRenderer in order to attach the DOM event listener to a specific DOM element instead of the entire document: now it's s:hotkey instead of p:hotkey. My implementation attachs it to DOM element associated to parent component, continue read for implementation.
the modified renderer:
#FacesRenderer(componentFamily = Hotkey.COMPONENT_FAMILY, rendererType = "it.shape.HotkeyRenderer")
public class HotkeyRenderer extends org.primefaces.component.hotkey.HotkeyRenderer
{
#SuppressWarnings("resource")
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
Hotkey hotkey = (Hotkey) component;
String clientId = hotkey.getClientId(context);
String targetClientId = hotkey.getParent().getClientId();
writer.startElement("script", null);
writer.writeAttribute("type", "text/javascript", null);
writer.write("$(function() {");
writer.write("$(PrimeFaces.escapeClientId('" + targetClientId + "')).bind('keydown', '" + hotkey.getBind() + "', function(){");
if(hotkey.isAjaxified())
{
UIComponent form = ComponentUtils.findParentForm(context, hotkey);
if(form == null)
{
throw new FacesException("Hotkey '" + clientId + "' needs to be enclosed in a form when ajax mode is enabled");
}
AjaxRequestBuilder builder = RequestContext.getCurrentInstance().getAjaxRequestBuilder();
String request = builder.init()
.source(clientId)
.form(form.getClientId(context))
.process(component, hotkey.getProcess())
.update(component, hotkey.getUpdate())
.async(hotkey.isAsync())
.global(hotkey.isGlobal())
.delay(hotkey.getDelay())
.timeout(hotkey.getTimeout())
.partialSubmit(hotkey.isPartialSubmit(), hotkey.isPartialSubmitSet())
.resetValues(hotkey.isResetValues(), hotkey.isResetValuesSet())
.ignoreAutoUpdate(hotkey.isIgnoreAutoUpdate())
.onstart(hotkey.getOnstart())
.onerror(hotkey.getOnerror())
.onsuccess(hotkey.getOnsuccess())
.oncomplete(hotkey.getOncomplete())
.params(hotkey)
.build();
writer.write(request);
}
else
{
writer.write(hotkey.getHandler());
}
writer.write(";return false;});});");
writer.endElement("script");
}
}
and finally this is the taglib definition for the new s:hotkey (it's a copy/paste of the original with the only difference of <renderer-type>it.shape.HotkeyRenderer</renderer-type>):
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">
<namespace>http://shapeitalia.com/jsf2</namespace>
<tag>
<description><![CDATA[HotKey is a generic key binding component that can bind any formation of keys to javascript event handlers or ajax calls.]]></description>
<tag-name>hotkey</tag-name>
<component>
<component-type>org.primefaces.component.Hotkey</component-type>
<renderer-type>it.shape.HotkeyRenderer</renderer-type>
</component>
<attribute>
<description><![CDATA[Unique identifier of the component in a namingContainer.]]></description>
<name>id</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Boolean value to specify the rendering of the component, when set to false component will not be rendered.]]></description>
<name>rendered</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[An el expression referring to a server side UIComponent instance in a backing bean.]]></description>
<name>binding</name>
<required>false</required>
<type>javax.faces.component.UIComponent</type>
</attribute>
<attribute>
<description><![CDATA[An actionlistener that'd be processed in the partial request caused by uiajax.]]></description>
<name>actionListener</name>
<required>false</required>
<type>javax.faces.event.ActionListener</type>
</attribute>
<attribute>
<description><![CDATA[A method expression that'd be processed in the partial request caused by uiajax.]]></description>
<name>action</name>
<required>false</required>
<type>javax.el.MethodExpression</type>
</attribute>
<attribute>
<description><![CDATA[Boolean value that determines the phaseId, when true actions are processed at apply_request_values, when false at invoke_application phase.]]></description>
<name>immediate</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[The Key binding. Required.]]></description>
<name>bind</name>
<required>true</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Client side id of the component(s) to be updated after async partial submit request.]]></description>
<name>update</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Component id(s) to process partially instead of whole view.]]></description>
<name>process</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript event handler to be executed when the key binding is pressed.]]></description>
<name>handler</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript handler to execute before ajax request is begins.]]></description>
<name>onstart</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript handler to execute when ajax request is completed.]]></description>
<name>oncomplete</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript handler to execute when ajax request fails.]]></description>
<name>onerror</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript handler to execute when ajax request succeeds.]]></description>
<name>onsuccess</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Global ajax requests are listened by ajaxStatus component, setting global to false will not trigger ajaxStatus. Default is true.]]></description>
<name>global</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[If less than delay milliseconds elapses between calls to request() only the most recent one is sent and all other requests are discarded. The default value of this option is null. If the value of delay is the literal string 'none' without the quotes or the default, no delay is used.]]></description>
<name>delay</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Defines the timeout for the ajax request.]]></description>
<name>timeout</name>
<required>false</required>
<type>java.lang.Integer</type>
</attribute>
<attribute>
<description><![CDATA[When set to true, ajax requests are not queued. Default is false.]]></description>
<name>async</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[When enabled, only values related to partially processed components would be serialized for ajax
instead of whole form.]]></description>
<name>partialSubmit</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[If true, indicate that this particular Ajax transaction is a value reset transaction. This will cause resetValue() to be called on any EditableValueHolder instances encountered as a result of this ajax transaction. If not specified, or the value is false, no such indication is made.]]></description>
<name>resetValues</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[If true, components which autoUpdate="true" will not be updated for this request. If not specified, or the value is false, no such indication is made.]]></description>
<name>ignoreAutoUpdate</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
</tag>
</facelet-taglib>
whew, it was hard ;)
Until now, there's no really satisfying answer so far. I summarize my findings:
Some JSF components have "inner logic" that bind some keys to component specific features. Too bad that "intelligent components" like <p:tree /> don't even bind arrow key navigation.
So you try to emulate and find <p:hotkey/>. No you can (as shown in the very extensive answer by #michele-mariotti) feel a little comfortable wih your component.
Then you add input features to the tree... And hotkeys are breaking down. You do not know for what reasons (and, really, i think you should not have to...).
So you start digging around and suddenly find yourself in JavaScript and DOM wonderland.
The "hotkey" library for the ubiquitous jQuery seems to bring help. Or one of the 1000 others you bring up when searching for this stuff. Better take the right one from the start (which one is it?).
So you start adding ugly jQuery expressions for each and every accelerator, first on the document, then down on every input component (as shown e.g. here). Your page starts beeing a mess.
But you're happy - at least after two days you have brought up a simple tree..
Now you add sugar. You add <p:inplace /> or simply add new tree nodes. Your hotkeys break down.
Oh, yes, you should have known: The dynamic inputs are not bound to hotkeys. Add some more JavaScript hacks to the page...
But hey, what's this: Testing all your hotkey stuff, you forgot to enter values in the tree input fields. Now you realize: it's not working!! Again some searching: Seems to be a well known bug/missing feature for years. Primefaces removes focus immediately after activating the tree input. Well, who on earth makes input in a tree...
So, here's where you could debug some some sophisticated Primefaces JavaScript or add some other equally sophisticated JavaScript to force the focus back to this field. You could realize you use the wrong component library and restart with Richfaces tree, Omnifaces tree or whatever. You could resign to use web technology, sleep another 2 years and come back to see if basic technology has evolved to be usable. Is Java web simply a playground for tinkerers?
After this piece of rant, is there anybody that can help with some advice?
I found a workaround that does not exactly what was asked, but can handle my scenario.
Adding a "hotkey" component to the form calls the server as requested:
<p:hotkey bind="ctrl+shift+a" update="messages" actionListener="#{demo.doTest}"/>
Similiar component exists in RichFaces, don't know about plain JSF.
What i can't believe is that there's no other way then reverting down to JavaScript (like http://winechess.blogspot.ru/2014/02/datatable-keyboard-navigation.html or http://www.openjs.com/scripts/events/keyboard_shortcuts/) to write usable JSF apps?
And that standard components like tree or table do not have standard keyboard navigation (its 2015, i even do not remember when Web 2.0 was invented).
Any hint to best practice?
And some more investigation before a more enlightened brain can lift the secret...
A somewhat similiar q/a solves the problem on how to get to call a backend method from JS if a key was handled in JS - use the
<p:remoteCommand>
see Catch key pressed ajax event without input fields for the ugly details.
Again, this is a global key catch, not component sensitive. But nice to know. Does this exist in plain JSF, too?
I have two radio buttons on JSF2/PrimeFaces page, which I need to enable/disable an inputText field depending on the choice.
<h:form prependId="false">
<p:panelGrid id="notif">
<p:row id="notif1">
<p:column>
<h:outputText value="label" />
</p:column>
<p:column>
<p:selectOneRadio id="radiobuttons" value="#{state.radioStatus}" layout="pageDirection">
<f:selectItem itemLabel="enable" itemValue="enable" />
<f:selectItem itemLabel="disable" itemValue="disable" />
<p:ajax update="date" />
</p:selectOneRadio>
</p:column>
<p:column>
<p:inputText id="date" value="#{state.infoDate}" disabled="#{!state.checkStatus}" >
</p:inputText>
<p:inputText id="test" value="#{state.infoDateTest}">
</p:inputText>
</p:column>
</p:row>
</p:panelGrid>
</h:form>
Also this all is inside a tab, but there is no nested forms when checked the generated html. The fields seem to get the id of the tab, but they seem to be correct like this (Did not work when tried to use id notation 'tabView:date' for the ajax.)
The problem is that when entering the page, the 'actual' text field is disabled by default and should be enabled when the radio button is set to enable it. When the radio button status is updated, I can see the event is registered on the server side and the inputText field gets "visually" enabled (by default it is disabled as supposed to). However the field is not "focusable" after enabling it, so can't write to it.
The more bizarre it gets when I added the 'test' inputText field, which does not get it self enabled/disabled regarding to radio buttons. On a fresh page load you can write to it, but after changing the radio button status, it is not writable anymore (the text that was entered prior to ajax call, will remain, but is not editable).
However the radio buttons continue to work and will fire events on the server side.
Could someone point out what I may be doing incorrectly, or could this be a bug with PrimeFaces?
// Update
Like said, the managed bean works as supposed to, so I don't see any problems there, but here is the code (simplified and renamed for readability as the relevant parts are taken from a bigger context).
#ManagedBean(name = "state")
#SessionScoped
public class ExampleBean implements Serializable {
private static final String disable = "disable";
private static final String enable = "enable";
private String text;
private List<TestRow> rows = null;
private TestRow row = null;
private String radioStatus = "enable";
private String infoDate = "";
private String infoDateTest = "";
#PostConstruct
public void initEmpty() {
}
public String getRadioStatus() {
return this.radioStatus;
}
public void setRadioStatus(String radioStatus) {
this.radioStatus = radioStatus;
}
public String getInfoDate() {
return infoDate;
}
public void setInfoDate(String infoDate) {
this.infoDate = infoDate;
}
public String getInfoDateTest() {
return infoDateTest;
}
public void setInfoDateTest(String infoDateTest) {
this.infoDateTest = infoDateTest;
}
public boolean isCheckStatus() {
return this.radioStatus.equalsIgnoreCase(enable) ? true : false;
}
}
Unfortunately I was not able to figure out what is causing the actual problem. The basic thing seems to be that the inherited javascript (primefaces) is not working properly on this page (maybe because of some other js inherited). Finally I just made it a custom enable/disable js-function:
js.toggleDisableStatus = function(elementId) {
// Can't use jQuery $("#xx:yy"), the syntax is not accepted by jquery but
// used by JSF/PrimeFaces
var element = document.getElementById(elementId);
var attrValue = element.getAttribute('disabled');
if (attrValue === undefined || attrValue == null) {
element.disabled = true;
element.style.background = "#dddddd";
} else {
element.removeAttribute('disabled');
element.style.background = "";
}
};
This may be flawed but works for now. The radio buttons are selected by the persisted status (loaded in state) and the enable/disable state for the input-field is also set when initially loading the page. Generally I think this should work for this particular case.
I am working on a Windows Phone 7 Application using Local SQLite Database and I'm having an issue with the rendering time of pages that use DataBinding.
Currently it takes 60-70ms to retrieve the data from the database. Then it takes about 3100ms to render the data retrieved using a ListBox with DataBinding.
Here you can see the DataTemplate of the ListBox:
<DataTemplate x:Key="ListBoxItemTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="68" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock x:Name="TimeColumn"
Text="{Binding TimeSpan}" Grid.Column="0" Grid.Row="0"
Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Text="{Binding Stop.StopName}" Grid.Column="1" Grid.Row="0"
Margin="15,0,0,0" TextWrapping="NoWrap" Foreground="Black"
HorizontalAlignment="Left" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
Comment: I have tried it using Canvas instead of Grid too, same result.
Then, the database loads data into a CSList (using ViciCoolStorage) and that gets Binded to the ListBox:
StationList.ItemsSource = App.RouteViewModel.RouteStops;
Comment: I have tried to add the elements of the CSList to an ObservableCollection and bind that to the interface but didn't seem to change anything.
Question:
Am I doing something wrong that results in a huge load time - even if just loading 10 elements -, or this is normal? Do you have any recommendations to get a better performance with DataBinding?
Thank you for your answers in advance!
Corresponding Code Parts:
RouteViewModel.cs
private Route rRoute;
public Route Route
{
get
{
if (rRoute != null)
{
return rRoute;
}
else
{
return new Route();
}
}
}
public void LoadRoute(string index)
{
try
{
if (rRoute.RouteId != index)
{
RouteLoaded = false;
StationsLoaded = false;
TimetableLoaded = false;
}
}
catch (Exception) { }
this.index = index;
if (!RouteLoaded)
{
NotifyPropertyChanging("Route");
rRoute = Route.ReadSafe(index);
RouteLoaded = true;
NotifyPropertyChanged("Route");
}
}
private CSList<RouteTime> rtLine;
public CSList<RouteTime> RouteStops
{
get
{
if (rtLine != null)
{
return rtLine;
}
else
{
return new CSList<RouteTime>();
}
}
}
public void LoadRouteStops()
{
LoadRoute(index);
if (!this.StationsLoaded)
{
NotifyPropertyChanging("RouteStops");
rtLine = rRoute.RouteTimes.FilteredBy("DirectionId = #DirectionId", "#DirectionId", this.direction).OrderedBy("TimeSpan");
NotifyPropertyChanged("RouteStops");
StationsLoaded = true;
}
}
RouteView.xaml.cs
private string index;
private bool visszaut = false;
public RouteView()
{
InitializeComponent();
Loaded += new System.Windows.RoutedEventHandler(RouteView_Loaded);
}
void RouteView_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
DataContext = App.RouteViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
NavigationContext.QueryString.TryGetValue("index", out index);
App.RouteViewModel.LoadRoute(index);
App.RouteViewModel.Direction = Convert.ToInt32(visszaut);
App.RouteViewModel.LoadRouteStops();
StationList.ItemsSource = App.RouteViewModel.RouteStops;
}
RouteTime.cs - Class Implementation
[MapTo("RouteTimes")]
public class RouteTime : CSObject<RouteTime, int>
{
public int RouteTimeId
{
get
{
return (int)GetField("RouteTimeId");
}
set
{
SetField("RouteTimeId", value);
}
}
public int RouteId
{
get
{
return (int)GetField("RouteId");
}
set
{
SetField("RouteId", value);
}
}
public int StopId
{
get
{
return (int)GetField("StopId");
}
set
{
SetField("StopId", value);
}
}
public int TimeSpan
{
get
{
return (int)GetField("TimeSpan");
}
set
{
SetField("TimeSpan", value);
}
}
public Direction DirectionId
{
get
{
return (Direction)GetField("DirectionId");
}
set
{
SetField("DirectionId", value);
}
}
[OneToOne(LocalKey = "StopId", ForeignKey = "StopId")]
public Stop Stop
{
get
{
return (Stop)GetField("Stop");
}
set
{
SetField("Stop", value);
}
}
[ManyToOne(LocalKey = "RouteId", ForeignKey = "RouteId")]
public Route Route
{
get
{
return (Route)GetField("Route");
}
set
{
SetField("Route", value);
}
}
}
Okay so, in this scenario it seems the source of the slow rendering was the [OneToOne] connection between the RouteTime and Stop classes. If I bind to any of the variables that is stored in the linked class, the rendering takes a long time.
Fixed by
I have added a new partial class in the code where I need to show the results.
public partial class StopTimes
{
public int TimeSpan
{
get;
set;
}
public string StopName
{
get;
set;
}
}
Using an ad-hoc query in Vici CoolStorage, I've made my own query to request the data needed and viola, everything is there and rendering didn't take more then 1 second. Perhaps, during the rendering it requests the StopName field with an SQLQuery one by one?
Don't know, but thank you for your help anyway :)
DataBinding should not be the problem in this scenario - I never had any problems with it. It has something to do with your SQL DB for sure. I had lists of around 200 items, and it rendered fine under a reasonable time-slot of 100ms.
It either is your SQL implementation or you are using ViciCoolStorage wrong. Post your code.
Have you tried doing the filtering of your list outside of the LoadRouteStops() method? Maybe in the code behind instead of the viewModel? It seems like a lot of work to be done in between propertyChanging and propertyChanged notifications.
I'm confused: How do you do local SQLite on Windows Phone 7? Did you mean WP8 where this is supported?
In any case, consider using LongListSelector (built into WP8 - Toolkit in WP7), since it does a better job with virtualization when you have a lot of items to render.
The problem is mostly because of setting a lot of binding. Try to keep bindings minimal and try to do the hard work on the code behind. Such as;
"binding a list of string to xaml one by one"
versus
"by string.join method with just one binding to a text block"
I had
<TextBlock Text="{Binding Times[0]}"/>
<TextBlock Text="{Binding Times[1]}"/>
...
<TextBlock Text="{Binding Times[9]}"/>
And changed to
<TextBlock Text="{Binding TimesToLongString}"/>
This fixed the delay.
Used to load in 9 sec with 3 items.
Now it loads within 110ms with 10 items.
To check the time it takes to load the list, you can check out this post
I happen not to understand why my load method is not called in a lazydatamodel of my primefaces table. My xhtml page goes like this
<h:form id="myForm">
<p:dataTable value="#{myBean.configDataModel}"
id="configTable" var="config" paginator="true" rows="10"
selectionMode="single"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="5,10,20">
.
.
</h:form>
My Bean code goes like this and put up system.out.println statements but I notice it isn't called.
public class MyBean{
// private List<MyBean> configList;
private LazyDataModel<MyBean> configDataModel;
#SuppressWarnings("serial")
public LazyDataModel<MyBean> getConfigDataModel() {
if (configDataModel == null) {
configDataModel = new LazyDataModel<MyBean>() {
#Override
public List<MyBean> load(int arg0, int arg1, String arg2,
SortOrder arg3, Map<String, String> arg4) {
System.out.println("Here!!!!!");
return null;
}
};
}
return configDataModel;
}
public void setConfigDataModel(LazyDataModel<MyBean> configDataModel) {
this.configDataModel = configDataModel;
}
}
What could be the cause?
Since PrimeFaces 3.3, you'd need to explicitly set the lazy attribute of the repeating component to true in order to enable the support for LazyDataModel.
<p:dataTable ... lazy="true">
See also:
PrimeFaces showcase - DataTable - Lazy Loading
If you are IE, be sure to check which so-called "compatibility mode" it is quirking in. I have had very annoying problems with lazy datatable failing to call its load method after I typed text into the filter field. After much wasted time, I eventually realized that the browser was running in default mode 7. Datatable lazy load works for modes 8, 9, 10
If your lazy datatable is in a dialog, then be sure to place the dialog within the form, otherwise, typing values into a filter field will not submit to the load method.