Table of Contents

Catalis con Ext

Algunas notas acerca del rediseño de Catalis usando el framework Ext JS.

(Cosas en español, cosas en inglés, según el ánimo del momento.)

Agosto 2011 – Repensar este diseño según la API de Ext JS 4.

What's a Database

These are some ideas about implementing a MarcDatabase class in Catalis + Ext.

Ext and SQL

Ext includes some classes to work with SQL (svn/extjs/trunk/air/src/sql/):

Those classes may give some orientation about organizing our database-related code.

Databases and SearchPanels

Code to "open a database"

  var db = new Catalis.MarcDatabase('demo');
  var sp = new Catalis.DatabasePanel({db: db}); // create a DatabasePanel bound to this database
  mainPanel.add(sp);  // add the panel to the TabPanel container
  db.showNewRecords();  // or sp.showNewRecords(). It could also be sp.listPanel.showNewRecords()

What's a Record

Some ideas about implementing a MarcRecord class in Catalis.

First of all: do we need such a class?

Most of the functionality associated with handling a MARC record in Catalis may be encapsulated in the appropriate components:

But others…

Still, it may be convenient to collect some common functionality in a separate class. For example, a DetailPanel and a RecordEditor may share some functionality, since both need to get a record from the database. But I'm not sure about this.

Also, display styles for records (e.g. MARC tagged, AACR card) could be built using methods of a Record class.

In fact, to build the AACR display, we need to get field and subfield values from a record.

To apply modifications to an imported MARC record, it would be convenient to use methods of a MarcRecord class.

TO-DO: review the existing JS code

Application design

The official Ext example Data Binding Example - Implemented with classes is a good source to learn how to design an Ext application using custom components and managing their communication through events.

This is a summary of the file examples/grid/binding-with-classes.js:

TO-DO: check other Ext examples that display “details”: remoteload (employee database, requires PHP), view > image chooser, layout browser.

Custom data stores

Do we need to create custom stores, such as those defined in the Data Binding Example?

To create a store, we specify an Ext.data.DataProxy (Http, Memory, or ScriptTag) and an Ext.data.DataReader (Json or Xml). Instead of a proxy, we may specify a url, and an HttpProxy is automatically created for it. Behind an HttpProxy there is an Ext.data.Connection object. The store uses its proxy to access raw data, and then uses its reader to create instances of Ext.data.Record from that data.

Instead of getting its data through a proxy, a store can use its loadData method to load any data (such as data not directly sent by a server). In Catalis this could be used for storing holdings data, where information about each “item” is stored as an Ext.data.Record, and edited through a FormPanel contained in the HoldingsEditor.

For array data, we can also use the simpler class Ext.data.SimpleStore.

Which stores does Catalis need? The general idea is that we use a store for every “collection of data of the same kind”, i.e., the kind of things that one would store in a database. More specifically, we use stores for data that will be used as input to some Ext components, such as: DataView or ComboBox. (Note that in some other cases, a Store may be more than we need, and an array can be enough.)

Examples: the list of MARC language codes; a list of records resulting from a database search; a list of dictionary keys. Some of these stores must be global: we need them in a single instance, available for the whole application. Others —being associated with “repeatable” components— may themselves be instantiated multiple times.

NOTE: see the autoLoad config option, may be useful for our XML data stores.

A catalog of data stores for Catalis

Ext components for Catalis

Finally, it's time to create good, reusable GUI components (as extensions of native Ext components). After several months of slowly moving the code from the old Catalis version to the new Ext-based one, and reading the recommendations of knowledgeable Ext developers —especially Saki— about organizing JS code, using pre-configured classes, defining reusable components, etc… I have no choice but to properly design the components needed by Catalis.

These components should be designed so that they can be used as stand-alone widgets, even though some of them would not be used (in practice) isolated from other related components.

All the Catalis components should behave as native Ext components, in the sense of having their own config options, public properties, public methods, and even public events. In the descriptions below, we only mention config options, public methods and public events that are defined by these components; naturally all the inherited options, methods and events are available too.

Be careful not to redefine the load method, which already exists for Ext.Panel

To test individually each component, use frame: true.

Also, include a test of the whole app inside a Window, not only in a Viewport.

