Ticket S18977
Visible to All Users

How to support keyboard shortcuts for Actions in an XAF ASP.NET WebForms and Blazor applications (e.g., to save view changes)?

created 17 years ago (modified 3 years ago)

Although currently there are no built-in shortcuts in XAF Web UI & Blazor, it is not that difficult to implement them using the standard HTML and JavaScript approaches linked to the corresponding XAF's API.

XAF Web UI

I have made a quick example based on my previous article (KA18958) on how to attach some client-side functionality to the XAF's form:

C#
using System; using DevExpress.ExpressApp; using DevExpress.ExpressApp.SystemModule; using DevExpress.ExpressApp.Web; using DevExpress.ExpressApp.Web.Templates; using DevExpress.ExpressApp.Actions; namespace MainDemo.Module.Web.Controllers { public class WebShortcutsWindowController : WindowController, IXafCallbackHandler { public WebShortcutsWindowController() { TargetWindowType = WindowType.Main; } protected override void OnActivated() { base.OnActivated(); Frame.ViewChanged += Frame_ViewChanged; ((WebWindow)Window).PagePreRender += CurrentRequestWindow_PagePreRender; } protected override void OnDeactivated() { ((WebWindow)Window).PagePreRender -= CurrentRequestWindow_PagePreRender; Frame.ViewChanged -= Frame_ViewChanged; base.OnDeactivated(); } void Frame_ViewChanged(object sender, ViewChangedEventArgs e) { if(Frame.View != null) { Frame.View.ControlsCreated += View_ControlsCreated; } } void View_ControlsCreated(object sender, EventArgs e) { RegisterXafCallackHandler(); } private void RegisterXafCallackHandler() { if(XafCallbackManager != null) { XafCallbackManager.RegisterHandler("T171941", this); } } protected XafCallbackManager XafCallbackManager { get { return WebWindow.CurrentRequestPage != null ? ((ICallbackManagerHolder)WebWindow.CurrentRequestPage).CallbackManager : null; } } void CurrentRequestWindow_PagePreRender(object sender, EventArgs e) { WebWindow window = (WebWindow)sender; string script = IsSuitableView() ? @"window.onkeydown = function(e) { var key = e.keyCode ? e.keyCode : e.which; if (key == 13 && e.ctrlKey) { //Control+Enter RaiseXafCallback(globalCallbackControl, 'T171941', 'SaveAction', '', false); } };" : "window.onkeydown=undefined;"; window.RegisterStartupScript(GetType().FullName, script); } public void ProcessAction(string parameter) { if(IsSuitableView() && (Frame != null)) { switch(parameter) { case "SaveAction": ModificationsController controller = Frame.GetController<ModificationsController>(); if(controller != null && controller.SaveAction.Enabled && controller.SaveAction.Active) { controller.SaveAction.DoExecute(); } break; case "OtherParameter": //DoSomething(); break; } } } protected virtual bool IsSuitableView() { return Frame.View != null && Frame.View.IsRoot && !(Frame.View is ListView) && !(Frame is NestedFrame) && !(Frame is PopupWindow); } } }
Visual Basic
Imports Microsoft.VisualBasic Imports System Imports DevExpress.ExpressApp Imports DevExpress.ExpressApp.SystemModule Imports DevExpress.ExpressApp.Web Imports DevExpress.ExpressApp.Web.Templates Imports DevExpress.ExpressApp.Actions Namespace MainDemo.Module.Web.Controllers Public Class WebShortcutsWindowController Inherits WindowController Implements IXafCallbackHandler Public Sub New() TargetWindowType = WindowType.Main End Sub Protected Overrides Sub OnActivated() MyBase.OnActivated() AddHandler Frame.ViewChanged, AddressOf Frame_ViewChanged AddHandler CType(Window, WebWindow).PagePreRender, AddressOf CurrentRequestWindow_PagePreRender End Sub Protected Overrides Sub OnDeactivated() RemoveHandler CType(Window, WebWindow).PagePreRender, AddressOf CurrentRequestWindow_PagePreRender RemoveHandler Frame.ViewChanged, AddressOf Frame_ViewChanged MyBase.OnDeactivated() End Sub Private Sub Frame_ViewChanged(ByVal sender As Object, ByVal e As ViewChangedEventArgs) If (e.SourceFrame IsNot Nothing) AndAlso (e.SourceFrame.View IsNot Nothing) Then AddHandler e.SourceFrame.View.ControlsCreated, AddressOf View_ControlsCreated End If End Sub Private Sub View_ControlsCreated(ByVal sender As Object, ByVal e As EventArgs) RegisterXafCallackHandler() End Sub Private Sub RegisterXafCallackHandler() If XafCallbackManager IsNot Nothing Then XafCallbackManager.RegisterHandler("T171941", Me) End If End Sub Protected ReadOnly Property XafCallbackManager() As XafCallbackManager Get Return If(WebWindow.CurrentRequestPage IsNot Nothing, DirectCast(WebWindow.CurrentRequestPage, ICallbackManagerHolder).CallbackManager, Nothing) End Get End Property Private Sub CurrentRequestWindow_PagePreRender(ByVal sender As Object, ByVal e As EventArgs) Dim window As WebWindow = DirectCast(sender, WebWindow) Dim script As String = If(IsSuitableView(), "window.onkeydown = function(e) {" & ControlChars.CrLf & _ " var key = e.keyCode ? e.keyCode : e.which;" & ControlChars.CrLf & _ " if(key == 13 && e.ctrlKey) { //Control+Enter" & ControlChars.CrLf & _ " RaiseXafCallback(globalCallbackControl, 'T171941', 'SaveAction', '', false);" & ControlChars.CrLf & _ " }" & ControlChars.CrLf & _ " };", "window.onkeydown = undefined;") window.RegisterStartupScript(Me.GetType().FullName, script) End Sub Public Sub ProcessAction(ByVal parameter As String) If IsSuitableView() AndAlso (Frame IsNot Nothing) Then Select Case parameter Case "SaveAction" Dim controller As ModificationsController = Frame.GetController(Of ModificationsController)() If controller IsNot Nothing AndAlso controller.SaveAction.Enabled AndAlso controller.SaveAction.Active Then controller.SaveAction.DoExecute() End If Case "OtherParameter" 'DoSomething(); End Select End If End Sub Protected Overridable Function IsSuitableView() As Boolean Return Frame.View IsNot Nothing AndAlso Frame.View.IsRoot AndAlso Not(TypeOf Frame.View Is ListView) AndAlso Not(TypeOf Frame Is NestedFrame) AndAlso Not(TypeOf Frame Is PopupWindow) End Function End Class End Namespace

XAF Blazor UI

Access a Component Adapter and a Component Model of a Property Editor as described here: Access Detail View Property Editor Settings Using a Controller. Then, use the ComponentModel.SetAttribute method to handle the "onkeydown" event. Here is an example that demonstrates how to execute the SaveAndNew action when the Enter key is pressed in the NumericPropertyEditor:

C#
using DevExpress.ExpressApp; using DevExpress.ExpressApp.Blazor.Editors; using DevExpress.ExpressApp.Blazor.Editors.Adapters; using DevExpress.ExpressApp.Blazor.SystemModule; using Microsoft.AspNetCore.Components.Web; using MySolution.Module.BusinessObjects; namespace MySolution.Module.Blazor.Controllers { public class MyViewController : ObjectViewController<DetailView, BusinessClassName> { protected override void OnActivated() { base.OnActivated(); View.CustomizeViewItemControl<NumericPropertyEditor>(this, SetSpinEditView, nameof(BusinessClassName.Quantity)); //"Quantity" is an integer property } private void SetSpinEditView(NumericPropertyEditor propertyEditor) { var spinEditAdapter = (DxSpinEditAdapter)propertyEditor.Control; spinEditAdapter.ComponentModel.SetAttribute("onkeydown", Microsoft.AspNetCore.Components.EventCallback.Factory.Create<KeyboardEventArgs>(this, OnKeyDown)); } protected void OnKeyDown(KeyboardEventArgs e) { if(e.Key == "Enter") { var blazorModificationsController = Frame.GetController<BlazorModificationsController>(); if(blazorModificationsController != null && blazorModificationsController.SaveAndNewAction.Active && blazorModificationsController.SaveAndNewAction.Enabled) { blazorModificationsController.SaveAndNewAction.DoExecute(blazorModificationsController.SaveAndNewAction.Items[0]); } } } } }

You can use the same idea for other property editors as well.

Take special note that these are not complete solutions, and you need to modify and test it further according to your business needs. See also the eXpressApp Framework > Concepts > Extend Functionality > Built-in Controllers and Actions and How to execute Actions in code articles for more information on how to access other XAF Controllers and execute their Actions in code.

Finally, you can check out third-party solutions like http://apobekiaris.blogspot.gr/2013/09/web-shortcuts-supported.html (for issues with the eXpand Framework implementation feel free to contact their support forums).

IMPORTANT NOTE:
A special handling is required for the ASPxDateEdit control due to its specifics. Refer to Web Shortcut Javascript Not Firing When ASPxDateTimePropertyEditor Has Focus for more solution details.

Show previous comments (8)
Dennis Garavsky (DevExpress) 9 years ago

    @Miguel: The approach for executing custom and standard Actions is the same. As mentioned above, you can learn more on it from the eXpressApp Framework > Concepts > Extend Functionality > Built-in Controllers and Actions and How to execute Actions in code articles. Both custom and standard Actions can also be referenced directly by a property within the Controller class or by the string identifier within the Actions collection.

    FB FB
    Franco Bonacchi 7 years ago

      @Dennis: I'm trying to implement this for a global key shortcut "Go To" action; using latest XAF web app (which AFAIK is a SPA app); the shortchut is handled, but it seems every time i press the shorcut, the startup script is registered again, so for example, if i simply do a "window.alert()" call , every time i press, i get the double alerts than the previous call.

      Any solution for that?

      Cheers!

      Dennis Garavsky (DevExpress) 7 years ago

        @Franco Bonacchi: I've created a separate ticket on your behalf (T646485: Implement this for a global key shortcut "Go To" action; using latest XAF web app).

        Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

        Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.