Herramientas de usuario

Herramientas del sitio


notas:catalis-objetos.js

objetos.js

<note> Primera experiencia de reescribir Catalis usando JS orientado a objetos. Archivo modificado 27-ene-2007. </note>

Introducción

Documentación generada con Natural Docs.

Código

/* Al final de este archivo hay una crónica de los pasos que seguí */
 
/*
------------------------------------------------------
  Object: Interface.
    The Interface object acts as a namespace for all those methods associated
    with user interface elements such as tabs, menus, dialogs and window.
------------------------------------------------------
*/
var Interface = {
 
    /*
    ---------------------------
    Method: setDimensions
    ---------------------------
    */
    setDimensions : function() {
        // Search screen
        $("searchResultsIframe").style.height = Config.dimensions["searchResultsIframe_height"] + "px";
        if (moz) {
            $("recordVisualization").style.height = Config.dimensions["recordVisualization_height"] + "px";
        } else if (ie) {
            $("searchpanel_right_bottom").style.height = Config.dimensions["recordVisualization_height"] + "px";
        }
        $("indexTerms").style.height = Config.dimensions["indexTerms_height"] + "px";
 
        // Edition screen
        $("theRightPanel").style.height = Config.dimensions["theRightPanel_height"] + "px";
        $("recordDiv").style.height = Config.dimensions["recordDiv_height_max"] + "px";
        $("docIframe").style.height = Config.dimensions["docIframe_height_max"] + "px";
    },
 
    /*
    ---------------------------
    Method: showEditDiv
    ---------------------------
    */
    showEditDiv : function() {
        $("searchResultsFrameWrapper").style.display = "none";
        $("theSearchDiv").style.display = "none";
        $("theMarcEditDiv").style.display = "block";
        document.title = SOFT_NAME;
 
        // Bloques de elementos visibles/invisibles
        $("single_record_block").style.visibility = "visible";
        $("field_subfield_block").style.visibility = "visible";
        $("btnBuscar").style.display = "inline";
        $("btnEditar").style.display = "none";
 
        // ATENCION: este elemento sólo debe ser visible si el registro editado es parte
        // del conjunto de resultados de la "búsqueda" más reciente. Actualmente, se lo
        // está mostrando de manera incondicional cada vez que se pasa a la pantalla
        // de edición.
        $("resultNavigation_block").style.visibility = "visible";
 
        // Solo necesitamos actualizar el título cuando ya hay un título (y por lo tanto
        // un registro) en el form de edición
        if ( $("field245") ) {
            refreshTitleBar();
        }
    },
 
    /*
    ---------------------------
    Method: showSearchDiv
    ---------------------------
    */
    showSearchDiv : function() {
        $("searchResultsFrameWrapper").style.display = "block";
        $("theSearchDiv").style.display = "block";
        $("theMarcEditDiv").style.display = "none";
        document.title = Config.SOFT_NAME;
 
        // Bloques de elementos visibles/invisibles
        $("single_record_block").style.visibility = "hidden";
        $("field_subfield_block").style.visibility = "hidden";
        $("resultNavigation_block").style.visibility = "hidden";
        $("btnBuscar").style.display = "none";
        $("btnEditar").style.display = "inline";
        // El botón 'btnEditar' se habilita sólo si hay un registro en el form de edición
        $("btnEditar").disabled = ( $("marcEditForm").f001.value === "" );
 
        try {
            $("kwSearchBox").focus();
        }
        catch (err) {
            $("dictBrowseBox").focus();
        }
    },
 
    /*
    ---------------------------
    Method: refreshTitleBar
    ---------------------------
    */
    refreshTitleBar : function() {
        if ( $("field245") ) {
            var title = getSubfields($("field245")).replace(/ \.(?=($|\^))/,".").replace(/\^\w/g," ");
            document.title = Config.SOFT_NAME + " :: " + title;
        }
    },
 
    Dialog : {
 
        /*
        ---------------------------
        Method: selectTemplate
        ---------------------------
        */
        selectTemplate : function() {
            var URL = "selectTemplate.htm";
            var width = ( screen.width == 800 ) ? 620 : 750;
            var height = ( screen.width == 800 ) ? 290 : 320;
            var callback = MarcDataBase.prototype.createRecord;
            showPopWin(URL, width, height, callback);
        },
 
        /*
        ---------------------------
        Method: selectField
        ---------------------------
        */
        selectField : function() {
            var URL = "selectField.htm";
            var width = 400;
            var height = 200;
            var callback = MarcRecord.prototype.addField;
            showPopWin(URL, width, height, callback);
        },
 
        /*
        ---------------------------
        Method: selectSubfield
        ---------------------------
        */
        selectSubfield : function() {
            var URL = "selectSubfield.htm";
            var width = 400;
            var height = 200;
            var callback = MarcDataField.prototype.addSubfield;
            showPopWin(URL, width, height, callback);
        },
 
        /*
        ---------------------------
        Method: inputIso2709
        ---------------------------
        */
        inputIso2709 : function() {
            var URL = "inputIso2709.htm";
            var width = 580;
            var height = 330;
            var callback = MarcDataBase.prototype.importRecord;
            showPopWin(URL, width, height, callback);
        },
 
        /*
        ---------------------------
        Method: editCodes
        ---------------------------
        */
        editCodes : function() {
            var URL = "editCodes.htm";
            var width = 400;
            var height = 200;
            var callback = MarcDataBase.prototype.editCodes;
            showPopWin(URL, width, height, callback);
        }
 
    }, // end Dialog object
 
    Menu : {
        showFieldMenu : function() {
        },
 
        showSubfieldMenu : function() {
        },
 
        showNewRecordMenu : function() {
        },
 
        killmenu : function() {
        }
    } // end Menu object
 
}; // end Interface object
 
 
/*
---------------------------------------
Class: MarcDataBase
---------------------------------------
    Constructor.
 
Notes:
    An object to handle database interactions.
    Properties:
        - name
        - templates? a template may be associated with a particular database,
          but can also be shared among databases.
    Methods:
        - status related: getUsers()
        - search & dictionary related ??
 
    To change database, a method changeDatabase() should be used. Whose object should
    that method belong to?
*/
 
function MarcDataBase(name) {
    this.name = name;
    //this.templates ?
};
 
/*
---------------------------------------
Method: createRecord
---------------------------------------
    Creates a new MARC record using a template, and presents it for edition.
 
Returns:
    A MarcRecord object.
*/
 
MarcDataBase.prototype.createRecord = function(template) {
    console.assert (template !== "");
    alert("template: " + template);
    return;
    r = new MarcRecord(template);
    r.initialState = r.serialize();
    // TO-DO: update toolbar elements
    r.display();
    return r;
};
 
/*
---------------------------------------
Method: importRecord
---------------------------------------
    Imports a MARC record, and presents it for edition.
 
Returns:
    A MarcRecord object.
*/
 
MarcDataBase.prototype.importRecord = function(isoRecord) {
    console.assert(isoRecord != "");
    // parse ISO-2709 record (creates a new MarcRecord object)
    alert(isoRecord);
    return;
    var r1 = MarcDataBase.parseISO(isoRecord);
    var r2 = r1.modifyImportedFields();
    r2.initialState = "*";
    // TO-DO: update toolbar elements
    r2.display();
    // show pre-AACR2 message, if applicable
    if ( r1.isPreAACR2() ) {
        Interface.showMessage("preAACR2");
    }
    return r2;
};
 
/*
---------------------------------------
Method: getNewRecords
---------------------------------------
    Obtains a list of the most recent records added to the database.
 
Arguments:
    n - number of records to get
 
Returns:
    A MarcRecordList object.
 
Notes:
    Makes an Ajax request to the server.
*/
 
MarcDataBase.prototype.getNewRecords = function() {
/*
    * make request
    * callback: display list
*/
};
 
 
/*
---------------------------------------
Method: deleteRecord
---------------------------------------
    Delete a MARC record.
 
Arguments:
    recordID - a string with the ID of the record to delete
 
Returns:
 
Notes:
    Makes an Ajax request to the server.
*/
 
MarcDataBase.prototype.deleteRecord = function(recordID) {
/*
    * ask confirmation
    * show message
    * send request
    * callback: inform result and close message
*/
};
 
/*
---------------------------------------
Method: getNumberOfRecords
---------------------------------------
    Informs the total number of active records in the database.
 
Returns:
    A number.
 
Notes:
    Makes an Ajax request to the server.
*/
 
MarcDataBase.prototype.getNumberOfRecords = function() {
/*
    * make request
    * callback: update display, apply "One-Second Spotlight" (http://ajaxpatterns.org/One-Second_Spotlight)
*/
};
 
/*
---------------------------------------
Method: getUsers
---------------------------------------
    Informs which users have access to this database.
 
Returns:
    An array.
 
Notes:
    Makes an Ajax request to the server.
*/
 
MarcDataBase.prototype.getUsers = function() {
/*
    * make request
    * callback: update display?
*/
};
 
 
/*
---------------------------------------
Class: MarcRecordList
---------------------------------------
Notes:
    Used e.g. for storing the result set of a search, or the list of newest records.
    Alternative names: MarcRecordSet, MarcRecordCollection
    Do we need a custom object, or is an array enough?
*/
 