Viewport

This is the container for the whole user interface. It has a border layout with a north region for the main toolbar(s), and a center region for the main tab panel. A south region for the doc panel should be considered.

Since there's a single instance of Viewport, we don't need to create a custom component by extending a class.

Contains / defines

Main TabPanel

This is the container for all the main panels.

Since this panel is unique for the whole application, we can simply instantiate an Ext.TabPanel with the appropriate config options (no need to extend a class).

Contains / defines

Component communication

DatabasePanel

DatabasePanel (or SearchPanel, or DatabaseBrowser) is the panel that allows us to explore and manage the database as a whole. It's basically a container who takes care of the communication between its child components.

Having multiple DatabaseBrowser instances means we may browse more than one database at a time (of the same type or not). It's like having multiple “open” databases in Winisis.

Catalis displays this component as an item in a TabPanel container. The tab should include a context menu to close the database, open a new database, or change the database for the current tab. Before closing a tab it should be checked that there aren't unsaved changes to records in the associated database.

Changing the database for a DatabasePanel may involve destroying the Panel and creating a new one. We could also define a bind method that takes care of reconfiguring all the child components, but then we'd need a bind method in all those components too.

Config options

Public methods

None

Public events

None

Contains / defines

Component communication

DictionaryBrowser

Dictionary Browser (or DictionaryPanel, or Index Browser) is a panel that displays the list of terms in the dictionary of an Isis database. It also displays the number of “postings” for each term, and optionally might display extra info about each posting (such as tag, mfn, etc).

This panel knows nothing about “doing a database search using a selected term”, because that's beyond its scope, though it could accept a config parameter onTermSelected or selectionHandler (a function used as a handler for the selectionchange event of the panel's dataview component). Or better yet: fire a termselected event, and let clients decide what to do when that happens.

The type of the underlying database (biblio/auto) does not seem to be relevant for this component may be useful for the optional prefixes functionality.

Config options

These config options should have a sensible default: pageSize (passed to the panel's paging toolbar), title, initialTerm. The panel could be collapsible.

Public methods

Public events

Contains / defines

ListBrowser

ListBrowser (or DatabaseBrowser, or RecordListPanel) is a component that displays a list of records from a database.

A search box is not mandatory for this component, since we can always assume, as a default, a sequential browse of the whole database (mfnrange). In practice, however, we'd like to include a search box.

Each instance of ListBrowser should have its own PrintStore. We may add export / download / email functionality, so that we can save a set of records. Some of this functionality may be shared with the DetailPanel component.

There are several things we can do to save (in a general sense) a set of records:

All these options may be offered to the user as items in a menu attached to a toolbar button.

This panel knows nothing about “displaying details for a selected record”, because that's beyond its scope, though it could accept a config parameter onRecordSelected or selectionHandler (a function used as a handler for the selectionchange event of the panel's dataview component). Or better yet: fire a recordselect event.

Similarly, the Edit button (or link) that is displayed in the ListView involves a functionality that goes beyond this component. (See also DetailPanel.) A config option editHandler could work, or a specific event such as recordedit.

Having multiple ListBrowser instances means we may e.g. browse the results of two different searches.

The type of the underlying database (biblio/auto) is important, for example, to determine the record structure to be used by the JsonReader, and the template to be used by the ListView.

Config options

These options should have a sensible default: pageSize, title, initialList.

initialList indicates how to populate the list when it's first displayed, e.g. a query.

Public methods

Public events

Contains / defines

NOTE: the searchbox listens for the “specialkey” event, checks for an ENTER key, and invokes the component's load() method with the appropriate parameters. See in docs.js (Ext API) a different approach, using the beforeload event.

SearchForm

SearchForm is a FormPanel with all the fields and buttons required to query a database. Currently we have only a combo box (for the search history) and a button (for showing new records), but we should expect the form to evolve and include more fields/options.

A SearchForm is coupled to a ListPanel (search results are supposed to be displayed somewhere!), so we should include a ListPanel object as a config parameter to the SearchForm constructor.

How can we open different ListPanels for different searches using the same SearchForm?

The type of database may affect the design of the search form, especially once we start adding extra search options such as limiting the search to a specific type of field (title, subject, etc).

