Parts allow you partition your application into multiple Javascript files.There is an initial part, the boot part, that is loaded at application start-up. All other parts have to be loaded explicitly in your application code.
To use parts in your application, you have to do two things:
Here is an example:
Suppose you have a settings dialog in your application that is only needed occasionally. You want to save the memory footprint of the involved classes, and only load the dialog on demand when the user hits an "Open Settings Dialog" button during a session. If the user doesn't invoke the dialog, the necessary classes are not loaded and the application uses less memory in the browser. In all cases, application start-up is faster since less code has to be loaded initially.
In your configuration file, add the following job entries (assuming you are using a standard GUI application with a name space of custom):
"my-parts-config":
{
"packages" :
{
"parts" :
{
"boot" :
{
"include" : [ "${QXTHEME}", "custom.Application" ]
},
"settings" :
{
"include" : [ "custom.Settings" ]
}
}
}
},
"source" :
{
"extend" : [ "my-parts-config" ]
},
"build" :
{
"extend" : [ "my-parts-config" ]
}
This will inject your part configuration into the standard build jobs (source and build), instructing the generator to generate JS files for the "boot" and the additional "settings" part (a single part may be made up of multiple JS files, depending on cross class dependencies with other parts). In the boot part, you are repeating the main include list of class patterns for you application (the example above mirrors this list of a standard GUI app). In the part you want to separate from the initial boot part, like settings above, you carve out some top-level classes or name spaces that constitute the part you want to specify. In our example, this is just the name of the top-level dialog class.
Next, you have to add code to your application to load any part other than the boot part. Carrying on with our example, at a suitable spot in your application code, you have to load the settings part, e.g. when some "Open Settings Dialog" button is pressed which is available from your main application class. We put the loading action in the click event listener of the button:
var settingsButton = new qx.ui.toolbar.Button("Open Settings Dialog");
settingsButton.addListener("execute", function(e)
{
qx.io.PartLoader.require(["settings"], function()
{
// if the window is not created
if (!this.__settingsWindow)
{
// create it
this.__settingsWindow = new custom.Settings();
this.getRoot().add(this.__settingsWindow);
}
// open the window
this.__settingsWindow.center();
this.__settingsWindow.open();
}, this);
}, this);
The main thing to note here is that upon pressing the "Open Settings Dialog" button qx.io.PartLoader.require is invoked to make sure the settings part will be loaded (It doesn't hurt to invoke this method multiple times, as the PartLoader knows which parts have been loaded already).
The first argument to the require method is a list containing the parts you want to be loaded (just "settings" in our example). The second argument specifies the code that should be run once the part is successfully loaded. As you can see, the custom.Settings class which is loaded with this part is being instantiated.
This section also shows that you cannot run the same application with and without parts. In order to use parts, you have to "instrument" your application code with calls to qx.io.PartLoader.require, and currently there is no way these calls can fail gracefully. You have to make a decision.
These are the essential ingredients to set up and use parts in your application. For full details on the packages configuration key, see the configuration reference. For some additional usage information relating to this key, see this article. For a complete application that uses parts, check the Feedreader sources.
The most crucial and at the same time most difficult aspect of using parts is configuring the parts in your config.json. More specifically, the definition of the include key for each part requires thought and consideration to get right. This section tries to give you a set of technical guidelines to help you with that.
This section reflects part collapsing as it is realized in qooxdoo version 0.8.3 and above.
You as the application developer define parts to partition your application. qooxdoo's build system then partitions each part into packages, so that each part is made up of some of the set of all packages. Each package contains class code, and maybe some more information that pertains to it. So the classes making up a part are spread over a set of packages. Several parts can share one or more packages. This way you obtain maximum flexibility for loading parts in your application code. Whenever a part is requested through the PartLoader it checks which packages have already been loaded with earlier parts, and loads the remaining to make the part complete. No class is loaded twice, and no unnecessary classes are loaded with each part.
But there are situations where you might want to give up on this optimal distribution of classes across packages:
These are situations where part collapsing is usefull, where packages are merged into one another. This is discussed in the next sections.
(This is a more theoretical section, but it is kept here for the time being; if you are only looking for how-to information, you can skip this section).
During what we call part collapsing, some packages are merged into others. That means the classes that are contained a source package are added to a target package, and the source package is deleted from all parts referencing it.
Obviously, it is crucial that the target package is referenced in all those parts where the source package was referenced originally, so that a part is not loosing the classes of the source package. This is taken care of by the selection process that for any given source package picks an appropriate target package. (Target packages are searched for in the set of already defined packages, and there are no new packages being constructed during the collapsing process).
After the source package has been merged into the target package, and has been removed from all parts, there are two cases:
Collapsing by package size is straight forward. You can specify a minimal package size (in KB) that applies to all packages of your application. If a package's size, and it is its compiled size that matteres here, is beneath this threshold the package will be merged into another. This avoids the problem of too much fragmentation of classes over packages, and trades optimally distributing the classes (to always load only necessary classes) for minimizing net requests (when loading packages for a part).
Collapsing by size is disabled by default. You enable it by specifying size attributes in your parts configuration:
"packages" :
{
"sizes" :
{
"min-package" : 20,
"min-package-unshared" : 10
},
...
}
The min-package setting defines a general lower bound for package sizes, the min-package-unshared, which defaults to min-package if not given, allows you to refine this value specifically for those packages which pertain to only one part.
Collapsing by load order is always useful when you know in advance the order of at least some of your parts, as they are loaded during the app's run time. This is e.g. the case when you have a part that uses other parts to do its work (a big dialogue that has sub-controls like a tabview). The enclosing part is always loaded before its sub-parts can be used. Or there is a part that is only accessible after it has been enabled in another part. These situations can be captured by assigning a load order to (some of) your parts in your configuration.
"packages" :
{
"parts" :
{
"boot" :
{
"include" : [ "${QXTHEME}", "app.Application" ]
},
"some-part" :
{
"include" : [ "app.Class1", "app.Class2" ],
"expected-load-order" : 1
},
"other-part" :
{
"include" : [ "app.Class3", "app.Class4" ],
"expected-load-order" : 2
},
...
},
...
}
The boot part has always the load index 0, as it is always loaded first. The other parts that have a load index (1 and 2 in the example) will be collapsed with the expectation that they are loaded in this order. Parts that don't have an expected-load-order setting are not optimized by part collapsing, and there are no assumptions made as to when they are loaded during run time.
The important thing to note here is that the load order you define is not destructive. That means that parts are still self-contained and will continue to function even if the expected load order is changed during run time. In such cases, you only pay a penalty that classes are loaded with a part that are actually not used by it. But the overall functionality of your application is not negatively affected.