function MarcRecordList() {
};
 
 
/*
---------------------------------------
Class: MarcRecord
---------------------------------------
    Constructor of MARC records.
 
Arguments:
    fields - array with record's data fields.
 
Notes:
    A record also has a leader, control fields, and some 'special' local data fields.
*/
 
function MarcRecord(fields,database) {
    this.dataFields = [];
    this.database = database;
 
    // NOTE: should every record have an 'id' property?
 
    // load the fields (if array 'fields' is not empty)
    // NOTE: this could be a new method 'addFieldList'
    for (var i = 0; i < fields.length; i++) {
        this.addDataField(fields[i]);
    }
};
 
/*
---------------------------------------
Method: addDataField
---------------------------------------
    Adds a data field to the record.
 
Arguments:
    field - object with information about the field to be created (tag, indicators, subfields)
 
Returns:
    A MarcDataField object.
*/
 
MarcRecord.prototype.addDataField = function(field) {
    var newField = new MarcDataField(field);
    newField.record = this; // so that the field knows which record it belongs to
    this.dataFields.push(newField); // add the new field to the record's dataFields array
 
    return newField;
};
 
/*
---------------------------------------
Method: removeDataField
---------------------------------------
    Removes a data field from the record.
 
Arguments:
    field - the MarcDataField object to be removed.
 
Returns:
    The MarcDataField object that was removed.
 
Note:
    In the original version, a reference to the next field is returned (to give it focus).
*/
 
MarcRecord.prototype.removeDataField = function(field) {
    // remove the associated DOM node, if any
    if (field.DOMnode) {
        field.DOMnode.parentNode.removeChild(field.DOMnode);
    }
 
    // remove field object from array
    this.dataFields.remove(field);
 
    return field;
};
 
/*
---------------------------------------    
Method: display
---------------------------------------    
    Renders the MARC record in the edition form.
*/
 
MarcRecord.prototype.display = function() {
    this.DOMnode = $("theRecordTbody");
 
    // TO-DO: display leader, control fields, holdings, etc.
 
    // display every data field <= displayDataFields()
    var dataFields = this.dataFields;
    for (var i = 0; i < dataFields.length; i++) {
        dataFields[i].display();
    }
 
    /*
        Interface.showEditDiv();
        Status.selectedField = Status.selectedSubfield = null;
        this.leader.display();
        this.displayDataFields();
        this.displayControlFields();
        // ejemplares & post-it note: update buttons
        Interface.refreshTitleBar();
        this.firstSubfield.focus(); // or 245$a   <= use setTimeout
    */
};
 
/*
---------------------------------------    
Method: hasTag
---------------------------------------    
    Informs whether a certain field tag is present in the record.
 
Arguments:
    tag - a string (or a number?)
 
Returns:
    A boolean value.
*/
 
MarcRecord.prototype.hasTag = function(tag) {
    var answer = false;
    for (var i = 0; i < this.dataFields.length; i++) {
        if (this.dataFields[i].tag == tag) {
            answer = true;
            break;
        }
    }
    return answer;
};
 
/*
---------------------------------------    
Method: canBeDuplicated
---------------------------------------    
    Informs whether the record can be duplicated.
*/
 
MarcRecord.prototype.canBeDuplicated = function() {
    return ( $("marcEditForm").f001.value != "" );
};
 
/*
The following 4 record-level functions share many similar functionalities:
    - createRecord()
    - duplicateRecord()
    - showRecordInForm()
    - importRecord()
 
These shared functionalities are:
 
    - execute showEditDiv() => display
        - createRecord:     YES
        - duplicateRecord:  YES
        - importRecord:     YES
        - showRecordInForm: YES
    - render or update data fields => display
        - createRecord:     renderDatafields
        - duplicateRecord:  none (but probably should)
        - importRecord:     renderDatafields
        - showRecordInForm: renderDatafields
    - initialize right panel data: => display
        recordID
            - createRecord:     "New"
            - duplicateRecord:  "New" 
            - importRecord:     "New"
            - showRecordInForm: receivedRecord.f001
        leader
            - createRecord:     renderLeader
            - duplicateRecord:  L_05 = "n"
            - importRecord:     renderLeader (from importedRecord.leader)
            - showRecordInForm: renderLeader
        control fields
            - createRecord:     "[pendiente]" and many empty values
            - duplicateRecord:  "[duplicado]" and many empty values
            - importRecord:     "[pendiente]", some empty, other from importedRecord,
                                renderField008
            - showRecordInForm: all from receivedRecord, renderField008
        post-it notes
            - createRecord:     empty string
            - duplicateRecord:  empty string
            - importRecord:     empty string
            - showRecordInForm: receivedRecord.postItNote
        ejemplares
            - createRecord:     empty array
            - duplicateRecord:  empty array
            - importRecord:     empty array
            - showRecordInForm: receivedRecord.ejemplares
    - enable/disable buttons and toolbar elements
        - createRecord:     enable grabar, disable borrar, hide result-navigator,
                            color post-it, color ejemplares
        - duplicateRecord:  enable grabar, disable borrar, hide result-navigator,
                            color post-it, color ejemplares
        - importRecord:     enable grabar, disable borrar, hide result-navigator,
                            color ejemplares, color post-it
        - showRecordInForm: enable/disable grabar, enable/disable borrar,
                            show result-navigator, color ejemplares, color post-it
    - set global variables:
        originalRecord
            - createRecord:     serializeRecord()
            - duplicateRecord:  "*"
            - importRecord:     "*"
            - showRecordInForm: serializeRecord()
        selectedField => display
            - createRecord:     null
            - duplicateRecord:  ??
            - importRecord:     null
            - showRecordInForm: null
        selectedSubfield => display
            - createRecord:     null
            - duplicateRecord:  ??
            - importRecord:     null
            - showRecordInForm: null
    - execute refreshTitleBar() => display
        - createRecord:     YES
        - duplicateRecord:  NO (unless title is changed)
        - importRecord:     YES
        - showRecordInForm: YES
    - set focus => display
        - createRecord:     245$a
        - duplicateRecord:  1st subfield of 1st datafield
        - importRecord:     1st subfield of 1st datafield
        - showRecordInForm: 245$a
    - show/hide some relevant message
        - createRecord:     NO
        - duplicateRecord:  show "registroDuplicado"
        - importRecord:     pre-AACR2 warning, if applicable
        - showRecordInForm: remove cartel
 
Most of this shared code should be written only once.
 
Something like this:
 
    For each different task:
        - modify the corresponding MarcRecord object
        - invoke the record object's display() method
        - initialize originalRecord (property initialState of the record object)
        - change state of relevant toolbar elements
        - hide or show relevant message
*/
 
 
/*
---------------------------------------
Class: MarcLeader
---------------------------------------
*/
 
function MarcLeader () {
};
 
MarcLeader.display = function () {
    // display whole table
};
 
MarcLeader.prototype.serialize = function() {
};
 
/*
---------------------------------------
Class: MarcField008
---------------------------------------
*/
 
function MarcField008 (type) {
    this.type = type;
};
 
MarcField008.display = function() {
    // display whole table
};
 
MarcField008.prototype.serialize = function() {
    // return 40 char string
    // alternative name: getValue?
};
 
 
/*
---------------------------------------
Class: MarcCodedDataElement
---------------------------------------
*/
 
function MarcCodedDataElement (name) {
    this.name = name;
};
 
MarcCodedDataElement.prototype.updateDisplay = function() {
};
 
MarcCodedDataElement.prototype.getValue = function() {
};
 
 
/*
---------------------------------------
Class: MarcDataField
---------------------------------------
    Constructor of data fields.
 
Arguments:
    field - object with data about the field to be created (tag, indicators, subfields)
 
Example:
    > f = new MarcDataField (
    >     {
    >         tag : '100',
    >         indicators : ['0','1'],
    >         subfields : [
    >             { code : 'a', value : 'Williams, Robert,' },
    >             { code : 'd', value : '1954-' }
    >         ]
    >     }
    > );
 
Notes:
    Maybe the argument shouldn't be an object...
*/
 
function MarcDataField (field) {
    this.tag = field.tag; // string
    this.indicators = new MarcIndicators(field.indicators || []);
    this.indicators.parentField = this;
    //this.indicators = field.indicators || []; // array
    this.subfields = [];   // ídem
 
    // load the subfields
    // NOTE: this could be a new method 'addSubfieldList'
    var subfields = field.subfields;
    for (var i = 0; i < subfields.length; i++) {
        this.addSubfield(subfields[i]);
    }
};
 
// -----------------------------------------------------
// Methods common to all instances of MarcDataField
// -----------------------------------------------------
 
/*
---------------------------------------    
Method: addSubfield
---------------------------------------    
    Adds a subfield to the data field.
 
Arguments:
    subfield - object with information about the subfield to be created (code, value).
 
Returns:
    A MarcSubfield object.
 
Notes:
    The position where the subfield will be inserted?
*/
 
MarcDataField.prototype.addSubfield = function(subfield) {
    // Invokes the MarcSubfield constructor
    var newSubfield = new MarcSubfield(subfield);
    newSubfield.parentField = this; // so that the subfield knows which field it belongs to
 
    // updates the list of subfields (¿in which position?)
    this.subfields.push(newSubfield);
 
    return newSubfield;
};
 
/*
---------------------------------------    
Method: addSubfieldList
---------------------------------------    
    Adds a list of subfields to the data field.
 
Arguments:
    codes - array of subfield codes
*/
 
