Jump to content
Edwin Yip

Sempare Template - How to get contextual data in a utility class method?

Recommended Posts

@darnocian, according to the documents, since custom function for the template must be defined as static class methods, how do I access contextual data in those utility methods?

 

I can see you can use `class var` to pass contextual data to the utility class before calling `Template.Eval`, but in case of multi-threaded template rendering, it seems that locking is unnecessarily needed. But at least it's workable? I'll try but you might have a better answer? Thanks.

 

It'll be great if ITemplateFunctions.AddFunctions supports non-class methods, so that in the methods the object's fields are accessible.

Share this post


Link to post

Hi @Edwin Yip

 

As functions are static, it means that you must pass all required data to them. This will further ensure they are in a way thread safe, unless you are referencing some data that is 'shared' across threads... So normal solutions apply:

1. get a copy before hand of whatever is required 

2. locking while the template is potentially referencing that shared data

 

doing 2. is probably the most expensive, but if you are referencing shared state, there are some as to the shared state changing or not. if it doesn't, well, then it shouldn't be a problem. This may be the case say with static config loaded at startup that may be referenced during rendering. if the shared state does change - say some service methods mutate the state, then you may need some locking to ensure the underlying structures are not corrupted. 

 

with the shared state,you  probably have a method that is used generally in anycase with locking for the multi threaded scenario. so exposing that function to the template is possible as well... so you may do the following:

1. pass in a reference to the static data when rendering the template

2. register a method with the template so that it can extract relevant information from the shared state

 

e.g.

type
   TMyProg = class
   private
      class var FLock:TCriticalSection;
      class constructor Create; // init flock
      class destructor Destroy;  // free flock
   public    
        class function getdata(const AKey:string; const AState:TDictionary<string, string>):string; static;
   end;

  TMyData = record
     SharedState : TDictionary<string,string>;
  end;

// -------------------

class function TMyProg.getdata(const AKey:string; const AState:TDictionary<string, string>):string;
begin
   FLock.Acquire;
   try
       if not AState.TryGetValue(akey, result) then
           exit('-');
   finally
      FLock.Release;
   end;
end;

// -------------------

var GState:=TDictionary<string, string>.Create();

var GTemplate := '<% getdata("somevalue", sharedstate) %>';
var GCtx := template.context; // context could be global as well, so you don't have to keep on creating it
GTtx.AddFunctions(TMyUtilities);

var GTpl := Template.Parse(GCtx, GTemplate); // template could be global as well

// local state
var LData : TMyData;
LData.SharedState := GState;

writeln(Template.Eval(GCtx, GTpl, LData); // this should be localised

I havn't thrown the above into an IDE, but it should work in principle... (code should be placed in relevant procedures and GState should be seen as global, etc...)

 

I hope the above makes sense...

Edited by darnocian

Share this post


Link to post

Oh, that make the template code complex, especially if you need third parties to write the template...

 

And actually I'm asking something else - What I mean is how to access relevant data in Delphi utility functions, like TMyUtilities.DoSomething in the document example...

Share this post


Link to post

Hi @Edwin Yip

 

I'm not sure why 

 

'<% getdata("somevalue", sharedstate) %>';

would be difficult? from a user perspective (someone you may have creating a template), they may not have to know about the parameter types - they just need to know what parameters are available to be passed in...

 

I do understand however that you may have non technical users from which you may want to hide this detail. At this stage, the only way is to use class variables - which is what would be available to the class functions.

 

Maybe a potential enhancement would be to allow the template context to be param 1:

     class function dosomething(const ACtx:ITemplateContext;const AFirstArg:string):string;

From a usage perspective, the template engine could expect the following usage:

    <% dosomething('hello') %>

where it auto inserts the context.

 

The reason I mention the context is because it allows you to store data in there as well which can generally be accessed by the template.

 

 

 

 

Share this post


Link to post

As you said, "I do understand however that you may have non technical users from which you may want to hide this detail.", and that's what I wanted, just like DMustache allows you to use methods (non-class methods) to define custom functions.

 

Anyways,  I understand that's the current status of the template engine, I can live with that.

Share this post


Link to post

I stumbled across this thread again and thought I'd just add a reference to https://dev.to/sempare/accessing-data-from-the-sempare-template-engine-for-delphi-5dg8

 

It illustrates various approaches to accessing data from the template engine. In the thread above, we discussed static methods, but in the article you can easily see how you can access data from data stored in the context, or by creating a custom object with methods on it, which can then be called.

 

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×