Edwin Yip 154 Posted October 3, 2021 @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
darnocian 83 Posted October 3, 2021 (edited) 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 October 3, 2021 by darnocian Share this post Link to post
Edwin Yip 154 Posted October 4, 2021 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
darnocian 83 Posted October 6, 2021 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
Edwin Yip 154 Posted October 7, 2021 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
darnocian 83 Posted November 9 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