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 BasicImports 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.
See Also:
ASPxMenu, ASPxNavBar etc - keyboard shortcut is required
See also: http://apobekiaris.blogspot.gr/2013/09/web-shortcuts-supported.html
Hi Apostolis,
I have tried to implement the solution provided in the above link but without including Expand Module… i.e. taking code. I does not work for me.
I have taken below selected class/Enums from Expand source code.
EnumExtensions
FlagsEnumDifference<T>
public enum Keys
public enum Shortcut
public interface IModelOptionsWebShortcut
public interface IModelWebShortcut : IModelNode
public partial class WebShortcutsController
public sealed class KeyShortcut
public class KeysConverter
jwerty.js
It is displaying option of Web Shortcut in model but after running application when I click Control+Alt+Shift+N it not responding, I have attached demo project. When I am debugging the application on Page Pre-Render below script get generated.
jwerty.key("F5", function(e) {RaiseXafCallback(globalCallbackControl, 'KeybShortCuts', 'Refresh', '', false);return false;}); jwerty.key("ctrl+Shift+Alt+N",function(e) {RaiseXafCallback(globalCallbackControl, 'KeybShortCuts', 'New','', false);return false;});
HI,
For issues around eXpandFramework implementation please use eXpand forums http://www.expandframework.com/forum/index.html
Thanks for your understanding and sorry for disturbing other trackers of this suggestion.
Sorry I was not aware of the same. I did not mean to cause inconvenience to anyone.
New Ticket - https://www.devexpress.com/Support/Center/Question/Details/Q537493
Is there already a built-in solution for this case, XAF web shortcuts?
@SWAPI - Alziro Moraes: I've created/answered a separate ticket on your behalf (T398235: Keyboard shortcuts in XAF Web apps). Thanks.
Hi There! I'm trying to implement this for both the built-in actions and for custom actions. I have not been able to find examples of how to call custom actions in code. I'd like to know if what I've done so far is correct for calling the built-in actions and my own actions. Here's what I've implemented so far for the ProcessAction() method from the example above.
public void ProcessAction(string parameter) { if (IsSuitableView() && (Frame != null)) { switch (parameter) { case "SaveAction": SimpleAction saveAction = Frame.GetController<ModificationsController>().SaveAction; if (saveAction.Enabled && saveAction.Active) { saveAction.DoExecute(); } break; case "SaveAndCloseAction": SimpleAction saveAndCloseAction = Frame.GetController<ModificationsController>().SaveAndCloseAction; if (saveAndCloseAction.Enabled && saveAndCloseAction.Active) { saveAndCloseAction.DoExecute(); } break; case "SaveAndNewAction": SingleChoiceAction saveAndNewAction = Frame.GetController<ModificationsController>().SaveAndNewAction; if (saveAndNewAction.Enabled && saveAndNewAction.Active) { saveAndNewAction.DoExecute(saveAndNewAction.SelectedItem); } break; case "CancelAction": SimpleAction cancelAction = Frame.GetController<ModificationsController>().CancelAction; if (cancelAction.Enabled && cancelAction.Active) { cancelAction.DoExecute(); } break; case "NewAction": SingleChoiceAction newAction = Frame.GetController<NewObjectViewController>().NewObjectAction; if (newAction.Enabled && newAction.Active) { newAction.DoExecute(newAction.SelectedItem); } break; case "DeleteAction": SimpleAction deleteAction = Frame.GetController<DeleteObjectsViewController>().DeleteAction; if (deleteAction.Enabled && deleteAction.Active) { deleteAction.DoExecute(); } break; case "EditAction": if (Frame.View is DetailView) { DetailView currentView = (DetailView)Frame.View; if (currentView.CanChangeCurrentObject() && currentView.ViewEditMode == ViewEditMode.View) { currentView.ViewEditMode = ViewEditMode.Edit; } } break; // Add custom actions here: case "SubmitPaymentAction": SimpleAction submitPaymentAction = (SimpleAction)Frame.GetController<PaymentSession_SendPaymentController>().Actions["SendPaymentAction"]; if (submitPaymentAction.Enabled && submitPaymentAction.Active) { submitPaymentAction.DoExecute(); } break; case "ViewEditDonorAction": PopupWindowShowAction viewEditDonorAction = (PopupWindowShowAction)Frame.GetController<PaymentSession_ViewRequestingDonorController>().Actions["ViewRequestingDonorShowAction"]; if (viewEditDonorAction.Enabled && viewEditDonorAction.Active) { viewEditDonorAction.DoExecute(null); } break; case "ViewEditCreditCardAction": PopupWindowShowAction viewEditCcAction = (PopupWindowShowAction)Frame.GetController<PaymentSession_ViewCreditCardController>().Actions["ViewCreditCardShowAction"]; if (viewEditCcAction.Enabled && viewEditCcAction.Active) { viewEditCcAction.DoExecute(null); } break; case "ViewEditBankAccountAction": PopupWindowShowAction viewEditBankAction = (PopupWindowShowAction)Frame.GetController<PaymentSession_ViewBankAccountController>().Actions["ViewBankAccountShowAction"]; if (viewEditBankAction.Enabled && viewEditBankAction.Active) { viewEditBankAction.DoExecute(null); } break; case "SubmitAndNewPaymentAction": // Action not implemented yet break; default: //do nothing break; } } }
I look forward to any help and guidance you can provide.
Thanks!
Miguel
@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.
@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!
@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).