[cfobjects-developers] RE: cfoMVC
Brought to you by:
orbwave
From: Steven B. <or...@ms...> - 2002-03-01 02:49:34
|
Rocky: ******** On all future correspondance, let's use the cfobjects-developers mailing list to post our ideas. I like to have an archive of all meaningfull conversations about the framework, and since the cfObjects.com site is down, the mailing list is the only to do it. I added you to the subscribers list. ******** I get the concept, but I'll take a closer look at it to wrap my brain around more thoroughly. In fact, your concept isn't far off from the approach I take. I create a parent class called UIElement for every application I build. As children are things like - page, toolbar, navigation - among others whose sole responsibilities are creating the UI for the application. These contain 95% of the HTML for the application. Now, the event system's purpose is to do exactly what you are doing, and what I *should* be doing, but I rarely take the time to be 100% OO in my approach. Nor do I think it necessary to be 100% compliant. Here's an example of a completely event-based site's structure. The methods below are the ones that a designer would build and then pass off to the developers. It simply builds the HTML structure of the site. UIElement.Page.buildStructure method ------------------------------------ <--- Show the page definition tags (HTML, HEAD, TITLE, BODY, etc..) ---> <cf_cfoInvokeMethod object="#self#" method="buildDocumentHeader"> <cf_cfoRaiseEvent eventName="afterDocumentHeaderBuild"> <table width="780" border="0" cellspacing="0" cellpadding="0"> <tr> <td> <cf_cfoRaiseEvent eventName="beforePageHeaderBuild"> <cf_cfoInvokeMethod object="#self#" method="buildPageHeader"> <cf_cfoRaiseEvent eventName="afterPageHeaderBuild"></td> </tr> </table> <table width="780" border="0" cellspacing="0" cellpadding="0" id="mainbgcolor"> <tr> <td width="100%"> <table border="0" cellspacing="0" cellpadding="0" bgcolor="#ffffff"> <tr> <!--- BEGIN LEFT BAR SECTION ---> <td width="155" valign="top" class="leftSectionBody"> <cf_cfoRaiseEvent eventName="beforeLeftBarBuild"> <cf_cfoInvokeMethod object="#self#" method="buildLeftBar"> <cf_cfoRaiseEvent eventName="afterLeftBarBuild"> </td> <td width="6"><cfoutput><img src="#application.imageDir#blank.gif" width="6" height="1" border="0" alt=""></cfoutput></td> <!--- BEGIN MAIN CONTENT SECTION (RIGHT SIDE) ---> <td valign="top" width="619"> <cfoutput><img src="#application.topDir#images/blank.gif" width="126" height="5" alt="" border="0"></cfoutput><br> <cf_cfoRaiseEvent eventName="beforeContentDisplay"> UIElement.Page.destructor method -------------------------------- <cf_cfoRaiseEvent eventName="afterContentDisplay"> </td> </tr> </table> </td> </tr> </table> <cf_cfoRaiseEvent eventName="beforeFooterDisplay"> <cf_cfoInvokeMethod object="#self#" method="showFooter"> UIElement.Page.showFooter method -------------------------------- <!-- Footer --> <table width="760" border="0" cellspacing="10" cellpadding="0"> <tr> <td class="small"> <br><br><br> <div align="center" id="footer">All contents © 2000-2002 MyCompany.</div> </td> </tr> </table> <cf_cfoRaiseEvent eventName="afterFooterDisplay"> </body> </html> To build a web page, you simply need to create the page class. <cf_cfoCreateObject class="UIElement.page" title="Page Title"> As you can see, every element of the site is being passed off to a separate method, and an event is raised before and after each of these elements. This way, the developer can customize the functionality of the entire application without ever needing to worry about the design of the site. Data/output can be displayed anywhere she wishes. This leads to an incredible amount of flexibility. A designer can completely redesign the site and it wouldn't affect the work of the developers at all. Now this can be taken one step further and a designer can actually lay out the different views (as you have done), stuff them in methods of, say, the UIElement.view class. The developer only need to code the output and then pass the results to the view. User.showRandomData method -------------------------- <cfquery name="q_query_name"> {SQL Code Here} </cfquery> <cf_cfoInvokeMethod method="UIElement.view.multiRowOutput.buildRows" query="#q_query_name#" title="Random Data Output"> As I hope you can see, we achieve the same level of seperation of design and code, but we approach it in different ways. Designers never have to worry about breaking code and developers never have to worry about making their data look pretty. Either way, we both achieve the best possible environment for building applications for the Web. - Steve -----Original Message----- From: Rocky Palladino [mailto:roc...@us...] Sent: Thursday, February 28, 2002 8:27 PM To: web...@or... Subject: Re: cfoMVC > >> Please explain. I'd be interested to hear why you felt you >> couldn't >do it with the existing set of tags. I've built many sites already that >separate logic and design using the tags. I know that you've done a lot of work with the event handling system. I must confess to not spending much time using the associated tags. While it is likely that I could achieve the same result -- separation of logic and display -- using these tags, I haven't yet taken the time to investigate them thoroughly. It's one of the many things on my to-do list! I suspect, however, that cfoMVC offers a different, not necessarily better or worse (at least, I hope not!), means towards our stated end. The two approaches could possibly be used in combination to great effect. When I first started using cfobjects (back in the 1.0 days), the whole idea of OOP was new to me. I had taken a C++ class at university, but just barely passed and never suspected I'd end up as a programmer in any capacity. But here I am. At any rate, the idea of objects was something about which I had only a vague notion. Reading through Ralph's documentation, I became intrigued by the model-view-controller paradigm to which he referred often. The MVC paradigm formed the basis for the implementation of cfobjects as an application framework. The model end of the triad is well thought out, developed and tested; it was the primary motivation for creating cfobjects in the first place. The view and controller are, in my opinion, somewhat less well shaped. We have "view" pages corresponding to navigable files in a web directory and a controller mechanism ("handler.cfm") that merely selects one of these views based on the value of a url or form scoped variable ("event"). It is simply effective. But I found it lacking, and not completely in keeping, with respect to the separation of model, view and controller as I understood it. Keep that last thought in mind as you read on, because my entire conception of MVC may be off base. I have no experience with implementation of MVC in any language outside of coldfusion. Everything I know about OOP I learned from cfobjects. It is true that this original implementation achieved a separation between the model and the view, but I failed to grasp how to achieve a clear separation between the view and the controller. The problem for me was always this: Where should I put my html? Should html go in the method? Should it go in the view? Can it go in both? Does it matter? If one of the aims is to separate roles of designer and developer, how can I justify putting html in the method? Or conversely, putting cfo calls in the view? I couldn't. My typical cfobjects "view" page became a mix of cfoCreateObject, cfoInvokeMethod, etc. tags that controlled *what* data was to be displayed and of html and cfml tags that controlled *how* that data was to be displayed. I began to place all the cfobjects code up at the top of my template and all the html code underneath to approximate some (visual) separation between the two. This led me to the realization that everything in the top of the page (the cfo tag calls) could be taken out of the "view" and placed in a "controller" file that was somehow linked. Including a file on every page was one solution, but I figured it would be more messy than helpful. What I ended up with is much cleaner and makes use of existing mechanisms provided by the framework. For every application, I create a class that I label "app," although the specific name chosen is entirely unimportant. I add a method to this class (or its subclasses) for every view page in my application that I want to "control". In these methods I code the cfobjects calls that I need to get the data I want to display. Then, I return that data to the view. The view page contains only the html and cfml necessary to display the data. The view and controller are tied together using cfoMVC, which I designed to be placed in Application.cfm and called on each http request. I've found the processing overhead to be minimal and definitely worthy of the tradeoff with code maintainability. The tag determines what the current view is, invokes a corresponding method of a class that the developer can programmatically specify, and returns the data to the view. You could have a class hierarchy of controllers corresponding to the hierarchy of view directories, or put all the controllers in a single class, or some combination thereof. If there is no controller method for a particular view, (i.e. a cfo.Error.MethodNotFound error is thrown) then we just display the view and assume it knows what it is doing. The idea here is that using cfoMVC allows you to separate controller logic from the view, but doesn't *require* it. Some view pages may just display static data and need no controller. Some times it is just quicker to invoke a method and display the results all in the view. So why bother? I like this approach because I can code all the logic of an application in my "app" class(es) and build "dummy" views that simply show what data is passed to it from the controller. Then the designer (who also happens in most cases to be me, but never mind that...) can come in and code all the view pages knowing what data is present, but without knowing how or from whence any of it came. Or vice-versa, if you believe in building the UI before the application. I do not include a stitch of html in any of my model classes or any of the controller methods. As it should be, all html is in the view (or UI classes, see below for more on that). The model and the controllers only know about data: where it is, how to get it, and how to set it. The model has no idea that anyone is using its data for display purposes. It doesn't really care. The controller knows it's getting data from the model with the expressed intent of displaying it in a view. The only purpose of the view is to decide how to display the data it knows it will receive from the controller. It is possible that all of this could have been avoided by utilizing an extensive set of UI classes invoked in the view, but for the sake of processing speed, I decided against this early on. I don't know if you've had experience with using cfobjects on Solaris (which is what I primarily use), but until the recent versions, it was God-awfully slow. Any more than about 4-5 cfoInvokeMethods in any given request would be unbearable. Thus, I never used a Page object, or any such UI class. I use cfobjects *only* for modeling my data and use html, cfml, custom tags, includes, etc., for the display of it as they tend to process much more quickly. Anecdotally, this approach has already paid off in one case in a totally unintended way. Whilst working on an application, I accidentally deleted a directory (don't ask) that contained view pages. I hadn't yet imported the project into the CVS repository and had no backup of the files at all. But the class library was still intact, so I had all the controllers. It was a simple matter to rebuild each of the deleted pages just by looking at the controller and remembering how I displayed the data. Since most of the page layout is built from include files anyway (for the header, and footer, etc.), it took me only about an hour to recode about 20 view pages. If I had left the controller and view code all in the same page, I would have lost about 3 weeks of work. I shudder to think of the fallout that surely would have resulted from this stupid blunder had the application not been structure thusly. The experience taught me two lessons: 1) Daily CVS commits! 2) Having the view and the controller separate makes the code base much more maintainable. I also like it because it gets me thinking of the application itself as an object, not just a web page constructed of objects. Do I need to set some configuration settings for this application? Do it in the base application object constructor! What if these settings need to be user configurable? Simply create a file to store the user configurable settings and pass the path to it in as an attribute to the cfoCreateObject call, which we can do via cfoMVC. Want a simple, centralized way of handling custom application errors? No problem, just enclose the cfoMVC call in a cftry block and then cfcatch any custom errors thrown by the controllers. Building my applications in this way, I've found that the model classes I create become much more reusable, because they don't have any specific interface elements built into them. And my application classes can have multiple "faces," if necessary. If you've made it this far, I commend you. I didn't intend on writing a freakin' essay here, but it ended up that way. I hope the concepts make sense. I would be pleasantly surprised if you completely grasp it! I am including the code for cfoMVC and a sample Application.cfm that utilizes it. If you'd like, I can build a simple application that demonstrates the principles. I really have no idea how other cfobjects developers are building their applications. The cfobjects gallery started out with such promise but it seems to have been largely abandoned - and I'm just as guilty of this as anyone. I would be curious to hear what you think of this concept and please tell me if/how/why using the event handling mechanisms may be better suited to what we're attempting. Rocky Palladino _________________________________________________________________ Chat with friends online, try MSN Messenger: http://messenger.msn.com |