MarcDataField.prototype.addSubfieldList = function(codes) {
    //assert(codes.length != 0);
    var tag = this.tag;
    var path = "marc21_bibliographic/datafield[@tag='" + tag + "']";
    var xmlDatafield = crossBrowserNodeSelector(xmlMARC21,path);
 
    // remove invalid codes from the array
    // ATENCION: falta verificar la *repetibilidad* de subcampos (??)
    // NOTE: move this validation code into a new method (of whom?)
    var validCodes = [];
    for (var i = 0; i < codes.length; i++) {
        try {
            var repetible = xmlDatafield.selectNodes("subfield[@code = '" + codes[i] + "']/@repet")[0].value;
        }
        catch (err) {
            alert("No está definido el subcampo $" + codes[i] + " para el campo " + tag + ".\n\nSi considera que se trata de un error, por favor escriba a catalis@googlegroups.com");
            continue;
        }
 
        if ( "NR" == repetible && isSubfieldPresent(field,codes[i]) ) {
            alert("El subcampo $" + codes[i] + " no es repetible.");
        } else {
            validCodes.push(codes[i]);
        }
    }
 
    // nothing to do if all codes are invalid
    if ( validCodes.length == 0 ) {
        return;
    }
 
    // for each valid code, add a new subfield
    for (var i = 0; i < validCodes.length; i++) {
        var label = xmlDatafield.selectNodes("subfield[@code = '" + validCodes[i] + "']/@label-" + LANG)[0].value;
 
        //findNewSubfieldPosition(field,codes[i]); return;
 
        //var newSubfield = createSubfield(validCodes[i], "", label, tag);
        var newSubfield = this.addSubfield(validCodes[i], "", label);
 
        if ( !newSubfield ) {
            // si el subcampo no se pudo crear (subcampos no repetibles)
            alert("ATENCION: El subcampo $" + validCodes[i] + " no pudo crearse.");
        } else {
            //displaySubfield(newSubfield,field);
            newSubfield.display();
            //highlightSubfieldBox(childSubfieldBox(newSubfield)); // para actualizar el nodo tomado como referencia; no funciona bien usando childSubfieldBox(newSubfield).focus()
            newSubfield.highlight();
 
            // info needed to put focus later
            if ( 0 == i ) {
                var firstSubfield = newSubfield;
            }
            if ( validCodes.length - 1 == i ) {
                var lastSubfield = newSubfield;
            }
        }
    }
 
    // where should the focus go?
    if ( 1 == validCodes.length && "4" == validCodes[0] ) {
        firstSubfield.box.click();
    } else {
        // El foco, al primero de los subcampos creados.
        // ATENCION: dará error si no se pudo crear algún subcampo.
        lastSubfield.box.focus();        // para forzar un scroll vertical
        firstSubfield.box.focus();
        // TO-DO: si el campo creado queda como el último visible en el formulario,
        // pero debajo del él hay aún más campos, forzar un poco de scroll para que
        // no se vea como último.
    }
};
 
/*
---------------------------------------    
Method: removeSubfield
---------------------------------------    
    Removes a subfield from its data field.
 
Arguments:
    subfield - the MarcSubfield object to remove.
 
Returns:
    The MarcSubfield object that was removed.
 
Notes:
    When the removed subfield is the only one remaining in the field, the whole field is removed.
    Should it be a method of MarcSubfield? No, by analogy with Node.removeChild().    
*/
 
MarcDataField.prototype.removeSubfield = function(subfield) {
 
    var subfieldCount = this.subfields.length;
 
    if ( subfieldCount > 1 ) {
        // remove the associated DOM node, if there is one
        var DOMnode = subfield.DOMnode;
        if (DOMnode) {
            DOMnode.parentNode.removeChild(DOMnode);
        }
 
        // remove subfield object from subfields array
        this.subfields.remove(subfield);
 
        // side effects of removal
        this.updatePunctuation();
        if ( "245" == this.tag ) {
            refreshTitleBar();
        }
    }
    else if ( this.canBeRemoved() ) {
        this.record.removeDataField(this);
    }
 
    return subfield;
};
 
/*
---------------------------------------    
Method: getIndicators
---------------------------------------    
    Obtains the indicators of the data field.
 
Returns:
    A string.
*/
 
MarcDataField.prototype.getIndicators = function() {
    return this.indicators.getValue();
};
 
/*
---------------------------------------    
Method: getSubfields
---------------------------------------    
    Gets the subfields of a data field.
 
Returns:
    A string.
 
Notes:
    TO-DO: Allow to exclude/include empty subfields; see old getSubfields(field, array, empty)
    If instead of a string you want an array, then use the `subfields` property.
 
Example:
*/
 
MarcDataField.prototype.getSubfields = function() {
    var value = "";
    for (var i = 0; i < this.subfields.length; i++) {
        value += Config.SUBFIELD_DELIMITER + this.subfields[i].code + this.subfields[i].getValue();
    }
    return value;
};
 
/*
---------------------------------------    
Method: getValue
---------------------------------------    
    Obtiene el valor (o contenido) de un campo de datos (indicadores + subcampos).
 
Returns:
    Un string con el valor del campo.
*/
 
MarcDataField.prototype.getValue = function() {
    var value = "";
    value += this.getIndicators();
    value += this.getSubfields();
    return value;
};
 
/*
---------------------------------------
Method: canMoveUp
---------------------------------------
    Finds whether the field can be moved one position before its current position.
 
Returns:
    A boolean value.
 
Notes:
    previousSibling is for DOM elements; it must be replaced by another method.
*/
 
MarcDataField.prototype.canMoveUp = function() {
    var answer;
    var tag = this.tag;
    var fieldBlock = MarcRecord.getFieldBlockName(tag);
 
    // the 1st field, and those that are non-repeatable, can't move up
    if ( indexOfField(this,fieldBlock) == 0 || MARC_TAGS_NR.test(tag) ) {
        answer = false;
    }  else if ( /[4567]../.test(tag) )  {
        // blocks 4xx, 5xx, 6xx, 7xx: every field can move up, except when it's the first in the block
        answer = ( this.previousSibling.tag.substr(0,1) == tag.substr(0,1) );
    } else {
        // and for the rest, move up only if previous field has same tag
        answer = ( this.previousSibling.tag == tag );
    }
 
    return answer;
};
 
/*
---------------------------------------
Method: canMoveDown
---------------------------------------
    Finds whether the field can be moved one position after its current position.
 
Returns:
    A boolean value.
*/
 
MarcDataField.prototype.canMoveDown = function() {
    var answer;
    var tag = this.tag;
    var fieldBlock = MarcRecord.getFieldBlockName(tag);
    var lastIndex = getDatafields(fieldBlock).length - 1;
 
    // the last field, and those that are non-repeatable, can't move down
    if ( indexOfField(this,fieldBlock) == lastIndex || MARC_TAGS_NR.test(tag) ) {
        answer = false;
    } else if ( /[4567]../.test(tag) )  {
        // blocks 4xx, 5xx, 6xx, 7xx: every field can move down, except when it's the last in the block
        answer = ( this.nextSibling.tag.substr(0,1) == tag.substr(0,1) );
    } else {
        // and for the rest, move down only if next field has same tag
        answer = ( this.nextSibling.tag == tag );
    }
    return answer;
};
 
/*
---------------------------------------    
Method: move
---------------------------------------    
    Moves the data field one position before (up) or after (down) its current position.
 
Arguments:
    dir - direction of movement: 'up' or 'down'
 
Returns:
*/
 
MarcDataField.prototype.move = function(dir) {
    // NOTE: swapNode is Microsoft (IE) only
    var mField;
    switch ( dir ) {
        case "up" :
            mField = field.swapNode(field.previousSibling);
            break;
        case "down" :
            mField = field.swapNode(field.nextSibling);
            break;
        default :
            alert("Error (move): dir=" + dir);
            break;
    }
 
    return mField;
};
 
 
/*
---------------------------------------
Method: canBeDuplicated
---------------------------------------
    Finds whether another occurrence of the field can be created.
Returns:
    A boolean value.
*/
 
MarcDataField.prototype.canBeDuplicated = function() {
    return !MARC_TAGS_NR.test(this.tag);
};
 
/*
---------------------------------------    
Method: canBeRemoved
---------------------------------------    
    Finds whether the field can be removed.
 
Returns:
    A boolean value.
*/
 
MarcDataField.prototype.canBeRemoved = function() {
    var tag = this.tag;
    var oblig;
    try {
        oblig = xmlMARC21.selectNodes("/" + "/datafield[@tag='" + tag + "']/@oblig")[0].value;
    }
    catch(err) {
        oblig = "";
    }
 
    return ( "ALL" != oblig );
};
 
/*
---------------------------------------    
Method: getIndex
---------------------------------------    
    Gets the field's index within its fieldblock.
 
Returns:
    A number >= 0, or -1 if the field is not found in the block.
*/
 
MarcDataField.prototype.getIndex = function() {
    var index = -1;
    var dataFields = getDatafields(fieldBlock);
    for (var i = 0; i < dataFields.length; i++) {
        if ( dataFields[i] == this ) {
            index = i;
            break;
        }
    }
    return index;
};
 
