This demo exists to show how to use muiltiple MVC Groups within a single application. In this case, the MVC group will be a panel that is inserted on-demand into a tabbed pane.
Preparation
First, let's create the main applicaiton that will hold te tabbed pane.
create-app FileViewer
The model for the main applicaiton is simple, a String for the file name int eh text field.
import groovy.beans.Bindable class FileViewerModel { @Bindable String fileName = "" }
The view has a text field, browse and open buttons, and the tabbed pane
openAction = action(closure: controller.openFile, name:"Open") fileViewerFrame = application(title:'File Viewer', size:[500, 300], locationByPlatform:true) { borderLayout() hbox(constraints:NORTH) { textField(columns:20, action:openAction, text: bind('fileName', target:model, id:'textBinding')) button("...", actionPerformed:controller.browse) button(openAction) } filesPane = tabbedPane(constraints:CENTER) } fileChooserWindow = fileChooser()
The controller contains basic implementations for browse and open. For this demo the important line is the createMVCGroup method call in the handler for the open method. For the implications of this call, see the Wiring it Together section. Note that even though the class doesn't extend anything or declare the method it is available because of Groovy's meta programming capabilities.
import javax.swing.JFileChooser class FileViewerController { def view def model def openFile = { evt = null -> File f = new File(model.fileName) // create the new MVC Group createMVCGroup('FilePanel', f.path, [file: f, filesPane:view.filesPane, tabName:f.name]) } def browse = { evt = null -> def openResult = view.fileChooserWindow.showOpenDialog(view.fileViewerFrame) if (JFileChooser.APPROVE_OPTION == openResult) { model.fileName = view.fileChooserWindow.selectedFile.toString() view.textBinding.reverseUpdate() } } }
Creating the MVC Group
Now that we have an existing application shell, we can create the MVC Group that will show the file contentes.
To start, we call the create-mvc script from the command line:
griffon create-mvc FilePanel
This script will do two things. First, it will create the same style Model, View, and Controller groovy files like you are used to seeing for the applicaiton MVC group. The second item it does is insert the mappings for the MVC Group in the Application.groovy file.
//[...snip...] mvcGroups { // MVC Group for "FilePanel" FilePanel { model = 'FilePanelModel' view = 'FilePanelView' controller = 'FilePanelController' } //[...snip...] }
The model is very straight forward: the file we loaded, the last modified time, and the actual text we have cached.
import groovy.beans.Bindable class FilePanelModel { @Bindable File loadedFile @Bindable long lastModified @Bindable String fileText }
The view fairly simple: a scroll pane bound to the fileText of the model. Because the view is a Groovy script the Griffon framework can expose all of the variables passed in the third argument of the createMVCGroup method call. In our call we passed in the actual tabbed pane is passed in as filesPane. With this reference to an alrady constructed widget we can use use tabbedPane node in as a wrapper mode to wrap the tabbed pane and add children as though it was newly constructed. We can also use the number of tabs in the tabbed pane to set the selectedIndex to our new position (remember we count from zero in Groovy).
tabbedPane(filesPane, selectedIndex: filesPane.tabCount) {
scrollPane(title:tabName) {
textArea(editable:false, text:bind {model.fileText})
}
}
The controller contains a closure that checks the age of the file and updates the text if needed. Usually Griffon generates a reference to the view, but that references has been removed since all updates in this MVC group area all done via the model.
The most relevant piece of code for this example is the mvcGroupInit method. This method is called after the other classes are instantiated. The argument is the map of arguments passed into the createMVCGroup method. From this we can load up the model with the relevant values group and kick off any tasks that are associated with it, in this case updating the file text.
class FilePanelController {
def model
void mvcGroupInit(Map args) {
// load the file and fire updates
model.loadedFile = args.file
updateFile()
}
def updateFile = { evt = null ->
if (model.loadedFile.lastModified() > model.lastModified) {
model.fileText = model.loadedFile.text
model.lastModified = model.loadedFile.lastModified()
}
}
}
Wiring it Together
Now we have the two key pieces. A parent application that has a place to accept and use the MVC Groups that are generated, and an MVC group itself that is intended to be instantiated multiple times. The pieces are actually alreadu wired together, but lets discuss how that happened.
To create the MVC Triad first we called createMVCGroup. This method does not exist in any of the declared classes but is injected into the MVC instances when Griffon instantiates them. (This is actually a curried method of GriffonApplicaitonHelper which has the app context automatically inserted as the first argument, effectively hiding it from the user.)
The first argument to the exposed createMVCGroup method is mandatory: the name of the MVC Group we want to create. This is the name we pass into the create-mvc script. The second argument is a name we would want to associated with the MVC group. MVC Groups are accessible via the app.model, app.view, and app.controller variables and this name will be the key into those maps, for example as app.controller.group3.updateFile() would call the update file method on the MVC group controller for the group named 'group3'. If not provided the MVC type name is used as a name. The third and final argument is a map of values to be injected into the builder variables. The values in the map are passed into the binding of the View script and is passed in as a map argument to any mvcGroupInit methods that are present.
In the actual execution of the method createMVCGroup first instantiates new instances of the model, view, and controller classes without wiring them together or doing any initialization. The method next injects (either directly or into the meta class in some cases) various variables and methods. Some are inject if present (model, view, controller, and builder). Others are injected as methods into the metaclass. When selection methods to inject one method is always injected createMVCGroup and others are injected if the Builder directs them to be injected into the various instances. By default the Swing threading methods are injected into the controllers, but this can be removed from the Configuration. (The swing threading methods are edt, doLater, and doOutside). Finally, after all injections the mvcGroupInit method is called if present in the model, view, or controller (in that order).
Commentary
This reflects reality as of a mid-November build of the 0.1 snapshot. Things have changed. First, the createMVCGroup method was initially statically called from GriffonApplicationHelper. Second, an in-MVCGroup-init lifecycle was change to be implemented by methods in the MVC group itself. This allowed for the MVC Group to handle it's own startup and linkage rather than having to hand crank it after creation. (Although you still can hand crank it if you feel it clarifies the code).


