- jQuery Design Patterns
- Thodoris Greasidis
- 1546字
- 2021-07-16 12:52:28
Demonstrating a sample use case
In order to see how the Pub/Sub Pattern is used, and make it easy to compare it with the Observer Pattern, we are going to rewrite the dashboard example from Chapter 2, The Observer Pattern, using this pattern. This will also clearly demonstrate how this pattern can help us decouple the inpidual parts of an implementation and make it more extendable and scalable.
Using Pub/Sub on the dashboard example
For the needs of this demonstration, we will use the HTML and CSS files exactly as we saw them in Chapter 2, The Observer Pattern.

To apply this pattern, we will only need to change the code in the JavaScript file with our new implementation. In the following code snippet, we can see how the code was changed in order to adapt to the Publisher/Subscriber Pattern:
$(document).ready(function() { window.broker = $('.dashboardContainer'); $('#categoriesSelector').change(function() { var $selector = $(this); var message = { categoryID: $selector.val() }; broker.trigger('dashboardCategorySelect', [message]); }); broker.on('dashboardCategorySelect', function(event, message) { var $dashboardCategories = $('.dashboardCategory'); var selectedIndex = +message.categoryID; var $selectedItem = $dashboardCategories.eq(selectedIndex).show(); $dashboardCategories.not($selectedItem).hide(); }); $('.dashboardCategory').on('click', 'button', function() { var $button = $(this); var message = { categoryName: $button.text() }; broker.trigger('categoryItemOpen', [message]); }); broker.on('categoryItemOpen', function(event, message) { var boxHtml = '<p class="boxsizer"><article class="box">' + '<header class="boxHeader">' + message.categoryName + '<button class="boxCloseButton">✖' + '</button>' + '</header>' + 'Information box regarding ' + message.categoryName + '</article></p>'; $('.boxContainer').append(boxHtml); }); $('.boxContainer').on('click', '.boxCloseButton', function() { var boxIndex = $(this).closest('.boxsizer').index(); var message = { boxIndex: boxIndex }; broker.trigger('categoryItemClose', [message]); }); broker.on('categoryItemClose', function(event, message) { $('.boxContainer .boxsizer').eq(message.boxIndex).remove(); }); });
Just like in our previous implementation, we use $(document).ready()
in order to delay the execution of our code until the page has been fully loaded. First of all, we declare our broker and assign it to a new variable on the window
object so that it is globally available on the page. For our application's broker, we are using a jQuery object with the outermost container of our implementation, which in our case is the <p>
element with the dashboardContainer
class.
Tip
Even though using global variables is generally an anti-pattern, we store the broker into a global variable since it is an important synchronization point of the whole application and must be available for every piece of our implementation, even to those that are stored in separate .js
files. As we will discuss in the next chapter about the Module Pattern, the preceding code could be improved by storing the broker as a property of the application's namespace.
In order to implement the category selector, we are first observing the <select>
element for the change
event. When the selected category changes, we create our message using a plain JavaScript object with the value
of the selected <option>
stored in the categoryID
property. Then, we publish it in the dashboardCategorySelect
topic using the jQuery jQuery.fn.trigger()
method on our broker. This way, we move from a UI element event to a message with application semantics that contains all the required information. Right below, in our subscriber's code, we are using the jQuery.fn.on()
method on our broker with the dashboardCategorySelect
topic as a parameter (our custom event), just like we would do to listen for a simple DOM event. The subscriber then uses the categoryID
from the received message, just like we did in the implementation of the previous chapter, to display the appropriate category items.
Following the same approach, we split the code that handles adding and closing information boxes in our dashboard in publishers and subscribers. For the needs of this demonstration, the message of the categoryItemOpen
topic contains just the name of the category we want to open. However, in an application where the box content is retrieved from a server, we would probably use a category item ID instead. The subscriber then uses the category item name from the message to create and insert the requested information box.
Similarly, the message for the categoryItemClose
topic contains the index of the box that we want removed. Our publisher uses the jQuery.fn.closest()
method to traverse the DOM and reach the child elements of our boxContainer
element and then uses the jQuery.fn.index()
method to find its position among its siblings. The subscriber then uses jQuery.fn.eq()
and the boxIndex
property from the received message to filter and remove only the requested information box from the dashboard.
Tip
In a more complex application, instead of the box index, we can associate each information box element with a newly retrieved jQuery.guid
using a mapping object. This will allow our publisher to use that guid
in the message instead of the (DOM-related) element index. The subscriber will then search the mapping object for that guid
in order to locate and remove the appropriate box.
Since we are trying to demonstrate the advantages of the Pub/Sub Pattern, this implementation change was not introduced in order to ease the comparison with the Observer Pattern and is instead left as a recommended exercise for the reader.
To summarize the above, we used the dashboardCategorySelect
, categoryItemOpen
, and categoryItemClose
topics as our application-level events in order to decouple the handling of the user actions from their origin (the UI element). As a result, we now have dedicated reusable pieces of code that manipulate our dashboard's content, which is equivalent to abstracting them into separate functions. This allows us to programmatically publish a series of messages so that we can, for example, remove all the existing information boxes and add all the category items of the currently selected category. Alternatively, even better, make the dashboard show all the items of each category for 10 seconds and then move to the next one.
Extending the implementation
In order to demonstrate the scalability that the Pub/Sub Pattern brings with it, we will extend our current example by adding a counter with the number of boxes that are currently open in the dashboard.