/*
---------------------------------------    
Method: updatePunctuation
---------------------------------------    
    Updates the punctuation of a data field.
 
Notes:
    This is a *long* method. Can we make it shorter?
    TO-DO: add more fields (e.g. notes).
    Some sources consulted:
      > <http://tpot.ucsd.edu/Cataloging/Bib_records/punct.html>
      > <http://www.slc.bc.ca/cheats/marcpunc.htm>
      > <http://www.itcompany.com/inforetriever/punctuation.htm>
      > <http://ublib.buffalo.edu/libraries/units/cts/about/FINALPUNCTUATION.HTML>
*/
 
MarcDataField.prototype.updatePunctuation = function() {
 
    // Como primer paso, eliminamos la puntuación presente (interna y final),
    // usando una regexp adecuada para cada campo.
 
    // ATENCION: usar el "g" de global (al menos en los casos de subcampos repetibles)
    // ATENCION: para la puntuación final de los campos no debe tenerse en cuenta
    // a los subcampos numéricos.
 
    var sf = this.getSubfields();
 
    switch (this.tag) {
        // ---------------------------------------------------------
        case "020" :
        // ---------------------------------------------------------
            var re_clean = / :(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^c/, " :^c");
            break;
 
        // ---------------------------------------------------------
        case "240" :
        // ---------------------------------------------------------
            var re_clean = / \.(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^l/g, " .^l");
            break;
 
        // ---------------------------------------------------------
        case "245" :
        // ---------------------------------------------------------
            var re_clean = /( *[:\/]|,| \.)(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/([^;=])\^b/, "$1 :^b");
            sf = sf.replace(/ *([;=])\^b/, " $1^b"); // corrijo espacio
            sf = sf.replace(/\^c/, " /^c");
            sf = sf.replace(/\^n/g, " .^n");
            var aux = (sf.search(/\^n/) != -1) ? "," : " .";
            sf = sf.replace(/\^p/g, aux + "^p");
            var re_end = /([^\.])$/;
            sf = sf.replace(re_end, "$1 .");
            break;
 
        // ---------------------------------------------------------
        case "250" :
        // ---------------------------------------------------------
            var re_clean = /( *\/| \.)(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/([^=,])\^b/, "$1 /^b");
            var re_end = /([^\.])$/;
            sf = sf.replace(re_end, "$1 .");
            // Corrección:
            //sf = sf.replace(/ ed\.?/g, " ed.");
            break;
 
        // ---------------------------------------------------------
        case "254" :
        // ---------------------------------------------------------
            var re_clean = / \.(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/$/, " .");
            break;
 
        // ---------------------------------------------------------
        case "260" :
        // ---------------------------------------------------------
            // ATENCION: paréntesis al final de $a o $b no deben eliminarse!
            var regex = "("
                      + " *[:;]"  // zero or more spaces followed by a colon or semicolon,
                      + "|[,\)]"  // or a comma or right parenthesis,
                      + "| \."    // or a space followed by a period
                      + ")"
                      + "(?="     // lookahead
                      + "\\^"     // for subfield delimiter
                      + "|$)";    // or end of string
            var re_clean1 = new RegExp(regex,"g");   // Original: /( *[:;]|[,\)]| \.)(?=\^|$)/g;
            sf = sf.replace(re_clean1, "");
            var re_clean2 = /(\^[efg])\(/;
            sf = sf.replace(re_clean2,"$1");
            sf = sf.replace(/\^a/g, " ;^a");
            sf = sf.replace(/\^b/g, " :^b");
            sf = sf.replace(/\^c/, ",^c");
            sf = sf.replace(/\^e/, "^e(");
            var aux = (sf.search(/\^e/) != -1) ? " :^f" : "^f(";
            sf = sf.replace(/\^f/,aux);
            var aux = (sf.search(/\^[ef]/) != -1) ? ",^g" : "^g(";
            sf = sf.replace(/\^g/,aux);
            if (sf.search(/\^[efg]/) != -1) {
                sf = sf + ")";
            }
            var re_end = /([^\.\)\?\]\-])$/;
            sf = sf.replace(re_end,"$1 .");
            break;
 
        // ---------------------------------------------------------
        case "300" :
        // ATENCION: Ends with a period if there is a 4XX in the record; otherwise it ends
        // with a period unless another mark of punctuation or a closing parentheses is present.
        // TO-DO: punto final en abreviaturas, como "p." ?
        // ---------------------------------------------------------
            var re_clean = /( *[:;+]|,| \.)(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^b/, " :^b");
            sf = sf.replace(/\^c/, " ;^c");
            sf = sf.replace(/\^e/, " +^e");
            sf = sf.replace(/\^c(\d+) ?cm\.?/,"^c$1 cm.");
 
            var re_end = /([^\.\)\?\]\-])$/;
            sf = sf.replace(re_end, "$1 .");
            break;
 
        // ---------------------------------------------------------
        case "310" :
        case "321" :
        // ---------------------------------------------------------
            var re_clean = /,(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^b/, ",^b");
            break;
 
        // ---------------------------------------------------------
        case "440" :
        // ---------------------------------------------------------
            var re_clean = /( ;|,| \.)(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^n/, " .^n");
            var aux = (sf.search(/\^n/) != -1) ? "," : " .";
            sf = sf.replace(/\^p/, aux + "^p");
            sf = sf.replace(/\^v/, " ;^v");
            sf = sf.replace(/\^x/, ",^x");
            break;
 
        // ---------------------------------------------------------
        case "490" :
        // ---------------------------------------------------------
            var re_clean = /( ;|,)(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^v/g, " ;^v");
            sf = sf.replace(/\^x/, ",^x");
            break;
 
        // ---------------------------------------------------------
        case "510" :
        // ---------------------------------------------------------
            var re_clean = /,(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^c/g, ",^c");
            break;
 
        // ---------------------------------------------------------
        case "100" :
        case "700" :
        // ---------------------------------------------------------
            // Los $4 se deben mantener al margen de la puntuación
            // TO-DO: ¿podemos reescribirlo mejor?
            // Este bloque nos sirve para otros campos que usan $4. ¿Lo convertimos
            // en una function?
            //var re_relatorcode = /^(\^?[^4]*)\^4(.*)$/;
            var re_relatorcode = /^(.*?)\^4(.*)$/;   // ".*?" => non-greedy
            var relatorCodeExists = re_relatorcode.exec(sf);
            if ( relatorCodeExists ) {
                var sf1 = RegExp.$1;
                var sf2 = RegExp.$2;
                //alert(sf1 + "\n" + sf2);
            } else {
                var sf1 = sf;
            }
 
            var re_clean = /(,| \.)(?=\^|$)/g;
            sf1 = sf1.replace(re_clean, "");
            sf1 = sf1.replace(/\^c/, ",^c");
            sf1 = sf1.replace(/\^d/, ",^d");
            sf1 = sf1.replace(/\^e/, ",^e");
            var re_end = /([^\.\-\?\)])$/;
            sf1 = sf1.replace(re_end, "$1 .");
 
            sf = ( relatorCodeExists ) ? (sf1 + "^4" + sf2) : sf1;
            break;
 
        // ---------------------------------------------------------
        case "600" : //ATENCION: arreglar problema con 600$2 
        // ---------------------------------------------------------
            var re_clean = /(,| \.)(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^c/, ",^c");
            sf = sf.replace(/\^d/, ",^d");
            var re_end = /([^\.\-\?\)])$/;
            sf = sf.replace(re_end, "$1 .");
            break;
 
        // ---------------------------------------------------------
        case "110" :
        case "610" : //ATENCION: arreglar problema con 610$2 
        case "710" :
        // ---------------------------------------------------------
            var re_clean = / \.(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^b/, " .^b");
            var re_end = /([^\.\-\?\)])$/;
            sf = sf.replace(re_end, "$1 .");
            break;
            // ATENCION: revisar este caso: «^axxx. .^byyy»
 
        // ---------------------------------------------------------
        case "111" :
        case "611" : //ATENCION: arreglar problema con 611$2 
        case "711" :
        // ---------------------------------------------------------
            var re_clean1 = /( :|,|\)|\)? \.)(?=\^|$)/g;
            sf = sf.replace(re_clean1, "");
            var re_clean2 = /(\^[ndc])\(/
            sf = sf.replace(re_clean2, "$1");
 
            sf = sf.replace(/\^q/, " .^q");
            sf = sf.replace(/\^n/, "^n(");
            var aux = (sf.search(/\^n/) != -1) ? " :^d" : "^d(";
            sf = sf.replace(/\^d/,aux);
            var aux = (sf.search(/\^[nd]/) != -1) ? " :^c" : "^c(";
            sf = sf.replace(/\^c/,aux);
            if (sf.search(/\^[ndc]/) != -1) {
                sf = sf + ")";
            }
 
            var re_end = /([^\.\-\?\]\)])$/;
            sf = sf.replace(re_end, "$1 .");
            break;
 
        // ---------------------------------------------------------
        case "773" :  // ATENCION: parece necesario refinar un poco más estas reglas.
        // ---------------------------------------------------------
            // Los $7 se deben mantener al margen de la puntuación. Cf. 100/700 $4
            var re_subfield7 = /^(.*?)\^7(.*)$/;
            var subfield7Exists = re_subfield7.exec(sf);
            if ( subfield7Exists ) {
                var sf1 = RegExp.$1;
                var sf2 = RegExp.$2;
            } else {
                var sf1 = sf;
            }
 
            var re_clean = / \.(?=\^|$)/g;
            sf1 = sf1.replace(re_clean, "");
            sf1 = sf1.replace(/([^\.])\^t/, "$1 .^t");
            sf1 = sf1.replace(/([^\.])\^d/, "$1 .^d");
            sf1 = sf1.replace(/([^\.])\^b/, "$1 .^b");
            sf1 = sf1.replace(/([^\.])\^x/, "$1 .^x");
            sf1 = sf1.replace(/([^\.])\^z/, "$1 .^z");
            sf1 = sf1.replace(/([^\.])\^g/, "$1 .^g");
            var re_end = /([^\.\-\?])$/
            sf1 = sf1.replace(re_end,"$1 .");
 
            sf = ( subfield7Exists ) ? (sf1 + "^7" + sf2) : sf1;
            break;
 
        // ---------------------------------------------------------
        case "830" :
        // ---------------------------------------------------------
            var re_clean = /( ;|,| \.)(?=\^|$)/g;
            sf = sf.replace(re_clean, "");
            sf = sf.replace(/\^n/, " .^n");
            var aux = (sf.search(/\^n/) != -1) ? "," : " .";
            sf = sf.replace(/\^p/, aux + "^p");
            sf = sf.replace(/\^v/, " ;^v");
            break;
 
        default :
            // ---------------------------------------------------------
            // Algunas notas llevan punto final.
            // TO-DO: Agregar más campos de notas.
            // ---------------------------------------------------------
            if ( tag.search(/50[0124]/) != -1 ) {
                var re_end = /([^\.\-\?])(?=\^5|$)/;
                sf = sf.replace(re_end,"$1.");
            }
            break;
    }
 
    // Eliminamos "puntos dobles" que puedan quedar al final de un subcampo
    // ATENCION: ¿deberíamos limitar esto a ciertos subcampos (110, 710, etc.)?
    sf = sf.replace(/\. \.(?=\^|$)/g, ".");
 
    // Limpiamos cualquier puntuación que pudiera haber sido colocada al inicio
    // (i.e. antes del primer subcampo)
    sf = sf.replace(/^[^\^]+\^/, "^");
 
    // Et c'est fini :-)
    this.updateDisplay(sf);
};
 
 
/*
---------------------------------------    
Method: updateDisplay
---------------------------------------
    Redisplays the field's content, after a punctuation update.
 
Arguments:
    subfields - a string containing the subfields.
 
Returns:
*/
 
