in fw/1 I know you have `view()` to render another...
# fw1
s
in fw/1 I know you have
view()
to render another view(b) within an existing one(a), however this requires any data in (b) to be in the controller for (a). Does a way exist to have (b) be self contained? Basically ColdBox’s conception of a ‘viewlet’
w
doesn't that violate the idea that views are just rendering templates and receive their data from the service layer by way of the controller?
what's the actual scenario?
s
let’s say you have a metrics table of current accounts by account manager, that you’d want to be able to include anywhere
how do you do that without having to duplicate the call to the data in every controller method where you need it, instead of just having it self contained
w
is the data persisted in any way? cached?
s
for argument’s sake lets say no, but you’d still need to set that up in the overall controller for the page instead of that small view
so if you have page1 page2 page3, each of them will have a controller method and in order for that small view to be included in each of them, you’d need to have 3 calls to the data you need for it instead of just including the view and have it handled when you do it
w
reading
ok, so the viewlet's data and final rendered html is cached
i could see having a base controller cfc that other controllers extend and in that base you could have a before() that renders the b view and shoves it into rc. or simply have that call in each relevant controller's before() if you need more fine grained control
s
hmm we did discuss using the before but not in a superclass
might be what we need
w
i suppose the caching is optional
s
Cheers for the ideas!
w
assuming it's cached, i see no downside to arbitrarily getting it in a before() in a base class
np
fwiw, i do prefer the concept of prc to pass this type of data. fw1 doesn't have a prc natively (at this time) but it's not hard to write an extension to one.cfc that introduces prc alongside rc (we do just that in our fw1 apps)
s
that is also the direction we were leaning - like view() currently but a self contained one tentatively called viewlet() that would leverage it
It will take some more discussion either way I reckon
w
in application.cfc you could set up a namespace in rc early on, something like rc._viewlets, which would be the storage location for this kind of stuff. as long as it is established after fw1 has resolved url and form into rc, it's safe (the data can't be overridden by user input)
s
It was trying to avoid falling afoul of the error where you try and call a controller when one is already being executed
w
prc still better since it's immune to user input
not sure i followed that last thing
s
so let’s say you’re on a view, the controller method for that has been called. The first thing we tried out was if we created a method called viewlet as I said and passed in the action we needed, then the viewlet method would call controller() with the method passed, but this causes the framework to throw the error
Copy code
if ( structKeyExists( request._fw1, 'controllerExecutionStarted' ) ) {
            throw( type='FW1.controllerExecutionStarted', message="Controller '#action#' may not be added at this point.",
                detail='The controller execution phase has already started. Controllers may not be added by other controller methods.' );
        }
