Procedure #EXTENSION won't generate into a browse's ResetQueue method — %ActiveTemplateInstance doesn't resolve to the browse?

Clarion 11.1, ABC. I’m writing a procedure #EXTENSION template that should inject a few lines into a browse’s ResetQueue method (to DISABLE/ENABLE the Insert button based on a record count). The template registers fine, installs on the procedure fine, prompts accept values fine — but it generates NOTHING into the browse method. No error; the code simply never appears.

Here is the embed section:

#AT(%BrowserMethodCodeSection, %ActiveTemplateInstance, ‘ResetQueue’, ‘(BYTE ResetMode)’), PRIORITY(5000)
#IF(%UseEditionGuard)
IF %EditionGuard
IF RECORDS(%BrowseClass) >= %RecordLimit
DISABLE(%InsertControl)
%BrowseObject.InsertControl = False
ELSE
%BrowseObject.InsertControl = %InsertControl
ENABLE(%InsertControl)
END
DISPLAY(%InsertControl)
END
#ELSE
IF RECORDS(%BrowseClass) >= %RecordLimit
DISABLE(%InsertControl)
%BrowseObject.InsertControl = False
ELSE
%BrowseObject.InsertControl = %InsertControl
ENABLE(%InsertControl)
END
DISPLAY(%InsertControl)
#ENDIF
#ENDAT

The embed code: BRW1.ResetQueue PRIORITY 2500
IF Glo:Edition = 1
IF RECORDS(Queue:Browse) >= 10
DISABLE(?Insert)
ELSE
ENABLE(?Insert)
END
END

What I’ve already verified:

  1. The embed point exists and is reachable. If I hand-type “!here” directly into the BRW1.ResetQueue embed in the Embeditor, it generates into the .clw correctly, and the method BRW1.ResetQueue PROCEDURE(BYTE ResetMode) appears.

  2. The signature is exact. I used the EmbedInfo / “create #AT from embed” template inside that same ResetQueue embed, and it printed:

    !#AT(%BrowserMethodCodeSection,%ActiveTemplateInstance,‘ResetQueue’,‘(BYTE ResetMode)’),PRIORITY(5000)

    which matches my template’s #AT character-for-character.

  3. It’s not a caching/stale-generation issue — a fresh app, a brand-new simple browse (single sort order, ResetQueue present in the generated class), same result: nothing generates from the extension.

My working theory: because this is a procedure #EXTENSION, %ActiveTemplateInstance refers to the EXTENSION’s own instance, not the browse (BRW1) instance — so the #AT has no browse method to match and silently generates nothing. The EmbedInfo output showing that signature was presumably generated in the context of the BrowseBox control template, where %ActiveTemplateInstance DOES mean the browse.
My questions:

Q1. Is that theory correct — that a procedure #EXTENSION cannot use %ActiveTemplateInstance to target a specific browse’s method?

Q2. If so, what is the correct way, FROM A PROCEDURE #EXTENSION, to embed code into a specific browse’s ResetQueue method? Is there a symbol for the browse instance I should use instead, or do I need to #FOR/loop over the browse control instances (e.g. over %Control or a browse-instance symbol) and reference that instance in the #AT?

Q3. If a procedure #EXTENSION genuinely can’t do this cleanly, is the accepted approach to make it a BrowseBox #CONTROL template instead? If so, how is such a control template ATTACHED to an existing browse list control in the app (the step-by-step in the IDE), since it isn’t a visual control I’d drop on the window?

Context on why an extension is preferred: I want to add this limiter to ~12 browse procedures across an app (and reuse across other apps), toggling behavior via a single global (Glo:Edition). Hand-placing works but I’d like the template to manage placement. Whichever mechanism is correct (extension with the right instance reference, or control template), a short example of the working #AT line plus how it’s installed would be hugely appreciated.
DataLimiter.tpl (3.3 KB)

Thanks in advance.

Try changing the #AT with the following, which I found in some of the shipping 3rd party templates.