MarcDataField.prototype.updateDisplay = function(subfields) {
    // Actualizamos solamente los boxes no vacíos.
    // ATENCION: ¿qué se entiende por "vacío"?
    var values = subfields.substr(2).split(/\^\w/);
    // ATENCION: usamos substr(2) porque de lo contrario en Mozilla values[0] es vacío
    var rows = this.DOMnode.getElementsByTagName("TR");
    var j = -1;
    for (var i = 0; i < rows.length; i++) {
        var box = rows[i].box;
        if ( box.value.search(REGEX_EMPTY_SUBFIELD) == -1 ) {
            j = j + 1;
            box.value = values[j];
        }
    }
};
 
/*
---------------------------------------    
Method: hasSubfield
---------------------------------------    
    Informs whether the field contains a subfield with a given code.
 
Arguments:
    code - the subfield code.
*/
MarcDataField.prototype.hasSubfield = function(code) {
    var subfields = this.subfields;
    var answer = false;
    for (var i = 0; i < subfields.length; i++) {
        if ( subfields[i].code == code ) {
            answer = true;
            break;
        }
    }
    return answer;
};
 
/*
---------------------------------------    
Method: display
---------------------------------------    
    Creates a DOM object to represent visually the data field.
 
Returns:
    The DOM object that was created.
*/
 
MarcDataField.prototype.display = function() {
 
    // A data field is represented by a Table Row (TR)
    var row = document.createElement('TR');
    row.dataField = this;  // allows access from the form to the associated MarcDataField object
    this.DOMnode = row;  // the field must know which DOM node represents it
 
 
    // the field tag goes in this cell
    var cell = document.createElement('TD');
    cell.className = "tag";
    cell.innerHTML = this.tag;
    cell.onclick = function() {
        if (confirm("¿Elimina el campo " + this.innerHTML + "?")) {
            var field = this.parentNode.dataField;
            field.record.removeDataField(field);
        }
    };
    row.appendChild(cell);
 
    // indicators go in this cell
    var cell = document.createElement('TD');
    cell.className = "indicators";
    // the contents of this cell is controlled by MarcIndicators.prototype.display
    row.appendChild(cell);
 
    // subfields go in this cell
    var cell = document.createElement('TD');
    cell.className = "subfields";
    row.appendChild(cell);
 
    // create the table that will hold the subfields
    var table = document.createElement('TABLE');
    table.cellSpacing = "0";
    cell.appendChild(table);
    var tbody = document.createElement('TBODY');
    table.appendChild(tbody);
 
    // display the new row
    // TO-DO: insert row into the correct position; see old displayField(newField,refNode)
    $("theRecordTbody").appendChild(row);
 
    // add the indicators
    this.indicators.display();
 
    // if subfields are present, add them to the DOM
    var subfields = this.subfields;
    for (var i = 0; i < subfields.length; i++) {
        subfields[i].display();
    }
 
    /*
    Method: nextFocus
        Finds the field that will have focus if this field is removed.
    Returns:
    Notes:
        Should be a method of MarcDataField.prototype?
        What should this method return?
    */
    row.nextFocus = function() {
        var next = this.nextSibling  ? this.nextSibling : this.previousSibling;
        return next;
    };
 
    return row;
};
 
 
// -----------------------------------------------------
// Métodos de la 'clase' MarcDataField
// -----------------------------------------------------
// none for now
 
 
/*
---------------------------------------    
Class: MarcSubfield
---------------------------------------    
    Constructor de subcampos.
 
Arguments:
    subfield - objeto con información sobre el subcampo a crear (code, value).
 
Example:
    > s = new MarcSubfield (
    >     {
    >         code : 'a',
    >         value : 'Williams, Robert,' 
    >     }
    > );
 
Notes:
    Perhaps the argument should not be an object, but two strings.
*/
 
function MarcSubfield (subfield) {
    this.code = subfield.code; // string
    this.value = subfield.value || ""; // string
};
 
// -------------------------------------------------------
// Methods common to all instances of MarcSubfield
// -------------------------------------------------------
 
/*
---------------------------------------    
Method: getValue
---------------------------------------    
    Gets the value of the subfield, taken from its associated form element.
 
Returns:
    A string.
 
Notes:
    What happens if there's no associated DOM object?
*/
 
MarcSubfield.prototype.getValue = function() {
    // delegate this to the associated DOM object
    if (this.DOMnode) {
        return this.DOMnode.getValue();
    }
};
 
/*
---------------------------------------    
Method: getIndex
---------------------------------------    
    Gets the subfield's index in the array of subfields.
 
Returns:
    A number >= 0, or -1 in case of error.
*/
 
MarcSubfield.prototype.getIndex = function() {
    var index = -1;    // -1 => the subfield is not in the array
    var subfields = this.parentField.subfields;
    for (var i = 0; i < subfields.length; i++) {
        if ( subfields[i] == this ) {
            index = i;
            break;
        }
    }
    return index;
};
 
/*
---------------------------------------    
Method: display
---------------------------------------    
    Creates a DOM object to visually represent the subfield.
 
Returns:
    The DOM object that was created.
 
Notes:
    This is the HTML structure we construct (a table row with 3 table cells):
 
    >    TR
    >     |
    >     +--TD [class: subfieldCode]
    >     |   |
    >     |   +--DIV -- subfield code
    >     |
    >     +--TD [class: subfieldLabel]
    >     |   |
    >     |   +--DIV
    >     |       |
    >     |       +--SPAN -- descriptive subfield label 
    >     |       |
    >     |       +--BUTTON -- button for checking URIs, ISBNs, ISSNs
    >     |
    >     +--TD [class: subfieldValue]
    >         |
    >         +--TEXTAREA -- the subfield contents
*/
 