Config options

Public methods

None

Public events

None

Contains / defines

Private methods

DetailPanel

The DetailPanel component displays a single record in one or more display formats (or styles). To switch among the available formats, we may use toolbar buttons, or perhaps subpanels within a TabPanel.

To use a vertical toolbar, check http://extjs.com/forum/showthread.php?t=2459 or other related threads.

Having multiple DetailPanel instances means we may compare side by side two records, or two views of the same record.

Some of the record displays may be built in the browser from raw JSON data sent by the server, while others may be built by the server and thus received by the browser in final form.

For those displays that are built using JavaScript (AACR2 card, MARC tags), the corresponding methods should probably be defined outside of this component, since they might be used in other contexts. (For example, using a Catalis.util namespace.)

We may add print / export / download / email functionality, so that we can save a single record. Some of this functionality may be shared with the ListBrowser component.

Paging functionality could be useful, for moving back and forward through a list of records (as in Winisis). Such a paginated DetailPanel could be used independently of a ListPanel, using its own data store and some search box. Or, it could be used together with a ListPanel (or ListStore); in this case the search box would not be needed, and the stores in both panels would be linked.

An Edit button is needed, but since its functionality goes beyond this component (it requires the existence of a RecordEditor component), we should manage it from outside. That is, some common parent container of DetailPanel and RecordEditor should make this button appear and define its handler function. NOTE: this problem will also appear with the Edit button in the ListView. A config option editHandler could work, or an event recordedit.

How much detail are we supposed to display in this panel? Should we include leader, holdings, annotations, etc, i.e. display everything that the record has? How close should this be to a “read-only editor”?

The type of the database (biblio/auto) may affect the available display styles.

Config options

The following parameters should have a sensible default: title, initialRecordId, enabledStyles (e.g. [“AACR2”, “MARC”]), defaultStyle (e.g. “AACR2”).

Public methods

We may have a method that loads (i.e., displays) a record that is passed as argument; using this method, a database or proxy would not be needed. On the other hand, we may have a method that is passed just the record id, so that the component must first fetch the corresponding record, and then display it.

NOTE: this method must not be aware that the server must do a search using the supplied recordId plus a prefix. The server should provide a method (that is, a URL or HTTP parameter) called “getRecord” and do the necessary manipulations hidden from the client.

Public events

Contains / defines

DatabaseAdminPanel

(This is a preliminary concept.) A panel that allows us to display data about a database, such as:

and perform some actions, such as:

Some of this functionality might overlap what we are developing using the Django admin interface. Perhaps we should use Django admin for maximum control, and thus reserve it for the “system owner”, while the ordinary Catalis interface should offer functionality available to the typical user of the system. For example, using it we can't create nor delete a database, but we can unlock records.

Note also that performing some actions that modify the database goes against the definition stated above, that the DatabaseBrowse panel is used to “see what's in the database”.

Most of the above functionality does not depend on the type of the database.

Config options

Public methods

None

Public events

None

Contains / defines

RecordEditor

RecordEditor (or EditPanel, or MarcEditor) is the component that allows us to create, modify and delete MARC records. It's basically a container that takes care of communication between its child components.

Should this component know about communication with the database, to fetch and save records?

Catalis shows this component as an item in a TabPanel container. Each tab has a context menu that allows closing the current tab (previously checking if there are unsaved changes).

Although the components contained in a RecordEditor might not be used in stand alone form in a real situation, it could be useful —especially for testing purposes— to make them as independent from the rest of the editing interface as possible.

Note that not every record that gets into the editor is an existing record: newly created records and imported records are “loaded” into the editor first, and saved to the database later.

The type of the database (or type of record) —biblio, auto— is used to pass the correct config option to the child components of the RecordEditor. This type should be available as a property of the database object, as part of the record metadata, or as a separate config option.

Having multiple RecordEditor instances means we may be editing more than one record (usually a bib and an authority record, but we could edit more than one record of the same type at a time).

Paging: a paging toolbar that allows easy access from the editor interface to a list of records may be a desirable feature (already available in the original Catalis). Many of the implementation details are shared with the DetailPanel's paging functionality (see above).