#AT(%BrowserMethodCodeSection, %ActiveTemplateInstance, ‘ResetQueue’, ‘(BYTE ResetMode)’), PRIORITY(5000)
  #FOR(%ActiveTemplate),WHERE(%ActiveTemplate='BrowseBox(ABC)')
    #FOR(%ActiveTemplateInstance)
      #CONTEXT(%Procedure,%ActiveTemplateInstance)
#!....Your code here
      #ENDCONTEXT
    #ENDFOR
  #ENDFOR
#ENDAT

It’s not something I have tried, but might get it to work.

Mark

As a follow up, in case this is a problem for you:

Your code is checking the record limit using the browse queue, that will have a problem if you are page loading the browse, as records(queue) will not give you a full count of the records in the table.

If your template is a child of the browse template, then you need to use %ActiveTemplateParentInstance

SOLVED - thank you Mark and Rick. Posting the working solution in case it helps someone else.

The core problem: from a procedure #EXTENSION, I was trying to use %ActiveTemplateInstance directly as the instance argument in #AT(%BrowserMethodCodeSection, …). In an extension context %ActiveTemplateInstance came back blank, so the #AT had no instance to target and generated nothing (silently).

Two things fixed it:

  1. Get the browse instance with the INSTANCE() function, not %ActiveTemplateInstance. Loop %ActiveTemplate in #ATSTART, and when the template is the BrowseBox, capture INSTANCE(%ActiveTemplate) into a declared symbol. Then use that captured symbol as the instance argument on a single #AT (the #AT cannot be inside the #FOR - Clarion throws “#ENDFOR expected”).

  2. #Priority to place the code before the parent call. PRIORITY(2500) puts it ahead of PARENT.ResetQueue; 5000 landed after it.

Working extension (trimmed to the essentials):

#EXTENSION(LimitBrowseRecords, ‘Cap browse records’), PROCEDURE
#PROMPT(‘Table (file) to count:’, @S64), %LimitTable, REQ
#PROMPT(‘Records:’, @S8), %RecordLimit, DEFAULT(‘10’), REQ
#PROMPT(‘Insert control:’, CONTROL), %InsertControl, DEFAULT(‘?Insert’), REQ
#!
#ATSTART
#DECLARE(%LimitBrowseInst)
#FOR(%ActiveTemplate)
#IF(INSTRING(‘BrowseBox’, %ActiveTemplate, 1, 1))
#SET(%LimitBrowseInst, INSTANCE(%ActiveTemplate))
#ENDIF
#ENDFOR
#ENDAT
#!
#AT(%BrowserMethodCodeSection, %LimitBrowseInst, ‘ResetQueue’, ‘(BYTE ResetMode)’), PRIORITY(2500)
IF RECORDS(%LimitTable) >= %RecordLimit
DISABLE(%InsertControl)
ELSE
ENABLE(%InsertControl)
END
#ENDAT

A couple of notes for anyone following:

  • %Primary did not resolve from the extension context (came back blank), so I made the table name a prompt rather than trying to derive it. If someone knows the correct symbol to get a browse’s primary file from an extension, I’d be glad to hear it.

  • On the record count itself: as Mark pointed out, RECORDS(queue) is unreliable with page-loaded browses, so I count the file with RECORDS(tablename) instead. That gives the true record count for an edition-gating limit.

  • Debugging tip that finally cracked it: I had the template write diagnostic comments into the generated .clw (into %DataSection) dumping each %ActiveTemplate and its INSTANCE() value. That showed me the instance was blank, which pointed straight at the fix. Being able to SEE what the template was resolving, rather than guessing, was the turning point.

Thanks again - you got me started on the right path.

#IF( %Primary )

returns the name of the %File so can be treated like a %True or %False in the #If() statement.

From the help docs.

The key to understanding the use of the #CONTEXT structure is “as if the source code for the named section were being generated.” This statement means that the statements are evaluated as if #GENERATE were executing.

In the context of the Instance/named section, this isnt strictly true in that only a part of #Procedure will run, the whole #Procedure template still runs, so its easy to get caught in loops, trigger #Restrict/#EndRestrict & #Prepare/#EndPrepare code if not careful.


Create a #Code template which lists the builtin variables and their current value on whatever embed the #code template is placed as seen here.

This website is saying I’m editing this post in another tab when I’m not!