In many modern programming languages and frameworks, there are special
collection classes that can notify clients with each change. In Flex, this class is named ArrayCollection, in .Net it is ObservableCollection, in ExtJS it is Ext.util.MixedCollection and Ext.data.Store, in
jWidget it is
JW.Collection . Such data structures are simply necessary when developing applications using the MVC scheme (Model, View, Controller). Most often they are used as a model for all sorts of UI components: lists, tables, accordions, etc. In complex applications, collections are needed to link several layers of the system to each other.
Today I will tell you about one original way of working with
collections .
The typical behavior of a model-presentation bundle, where a collection is taken as a model, is their constant synchronization. In this case, the scheme is obtained, at least, a two-level: collection and element.
')
Model | Representation |
Collection | List table |
Item collection | Row list table |
Added an item to the collection - you need to immediately add a line to the list. The item was removed from the collection - you must immediately remove the corresponding line from the list. And so on.
How do collections work? Usually, the programmer is given the opportunity to subscribe to a series of trivial events:
- added (pasted item)
- removed
- replaced (replaced the item in the specified cell)
- moved (moved item from one place to another)
- cleared (removed all items)
More advanced implementations, in order to avoid performance losses, also include the following events:
- reordered (sorted items)
- filtered (filtered out items)
- resetted (arbitrarily changed collection)
All considerations regarding the model-representation bundle are also true for the model-controller bundle. The second bundle is necessary when implementing complex systems, when one model has several representations.
Consider a simple example. Suppose we create an AJAX version of LiveJournal, that is, a blog where the page is published, edited, commented, and so on. Actions are performed without reloading the page. As an example, the collection will take a list of articles. Articles are clearly ordered by date of publication, that is, each has its own index - a place in the general list. The oldest article has the index 0, the most recent N - 1, where N is the number of articles. It should be noted that every article has several submissions:
- Summary of article in the main part of the blog

- List "On this page" (sidebar)

- Article affects the display of the list of tags

- Article affects the calendar display

When the author publishes an article, it is added to the end of the list. After this, the event “added” is thrown with a parameter (index = N), which means that the element was added before the element with the index N, i.e. at the end of the collection. Since the event is thrown after inserting an item into the collection, customers can retrieve the content of the article by requesting it from the collection by index. Catching this event, the main part of the blog shows a summary of the new article, “On this page” adds a new line, the labels change their size, and the calendar turns one of the dates into a link.
When an author deletes an article, it is removed from the collection, and all elements after it are shifted one index back. After that, the “removed” event is thrown with the parameter (index, article). That is, while the event is being processed, the article is still alive in the article parameter so that customers can analyze this article (see tags and date, for example), and after processing, the article is deleted and the memory is cleared. Catching this event, the main part of the blog and the list “On this page” delete the corresponding lines, the labels change their dimensions, and the calendar deletes the link to the date (if no other articles were published on that day).
When the author changes the article, it is transferred to the end of the collection, as a result of which the “moved” event is thrown out, and the views are updated again one way or another.
And so on ...
In general, the collection is changing and notifies everyone about it, and all responsibility for changes on the blog’s web page lies with the presentation classes. This is what the MVC scheme dictates to us.
Note: Sometimes there is still an event “updated” - they changed the element (they didn’t replace it entirely, but simply changed some property of an existing element) - but I think that this event should be heard not from the collection, but from the elements themselves; automation of this event entails a great loss of performance.
Next, we will focus on the first five events as the most commonly used: added, removed, replaced, cleared.I worked on large projects where the whole architecture was built entirely on MVC, and I have some experience in this area. I learned that the handling of these five events is usually exactly the same for most views. In addition, the algorithms for processing these events overlap with each other, resulting in a ton of duplicate code. Classes of representations were immediately cumbersome and difficult to maintain.
It soon became clear that for most of the presentations you can set up just a few simple scripts, which will be enough to handle all the events. Moreover, these scenarios within the framework of a single representation have nothing in common with each other, and perform completely specific and independent atomic operations. Further, the processing of all collection events is uniquely described on the basis of these simple scenarios, and
it looks exactly the same for all representations ; therefore, it can be implemented only once in a certain auxiliary class. This auxiliary class works according to the “strategy” pattern, that is, as parameters it takes algorithms as input - the very atomic and independent scenarios that are unique for each representation.
So a new pattern was born. It would be correct to call it “Collection View Facilitator” (Collection Presentation Assistant), but for some reason I just like “Syncher”.The bottom line is this. All that is required from the developer to implement the next presentation of any collection is to define the following scenarios:
Semantics | Description | Example |
Creator (Data): View | Create view item | Create table row |
Inserter (View, Index) | Insert an existing view item to the specified location. | Insert row into table |
Remover (Index): View | Remove view item from specified location | Remove row from table |
Destroyer (View) | Destroy view item | Destroy a row in a table |
Clearer (): Array of View | Remove all items from the collection and return them. | Delete all rows in the table and return them. |
Obviously, these algorithms are very simple and completely independent of each other (except, perhaps, the latter - but it is necessary for optimization). Implementing them is not difficult. But they are enough to synchronize with a certain collection, moreover, without any loss of performance. See how simple and efficient the handling of standard collection events is now:
- added (index, data)
- view = Creator (data)
- Inserter (view, index)
- removed (index, data)
- view = Remover (index)
- Destroyer (view)
- replaced (index, oldData, newData)
- oldView = Remover (index)
- Destroyer (oldView)
- newView = Creator (newData)
- Inserter (newView, index)
- moved (fromIndex, toIndex)
- view = Remover (fromIndex)
- Inserter (view, toIndex)
- cleared ()
- views = Clearer ()
- foreach (view in views) Destroyer (view)
And now we combine all this into an auxiliary class and rejoice! Here is the implementation of this thing for the JW.Collection:
JW.Syncher . But the test that can be considered as an example:
JW.Tests.Util.SyncherTestCase .
Maybe you can come up with a solution for reordered, filtered and resetted events, but it hasn’t reached me yet (perhaps because I rarely had to use them for their intended purpose).