When a RecordEditor shows a record and we decide to open another record, should we destroy the current RecordEditor and replace it with a new instance, or rather should we “empty” the RecordEditor and then “fill” it with the new data? The second option looks better (Ext.form.BasicForm has a loadRecord() method which might serve as a very rough inspiration).

Config options

Public methods

Public events

None (?)

Contains / defines

Private methods

Component communication

DataFieldsPanel

DataFieldsPanel (or DataFieldsEditor) is a panel that allows editing the data fields of a MARC record. NOTE: A data field in MARC is a field with tag 010 or greater; field with tags 9xx (and others with tags having a digit 9) are reserved for local use, and we might want to edit some of them using a different interface, such as field 859 for holdings data.

Subfields that use coded values are edited using a CodedDataEditor.

Authority controlled fields are edited using a ComboBox.

Some data fields or subfields may require some other special editor, such as a 773 field for analytics.

Related stores: marc21.xml (biblio/auto), relator.xml. These are static stores, shared by all instances of DataFieldsEditor.

Related classes: Catalis.DataField, Catalis.Subfield, Catalis.FieldBlock?

The type of the database affects the chosen data store with the MARC 21 specs, and the set of “field blocks”.

Config options

Public methods

Public events

Contains / defines

Observable > Component > BoxComponent > Field (since it does not contain extra components)

By default, the associated Ext.Element is handled by Component.

On the other hand, FieldSet has this chain:

Observable > Component > BoxComponent > Container > Panel > FieldSet (since it may contain extra components)

Table of characters: menu or dialog?

ControlFieldsPanel

A panel to display and edit leader and control fields.

Each editable element can open its own CodedDataEditor. Alternatively, we could try using a ComboBox as editor (see Editor Grid Example, and this Ext Forum thread).

Associated store: fixedField.xml (biblio/auto).

The type of the database affects the choice of the XML store.

Config options

Public methods

Public events

None

Contains / defines

CodedDataEditor

A Window that offers a list of available options for a specific coded data element. As with other dialogs, there can only be a single instance of this component for the whole application; but the window's content varies depending on the data element being edited.

Alternatively, instead of a Window we may use a ComboBox as an Ext.Editor, as this tentative code shows:

Ext.getBody().update("<table><tr><td id='myCell' style='background: #ffc; border: 1px solid #999; padding: 0.5em'>Option 2</td></tr></table>");
myCombo = new Ext.form.ComboBox({
  store: new Ext.data.SimpleStore({
    fields: ['value', 'code'],
    data: [['Option 1', 'A'], ['Option 2', 'B'], ['Option 3', 'C']]
  }),
  mode: 'local',
  forceSelection: true,
  displayField:'value',
  typeAhead: true,
  triggerAction: 'all',
  //transform:'light',
  lazyRender:true,
  listClass: 'x-combo-list-small'
});
myEditor = new Ext.Editor(myCombo);
Ext.get('myCell').on('click', function(){
  //alert("edit");
  myEditor.startEdit(this, '');
});

Associated stores: fixedField.xml, language.xml, country.xml.

Config options

None

Public methods

Public events

None

Contains / defines

LocalDataPanel

LocalDataPanel is a simple panel that gives access to those elements that are associated with the current record but are beyond the MARC standard: annotations, holdings, analytic records, cover images, record history (who and when modified it), etc.

It is not clear yet which would be a good UI for managing all this data.

Config options

Public methods

Public events

Contains / defines

DocPanel

DocPanel is used to browse documentation. Includes a toolbar to select doc source (LC, OCLC, etc.) and go to a specific doc item within that source. The panel should be collapsible.

A TabPanel may be useful if we want to browse more than one doc source (e.g. MARC Bibliographic and MARC Authority).

Though this panel has been used within the editor panel, it could also be useful in other contexts, so we could move it to the south region of the viewport. Or, since it is only needed occasionally, it could be a Window. Another option is using another tab in the main tab panel, but since using a tab implies hiding all the other tabs, we could not look at the same time at the docs and at the place were our problem is.

Single instance for the whole application?

collapseMode: 'mini'?

Config options

Public methods

Public events

None.

Contains / defines

DataFieldBlock

We'd like to use an Ext.Panel, but let's keep close to the original approach: define a Catalis.DataFieldBlock class, which has two associated DOM nodes: a THEAD and a TBODY.

