JavaScript Widget Refactoring

A screen exists that allows users to edit the tools required for a procedure. I needed to change this screen to be a general "procedure configuration" screen that also allows users to manage the invoice line items that typically go with a procedure.

The prior setup already had a parent-child relationship with UI widgets; a parent container allowed users to select the procedure to edit; it contains a child widget that allows users to manipulate the list of tools.

When designing UI widgets one question that will guide you into good patterns is, "How hard will it be to use this child widget in a different place?" This gets to be tricky because there are interactions between the child and parent widgets; how can these interactions be designed so the components are loosely coupled?

That's essentially what my task became; this child widget was tightly coupled to its parent. It was necessary to make major changes to allow it to be used in a different place. Here are the ways I decoupled this child widget from its parent.

  1. Where does the child widget render its HTML?
    • Code: $("#tools-content").load("http://url/to/child-widget/fragment");
    • Issues:
      • This code in the child widget demands that the parent have a #tools-content element, and this isn't very reusable.
    • Instead:
      • Let's use the jQuery UI widget framework so the parent can tell the child where to load itself, and the child can access its sandbox in a standard way:
      • Parent code: $("#place-of-my-choosing").procedureTools({ options });
      • Child code: $.widget("ui.procedureTools", { widget definition });
      • and: this.load("http://url/to/child-widget/fragment");
  2. When a user selects a procedure in the parent widget, the child widget needs to know this and load the tools for that procedure.
    • Code: $("#ToolingProcedureId").unbind('change').change(function (x) { ... })
    • Issues:
      • Hard reference to parent select element's ID tag means we can't use this child widget with any other parent widgets.
      • unbind() clobbers anyone else who might have been listening for changes on this element (like the invoice line item widget I need to write).
      • change() couples this child widget to the implementation of the parent widget's implementation: it must use a <select> element, or it won't work.
    • Instead:
      • Add function setProcedureId(id) to the child widget, and make it the responsibility of the parent widget to call this method appropriately.
  3. When a user wants to save changes the child widget needs to know about this and POST its current state to the server.
    • Several decisions here based on how we want the UI to flow. This is appropriate - the UI structure and design should be a reflection of the natural way that a user will think and work with the system. For my scenario I decided to allow the child widget to own the Save button.
    • Issues:
      • The existing code had the Save button declared in the parent widget, but the parent widget never touched it. The child reached up and grabbed it by ID, bound event handlers to it, and even hid it and showed it.
      • Interestingly the logic for whether it should be visible was based on whether a procedure in the parent widget was selected. So to summarize: the parent widget had a procedure selector, and a Save button whose visibility was controlled by the procedure, but the logic wiring together these two pieces of the parent widget was in the child widget.
    • Instead:
      • In my case I think the child widget wanted to own the Save button; moving it there removed several odd bits of wiring code.

Just shuffling things around in this way had two clear benefits. First, the control can be reused in other contexts now. Second, even though I didn't remove any functionality, just shuffling things around in this way cut the number of lines of code down by about one-third. Apparently when your technical design matches the natural flow there's a lot less plumbing code required.

Comments !