James, these are all very good questions, and there are many possible answers. I will share a few pointers that I think translate well across most projects.
This will help you write clean and easy to read modules. In particular, take care not to expand your methods to do too many things. I would start by refactoring your init method to show only what is needed to initialize the module, break out the listener callbacks, they are separate functions.
// Store a reference to the module
var self = this;
// Listen for hub events
self.hub.listen('save-item-complete', self.getItemLists);
self.hub.listen('itemlist-search-complete', self.renderItems);
// Listen for user events
$("#item-list-refresh").click(self.getItemLists);
},
getItemLists: function (message) {
// Not needed but helpful as a matter of convention
var self = this;
//collect all the itemLists
self.hub.getItemLists();
},
renderItems: function (data) {
container = $("#ItemListsContainer");
container.empty();
$.each(data, function (index, value) {
container.append('<div class="accordian-group" id="subcontainer-' + index + '"></div>');
subcontainer = $("#subcontainer-" + index);
subcontainer.append('<div id="subcontainer-header-' + index + '" class="accordian-heading"><a class="accordian-toggle" href="#subcontainer-body-' + index + '" data-parent="#ItemListsContainer" data-toggle="collapse"><h3>' + value.Id + ': ' + value.Name + '</h3></a></div>');
subcontainer.append('<div id="subcontainer-body-' + index + '" class="accordian-body collapse"><div class="accordian-inner well"><button class="btn" id="show-map-' + value.Id + '">Show Map</button><button class="btn" id="make-map-' + value.Id + '">Map Map</button><button class="btn" id="make-report-' + value.Id + '">Make Report</button><table id="item-list-table-' + value.Id + '" class="table"><thead><tr><td>Description</td><td>CollectedBy</td><td>DateCollected</td></tr></thead><tbody></tbody></table></div></div>');
table = $("#item-list-table-" + value.Id + " tbody");
$.each(value.Inspections, function (index, value) {
table.append("<tr><td>" + value.Description + "</td><td>" + value.CollectedBy + "</td><td>" + value.DateCollected + "</td></tr>");
});
$('#show-map-' + value.Id).click(function () {
Doing this allows you to see more clearly what is going on here. When you initialize your module you tell it to listen for a few hub events and a user click event. That sounds like correct behavior for module initialization. The holes also become apparent when you break things out this way and it will be easy to fix. This is where I may not be helpful because I don't have the rest of your application. But one thing that stuck out to me is that you are getting the itemLists in two places (on 'save-item-complete' and on '#item-list-refresh' click), but nothing is done with this list. Maybe you are doing something with it in the call to get it. If so, I recommend doing a refactor as I did above and make sure that each method really does only one thing. hub.getItemLists should only get lists, nothing more.
2. Abstracting the base library can be helpful for certain cases but is costly
It is a great thought to move jQuery out of your module entirely, but this comes at a cost which doesn't make sense in all applications. You want to do this if you need maximum flexibility and the option to switch out jQuery for another library in the future. The downside here of course is that you can't use any library specific widgets, including jQuery widgets. The widgets themselves must be written to run on your abstracted base library. So this really makes more sense for applications that can afford a large investment. If you are able to commit to a single library it will save you time and give you the option to use anything else that requires that library, right out of the box. This is by far the easiest solution and honestly probably the most common. I would not worry too much about abstracting the base library unless you really need the flexibility.
3. Use a (precompilable) template library for markup
This will be very helpful in the long run even though it requires some setup. Writing markup as javascript strings is just a bad idea. Using a template library allows you to put all your markup into separate files where it belongs. Now, there is a distinction here between templates that compile on the fly in the browser and those that can be precompiled. There are pros and cons to each but I prefer precompiled templates.
If you precompile templates then they are available to you in your application when it loads. You don't have to fetch them via ajax which means you can synchronously render your application. This turns out to be a huge help when you are rendering your application in lots of little blocks (aka modules) and/or you need to control the timing. Of course you can do this via callbacks but the code cleans up nicely when you call render as a synchronous method.
Hogan.js does support precompiling so that should work fine. I use Handlebars.js.
4. Provide a rendering extension to Kernel and/or specific rendering module methods
If you want a common rendering pattern (which you likely do) then extend Kernel to provide it. Here is a very basic extension you can use (not tested):
Kernel.extend(Kernel, {
onStart: function(module) {
// Conditionally render the module
if (module.renderTo) {
// Define a render function if one doesn't exist
var render = module.render || function() {
// Get the template, assumes they are stored in global 'Templates'
var template = Templates[module._internals.type];
// Render the module
$(module.renderTo).html(template.render());
};
// Render
module.render();
}
}
});
This will automatically render your module when it is started. It assumes that you are storing your precompiled templates in the following format: Templates[moduleType]. In your case that would be Templates.ItemLists. You'll note, though that no data is being passed this template. That might be fine for putting a module on the page but as you mentioned you need to update the contents of the module. You can accomplish this by rerendering targeted parts of the module or even rerendering the entire module, backbone.js style. Now you have a module that is starting to look like this:
Kernel.module.define('ItemLists', {
renderTo: '#ItemListsContainer',
init: function () {
// Store a reference to the module
self = this;
// Listen for hub events
self.hub.listen('save-item-complete', self.getItemLists);
self.hub.listen('itemlist-search-complete', self.render);
// Listen for user events
$("#item-list-refresh").click(self.getItemLists);
// Make sure to use delegate so clicks are handled after rerender
$(self.renderTo).delegate(".show-map").click(function() {
self.showMapClick($(this));
});
},
getItemLists: function (message) {
// Not needed but helpful as a matter of convention
var self = this;
//collect all the itemLists
self.hub.getItemLists();
},
showMapClick: function(button) {
var self = this;
self.hub.broadcast('show-map');
self.hub.retreiveItemListLocations(button.id); },
// Add the data onto the ItemLists page
render: function (data) {
var self = this;
// Get the template, assumes they are stored in global 'Templates'
var template = Templates[self._internals.type];
// Render the module
$(self.renderTo).html(template.render(data));
}
});
Ok, these are just examples and really there are lots of ways to do things. But from this perspective you start to see more what is happening in your application. If I knew more about the overall application I could help make design decisions which is really where a lot of Kernel.js work is. Because the library is an architecture, you quickly learn that you'll be making architectural decisions as you build your app. Eventually I am going to provide more examples of some of the decisions I have made that should be helpful. Hopefully this helps in the meantime.
Alan