====== 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 [[http://docs.sencha.com/ext-js/4-0/|la API de Ext JS 4]].
===== What's a Database =====
These are some ideas about implementing a MarcDatabase class in Catalis + Ext.
* A MarcDatabase is basically a wrapper for the following set of Ext pieces (the "data only" pieces, with no GUI):
* a Connection + an HttpProxy + a JsonReader
* some data stores: history (memory only?), screen, print, dictionary <= **NOT ANYMORE!** (it seems better to associate data stores to the panels that use them)
* Besides, a MarcDatabase has some methods that involve updating data stores //(search//, //list//, //index//), and methods to get/save a record. But... //get/////save// could also be methods of the components that need them, i.e. the DetailPanel and the RecordEditor. Methods //search// and //list// are associated to the ListBrowser, and //index// is associated to the DictBrowser. So it seems that a "database" in this Ext context is nothing more than a connection + proxy + reader (and a name, of course) shared by several UI components. But... readers are better associated to their respective data stores, so finally the only thing that survives as a "database" is a name (a String), and an HttpProxy.
* So, again: what's a MarcDatabase for our purposes? An HttpProxy (which is basically a URL and some fixed CGI params), plus a String (the database's name), plus a String (the database's type). Do actions like "get the record with this ID" or "get the results for this query" have to be defined as methods of this abstract database object? Or should they be defined as methods of the components that are directly involved with them? For example, getting the list of results for a query seems an appropriate task for a ListPanel, or its ListStore. On the other hand, getting a record is needed by both the DetailPanel and the MarcEditor (in fact, these two components, which deal with a single MARC record, may have several features in common!), and so it should not be defined as a method of neither of them.
* Note that a database might need some extra UI components, e.g. for admin purposes: see status info, unlock records, generate inverted file, etc.
* A MarcDatabase does not have to be bound to a SearchPanel. That is, we should be able to query a database (and even write to it) without having a GUI. (But wait... what about the callbacks that process the received data?)
==== Ext and SQL ====
Ext includes some classes to work with SQL (''svn/extjs/trunk/air/src/sql/''):
* **Ext.sql.Connection** (extends Ext.util.Observable)
* Methods: isOpen, getTable, createTable, plus a list of //abstract// methods (implemented by subclasses, such as **Ext.sql.AirConnection**)
* Properties: maxResults, openState
* **Ext.sql.Proxy** (extends Ext.data.DataProxy)
* Methods: load, onLoad, processData, onUpdate, onAdd, onRemove
* Properties: conn, table, keyName, store, readonly
* **Ext.sql.Table**
* Methods: update, updateBy, insert, lookup, exists, save, select, selectBy, remove, removeBy
* Properties: conn, name, keyName.
Those classes may give some orientation about organizing our database-related code.
===== Databases and SearchPanels =====
* A SearchPanel is the GUI that allows reading/browsing a database.
* Each SearchPanel must be bound to a single MarcDatabase. The SearchPanel constructor requires a 'db' config option.
* We may have more that than one SearchPanel at the same time, thus having access to multiple databases.
* Should we allow more than one SearchPanel bound to the same MarcDatabase? If not, how to avoid it? Keeping a register of open databases and their associated panels?
* A SearchPanel is not re-bound to a different MarcDatabase; instead, the "change database" operation consists of destroying the current SearchPanel and creating a new one. That is, a SearchPanel spends its whole life tied to a single MarcDatabase.
* Each sub-component of a SearchPanel (its sub-panels, toolbars, fields, buttons, etc) must be able to "know" about its parent SearchPanel and about its associated MarcDatabase. Two possible solutions are:
* pass a **db** parameter to all the constructors involved, so that every sub-component has a **db** property.
* pass a **db** parameter only to the SearchPanel constructor, so that SearchPanel has a **db** property, and then from any sub-component use **findParentByType('searchpanel').db**. Note that this method can only be used //after// the sub-component has been added to its container (see ''Container.js'').
==== 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:
* The (brief) record as part of a list: ListPanel
* The (full) record as data to be displayed: DetailPanel
* The (full) record as data to be edited (created, modified, deleted): RecordEditor
But others...
* Parsing/generating ISO2709 records: ?
* Validation ?
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 **[[http://extjs.com/deploy/dev/examples/grid/binding-with-classes.html|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'':
* Ext.ns('App'): creates application's namespace
* **App.BookStore**: extends Ext.data.Store; defines constructor (uses Ext.applyIf)
* **App.BookGrid**: extends Ext.grid.GridPanel; overrides initComponent (uses Ext.apply)
* **App.BookDetail**: extends Ext.Panel; adds 2 custom properties (tplMarkup, startingMarkup) and a custom method (updateDetail); overrides initComponent (uses Ext.apply)
* **App.BookMasterDetail**: extends Ext.Panel; "glue" between the 2 panels defined above; overrides initComponent (uses Ext.applyIf) and initEvents; adds a custom method (onRowSelect)
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 [[http://www.extjs.com/deploy/dev/examples/grid/binding-with-classes.html|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 ====
* Stores for **lists of MARC records**: screen and print versions, one in each ListPanel (or DatabasePanel); one Ext.data.Record per MARC record. JsonReader. HttpProxy. Used in DataView.
* Stores for a **single MARC record** (?): one Store in each (paginated?) DetailPanel and EditPanel; a single Ext.data.Record per Store, representing a MARC record. JsonReader. HttpProxy. Used in...
* Stores for **static XML data**: global, one Store for each XML file, one Ext.data.Record for each "repeated element" (e.g. datafield, language, country). Xmlreader. HttpProxy. Used in ComboBox (and others). Bibliographic and Authority data.
* Stores for **parts of a MARC record**, such as holdings (?): one Store for each LocalDataPanel (or EditPanel), one Ext.data.Record for each "item" (ejemplar). JsonReader. No proxy (SimpleStore?). Used in SimpleForm?
* Store for available **record templates** (?): global, or one Store in each DatabasePanel (if templates depend on the database being edited); one Ext.data.Record for each template. JsonReader. HttpProxy. Used in ? . Bibliographic and Authority templates.
* Store for **search history**: one Store for each SearchForm (or DatabasePanel); one Ext.data.Record per each executed search. No proxy, no reader (unless search history is stored on the server, which we might want to do!). Used in ComboBox.
* Store for **lists of authority controlled data**: single global store, reset for each lookup; one Ext.data.Record per returned name. JsonReader. HttpProxy. Used in ComboBox.
* Store for **list of available databases** for the current user.
* Stores for small lists of options, usually used in combo boxes: **search types**, **sort types**.
===== 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 ===
* Toolbar
* Main TabPanel
* DocPanel (?)
==== 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 ===
* DatabasePanel (one for each open database)
* MarcEditor (one for each record being edited, preferably not more than one at a time)
* LCPanel
* DocPanel (?)
* Other panels (OPAC, Google Books, etc)
=== Component communication ===
* Is this panel responsible for opening/loading a RecordEditor each time an "Edit" button is pressed?
==== 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 ===
* **proxy** or **db**, to manage connections to the backend database
* **type** (of the associated database, biblio/auto) (implied by db)
=== Public methods ===
None
=== Public events ===
None
=== Contains / defines ===
* SearchForm
* ListBrowser
* DictionaryBrowser
* DetailPanel
* DatabaseAdminPanel
=== Component communication ===
* Select a term in the DictPanel => load the ListPanel
* Select a record in the ListPanel => load the DetailPanel
* SearchForm and its associated ListPanel are already communicated?
==== 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 ===
* **proxy** or **db**: (mandatory) used for "connecting" to the backend Isis database.
* **prefixes**: a list of prefixes and their description, used as navigation aids.
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 ===
* **loadTerms** (or **loadData**): loads terms beginning from a specific position in the dictionary (by default, the beginning).
=== Public events ===
* **termselected**: fires when the //selectionchange// event of the panel's dataview is fired. It can be used to perform a database query using the selected term.
=== Contains / defines ===
* A data store (uses the passed database proxy)
* A paging toolbar (with a **textbox** to specify a starting term, and a select box to choose prefix; uses the store)
* A template, for displaying each term
* A data view (uses store and template)
==== 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:
* **print** directly from the browser, i.e. get a printer friendly view (on a separate browser window)
* **download** the list as RTF, MRC ("pure MARC"), PDF, etc.
* send the list (in some format) via **email** to some address(es)
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 ===
* **proxy** or **db**: (mandatory) Used for "connecting" to the backend Isis database.
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 ===
* **loadRecords** (??): requests a set of records (invokes the //load// method of the panel's store).
=== Public events ===
* **recordselect**: fires when the //selectionchange// event of the panel's dataview is fired. Listeners can use this event to display the selected record in the DetailPanel.
* **recordedit**: fires when the "Edit" link or button for a record in the list is clicked. Listeners can use this event to invoke the MarcEditor for this record.
=== Contains / defines ===
* Data store (one for screen, one for print)
* (optional) Search toolbar, containing a "search box" or "filter box" (should use the history combobox?), a select box to choose type of search, a botton to retrieve latest records, ...
* Paging toolbar (bound to the data store), with a "print" button, a "sort by" menu, ...
* Template (same for screen and print?)
* DataView (one for screen, one for print; uses data store and template)
**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 ===
* **listPanel**: the ListPanel where search results are displayed.
* **db**: the database to query against (not necessary, implied by listPanel?)
=== Public methods ===
None
=== Public events ===
None
=== Contains / defines ===
* Data store (for search history)
* Template (for search history)
* ComboBox (for search history)
* Button (for "show new records")
=== Private methods ===
* submitting a query invokes the **search** method of the ListPanel's database/store/proxy.
* clicking the "new records" button invokes the **list** method of the ListPanel's database/store/proxy.
==== 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 ===
* **proxy** or **db**: so that the component can connect to the backend Isis database and fetch records
* **useSearchBox**: boolean; displays a search box in the toolbar, used for getting a record by its control number (default: false)
* **paging**: boolean; enables a paging toolbar (default: false)
* **listStore** or **listPanel**: source of (pointers to) records to be displayed, used with //paging: true//.
The following parameters should have a sensible default: **title**, **initialRecordId**, **enabledStyles** (e.g. ["AACR2", "MARC"]), **defaultStyle** (e.g. "AACR2").
=== Public methods ===
* **loadRecord**: loads a record. Invokes the //load// method of the component's data store. (Example of use: when a record is selected in a RecordListPanel.)
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 ===
* **recordedit**: fired by the "Edit" button. Listeners may use this event to load the record in a MarcEditor.
=== Contains / defines ===
* **toolbar**, to select among the available formats, and optionally with a **textbox** to specify the ID of a record to be fetched from the database, or even a **search box**.
* It's not clear if we also need one or more DataViews, with their corresponding data store and templates.
* **data store** (required by the PagingToolbar), using the passed proxy and... what //reader//? The store has at most one item (the requested record). The store's //load// event handler should build the display using the received record data as input.
* **paging toolbar**, with pageSize 1. Each //back// or //forward// reloads the store.
==== DatabaseAdminPanel ====
(This is a preliminary concept.) A panel that allows us to display data about a database, such as:
* Numbers of records
* Annotations about the database
* File sizes
* Fields/subfields used
* FST
* List of users that have access to this database
* Logs, statistics, reports
and perform some actions, such as:
* Unlock records
* Generate the inverted file
* Download a backup copy
* Edit annotations
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 ===
* **db**: a MarcDatabase.
=== 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 ===
* **proxy** or **db**: so that the RecordEditor can communicate with the backed database for saving the edited record, and for fetching the record to be edited if it already exists.
* **recordId**: the control number of the record to be edited, when it already exists in the database.
* **record**: a **MarcRecord** instance, or its JSON representation, when the record is being created (e.g. by importing or templating).
* **type**: biblio/auto (implied by **db**)
=== Public methods ===
* **loadRecord**: loads a record in the editor. See comments above for the //loadRecord// method of DetailPanel.
=== Public events ===
None (?)
=== Contains / defines ===
* Toolbar
* PagingToolbar (for sequentially editing a list of records) (?)
* DataFieldsPanel
* ControlFieldsPanel
* LocalDataPanel
* DocPanel (?)
* properties: **initialState** (to detect when the record is dirty). (Ext uses **originalValue** for form fields.)
=== Private methods ===
* **showDiff**
* **export** / **print** / **email** (see DetailPanel)
* **isDirty**
* **hasField**
* **getData**: collects all the record data
* **save**
* **delete**
=== Related dialogs ===
* DisplayRecord
* DiffView
* AdvancedEditor
* ConfirmDeletion
* UnsavedChanges
=== Component communication ===
* "Special" change in DataFieldsPanel => update ControlFieldsPanel (e.g. 008)
==== 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 ===
* **type**: biblio/auto
=== Public methods ===
* **load**: populates the panel with data fields
* **getData**: collects the current data fields
* **addDataField**
=== Public events ===
* **specialchange**: The component should "publish" changes that may impact other components. For example, a change in 260 $c (date of publication) may cause an update of positions 06-14 in field 008.
=== Contains / defines ===
* Originally, the panel's body contains an HTML table with one //thead// + //tbody// block for each block of data fields (description, access points, etc.). Within each //tbody// we have a //tr// for each data field, which in turn contains a //table// for each subfield, and each row in this table contains a **SubfieldBox**, a customized TextArea for editing subfields.
* An approach using Ext components ---a panel for each data field, and a panel for each subfield--- turned out too heavy (slow to render).
* An alternative intermediate approach ---not tested yet--- is this: define classes Catalis.DataField and Catalis.Subfield as **extensions of Ext.Element**. Since we are only interested in fields and subfields as //visual// components to be manipulated (created, modified, validated, moved, deleted), it seems unnecessary to define e.g. a DataField as an abstract object which //has// an associated DOM node; we may directly define the DataField as the DOM node, wrapped by some useful Ext methods (Ext.Element), and enhanced with all the extra functionality needed for a DataField in Catalis. **BUT**... I see no examples of //extending// Ext.Element, and Ext.Element has no config options. So probably the preferred approach is: "my class //has a// Ext.Element", instead of "my class //is a// Ext.Element"? To follow an example from Ext itself: a form Field has this inheritance chain:
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)
* The only thing that really matters: this is one of the most (if not //the// most) critical parts in Catalis' interface, and its original version worked really well on IE. No matter what framework we use, this part of the interface must remain very usable, render fast, and if that requires that we code everything at the DOM level, then no problem, we'll do it again.
=== Related dialogs ===
* IndicatorsEditor
* CodedDataEditor
* FieldSelector
* SubfieldSelector
//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 [[http://extjs.com/deploy/dev/examples/grid/edit-grid.html|Editor Grid Example]], and this [[http://extjs.com/forum/showthread.php?t=36615|Ext Forum thread]]).
Associated **store**: ''fixedField.xml'' (biblio/auto).
The **type** of the database affects the choice of the XML store.
=== Config options ===
* **type**: biblio/auto
* **recordType**: book, map, video, etc (used to interpret positions 18-34 of field 008)
=== Public methods ===
* **load**: loads leader and control fields data into the component.
* **getData**: collects the current values stored in the component.
* **set**: sets the value of a single data element.
* **get**: gets the value of a single data element.
=== Public events ===
None
=== Contains / defines ===
* ??? Store + DataView? Form? What?
=== Related dialogs ===
* CodedDataEditor
==== 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("");
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 ===
* **load**: loads and displays the list of codes for the specified data element, and selects the current value.
=== Public events ===
None
=== Contains / defines ===
* ComboBox ?
==== 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 ===
* Load / get (?)
=== Public events ===
=== Contains / defines ===
* Buttons
=== Related dialogs ===
* HoldingsEditor
* AnnotationsEditor
* GetImages (?)
==== 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 ===
* available doc sources?
=== Public methods ===
* **loadDoc**: loads a specific doc item (e.g., LC doc for field 300 of the MARC Bibliographic format).
=== Public events ===
None.
=== Contains / defines ===
* TabPanel ?
* Ext.ux.IFrameComponent (see also: ManagedIframe extension)
* Toolbar (see Animal's solution)
==== 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:
- Extend an existing Ext component, such as Ext.Panel. A DataField would be a Panel with extra functionality ("is-a" relation).
- Create a custom class with the needed functionality, and associate with it an Ext.Panel ("has-a" relation)
- Create a custom class with the needed functionality, and associate with it a DOM node or an Ext.Element
- 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 ===
* **tag**: required
* **ind1**: optional. Default based on template for the given tag
* **ind2**: optional. Default based on template for the given tag
* **subfields**: optional. Default based on template for the given tag
Example:
var f = new Catalis.DataField({
tag: '245',
ind1: '0',
ind2: '0',
subfields: '^aPhysics :^ban introduction for all people /^cby Robert Albert.'
});
=== Public methods ===
* **addSubfield**
* **insertSubfield**
* **isDirty**
* **getSubfields**
* **getIndicators**
* **setIndicators**
=== Public events ===
=== Private methods ===
* **createNode**: creates the associated DOM node (but does not render it)
* **render**: renders the DOM node as a child of the parent TBODY node (a field block)
=== Contains / defines ===
* **node**: the DOM node used to display this field, a TR element with three child TDs, used for: tag, indicators, subfields.
* subnodes of **node** may be assigned as properties to the DataField object.
==== 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 ===
* **code**: required
* **value**: optional. Default: empty
* **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 ===
* **isDirty**
=== Private methods ===
* **createNode**
* **render**
=== Contains / defines ===
* **node**: the DOM node, a TR with three child TDs, used for: code, label, value
* **Catalis.SubfieldBox**: a custom Ext.TextArea used to store and display the subfield's value, and also the default //editor// for the subfield. Special subfields may use other editors (e.g. a ComboBox).
==== SubfieldBox ====
An extension of Ext.form.TextArea.
Three events are of special interest:
* focus: change CSS class to this SubfielBox and to the previously focused one
* change: fires validation, corrections, punctuation update at the field level, ...
* specialkey (or keydown): the ENTER key moves focus to the next SubfieldBox
=== Config options ===
* **value**: optional. Default: empty
=== 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?
{{tag>catalis desarrollo extjs}}