MarcSubfield.prototype.display = function() {
    var row = document.createElement('TR');
    row.subfield = this;  // the DOM node must know which MarcSubfield object it is representing
    this.DOMnode = row;  // the MarcSubfield object must know who its associated DOM node is
 
    // --------------------------------------
    // the subfield code goes in this cell
    // --------------------------------------
    var cell = document.createElement('TD');
    cell.className = "subfieldCode";
    /*cell.onclick = function() {
        if (confirm("¿Elimina el subcampo " + this.innerHTML + "?")) {
            var subfield = this.parentNode.subfield;
            subfield.parentField.removeSubfield(subfield);
        }
    }*/
 
    var codeDiv = document.createElement("DIV");
    codeDiv.innerHTML = this.code;
    codeDiv.onclick = function() {
        //showSubfieldMenu(parentSubfield(this, "code"));
        return false;
    };
    codeDiv.oncontextmenu = codeDiv.onclick;
    try    {
        codeDiv.title = code + " : " + label;
    }
    catch(err) {}
    cell.appendChild(codeDiv);
 
    // cell is ready
    row.appendChild(cell);
 
    // --------------------------------------
    // a descriptive label goes in this cell
    // --------------------------------------
    var cell = document.createElement('TD');
    cell.className = "subfieldLabel";
    if ( !Config.DISPLAY_SUBFIELD_LABELS ) {
        cell.style.display = "none";
    }
 
    var label = document.createElement("div");
    label.className = "subfieldLabel";
    label.oncontextmenu = function() {
        showSubfieldMenu(parentSubfield(this, "label"));
        return false;
    };
 
    var span = document.createElement("SPAN");
    span.style.cursor = "hand";
    span.onmouseover = function() {
        this.style.textDecoration = "underline";
    };
    span.onmouseout = function() {
        this.style.textDecoration = "none";
    };
    span.onclick = function() {
        //marcHelpPopup(/*parentField(this.parentNode, "subfieldLabel").tag*/fieldTag, code);
    };
    var labelText = "xxxx";
    span.innerHTML = labelText.substr(0,30);
    label.appendChild(span);
 
    // add check buttons for certain special subfields
    var tag = this.parentField.tag;
    var code = this.code;
    if ( "URI" == labelText || "856u" == tag + code ) {
        var button = this.createCheckButton("URI");
        label.appendChild(button);
    }
    else if ( "ISBN" == labelText ||  "020a" == tag + code ) {
        var button = this.createCheckButton("ISBN");
        label.appendChild(button);
    }
    else if ( "ISSN" == labelText || "022a" == tag + code ) {
        var button = this.createCheckButton("ISSN");
        label.appendChild(button);
    }
 
    cell.appendChild(label);
 
    // cell is ready
    row.appendChild(cell);
 
    // --------------------------------------
    // the subfield value goes in this cell
    // --------------------------------------
    var cell = document.createElement('TD');
    cell.className = "subfieldValue";
    row.appendChild(cell);
    var box = this.createSubfieldBox();
    cell.appendChild(box);
    row.subfieldBox = box; // this property replaces the old function childSubfieldBox(subfield)
 
    /*
    Method: getValue
 
    Returns:
        The subfield value (stored in the textarea).
    */
    row.getValue = function() {
        //             TR   TD         TD          TD          TEXTAREA
        return value = this.firstChild.nextSibling.nextSibling.firstChild.value;
    };
 
    /*
    Method: getLabel
        Gets the subfield label.
    Returns:
        A string.
    */
    row.getLabel = function() {
        //     TR   TD         TD          TD         ??
        return this.firstChild.nextSibling.firstChild.firstChild.innerHTML;
    };
 
    /*
    Method: nextFocus
        Finds the subfield that will have focus after removal of this subfield.
    Returns:
    Notes:
        Should be a method of MarcSubfield.prototype?
        What should this method return?
    */
    row.nextFocus = function() {
        var next = this.nextSibling ? this.nextSibling.box : this.previousSibling.box;
        return next;
    };
 
    // finally add the new node to the DOM tree
    // ¿In which position should the TR be inserted? See old function displaySubfield(newSubfield, field)
    this.parentField.DOMnode.firstChild.nextSibling.nextSibling.firstChild.firstChild.appendChild(row);
    //               TR      TD         TD          TD          TABLE      TBODY
 
    return row;
};
 
/*
---------------------------------------
Method: createCheckButton
---------------------------------------
    Creates an HTML button used to validate the subfield's content (e.g. ISBN, URL).
 
Arguments:
    type - the type of data that must be checked (URI, ISBN, ISSN)
 
Returns:
    The Button object.
*/
 
MarcSubfield.prototype.createCheckButton = function(type) {
    var checkButton = document.createElement("BUTTON");
    checkButton.className = "checkButton";
    checkButton.tabIndex = -1;
    checkButton.innerHTML = "verificar";
 
    function checkURI() {
        var box = childSubfieldBox(this.parentNode.parentNode.parentNode);
        box.focus();
        if ( box.value != "" ) {
            var checkWin = window.open(box.value,"checkWin","");
            checkWin.focus();
        }
    };
 
    function checkISBN() { 
        var box = childSubfieldBox(this.parentNode.parentNode.parentNode);
        if ( box.value != "" ) {
            checkStandardNumber(box,"ISBN");
        } else if ( box.error ) {
            box.style.color = "black";
            box.style.backgroundColor = "";
            box.error = false;
        }
        box.focus();
    };
 
    function checkISSN() { 
        var box = childSubfieldBox(this.parentNode.parentNode.parentNode);
        if ( box.value != "" ) {
            checkStandardNumber(box,"ISSN");
        } else if ( box.error ) {
            box.style.color = "black";
            box.style.backgroundColor = "";
            box.error = false;
        }
        box.focus();
    };
 
    switch (type) {
        case "URI" :
            checkButton.onclick = checkURI;
            checkButton.title = "Verificar URL en una ventana auxiliar";
            break;
        case "ISBN" :
            checkButton.onclick = checkISBN;
            checkButton.title = "Verificar la validez del ISBN";
            break;
        case "ISSN" :
            checkButton.onclick = checkISSN;
            checkButton.title = "Verificar la validez del ISSN";
            break;
        default : 
    }
 
    return checkButton;
};
 
/*
---------------------------------------
Method: createSubfieldBox
---------------------------------------
    Creates a DOM object (TextArea) to hold the subfield's value.
 
Arguments:
    value - the subfield's initial value
 
Returns:
    The DOM object that was created.
 
Notes:
    ¿Should this method also assign the textarea's value?
*/
 
MarcSubfield.prototype.createSubfieldBox = function() {
    var subfieldBox = document.createElement('TEXTAREA');
    subfieldBox.parentSubfield = this;
    this.subfieldBox = subfieldBox;
    //subfieldBox.parentField = this.parentField;
 
    // assign initial value
    subfieldBox.value = this.value || "";
 
    // autoadjust textarea's height
    if (ie) {
        subfieldBox.style.height = subfieldBox.scrollHeight;
    }
 
    // TO-DO: Extra attributes for controlled subfields (make them readonly, etc)
 
    // attach event handlers
    subfieldBox.onfocus = function() { // this sets the context correctly
        this.parentSubfield.highlight();
    };
    if (ie) {
        subfieldBox.onkeydown = checkKey;
    } else if (moz) {
        subfieldBox.onkeypress = checkKey;
    }
    subfieldBox.onblur = MarcSubfield.subfieldBlur;
    subfieldBox.onchange = MarcSubfield.subfieldChange;
 
    return subfieldBox;
};
 
/*
---------------------------------------
Method: highlight
---------------------------------------
    Highlights the currently selected subfield.
*/
 
MarcSubfield.prototype.highlight = function() {
    // remove highlighting from old selected subfield
    if ( Status.selectedSubfield != null && !Status.selectedSubfield.error ) {
        Status.selectedSubfield.subfieldBox.removeClass("highlight");
    }
 
    // highlight new selected subfield
    if ( !this.error ) {
        this.subfieldBox.addClass("highlight");
    }
 
    // update status info
    Status.selectedSubfield = this;
    Status.selectedField = this.parentField;
};
 
/*
---------------------------------------    
Method: canBeRemoved
---------------------------------------    
    Finds whether the subfield can be removed.
 
Returns:
    A boolean value.
 
Notes:
    Criterio a usar: if (code es oblig y field contiene una sola occ de code) --> false
    TO-DO: contar ocurrencias presentes
    TO-DO: la obligatoriedad de un subcampo depende de la plantilla elegida
    (i.e., del material catalogado)
*/
 
MarcSubfield.prototype.canBeRemoved = function() {
    var tag = this.parentField.tag;
    var code = this.code;
    var oblig;
    try {
        var path = "//datafield[@tag='" + tag + "']/subfield[@code='" + code + "']/@oblig";
        if (ie)
            oblig = xmlMARC21.selectSingleNode(path).nodeValue;
        else if (moz)
            oblig = xmlMARC21.evaluate(path,xmlMARC21,null,2,null).stringValue;
    }
    catch(err) {
        var oblig = "";
    }
 
    return ( "ALL" != oblig );
};
 
/*
---------------------------------------
Method: canMoveUp
---------------------------------------
    Finds whether the subfield can be moved one position before its current position.
 
Returns:
    A boolean value.
 
Note:
    This very simple criterion does not take into account the subfield semantics.
*/
 
MarcSubfield.prototype.canMoveUp = function() {
    return ( this.getIndex() != 0 ); 
};
 
/*
---------------------------------------
Method: canMoveDown
---------------------------------------
    Finds whether the subfield can be moved one position after its current position.
Returns:
    A boolean value.
Note:
    This very simple criterion does not take into account the subfield semantics.
*/
 
MarcSubfield.prototype.canMoveDown = function() {
    var lastIndex = this.field.subfields.length - 1;
    return ( this.getIndex() != lastIndex );
};
 
/*
---------------------------------------
Method: move
---------------------------------------
    Moves the subfield one position before (up) or after (down) its current position.
 
Arguments:
    dir - direction of movement: 'up' or 'down'
 
Returns:
 
Notes:
    The code is shared with method MarcDataField.prototype.move.
*/
 
MarcSubfield.prototype.move = function(dir) {
    // NOTE: swapNode is Microsoft (IE) only
    var mSubfield;
    switch ( dir ) {
        case "up" :
            mSubfield = this.swapNode(this.previousSibling);
            break;
        case "down" :
            mSubfield = this.swapNode(this.nextSibling);
            break;
        default :
            alert("Error (move): dir=" + dir);
            break;
    }
 
    // Side effects of movement
    this.field.updatePunctuation(); // ATENCION: ¿testeamos primero si se trata de un campo para el que hay puntuación automática?
    if ( "245" == this.field.tag ) {
        refreshTitleBar();
    }
 
    return mSubfield; // check this!
};
 