Config options

Public methods

DataField

There are several possible approaches to implement a DataField class:

  1. Extend an existing Ext component, such as Ext.Panel. A DataField would be a Panel with extra functionality (“is-a” relation).
  2. Create a custom class with the needed functionality, and associate with it an Ext.Panel (“has-a” relation)
  3. Create a custom class with the needed functionality, and associate with it a DOM node or an Ext.Element
  4. Extend Observable (so that we get event management), and associate with it a DOM node or an Ext.Element

Options 1 and 2 have rather slow performance.

Option 3 follows more closely the approach used in the first version of Catalis, where everything was done with direct manipulation of the DOM.

Option 4 was not tested yet. It seems that we don't need event management at the class level, since the associated Ext.Element already offers event management.

Config options

Example:

var f = new Catalis.DataField({
  tag: '245',
  ind1: '0',
  ind2: '0',
  subfields: '^aPhysics :^ban introduction for all people /^cby Robert Albert.'
});

Public methods

Public events

Private methods

Contains / defines

Subfield

The implementation of a Subfield class presents some of the same alternatives described above for the DataField class. Following a simple approach based on direct access to the DOM (just like in the original Catalis) seems to be the best option in terms of performance.

Config options

* controlled: boolean. Default: false. If true,

The Subfield object must have a reference to its parent DataField object. This is needed, for example, to invoke the DataField's updatePunctuation() method each time a Subfield's value is changed. This reference may be passed as a config option to the Subfield constructor, or it may be created by the DataField object when executing its addSubfield() method.

Public methods

Private methods

Contains / defines

SubfieldBox

An extension of Ext.form.TextArea.

Three events are of special interest:

Config options

Public methods

Public events

Private methods

File organization

This is a draft scheme for how to put components into files.

key
  comp    Component
  sing    Singleton
  class   Class


  FILE                                         GLOBAL OBJECTS (in the "Catalis" namespace)
----------------------------------------------------------------------------------------------------

  Catalis.js
  
  MarcDatabase.js                              MarcDatabase (class)
  
  DatabasePanel.js                             DatabasePanel (comp)
  
  SearchForm.js                                SearchForm (comp)
  Dictionary.js                                DictPanel (comp)
  RecordList.js                                RecordListPanel (comp)
  RecordDetail.js                              RecordDetailPanel (comp)
  DatabaseAdmin.js                             DatabaseAdminPanel (comp)
  
  MarcEditor.js                                MarcEditor (comp)
  
  DataFieldsPanel.js                           DataFieldsPanel (comp)
  FieldBlock.js                                FieldBlock (class)
  DataField.js                                 DataField (class)
  Subfield.js                                  Subfield (class)
  SubfieldBox.js                               SubfieldBox (comp)
  FieldSelector.js                             FieldSelector (comp, dialog)
  SubfieldSelector.js                          SubfieldSelector (comp, dialog)
  IndicatorsEditor.js                          IndicatorsEditor (comp, dialog)

  FixedFieldEditor.js                          FixedFieldEditor (comp)
  CodedDataEditor.js                           CodedDataEditor (comp, dialog)

  LocalDataPanel.js                            LocalDataPanel (comp)  
  AnnotationsEditor.js                         AnnotationsEditor (comp, dialog)
  HoldingsEditor.js                            HoldingsEditor (comp, dialog)

  AdvancedEditor.js                            AdvancedEditor (comp, dialog)
  DiffViewer.js                                DiffViewer (comp, dialog)
  
  DocPanel.js                                  DocPanel (comp, dialog?)
  
  AACR.js                                      util.AACR (sing)
  MARC.js                                      util.MARC (sing)
  Punctuation.js                               util.Punctuation (sing)
  ISO2709.js (import/export methods)           util.ISO2709 (sing)
  ISBN.js                                      util.ISBN (sing)
  CharacterTable.js                            util.CharTable (comp) (?)
  
  LCPanel.js                                   LCPanel (comp)
  
  Templates: definitions + menu/dialog?
  Import dialog?
  Main toolbars & menus?
  Authority control?
  Record validation?
  MarcRecord class?

  Dialog markup?