Synchronizing View with Collection

Many modern programming languages ​​and frameworks have special collection classes that can notify clients with every change they make. In Flex, this class is called ArrayCollection, in .Net - ObservableCollection, in ExtJS - Ext.util.MixedCollection and Ext.data.Store, in jWidget - 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 various kinds of UI components: lists, tables, accordions, etc. In complex applications, collections are needed to connect several layers of the system with each other.

Today I will tell you about one original way of working with collections .


The typical behavior of a model-view bundle, where a collection is taken as a model, is their constant synchronization. In this case, the scheme is obtained at least two-level: collection and element.

ModelRepresentation
CollectionList, table
Collection itemList row, table


Added an item to the collection - you need to immediately add a line to the list. Removed the item from the collection - you must immediately remove the corresponding line from the list. And so on.

How do collections work? Usually, a programmer is given the opportunity to subscribe to a series of trivial events:

  • added (inserted element)
  • removed (item removed)
  • replaced (replaced the item in the specified cell)
  • moved (moved an item from one place to another)
  • cleared (deleted all items)


More advanced implementations, in order to avoid performance losses, also include events:

  • reordered (sorted items)
  • filtered (filtered items)
  • resetted (changed the collection arbitrarily)


All considerations regarding the model-representation connection are also true for the model-controller connection. The second bunch 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 publishing a page, editing it, commenting, etc., takes place without reloading the page. Take the list of articles as an example of the collection. Articles are clearly ordered by publication date, that is, each has its own index - a place in the general list. The oldest article has an index of 0, the most recent N is 1, where N is the number of articles. It should be noted that every article has several representations:
  1. Summary of the article in the main part of the blog
    image
  2. List on this page (sidebar)
    image
  3. The article affects the display of the list of tags.
    image
  4. Article affects calendar display
    image


When an author publishes an article, it is added to the end of the list. After that, the “added” event is thrown with the parameter (index = N), which means that the element was added before the element with index N, i.e. at the end of the collection. Since the event is thrown after inserting an element into the collection, clients can retrieve the contents of the article by requesting it from the collection by index. Having caught this event, the main part of the blog shows a brief summary of the new article, “On this page” adds a new line, the tags change their sizes, and the calendar turns one of the dates into a link.

When the author deletes the article, it is retrieved from the collection, and all the elements after it are moved back one index. 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 clients can analyze this article (see labels and date, for example), and after processing the article is deleted and the memory is cleared. Having caught this event, the main part of the blog and the list “On this page” delete the corresponding lines, the labels change their sizes, and the calendar removes the link to the date (if no other articles were published that day).

When the author changes the article, it is thrown to the end of the collection, as a result of which the “moved” event is thrown, and the views are again updated in one way or another.

Well and so on ...




In general, the collection is changing and notifies everyone about it, and the responsibility for the changes on the blog web page lies with the presentation classes. That's what the MVC scheme dictates to us.

Note: Sometimes there is also an “updated” event - they changed an element (they didn’t completely replace them, they just changed some property of an existing element), but I think that this event should not be listened to by the collection, but by the elements themselves; automation of this event entails a large loss in performance.

Next, we will focus on the first five events, as the most commonly used: added, removed, replaced, moved, 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 processing of these five events is usually done exactly the same for most views. In addition, the processing algorithms for these events intersect strongly with each other, resulting in a ton of repetitive code. Representation classes were immediately cumbersome and difficult to maintain.

It soon became clear that for most of the views, you could ask just a few simple scripts that would be enough to handle all the events. Moreover, these scenarios within the framework of one presentation 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 for all representations it looks exactly the same , therefore, it can be implemented only once in some auxiliary class. This auxiliary class works according to the “strategy” pattern, that is, it accepts algorithms as parameters — the very atomic and independent scripts that are unique to each representation.

So a new pattern was born. It would be correct to call it “Collection View Facilitator”, but for some reason I just like “Syncher”.

The bottom line is as follows. All that is required from the developer to implement the next presentation of a collection is to define the following scenarios:

SemanticsDescriptionExample
Creator (Data): ViewCreate Presentation ElementCreate table row
Inserter (View, Index)Insert an existing view item at the specified locationInsert row in table
Remover (Index): ViewRemove view item from specified locationDelete row from table
Destroyer (View)Destroy view itemDestroy a table row
Clearer (): Array of ViewRemove all items from the collection and return them.Delete all table rows 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). Implement 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 handling of standard collection events is now:

  • added (index, data)
    1. view = Creator (data)
    2. Inserter (view, index)

  • removed (index, data)
    1. view = Remover (index)
    2. Destroyer (view)

  • replaced (index, oldData, newData)
    1. oldView = Remover (index)
    2. Destroyer (oldView)
    3. newView = Creator (newData)
    4. Inserter (newView, index)

  • moved (fromIndex, toIndex)
    1. view = Remover (fromIndex)
    2. Inserter (view, toIndex)

  • cleared ()
    1. views = Clearer ()
    2. 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 JW.Collection: JW.Syncher . And here is a 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 it was very rare to use them for their intended purpose).

Also popular now: