Hotkey doesn't fire on "expected" target when in nested component (e.g. drawer)

Disclaimer - This is a rather knarly one for which I’m not sure there is an “easy” solution that meets all use cases. Looking for some creative thinking on this one…

When a hotkey is assigned to a button that exists within a table drawer, when the hotkey is invoked, it is not executed on the drawer that has the focus.

For example, let’s say a Tab page for Accounts has a drawer containing a table of contacts for that account. The table for contacts has a global action for “add new” with an assigned hotkey of Ctrl-Shift-C. When on the page with one of the drawers having the focus, pressing Ctrl-Shift-C results in the new contact being added to be the last row in the Accounts model whose drawer is visible. I’m assuming this is because it was the last rendered row to register the hotkey event delegate and it’s using executing preventDefault/stopPropagation.

Our users are doing data entry so having to use the mouse is less than ideal.

One idea I had on how to resolve would be to perform a conditional check in the delegate to ensure that element is in the focus area before executing the logic. It appears the code already does a visible check but possibly adding “:focus” check as well to the element & its children (maybe even parent depending on HTML structure). For example, adding something like (e.hasFocus() || e.children(“:focus”).length) might work (could also use addBack() instead)?

Thoughts/Ideas?

Steps to repro:

  1. Create page using below XML
  2. Open page in preview mode
  3. Expand all drawers
  4. Click “Ctrl-Shift-I” to add a new account - Account gets added to top of table as expected
  5. Click “Ctrl-Shift-C” to add a new contact - The contact will be added to the last visible drawer

Sample Page XML

<skuidpage unsavedchangeswarning="yes" showsidebar="true" showheader="true" tabtooverride="Account">   <models>
      <model id="Account" limit="100" query="true" createrowifnonefound="false" sobject="Account">
         <fields>
            <field id="Name"/>
            <field id="CreatedDate"/>
         </fields>
         <conditions/>
         <actions/>
      </model>
      <model id="Contact" limit="" query="true" createrowifnonefound="false" sobject="Contact" doclone="" type="">
         <fields>
            <field id="FirstName"/>
            <field id="LastName"/>
            <field id="Name"/>
         <field id="AccountId"/>
<field id="Account.Name"/>
</fields>
         <conditions>
            <condition type="join" value="" field="AccountId" operator="in" mergefield="Id" novaluebehavior="deactivate" enclosevalueinquotes="true" joinobject="Account" joinfield="Id"/>
         </conditions>
         <actions/>
      </model>
   </models>
   <components>
      <pagetitle model="Account">
         <maintitle>
            <template>{{Model.labelPlural}}</template>
         </maintitle>
         <subtitle>
            <template>Home</template>
         </subtitle>
         <actions>
            <action type="savecancel">
               <models>
                  <model>Contact</model>
               </models>
            </action>
         </actions>
      </pagetitle>
      <skootable showconditions="true" showsavecancel="false" searchmethod="server" searchbox="true" showexportbuttons="false" pagesize="10" createrecords="false" model="Account" mode="read">
         <fields>
            <field id="Name" allowordering="true"/>
         </fields>
         <rowactions>
            <action type="edit"/>
            <action type="delete"/>
            <action type="drawer" label="View Contacts" icon="sk-icon-magic">
               <drawer title="Drawer Area" width="90%" closehandle="true">
                  <components>
                     <skootable showconditions="true" showsavecancel="false" searchmethod="server" searchbox="true" showexportbuttons="false" pagesize="10" createrecords="false" model="Contact" buttonposition="" mode="read">
                        <fields>
                           <field id="Name" valuehalign="" type=""/>
                           <field id="FirstName"/>
                           <field id="LastName"/>
                        </fields>
                        <rowactions>
                           <action type="edit"/>
                           <action type="delete"/>
                        </rowactions>
                        <massactions usefirstitemasdefault="true"/>
                        <views>
                           <view type="standard"/>
                        </views>
                        <conditions>
                           <condition type="contextrow" field="AccountId" mergefield="Id"/>
                        </conditions>
                        <actions defaultlabel="Global Actions" defaulticon="sk-icon-magic" usefirstitemasdefault="true">
                           <action type="multi" appendorprepend="prepend" label="Add New Contact" icon="sk-icon-add">
                              <hotkeys>
                                 <hotkey modifiers="ctrl,shift" key="c" ignoretextinputs="true"/>
                              </hotkeys>
                              <actions>
                                 <action type="createRow" model="Contact" appendorprepend="prepend" defaultmodefornewitems="edit"/>
                              </actions>
                           </action>
                        </actions>
                     </skootable>
                  </components>
               </drawer>
            </action>
         </rowactions>
         <massactions usefirstitemasdefault="true"/>
         <views>
            <view type="standard"/>
         </views>
         <actions defaultlabel="Global Actions" defaulticon="sk-icon-magic" usefirstitemasdefault="true">
            <action type="createrow" appendorprepend="prepend">
               <hotkeys>
                  <hotkey modifiers="ctrl,shift" key="i" ignoretextinputs="true"/>
               </hotkeys>
            </action>
         </actions>
      </skootable>
   </components>
   <resources>
      <labels/>
      <css/>
      <javascript/>
   </resources>
</skuidpage>

Hi Barry,

This makes sense, and we could do something like this for the short term.  In the long term, I really want Skuid tables (and other components where relevant) to have a concept of a “highlighted row”.  This would be somewhat similar to “selected”, but only one row could be “highlighted” at a time. Coupled with a concept of “highlighted cell”, this would allow for arrow key navigation between cells as well as easy determination about which row has the focus of the user.

I think that using browser focus would get us mostly there, but being even more explicit about it (and controllable through the api) would be better.

Hey Ben -

Thanks for taking a look in to this and I couldn’t agree more with your assessment.  I think Skuid has reached the point with all the new features were introducing the “active/highlighted” concept would cure a number of ills.  As you mention, this would be different than selected and only one “element” could be “active/highlighted” at any point in time.

In the meantime, would be great to see something like the focus() idea implemented :slight_smile:

Thanks again!

I forgot to mention, but wanted to add to this thread that this functionality would allow us to put hotkeys on row actions which I think could be a pretty good productivity enhancer.

Great call Ben.  “Pretty good” is a mild understatement, this would be fantastic!