For the counter implementation, we will need to add some extra HTML to our page and also create and reference a new JavaScript file to hold the counter implementation:
... </section> <p style="margin-left: 5px;"> Open boxes: <output id="dashboardItemCounter">1</output> </p> <section class="boxContainer"> ...
In the HTML page of the example, we will need to add an extra <p>
element to hold our counter and some description text. For our counter, we are using an <output>
element, which is a semantic HTML5 element ideal to present results of user actions. The browser will use it just like a normal <span>
element, so it will appear right next to its description. Also, since there is initially a hint box open in our dashboard, we use a 1
for its initial content:
$(document).ready(function() { broker.on('categoryItemOpen categoryItemClose', function (event, message) { var $counter = $('#dashboardItemCounter'); var count = parseInt($counter.text()); if (event.type === 'categoryItemOpen') { $counter.text(count + 1); } else if (event.type === 'categoryItemClose' && count > 0) { $counter.text(count - 1); } }); });
For the counter implementation itself, all we need to do is add an extra subscriber to the dashboard's broker, which is globally available to other JavaScript files loaded in the page, since we have attached it to the window
object. We are simultaneously subscribing to two topics, by passing them space delimited to the jQuery.fn.on()
method. Right after this, we locate the counter <output>
element that has the ID dashboardItemCounter
and parse its text content as a number. In order to differentiate our action, based on the topic that the message has received, we use the event
object that jQuery passes as the first parameter to our anonymous function, which is our subscriber. Specifically, we use the type
property of the event
object that holds the topic name of the message that was received and based on its value, we change the content of the counter.
Note
For more information on the event object that jQuery provides, you can visit http://api.jquery.com/category/events/event-object/.
Similarly, we could also rewrite the code that prevents accidental double-clicks on the category item buttons. All that is needed is to add an extra subscriber for the categoryItemOpen
topic and use the categoryName
property of the message to locate the pressed button.
Using any object as a broker
While in our example we used the outermost container element of our dashboard for our broker, it is also common to use the $(document)
object as a broker. Using the application's container element is considered a good semantic practice, which also scopes the emitted events.
As we described earlier in this chapter, jQuery actually allows us to use any object as a broker, even an empty one. As a result, we could instead use something such as window.broker = $({});
for our broker, in case we prefer it over using a page element.
By using newly constructed empty objects, we can also easily create several brokers, in case such a thing would be preferred for a specific implementation. Moreover, in case a centralized broker is not preferred, we could just make each publisher the broker of itself, leading to an implementation more like the first/basic variant of the Pub/Sub Pattern.
Since in most cases, a declared variable is used to access the application's broker within a page, there is little difference between the above approaches. Just choose the one that better matches your team's taste, and in case you change your mind at a later point, all you have to do is use a different assignment on your broker
variable.
- 程序員面試筆試寶典(第3版)
- PHP 從入門到項目實踐(超值版)
- C# Programming Cookbook
- Flink SQL與DataStream入門、進階與實戰
- Network Automation Cookbook
- SQL for Data Analytics
- Visual Basic學習手冊
- BIM概論及Revit精講
- Teaching with Google Classroom
- SQL Server 2008 R2數據庫技術及應用(第3版)
- BeagleBone Robotic Projects(Second Edition)
- 數據科學中的實用統計學(第2版)
- Applied Deep Learning with Python
- JavaEE架構與程序設計
- Android應用開發攻略