/*
---------------------------------------
Method: canBeDuplicated
---------------------------------------
    Finds whether another occurrence of the subfield can be created.
 
Returns:
    A boolean value.
*/
 
MarcSubfield.prototype.canBeDuplicated = function() {
    var tag = this.parentField.tag;
    var code = this.code;
    var repeatable;
    try {
        repeatable = xmlMARC21.selectNodes("//datafield[@tag='" + tag + "']/subfield[@code='" + code + "']/@repet")[0].value;
    }
    catch(err) {
        repeatable = "NR";
    }
    return (repeatable == "R");
};
 
 
// -----------------------------------------------------
// Methods of the 'class' MarcSubfield
// -----------------------------------------------------
 
/*
---------------------------------------
Method: subfieldBlur
---------------------------------------
    Manejador del evento 'blur'. Modifica la clase del textarea.
*/
 
MarcSubfield.subfieldBlur = function() {
    this.className = "";
};
 
/*
---------------------------------------
Method: subfieldChange
---------------------------------------
    Manejador del evento 'change'.
*/
 
MarcSubfield.subfieldChange = function() {
 
    // Remove multiple and begin/end spaces
    // ATENCION: ¿es siempre correcto eliminar espacios iniciales? Véase e.g. 050$b en OCLC
    this.value = this.value.replace(/\s+/g," ").replace(/^\s/,"").replace(/\s$/,"");
 
    // Substitute: EM DASH -> double dash
    this.value = this.value.replace(/\u2014/g,"--");
 
    // Substitute: typographic double quotes (¿deberíamos advertir del cambio?)
    this.value = this.value.replace(/\u201C|\u201D/g,'"');
 
    // Substitute: typographic single quotes (¿deberíamos advertir del cambio?)
    this.value = this.value.replace(/\u2018|\u2019/g,"'");
 
    // Substitute: special characters in URIs (according to OCLC)
    if ( "URI" == labelText || "856u" == tag_code ) {
        this.value = this.value.replace(/_/g,"%5F").replace(/~/,"%7E");
    }
 
    // Corrección: raya sin espacios alrededor en una nota de contenido
    if ( "505" == fieldTag ) {
        this.value = this.value.replace(/(\S)--/g,"$1 --").replace(/--(\S)/g,"-- $1");
    }
 
    // Correction: remove space before a coma
    this.value = this.value.replace(/\s,/g,",");
 
    // Correction in 300 $a: 245 p --> 245 p.
    if ( "300a" == tag_code ) {
        this.value = this.value.replace(/(\d+)\s*([ph])$/,"$1 $2.");
    }
 
    // Correction in 300 $b: il.col. --> il. col.
    if ( "300b" == tag_code ) {
        this.value = this.value.replace(/^il(?=$| )/,"il.");
    }
 
    // TO-DO: luego de mostrar una advertencia como las que siguen, el foco debe
    // permanecer en el mismo textarea.
 
    // Warning: we can't (now) allow a '^' within a subfield
    if ( this.value.indexOf("^") != -1 ) {
        catalisMessage("ATENCION: por el momento no es posible almacenar el carácter '^' dentro de un subcampo.", true);
        return;
    }
 
    // Warning: ISBD punctuation within a subfield of field 260
    if ( tag_code.search(/260[ab]/) != -1 && this.value.search(/\S ([;:]) (.+$)/) != -1 ) {
        var msg = "ATENCION: no es correcto usar la puntuación «&nbsp;" + RegExp.$1 + "&nbsp;» dentro de este subcampo. Coloque «" + RegExp.$2 + "» en otro subcampo $" + code + ", o bien corrija la puntuación."        
        catalisMessage(msg, true);
        return;
    }
 
    // once the cleanup is finished, update punctuation of the whole field
    if ( Config.AUTOMATIC_PUNCTUATION && tag_code.search(Config.AUTO_PUNCT_TAGS) != -1 ) {
        this.parentSubfield.parentField.updatePunctuation();
    }
 
    // now some side effects, i.e. other parts of the user interface being updated
 
    // ----------------------------------
    // Campo 245
    // ----------------------------------
    if ( "245" == fieldTag ) {
        refreshTitleBar();
        if ( "a" == code ) {  // Ajuste del 2do indicador (REVISAR)
            //var nonfilchars =
        }
    }
 
    // ----------------------------------
    // Campo 260
    // ----------------------------------
    else if ( "260" == fieldTag ) {
        if ( "c" == this.code ) {
            // adjust date in 008
            var re = /^\[?c?(\d{4})(?=\]? ?[\.,]?$)/;   // REVISAR, especialmente para el caso "fecha1-fecha2" (código "m")
            var matchArray = re.exec(this.value);
            if ( matchArray != null ) {
                $("marcEditForm").f008_06.value = "s";
                $("marcEditForm").f008_07_10.value = RegExp.$1;
            }
        }
        else if ( "a" == this.code ) {
            // adjust country in 008
            // ATENCION: restringir a la PRIMERA ocurrencia del subcampo $a
            // TO-DO: enviar todo esto a una tabla externa
            var re = /^\[?(Buenos Aires|Bahía Blanca)\]?/;
            var result = re.exec(this.value);
            if ( result ) {
                $("marcEditForm").f008_15_17.value = "ag#";
                $("marcEditForm").f008_15_17.title = "Argentina";
            }
            var re = /^\[?New York\]?/;
            var result = re.exec(this.value);
            if ( result ) {
                $("marcEditForm").f008_15_17.value = "nyu";
                $("marcEditForm").f008_15_17.title = "New York (State)";
            }
            var re = /^\[?(London|Oxford)\]?/;
            var result = re.exec(this.value);
            if ( result ) {
                $("marcEditForm").f008_15_17.value = "enk";
                $("marcEditForm").f008_15_17.title = "England";
            }
            var re = /^\[?Paris\]?/;
            var result = re.exec(this.value);
            if ( result ) {
                $("marcEditForm").f008_15_17.value = "fr#";
                $("marcEditForm").f008_15_17.title = "Francia";
            }
            var re = /^\[?(Roma|Milano|Firenze)\]?/;
            var result = re.exec(this.value);
            if ( result ) {
                $("marcEditForm").f008_15_17.value = "it#";
                $("marcEditForm").f008_15_17.title = "Italia";
            }
            var re = /^\[?México\]?/;
            var result = re.exec(this.value);
            if ( result ) {
                $("marcEditForm").f008_15_17.value = "mx#";
                $("marcEditForm").f008_15_17.title = "México";
            }
            var re = /^\[?(Madrid|Barcelona|Sevilla)\]?/;
            var result = re.exec(this.value);
            if ( result ) {
                $("marcEditForm").f008_15_17.value = "sp#";
                $("marcEditForm").f008_15_17.title = "España";
            }
        } // end 260a
    } // end 260
 
    // ----------------------------------
    // Campo 300
    // ----------------------------------
    else if ( "300" == fieldTag ) {
        if ( "b" == this.code ) {
            // Ilustraciones: actualizamos f008_BK_18_21
            var re = /^il\. ?;?$/;
            var hasIllustrations = re.test(this.value);
            if ( hasIllustrations ) {
                $("marcEditForm").f008_BK_18_21.value = "a###";
            } else if ( "" == this.value ) {
                $("marcEditForm").f008_BK_18_21.value = "####";
            }
        }
    } // end 300
 
    // field 111: update f008_BK_29
    else if ( "111a" == tag_code ) {
        $("marcEditForm").f008_BK_29.value = ( this.value != "" ) ? "1" : "0";
    }
 
    // ----------------------------------
    // Campo 504
    // actualizamos f008_BK_24_27
    // ----------------------------------
    // TO-DO: hay que hacer también el ajuste cuando el campo 504 es *eliminado*
    // ATENCION: la correspondencia entre 504 y "b" no es exacta; cuando hay
    // discografías también se usa un 504, pero el código en el 008 es "k".
    else if ( "504" == fieldTag ) {
        var currentCodedValues = $("marcEditForm").f008_BK_24_27.value;
        if ( "" == this.value ) {
            // Campo 504 vacío => quitamos el código "b"
            // ATENCION: a veces, p.ej. si la obra es en sí misma una bibliografía,
            // el código "b" debe permanecer, aun cuando no exista un 504.
            // También hay que verificar que no haya *otro* campo 504 presente
            // en el registro!
            $("marcEditForm").f008_BK_24_27.value = currentCodedValues.replace(/b/,"") + "#";
        } else {
            // Si no hay un "b", colocamos uno en la primera posición no ocupada (24 o 25)
            // TO-DO: revisar para el caso en que haya otros códigos presentes
            if ( currentCodedValues.search(/b/) == -1 ) {
                $("marcEditForm").f008_BK_24_27.value = "b" + currentCodedValues.substr(0,3);
            }
        }
    } // end 504
 
    // ----------------------------------
    // Verificación de ISBN
    // ----------------------------------
    else if ( tag_code.search(/020a|7[6-8][0-9]z/) != -1 ) {
        if ( this.value != "" ) {
            if ( this.value.substr(0,10).indexOf("-") != -1 ) {
                catalisMessage("Ingrese el ISBN sin guiones.", true);
            } else {
                checkStandardNumber(this,"ISBN");
            }
        } else if ( this.error ) {
            this.style.color = "black";
            this.style.backgroundColor = "";
            this.error = false;
        }
    }
 
    // ----------------------------------
    // Verificación de ISSN
    // ----------------------------------
    else if ( tag_code.search(/022a|4[49]0x|7[6-8][0-9]x/) != -1 ) {
        if ( this.value != "" ) {
            checkStandardNumber(this,"ISSN");
        } else if ( this.error ) {
            this.style.color = "black";
            this.style.backgroundColor = "";
            this.error = false;
        }
    }
 
    // Idioma con mayúscula (para títulos uniformes)
    else if ( tag_code.search(/(100|110|111|130|240|243|600|610|611|630|700|710|711|830)l/) != -1 ) {
        this.value = this.value.substr(0,1).toUpperCase() + this.value.substr(1);
    }
};
 
 
/*
---------------------------------------
Class: MarcIndicators
---------------------------------------
    Indicators of a data field.
 
Arguments:
    A 2-element array, or 2 strings?
*/
 