that one
w
understood
i don't see the mvc hierarchy that way, and apparently neither does fw1. i only expect a single controller method to handle the single request. and any data required to satisfy that is pulled from the services. are you saying that for any given request, you don't know whether you actually need the b viewlet at some point?
at the controller level, that is
s
that’s where the self contained aspect comes in, it should correctly display whether I include in one view or another
the controller doesn’t need to be aware of it if it handles itself
c
@websolete the fact that
rc
is vulnerable to manipulation only struck me failry recently (after a security audit!). I considered hand-rolling a
prc
scope, but decided just to switch to the
request
scope for all data which hasn't come in via form/url. Makes the untrustworthy data that needs validation left in
rc
stand out.
w
clearly it can be resolved by what we said before, a before() method that sticks it into rc. then, if a view calls a viewlet it just passes rc into it, something like
#view("viewlets/viewletname",rc)#
and viewlet just pulls it from rc and outputs it. the viewlets would only need enough code to examine rc and find the named key of what is being asked for
s
indeed, yeah
w
regarding rc, i came into a codebase where it was being heavily abused as the transport layer for data from queries and whatnot and was very concerned about it's vulnerability to being overwritten. however, after analyzing the code in fw1, and where this app actually does it's setting of data into the rc scope, the url/form resolution had already been completed before adding rc.userQuery and crap like that so really it was safe.
clearly using request addresses it too, but it's not 'the fw1 way' for however important that is
c
The security issue we were picked up on was where we were doing
param rc.whatever="default"
If you scrupulously avoid that you're probably safe as you say, but I like the clarity of a completely separate, untouchable scope.
w
100% agree. honestly, the whole param approach to arguments is the only thing i never liked in fw1
it's too prone to being used in a way that many developers don't realize is 'bad'
👍 1
like the difference between isdefined() and structkeyexists(). the diff is subtle but significant
@salted i have a renderService that contains the following two methods in it to allow calling views and layouts and rendering them from the service layer. it requires injecting fw into the service, but what is a rendered view or viewlet? it's just service data rendered through a view template, no controller required. anyway, these methods allow services to return what i think is equivalent to your viewlet idea:
Copy code
/**
     * Allows view rendering capabilities from this component
     *
     * @path string                 the path to the view, relative to the /views/ directory; ex: reportgenerator/content/viewName
     * @rc struct                   ?: struct to pass as the rc scope
     * @prc struct                  ?: struct to pass as the prc scope
     *
     * @return string
     */
    public string function renderView(
        required string path,
        struct rc = {}, 
        struct prc = {}
    ) {

        var result = fw.view( arguments.path,{ "rc" : arguments.rc, "prc" : arguments.prc } );

        return result;

    }

    /**
     * Allows layout rendering capabilities from this component
     *
     * @path string                 the path to the layout, relative to the /layouts/ directory; ex: reportgenerator/layoutName
     * @body string                 ?: the body content to pass into the layout, typically the result of a view() call
     * 
     * @return string               the rendered layout content
     */
    public string function renderLayout(
        required string path,
        string body = ""
    ) {

        var result = fw.layout( arguments.path, arguments.body );

        return result;

    }
not rocket science, didn't invent anything, just the idea of these methods being available in a service is the point
obviously prc is available in this customized fw1 app
m
My take, i think is a little different. I think he is trying to add it into a view, to load a view and execute controller methods for that view if they exist.
Copy code
public function runEvent( event="", eventArguments={}) {
		var subsystem = getSubsystem( arguments.event );
		var section = getSection( arguments.event );
		var item = getItem( arguments.event );
		var cfc = getController( section = section, subsystem = subsystem );
		var _rc = request.context;
		_rc.append( arguments.eventArguments );

		if ( structKeyExists( cfc, item ) ) {
			invoke( cfc, item, { rc : _rc, headers : request._fw1.headers } );
		}

		return  view( listChangeDelims(arguments.event,"/",".") );
	}
I added the above to Application.cfc, and in my view that i wanted to bring in the 'viewlet' added this: <cfoutput>#runEvent("main.viewlet", {"msg": "hi"})#</cfoutput>
w
yes, seems like what he was after
s
thanks guys, I think that is closer to the idea I was having right enough, though some good ideas in your example too Kevin
m
personally, my normal way of it, would likely be considered dirty.
s
@Matt Jones most likely, but if it works…
m
i usually just call a view, and check if the required stuff exists, if it doesn't, i might call getBeanFactory().getBean("whateverService") and then make it available
but i have a very small number of specific items that i render in a manner somewhat similar to viewlets
w
not to be too meta, i just look at controllers differently. my controllers are very thin, they really only establish request entry points and may do some minimal parameter validation, but they're only there to be the glue in the middle of services and views. there's something off (to me) about needing to make additional calls that require controller execution. not sure if i articulated that well, just saying. i haven't cured cancer, so i have to obsess about more important things like this.
👍 1
i also think that's a big difference between coldbox and fw1. coldbox has already 'solved' a lot of these things for you, whereas fw1 let's you solve them how you see fit
s
Yes, these things have pros and cons on both sides
w
nevertheless, i think your runEvent above would be a good fw1 addition, don't have to use it if one doesn't want, but it's like an enema - it may not help, but it sure couldn't hurt
😂 1