function MarcIndicators(ind) {
    this[1] = ind[0];
    this[2] = ind[1];
};
 
/*
---------------------------------------
Method: getValue
---------------------------------------
    Finds the indicators value.
 
Notes:
    What if there's no DOM node?
*/
 
MarcIndicators.prototype.getValue = function() {
    if (this.DOMnode) {
        // delegate this task to the associated DOM object
        return this.DOMnode.getValue();
    }
};
 
/*
---------------------------------------
Method: display
---------------------------------------
    Generates the DOM representation for the indicators.
*/
 
MarcIndicators.prototype.display = function() {
    var div = document.createElement('DIV');
    this.DOMnode = div;
    div.parentField = this.parentField;
 
    div.innerHTML = this[1] + this[2];
    div.onclick = this.edit;
 
    /* Method: getValue */
    div.getValue = function() {
        return this.innerHTML;
    };
 
    //               TR      TD         TD
    this.parentField.DOMnode.firstChild.nextSibling.appendChild(div);
 
    return div;
};
 
/*
---------------------------------------
Method: edit
---------------------------------------
    Presents an interface for editing indicators.
*/
 
MarcIndicators.prototype.edit = function() {
    alert('Ud. quiere editar los indicadores');
};
 
/*
---------------------------------------
Method: updateDisplay
---------------------------------------
    Updates the displayed values, after an edit.
 
Arguments:
    ind - a string with the new indicator values
*/
 
MarcIndicators.prototype.updateDisplay = function(ind) {
    /*var indSpans = field.firstChild.nextSibling.firstChild.getElementsByTagName("span");
    indSpans[0].innerHTML = newValues.substr(0,1);
    indSpans[2].innerHTML = newValues.substr(1,1);*/
    this.DOMnode.innerHTML = ind;
};
 
 
// ----------------------------------------------------------------
// Extra Array methods (from Ajax in action, Dave Crane 2005)
// ----------------------------------------------------------------
 
/*
insert element at given position in the array, bumping all
subsequent members up one index
*/
Array.prototype.insertAt=function(index,obj){
  this.splice(index,0,obj);
};
 
/*
return index of element in the array
*/
Array.prototype.indexOf = function(obj){
  var result = -1;
  for (var i = 0; i < this.length; i++){
    if (this[i] == obj){
      result = i;
      break;
    }
  }
  return result;
};
 
/*
remove element at given index
*/
Array.prototype.removeAt = function(index){
  this.splice(index,1);
};
 
/*
remove object
*/
Array.prototype.remove = function(obj){
  var index = this.indexOf(obj);
  if (index >= 0){
    this.removeAt(index);
  }
};
 
 
/*
----------------------------------------------------------------
Functions addClass and removeClass taken from:
Filename:       jsUtilities Library
Author:         Aaron Gustafson (aaron at easy-designs dot net)
http://www.easy-designs.net/code/jsUtilities/working/jsUtilities.js
 
Check also how jQuery does it.
----------------------------------------------------------------
*/
 
// ---------------------------------------------------------------------
//                             addClass()
//                 appends the specified class to the object
// ---------------------------------------------------------------------
function addClass(theClass) {
  if (this.className != '') {
    this.className += ' ' + theClass;
  } else {
    this.className = theClass;
  }
};
 
Object.prototype.addClass = addClass;
// ---------------------------------------------------------------------
//                           removeClass()
//                 removes the specified class to the object
// ---------------------------------------------------------------------
function removeClass(theClass) {
  var oldClass = this.className;
  var regExp = new RegExp('\\s?'+theClass+'\\b');
  if (oldClass.indexOf(theClass) != -1) {
    this.className = oldClass.replace(regExp,'');
  }
};
 
Object.prototype.removeClass = removeClass;
 
 
 
// ------------------------------------------------------
// Configuration options
// The Config object acts as a namespace. We'll probably need to add name-subspaces.
// ------------------------------------------------------
var Config = {
    // enable automatic punctuation handling
    AUTOMATIC_PUNCTUATION : true,
 
    // regexp with tags admitting automatic punctuation handling
    AUTO_PUNCT_TAGS : /020.|[167]00.|[167]10.|[167]11.|24[05].|2[56]0.|254.|300.|310.|321.|4[49]0.|50[0124]a|510.|773[^xz]|830./,
 
    // enable display of subfield labels
    DISPLAY_SUBFIELD_LABELS : true,
 
    // the software name
    SOFT_NAME : "Catalis",
 
    // subfield delimiter used for visual representation of records
    SUBFIELD_DELIMITER : "^",
 
    // group data fields in blocks
    USE_FIELD_BLOCKS : true,
 
    dimensions : {
        docIframe_height_max       : "",
        indexTerms_height          : "",
        recordDiv_height_max       : "",
        recordVisualization_height : "",
        searchResultsIframe_height : "",
        theRightPanel_height       : ""
    }
};
 
// ------------------------------------------------------
// Status info
// Note: should this be global or attached to each MarcRecord object?
// ------------------------------------------------------
var Status = {
    selectedSubfield : null,
    selectedField : null
};
 
/*
---------------------------------------
Method: $
---------------------------------------
    A handy alias for document.getElementById().
*/
 
function $(id) {
    return document.getElementById(id);
};
 
 
/* temporal dummy definitions, just to avoid errors */
function crossBrowserNodeSelector() {};
var xmlMARC21 = "";
var ie = document.all;
var moz = !document.all;
function checkKey() {}; 
function assert() {};
function unsavedChanges() {};
function saveChanges() {};
 
 
// --------------------------------------------------
function init() {
// --------------------------------------------------
    // La tabla que contendrá los campos de datos
    var table = document.createElement('TABLE');
    var tbody = document.createElement('TBODY'); // needed for IE
    tbody.id = 'theRecordTbody';
    document.body.appendChild(table);
    table.appendChild(tbody);
 
    /*
    f = new MarcDataField (
        {
            tag : '100',
            indicators : ['0','1'],
            subfields : [
                { code : 'a', value : 'Williams, Robert,' },
                { code : 'd', value : '1954-' }
            ]
        }
    );
    */
 
    // creamos un registro a partir de su versión JSON (un array de campos)
    var fields = [
        {
            tag : '245',
            indicators : ['1','0'],
            subfields : [
                { code : 'a', value : 'Introducción a la mecánica :' },
                { code : 'b', value : 'un enfoque histórico /' },
                { code : 'c', value : 'por Jorge Gómez.' }
            ]
        },
        {
            tag : '260',
            indicators : ['#','#'],
            subfields : [
                { code : 'a', value : 'Buenos Aires :' },
                { code : 'b', value : 'Eudeba,' },
                { code : 'c', value : '1994.' }
            ]
        },
        {
            tag : '100',
            indicators : ['0','1'],
            subfields : [
                { code : 'a', value : 'Gómez, Jorge,' },
                { code : 'd', value : '1964-' }
            ]
        }
    ];
 
    r = new MarcRecord (fields);
 
    // y lo mostramos
    r.display();
 
}; // end function init()
 
function exit() {
    if ( unsavedChanges() ) {
        var MSG_UNSAVED_CHANGES = "Está por abandonar Catalis, pero hay cambios que no han sido guardados. ¿Desea salir de todos modos?"
        var wantToSave = !confirm(MSG_UNSAVED_CHANGES);
        if (wantToSave) {
            saveChanges();
        }
    }
};
 
window.onload = init;
window.onunload = exit;
 
 
/*
16 de enero 2007
Intento definir objetos y métodos aplicando la metodología de Flanagan, The Definitive Guide, 5th ed.
9.3.6. Example: Complex Numbers
 
18 de enero
Logré separar la creación de objetos de su representación en el DOM.
Esto anda bien (chainable methods?):
    r.dataFields[2].addSubfield({code:'4',value:'edt'}).display()
 
19 de enero
Comienzo a mover y adaptar las funciones que había estado organizando en archivos
*.js separados. Comienzo por subfield.js.
 
21 de enero
Continúo con field.js.
 
22 de enero
field.js finalizado, aunque no todas las funciones fueron copiadas.
 
23 de enero
ahora sigo con record.js.
 
26 de enero
pruebas exitosas con subModal.js para diálogos modales.
*/

catalis desarrollo javascript

notas/catalis-objetos.js.txt · Última modificación: por 127.0.0.1