Rediseño de Catalis (2007)
[Archivo original modificado 13-mayo-2007 - El formato no ha sido adaptado para el wiki]
If I had to start thinking of Catalis as a new application…
REFACTORIZACION
* Mejorar el código JS: diseño, estructura, objetos Esto se llama REFACTORIZACION (refactoring). En del.icio.us puse algunos links sobre este tema. En cuanto a libros, los que tratan en más detalle cuestiones de refactorización vinculadas a JavaScript son:
- Ajax in action
- Ajax design patterns
Este artículo introduce el tema (lo imprimí):
OBJETOS
Los objetos básicos que necesitamos manejar en el cliente son: -------------------------------------------------------------------------- - REGISTRO MARC, como estructura de datos (bibliográfico, autoridades) -------------------------------------------------------------------------- propiedades: - leader - directorio? - campos de control - campos de datos (indicadores, subcampos) getDatafields() métodos: - editar el registro (i.e. solicitarlo al servidor y presentarlo en el form de edición) editRecord() - render (en formulario) showRecordInForm() renderDatafields() renderLeader() renderField008() toggleSubfieldLabels() ??? - display del registro completo (diversos formatos) viewRecordDetails() showRecordDetails() viewRecord() marc2marcTagged() marc2aacr() más las funciones en recordVisualization.htm - display de porciones del registro (e.g. leader) ? - historia del registro (quién y cuándo lo modificó) - duplicar el registro duplicateRecord() - exportar buildISO2709() exportRecord() serialize008() serializeRecord() - importar/load (desde diversos formatos) ¿Método de qué objeto? En marc_record.js (http://www.pusc.it/bib/mel/marc_record.js), el objeto MARC_record.prototype tiene un método load(). ¿Y si queremos importar un lote? aacr2marc() getIsoRecord() importRecord() DGM_translate() modifyImportedField() parseISO2709() - actualizar propiedades dinámicas, e.g. directorio, longitud, 005 (ver marc_record.js) - responder preguntas varias: ¿está presente un campo? ¿está presente un campo+subcampo? ¿fue modificado el registro? ¿tipo de registro? ¿cuál es la posición de este campo? isTagPresent() isSubfieldPresent() modifiedRecord() + las funciones en saveChanges.htm checkModified() promptSaveChanges() ? getMaterialType() firstPositionAfter() findNewFieldPosition() - devolver un array de los campos con cierto tag - agregar un campo (o el objeto campo debe tener un método "agregar al registro"?) promptNewField() createField() createFieldList() newFieldShortcut() más las funciones en selectField.htm - editar datos que requieren una ventana especial rawEdit() editCodedData() <= no es necesaria una ventana modal editField041() <= no es necesaria una ventana modal editField044() <= no es necesaria una ventana modal editField046() <= no es necesaria una ventana modal editField047() <= no es necesaria una ventana modal editEjemplares() <= no es necesaria una ventana modal editPostItNote() <= no es necesaria una ventana modal showCodeTable() más todas las funciones en editCodes.htm - validar los datos marcValidate() - mostrar las claves que irán al diccionario showKeys() - modificaciones automáticas (¿métodos del registro o del campo?) canConvertTo440() canConvertTo700() canConvertTo246() map490to440() map830to440() map100to700() map740to246() enhance505() - grabar el registro en la base de datos saveRecord() - borrar el registro deleteRecord() - crear un registro? Método de qué objeto? getNewRecordParams() createRecord() --------------------------------------- Objeto CAMPO DE DATOS: un elemento TR --------------------------------------- propiedades - tag - indicadores getIndicators() updateIndicators() [setIndicators?] - subcampos getSubfields() - bloque al que pertenece getFieldBlockName() - tipo de campo, i.e. controlado, sólo lectura. etc. (??) métodos - agregar un subcampo (o el subcampo debe tener un método "agregar al campo"?) promptNewSubfield() createSubfield() createSubfieldList() newSubfieldShortcut() - eliminar el campo canRemoveF() removeField() deleteObj() ? - editar indicadores editIndicators() <= no es necesaria una ventana modal más todas las funciones en editIndicators.htm - duplicar el campo (si es repetible) canDuplicateF() - subir/bajar canMoveUpF() canMoveDownF() moveField() - actualizar la puntuación updatePunctuation() punctuation() - posición del campo en el registro (index) indexOfField() - siguiente campo en el registro nextField() - ¿campo anterior? - ¿está presente un subcampo? isSubfieldPresent() ? - firstSubfieldBox(), lastSubfieldBox() - displayField() <= render ----------------------------------- Objeto SUBCAMPO: un elemento TR ----------------------------------- propiedades - código de subcampo - valor del subcampo - campo en el cual se encuentra (referencia al objeto DOM, tag) - ¿es editable, readonly, controlado? métodos - posición del subcampo dentro del campo (index) indexOfSubfield() - resaltar el subcampo cuando recibe foco highlightSubfieldBox() - eliminar el subcampo canRemoveSf() deleteObj() ? removeSubfield() - duplicar el subcampo (si es repetible) canDuplicateSf() - subir/bajar canMoveUpSf() canMoveDownSf() moveSubfield() - displaySubfield() <= render - ¿subcampo siguiente/anterior? - otros getSubfieldLabel() ¿Necesitamos un objeto INDICADORES para cada campo de datos? posibles miembros: 1 => primer indicador 2 => segundo indicador editar ¿Y los CAMPOS DE CONTROL? Cada uno tiene características especiales. Podemos tener un objeto Field008, con una propiedad por cada elemento de datos, y métodos para serializar y renderizar. Análogamente, objetos Field006, Field007. Más aun, podemos tener un objeto CODE, para modelar los códigos que se usan en cualquier parte del registro. -------------------------------------------------------------------------- ¿Y los HOLDIGNS? -------------------------------------------------------------------------- El campo 859 es un campo de datos, pero cumple una función muy especial. Cada ocurrencia del 859 debe ser un objeto (item, ejemplar). sortEjemplares() más todas las funciones en editEjemplares.htm -------------------------------------------------------------------------- ¿Cómo se modelan las RELACIONES ENTRE REGISTROS? -------------------------------------------------------------------------- E.g. para analíticas, o seriadas. -------------------------------------------------------------------------- - REGISTRO MARC, como objeto DOM (tal como se ve en el form) -------------------------------------------------------------------------- ¿pueden ambos objetos ser el mismo, o 'casi' el mismo? El objeto DOM 'sabe' algo importante: el *orden* de sus elementos. En el objeto registro, cada campo puede tener un puntero al objeto DOM respectivo. -------------------------------------------------------------------------- - Objeto COLECCIÓN DE REGISTROS MARC: un custom object, o array -------------------------------------------------------------------------- Resultado de una consulta a la base de datos, o importación en lote. -------------------------------------------------------------------------- - NOTIFICACIONES y demás MENSAJES de la interfaz -------------------------------------------------------------------------- Con texto generado desde el cliente o desde el servidor, ver Ajax in action, Ch. 6. catalisMessage() head.htm -------------------------------------------------------------------------- - ELEMENTOS DE LA INTERFAZ: dialog windows, tabs, menúes, toolbars -------------------------------------------------------------------------- showPopup() ? hidePopup() setDimensions() catalis_confirm() updateDialogHeight() updateDialogWidth() showSearchDiv() showEditDiv() handleNextTask() (callback?) refreshTitleBar() showNewRecordMenu() showFieldMenu() showSubfieldMenu() killmenu() showHiddenData() disableKeys() showSpecialChars() -------------------------------------------------------------------------- - BASE DE DATOS (bibliográfica, de autoridades, ...) -------------------------------------------------------------------------- base de datos activa nombre (ver head.htm) cantidad de registros últimas modificaciones usuarios que trabajan sobre esta base handleMaxMfn() showNewRecords() búsquedas? showSearchForm() ? handleKwSearch() searchFromDictionary() diccionario ? showDictionaryKeys() updateDictionaryList() cambio de base de datos databaseChange() ¿Podemos estar trabajando con más de una base a la vez? -------------------------------------------------------------------------- - USUARIO (sólo puede haber uno por vez) -------------------------------------------------------------------------- usuario activo bases a las que tiene acceso, permisos historial, últimos registros procesados ? preferencias ? -------------------------------------------------------------------------- - SESIÓN? -------------------------------------------------------------------------- handleLoginSubmit() endSession() Historial (¿persistente? Puede estar asociado al usuario) -------------------------------------------------------------------------- - COMUNICACIÓN CON EL SERVIDOR (AJAX) -------------------------------------------------------------------------- tareas actuales: carga de tablas, búsquedas, acceso al diccionario, cambio de base, editar/grabar/borrar registro tareas futuras: guardar preferencias, plantillas, notas personales importXML() getXMLFile() loadXML() saveRecord() handleLoginSubmit() endSession() -------------------------------------------------------------------------- - ERRORES? (errores al ejecutar código JS) -------------------------------------------------------------------------- Cada error que se produzca durante la ejecución de JS debe ser interceptado para procesarlo: mostrar mensaje al usuario, guardarlo en un log, reportarlo. Si JS tiene un objeto Error, ¿necesitamos crear algo? handleErrors() -------------------------------------------------------------------------- Objetos con información estática (no se modifican desde JS) -------------------------------------------------------------------------- - TABLA DE CAMPOS Y SUBCAMPOS MARC (marc21.xml) <= JSON? - TABLAS DE CÓDIGOS (country.xml, language.xml, etc.) <= JSON? - REGLAS DE PUNTUACIÓN: por ahora, es una única función con un switch/case - REGLAS DE VALIDACIÓN ? -------------------------------------------------------------------------- Objetos con información cuasi-estática (se pueden llegar a modificar desde JS, pero con poca frecuencia) -------------------------------------------------------------------------- - PLANTILLAS PARA REGISTROS (¿son casos particulares de registros MARC?) loadTemplates() loadLocalTemplateData() - PARÁMETROS DE CONFIGURACIÓN Actualmente, en head.htm - DOCUMENTACIÓN, AYUDA CONTEXTUAL Las modificaciones podrían ser notas personales del catalogador, que deberían guardarse en una base de datos. showDoc() docURL() docIframeHide() docIframeShow() marcHelpPopup() generalHelpPopup() canShowDocF() canShowDocSf() En el cliente necesitamos tener un modelo (objetos JS) de registro MARC, que será poblado con datos recibidos del servidor, para luego mostrarlos en el form, y recíprocamente será poblado con datos del form para luego enviarlos al servidor. En el servidor casi no necesitaremos manipular registros, pero igualmente convendría tener un modelo que nos separe de la base de datos.
- Separación más estricta entre HTML, CSS y JS.
- MVC? Ajax in Action se dedica al tema, especialmente Ch. 4
- chequeo del código (JSLint, JavaScript Lint)
- compresión del código (JsMin, packer, jsjuicer)
- Mejor documentación del código (JSDoc, Natural Docs)
Ejemplo de uso de ND: http://docs.mootools.net/files/Moo-js.html
Otro ejemplo: http://adlib.ozonecreations.com/documentation.php Mirando el código fuente (completo) de mootools podemos aprender a usar Natural Docs aplicado a JS. Para ejecutar ND en Windows: Directorio: G:\programas\NaturalDocs Comando: NaturalDocs -i G:\httpd\htdocs\catalis-obj\catalis\js -o HTML G:\catalis\doc -p G:\catalis\ndinfo * Testeo, assert (ver libro pdf sobre DOM que tengo en el INMABB, y http://aymanh.com/9-javascript-tips-you-may-not-know) Para mostrar el nombre de la función que llamó a assert: if (arguments.callee.caller != null) { msg += 'function ' + arguments.callee.caller.toString().match(/function\s+(\w+)/)[1] } (tomado de http://www.visibone.com/javascript/assertiveness.jpg) * Ajax propiamente implementado (XMLHttpRequest) OBVIO! * Abstraer acceso a la base de datos * Server-side language: PHP * PHP+WXIS, PHP+Malete? * Ampliar número de browsers soportados * IE memory leaks http://outofhanwell.com/ieleak/ * Firefox memory leaks? http://dbaron.org/mozilla/leak-monitor/
Interfaz
- Análisis y rediseño
- Interfaz multilingüe
Funcionalidad
- Control de autoridades (en sentido amplio, i.e. incluyendo tesauros)
- Validación (ver p.ej. http://search.cpan.org/dist/MARC-Lint/ )
- Analíticas, y otras relaciones entre registros
- Interfaz web para administración: bases, usuarios, backups, etc.
- Exportación de registros en lotes.
- Poll the server, para saber si otro usuario modificó la base.
- Integración con OPACMARC?
Otros aspectos
- MARCXML
- Formatos visuales con XSLT (ver Ajax in action, ch. 12)
- Transferencia de datos: ¿XML o JSON?
- Seguridad: Proteger passwords, sesiones, restringir acceso
- Ajax in action, ch. 7
- Ajax Securely, http://www.nebraskacert.org/CSF/CSF-Jan2006.pdf
- Performance: Ajax in action, ch. 8
ASPECTOS DE LA INTERFAZ DE USUARIO
- dimensiones de la página? ver el autoajuste de Google Calendar
- menúes
- tooltips
- generación de mensajes
- ventanas modales
posibles: Cross Browser DHTML Modal Dialogs For Web Apps <http://sublog.subimage.com/articles/2006/01/01/subModal>
(parece satisfactoria) jquery-modalContent plugin (No callbacks?) <http://jquery.glyphix.com/> Google Docs, ex Writely (JS code legible y comentado! http://docs.google.com/javascript/dlgutils.js) Yahoo! Mail (código no legible) * autocompletamiento (para campos controlados, con Ajax) * mensajes de ayuda y de validación
Estudiar interfaces avanzadas, como las de varios productos de Google o Yahoo. La más interesante parece ser la de Google Calendar: http://www.google.com/calendar/. Aspectos que me interesan de Google Calendar:
- Las dimensiones del área de trabajo se adaptan automáticamente a las dimensiones de la ventana.
- Diálogos popup (son varios, mirarlos bien)
- Pestañas (tabs)
- Navegación (mes a mes, semana a semana, etc.)
- scroll de la grilla: usa dommousescroll
Si miro el código JS con WebDevToolbar, no veo *todo*. P.ej., hay una llamada a la función _ResizeCalFrame(), pero no se ve esa función en el código. Si hacemos javascript:alert(_ResizeCalFrame) se muestra un código comprimido, donde la función se llama Jf(), pero que *tampoco* lo encuentro en el código. Algo similar sucede con _initScrollFF(), donde ahora la función es mostrada con el nombre bC(). Podemos estudiarlo con Firebug.
Google Book Search tiene un scroll por paneles que funciona bien en Firefox, y un bonito “Accordion” en el panel derecho.
Google Docs & Spreadsheets también puede tener algo aprovechable:
- Diálogo modal para insertar caracteres especiales, links, tablas, etc.
- Menú contextual
Fading notifications: http://javascript.nwbox.com/asyncAlert/
En el capítulo 7 de Ajax Patterns and Best Practices encontré una referencia a TiddlyWiki, un wiki hecho enteramente en JavaScript (unas 7000 líneas de código!), del que se pueden aprender unas cuantas cosas, en aspectos de GUI y de código. Me gusta la transición del link “opciones”. La forma en que se almacena la configuración puede ser útil.
En http://www.bambooinvoice.org/clients hay un efecto “acordeón” que me gusta.
En http://demo.roundcube.net/?_task=mail hay autoajuste del tamaño de la interfaz.
En http://a9.com/refactoring?grp=reference tenemos
- un widget que permite modificar el tamaño de los paneles
- el tamaño de la interfaz se autoajusta a la ventana
- tooltips en la “i”
En http://www.answers.com/user tenemos un interesante sistema de “ayuda contextual” con AJAX, activado al hacer doble click sobre un término de la página.
El nuevo Yahoo! Mail tiene cosas para investigar: diálogos modales, p.ej.
Diálogo para pedido de confirmación: http://developer.yahoo.com/yui/examples/container/simpledialog/2.html
Jack Slocum (yui.ext) http://www.jackslocum.com/blog/index.php
Our Story http://www.ourstory.com/ Algunos detalles interesantes.
Google Reader usa un mecanismo no usual para el preview de posts: en la “List view”, expande cada ítem de la lista, dentro de la misma lista (en vez de usar otro panel). También ofrece una “Expanded view”, donde todos los posts están expandidos.
¿USAREMOS ALGUNA BIBLIOTECA (LIBRARY) DE JAVASCRIPT? Hay que leer un poco sobre las ventajas/desventajas de cada una de las más populares: jQuery, moo.fx, Dojo, Prototype, Yahoo, Mochikit, JSAN, Dean Edwards, Fork JavaScript
http://www.sitepoint.com/print/javascript-library http://www.paulhammond.org/2006/06/atmedia2/javascript
jQuery parece suficiente para lo que necesitamos. Es liviana, sencilla, y con numerosos plugins. De todos modos, no me gusta cargar el browser con funciones que no serán utilizadas.
Leer sobre jQuery en: Beginning JavaScript with DOM Scripting / C. Heilmann.
Algunos plugins de jQuery que pueden ser útiles en Catalis:
- Tabs
- ExtendedTabs: http://cbwhiz.com/programming/js/jq-tabs.html
Patterns ⇒ el libro Ajax Design Patterns, de Michael Mahemoff, parece útil. Tiene un sitio web también.
YUI ofrece tabs: http://developer.yahoo.com/yui/tabview/
Efectos bonitos: bytefx http://www.devpro.it/bytefx/
prototype property: según Flanagan, // The constructor function initializes those properties that // will be different for each instance. function Rectangle(w, h) { this.width = w; this.height = h; } // The prototype object holds methods and other properties that // should be shared by each instance. Rectangle.prototype.area = function( ) { return this.width * this.height; }
A constructor provides a name for a "class" of objects and initializes properties, such as width and height, that may be different for each instance of the class. The prototype object is associated with the constructor, and each object initialized by the constructor inherits exactly the same set of properties from the prototype. This means that the prototype object is an ideal place for methods and other constant properties. Flanagan explica claramente la diferencia entre propiedades y métodos de una 'instancia', versus propiedades y métodos de la 'clase' (9.3. Simulating Classes in JavaScript).
Un buen paso inicial puede ser éste: recopilar las funciones JS usadas en Catalis, que son todas globales, y agruparlas en objetos.
Las que comienzan con * ya han sido asociadas a un objeto (ver más arriba).
aacr2marc.js
- aacr2marc (aacrText) ⇐ para importar asientos AACR2
aux-windows.js
- rawEdit(oldDatafields, aacr)
- editCodedData(dataElement)
- editField041()
- editField044()
- editField046()
- editField047()
- editIndicators(field)
- editEjemplares()
- editPostItNote()
- promptNewField()
- promptNewSubfield(field)
- catalis_confirm(question,w,h,pos)
- showSpecialChars()
- promptSaveChanges()
catalis.js
- importXML(sourceURL)
- getXMLFile(sourceURL)
- loadXML()
- setDimensions()
- toggleSubfieldLabels()
- highlightSubfieldBox(subfieldBox)
- showDoc(tag)
- docURL(docSource,tag)
- docIframeHide()
- docIframeShow()
- marcHelpPopup(tag,code)
- generalHelpPopup(messageType)
- getDatafields(fieldBlock)
- getFieldBlockName(tag)
- indexOfField(field,fieldBlock)
- indexOfSubfield(subfield)
- firstPositionAfter(tag,fieldBlock)
- findNewFieldPosition(tag,fieldBlock)
- isSubfieldPresent(field,code)
- deleteObj(subfield)
- refreshTitleBar()
- showCodeTable(name)
- showKeys()
- showSearchDiv()
- showEditDiv()
- handleMaxMfn(maxmfn)
- handleKwSearch()
- deleteRecord()
- databaseChange(newdb)
- viewRecordDetails(evt,recordID,recordDisplayStyle)
- editRecord(recordID,evt)
- showSearchForm(formType)
- searchFromDictionary(dictionaryTerm,rowNumber)
- updateDictionaryList(key1,reverse)
- modifiedRecord()
- handleNextTask(elementID)
- updateDialogHeight(objWindow)
- updateDialogWidth(objWindow)
- showHiddenData()
- showPopup(x,y,width,height,refObject)
- hidePopup()
- showNewRecords(evt)
- map490to440(field490)
- map830to440(field830)
- map100to700(field100)
- map740to246(field740)
- enhance505(old505,enhance)
- catalisMessage(msg,button)
- endSession()
- newFieldShortcut()
- newSubfieldShortcut(field)
- checkModified(elementID)
- sortEjemplares(ej1,ej2)
init()
defineSomeVars() checkKey(evt) indexFromRecordID (recordID) <= no es necesaria, podemos usar Array.prototype.indexOf() displayPermanentTitle(uiElement,text,w,h) [EXPERIMENTO]
contextmenu-field.js
- canMoveUpF(field)
- canMoveDownF(field)
- canRemoveF(field)
- canDuplicateF(field)
- canShowDocF(field)
- canAddField(field)
- canConvertTo440(field)
- canConvertTo700(field)
- canConvertTo246(field)
- showFieldMenu(field)
- killmenu()
contextmenu-subfield.js
- canMoveUpSf(subfield)
- canMoveDownSf(subfield)
- canRemoveSf(subfield)
- canDuplicateSf(subfield)
- canShowDocSf(subfield)
canAddSubfield(subfield)
- showSubfieldMenu(subfield)
- killmenu()
create-field-subfield.js
- createFieldList(tags)
- createSubfieldList(field,codes)
- createField(tag, ind, subfields)
- createSubfield( code, sfText, label, fieldTag )
data-in.js
- getNewRecordParams()
- createRecord(newRecParams)
- showRecordInForm(receivedRecord)
- showRecordDetails(receivedRecord)
- showDictionaryKeys(dictionaryKeys,reverse)
- duplicateRecord()
- renderDatafields(datafields)
- renderLeader(leaderData)
- renderField008(f008,materialType)
data-out.js
padWithZeros(num, totalLength) invalidChars(subfield) * serialize008(materialType) * serializeRecord(leader, controlFields, dataFields, localData) * buildISO2709() * exportRecord() * viewRecord() * saveRecord()
disable-keys.js
- disableKeys()
dom-functions.js
crossBrowserNodeSelector(xmlObj,path) * removeField(field) removeAllChildNodes(theObject) * removeSubfield(subfield) * getSubfields(field, array, empty) * getIndicators(field) * updateIndicators(field, newValues) * getSubfieldLabel(subfield) * firstSubfieldBox(field) * lastSubfieldBox(field) * displayField(newField,refNode) * displaySubfield(newSubfield, field) parentField(object, objType) parentSubfield(object, objType) childSubfieldBox(subfield) * nextField(field) nextSubfieldBox(subfield) * isTagPresent(tag) * moveField(field, dir) * moveSubfield(subfield, dir)
eventHandlers.js
setSearchFormEvents() setEditionFormEvents() setControlFormEvents() setWindowEvents() setToolbarEvents() setHeaderEvents() setEventHandlers()
import.js
- getIsoRecord()
- importRecord(isoRecord)
- DGM_translate(dgm)
- modifyImportedField(tag,ind,sf)
- parseISO2709(isoRecord)
isbncheck.js
displayMessage(box,type,number_OK) checkStandardNumber(box,type) isNumberValid(myNumber,myType) { gettype(num) getckdig(num) fullnum(num) hyphenate(str) prefix(str) shp(pref) valid(num,type) getISBN(istring)
marc2marcTagged.js
- marc2marcTagged(leader, f001, f003, f005, f006, f007, f008, marcDatafields, ejemplares, postItNote)
menu-newrecord.js
- canDuplicate()
- showNewRecordMenu()
- killmenu()
punctuation.js
- updatePunctuation(field)
- punctuation(tag,sf)
simModal.js
deadend() { disableForms() { enableForms() { blockEvents() { unblockEvents() { openSimDialog(url, width, height, returnFunc, args) { checkModal() { finishChecking() {
templates.js
- loadTemplates() {
- loadLocalTemplateData() {
validate.js
- marcValidate()
COMMON
getMaterialType.js
- getMaterialType(leader06,leader07)
isbn-hyphen.js
ISN_clean (isbn_proto) prefix_length_from_map (s, map) country_group_code (isbn_proto) canonical_ISBN (isbn_proto)
marc2aacr.js
arabic2Roman(arabic) extractSubfield(field,sfCode) fieldHighlight(tag,className) newNote(tag) * sortEjemplares(ej1,ej2) printCallNumber(ejemplar) * marc2aacr(materialType,f001,f005,f008,marcDatafields,ejemplares)
Hay también algunas funciones definidas en archivos HTML (en cgi-bin): $ find . | xargs grep 'function '
./catalis/html/login.htm:function handleKey(evt) ./catalis/html/login.htm: * handleLoginSubmit() ./catalis/html/head.htm: * handleErrors(errorMessage, url, line) ./catalis/html/head.htm: showSystemInformation() ./catalis/html/head.htm: handleLoad() ./catalis/html/updMaxMfn.htm: init() ./catalis/html/head-recordList.htm: selectFirstRecord() ./catalis/html/head-recordList.htm: init() ./catalis/html/saved-record.htm: init() ./catalis/html/send-keys.htm: init() ./catalis/html/display-record.htm: init() ./catalis/html/deleted-record.htm: init()
Aprendiendo del libro 'Pro JavaScript Techniques', by John Resig (Published December 11th, 2006, by Apress)
Cap. 13: An Ajax Wiki
Es el ejemplo más complejo del libro.
List of all the files needed to power this wiki application:
- index.html: The main application page, pulling all the client code together.
- install.html: The main installation file, to be run before first using the application.
- css/style.css: All the CSS styling for the client side of the application.
- js/wiki.js (Listing 13-9): The main JavaScript code, responsible for binding events and running SQL queries.
- js/sql.js (Listing 13-10): The code responsible for communicating with the server, retrieving the JSON data from the SQL queries.
- js/textile.js: A copy of the JavaScript Textile library (for converting text to HTML): http://jrm.cc/extras/live-textile-preview.php.
- js/jquery.js: A copy of the current release of jQuery: http://jquery.com/.
- api/: The main server code responsible for translating SQL query results to JSON and returning them to the client. This directory contains each code version in Perl, PHP, Python, and Ruby. I've included a copy of the Ruby version of the code in Listing 13-11.
- data/wiki.db: The SQLite database that stores the wiki.
wiki.js
Quiero aprender a usar OO JavaScript, pero me cuesta tomarle la mano.
Requester = function() {}
es lo mismo que
Requester = new Object() ?
No!
El problema que no termino de resolver es cómo manejar estas dos representaciones del registro y sus campos: por un lado, un objeto JS 'puro', y al mismo tiempo un objeto DOM, la versión 'viva' que el usuario maneja directamente. Al objeto 'puro' lo necesito para * manipular registros recibidos desde afuera (es decir, desde el servidor, o importados), antes de presentarlos en el formulario; * posiblemente para generar salidas (es decir, visualizaciones, exportaciones, grabaciones); en este caso creo que sólo necesitaría leer, pero no modificar datos. Mientras se está editando un registro, el registro *es* los datos del formulario, y toda operación sobre el registro es una operación sobre el formulario. Pero entonces, ¿eso implica que toda la lógica de manipulación del registro --esto es, crear un campo, crear un subcampo, verificar la presencia de un campo, etc.-- estará 'contaminada' por la particular estructura DOM elegida para este formulario, sin poder hacer abstracción de ésta? Las funciones del archivo dom-functions.js de Catalis ...
- ——————–
¿Vale la pena dividir los métodos en “getter” vs. “setters”? Uso comillas porque no
estoy seguro del sentido preciso con que se usan esos términos en otros lenguajes, pero la idea es distinguir entre aquellos métodos que solamente devuelven un valor, de aquellos que además modifican algún objeto.
También podemos organizar el código de esta manera:
MarcField.prototype = {
addSubfield = function() { ... } remove = function(arg) { ... }
}
Enero 10 y 11 Vamos a recorrer uno por uno los archivos *.js, para extraer las funciones existentes y organizarlas en base a los objetos definidos. Sólo las copiamos, sin modificar el código.
1. dom-functions.js [OK] 2. catalis.js [OK] 3. create-field-subfield.js [OK] 4. contextmenu-field.js [OK] 5. contextmenu-subfield.js [OK] 6. data-out.js [OK] 7. data-in ⇐ getNewRecordParams() debería estar en aux-windows.js 8. eventHandlers.js [OK] 9. marc2MarcTagged.js [OK] 10. disable-keys.js [OK] 11. punctuation.js [OK] 12. menu-newrecord.js [OK] 13. templates [OK]
Enero 13 ¿El siguiente paso? Queremos empezar a modificar el código, de manera progresiva. Pero ¿cómo? Creo que deberíamos hacerlo desde afuera hacia adentro (o desde arriba hacia abajo). Es decir, continuar con el rediseño general de la aplicación, definiendo los objetos y métodos a utilizar.
Enero 14. Si el objeto MarcSubfield termina siendo una fila de tabla (TR), ¿cómo se supone que le asociamos métodos? Con Mozilla podríamos usar HTMLTableRowElement.prototype, pero IE no nos deja.
Estos problemas con IE se tratan en:
- Objectified: Custom element methods in Prototype <http://www.andrewdupont.net/2006/04/15/objectified-custom-element-methods-in-prototype/>
- Crossbrowser HTMLElement Prototyping <http://www.browserland.org/scripts/htmlelement/>
- Adding Methods to Native HTML Elements [Jan 24, 2006] <http://huwebdev.blogspot.com/2006/08/adding-methods-to-native-html-elements.html>
- Emulating Prototyping of DOM Objects in Internet Explorer <http://delete.me.uk/2004/09/ieproto.html>
Pero, aun suponiendo que pudiésemos añadir métodos al prototype de los elementos TR, no los estaríamos restringiendo a los TR de un tipo específico (campos, subcampos), que es justamente el objetivo de usar el enfoque orientado a objetos.
Por lo tanto, parece que sólo nos quedaría esta posibilidad: que nuestro objeto sea un 'custom object' con una propiedad que lo vincule al objeto DOM (TR) asociado; a su vez el objeto DOM tendría una propiedad que lo vincule con el custom object. Así, a partir de un evento que sucede en el objeto DOM, tenemos que invocar un método del custom object asociado, el cual tendrá como resultado visible una modificación en el objeto DOM. Suena a mucha complicación innecesaria, esto de tener básicamente *dos* objetos hermanados.
Leer:
EJ - The only JavaScript library you'll ever need http://www.robertnyman.com/2006/11/07/ej-the-only-javascript-library-youll-ever-need/
Event Delegation versus Event Handling (sobre las ventajas de usar event bubbling, especialmente para interfaces con muchos elementos; ver también ppk 7D.) http://icant.co.uk/sandbox/eventdelegation/ http://icant.co.uk/sandbox/bonsaimenu/index.html
==Working examples: Custom events: http://developer.yahoo.com/yui/examples/event/custom-event.html Event delegation: http://developer.yahoo.com/yui/examples/event/event-delegation.html
Event-Driven Web Application Design (Christian Heilmann, YUI Blog) (acerca de event delegation y custom events) http://yuiblog.com/blog/2007/01/17/event-plan/
Publishing Custom Events in JavaScript Dustin Diaz (Yahoo) http://www.dustindiaz.com/custom-events/
El concepto de “event delegation” puede sernos útil para manejar los eventos asociados a los numerosos elementos “repetibles” en la interfaz: campos, subcampos, datos codificados.
Please read this! OpenCataloger, Koha http://wiki.liblime.com/doku.php?id=catalogingproject
Command pattern (Wikipedia) http://en.wikipedia.org/wiki/Command_pattern
Sobre este tema (command pattern) hay material en varios capítulos de Ajax in action.
Sobre bloqueo de registros (record locking): AJAX & Record Locking http://www.ddj.com/dept/lightlang/192700218
On-Demand Javascript http://ajaxpatterns.org/wiki/index.php?title=On-Demand_Javascript
Para evitar pérdida de datos al abandonar la página accidentalmente: onbeforeunload - http://www.4guysfromrolla.com/webtech/100604-1.shtml Verificar si hay cambios sin guardar.
Feb 17.
User interface components - A survey of everything (?) we need.
1st level:
login logout select database
2nd level:
create/import record
Documentation: should be accessible not only from the editing panel but from everywhere
Tooltips or other contextual help popups: used all over the application.
Main division in two panels:
(1) DATABASE SEARCH & BROWSE
Search: forms for different types of search (keyword, control number, boolean condition) Also: recently added/modified records Probably similar to OPAC search form. Browse index: input initial term, display list of terms. Lists of retrieved records: each "search" produces a list, consisting of 0 or more records (possibly paginated), and a "header" with data about the search. We can switch between lists using e.g. tabs. Search history? Record details: different views of a record (AACR2, MARC, OPAC, etc.). This functionality is also present in the editing panel.
(2) RECORD EDITING
List of data fields (TABLE ROWS) Leader and control fields Holdings => DIALOG Annotations => DIALOG (can we attach annotations to specific fields?) Toolbar: add field, add subfield, view, save, export, advanced editing (all open DIALOGs)
Switch between (1) and (2): TABs. We could add optionally extra tabs to access related websites: LC catalog, local OPAC, etc. Firefox or IE7 users may prefer to use native browser tabs.
We also need to take into account working with an authority database, besides the usual bibliographic db.
Simulación de frames usando CSS http://tests.themasta.com/scrollheaderfooter/ http://www.456bereastreet.com/lab/css-frames-v2/ http://www.456bereastreet.com/lab/cssframes/
Usando YUI.ext Exploring Cross-browser Web 2.0 Layouts with Yahoo! UI http://www.jackslocum.com/blog/2006/10/19/cross-browser-web-20-layouts-with-yahoo-ui/ Cross-browser Web 2.0 Layouts (Part 2) and Ajax Feed Viewer 2.0 http://www.jackslocum.com/blog/2006/10/28/cross-browser-web-20-layouts-part-2-ajax-feed-viewer-20/
For next version of Catalis, consider using the BSD License http://www.opensource.org/licenses/bsd-license.php
07 marzo 2007 ⇐= esto lo imprimí el mismo 7 de marzo
Análisis de las solicitudes al servidor desde formularios y links. ¿Qué se puede sacar en limpio de todo esto? ¿Cuál es una mejor manera de organizar estas peticiones? Mejor = más fácil de administrar/mantener
FORM LOGIN
<form name="loginForm" id="loginForm" action="[pft]v6001^u[/pft]" method="POST"> <input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="tarea" value="INICIAR_SESION"> <input type="text" name="userid" value="[pft]v2002[/pft]"> <input type="password" name="pw"> <input type="submit" value="Iniciar sesión"> </form> El envío de este formulario causa la carga de una nueva página. La base no se especifica, y se accede a la primera de la lista de bases para el usuario.
BOTON 'ULTIMOS REGISTROS'
<button id="btnNewRecords">Últimos 10 registros</button> document.getElementById("btnNewRecords").onclick = showNewRecords; function showNewRecords() { if (ie) { document.frames.searchResultsIframe.location.href = document.getElementById("searchResultsIframe").src; } else if (moz) { document.getElementById("searchResultsIframe").src = document.getElementById("searchResultsIframe").src; } } Carga una nueva página en el iframe.
CAMBIO DE BASE / BOTÓN 'FIN SESIÓN' (un mismo form para ambas tareas)
<form id="logoutForm" action="[pft]v6001^u[/pft]" method="POST"> <input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="tarea" value="FIN_SESION"> <select id="selDatabase"> options...</select> <== no tiene sentido dentro de este form, pues usa hiddenFORM <button id="btnFinSesion">Fin sesión</button> </form> document.getElementById("selDatabase").onchange = function() { checkModified(this.id); ==> databaseChange() } document.getElementById("btnFinSesion").onclick = function() { checkModified(this.id); ==> endSession() }
function databaseChange(newdb) { var form = document.getElementById("hiddenFORM"); form.db.value = newdb; form.tarea.value = "MAIN_PAGE"; form.method = "GET"; form.target = "_top"; form.submit(); } function endSession() { if ( confirm("¿Confirma que desea finalizar la sesión?") ) { document.getElementById("logoutForm").submit(); } } El envío de este formulario causa la carga de una nueva página.
Búsquedas:
- ——————————–
FORM BUSQUEDA POR PALABRAS
- ——————————–
<form id=“kwSearchForm” action=“[pft]v6001^u[/pft]” method=“GET” target=“searchResultsIframe”>
<input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="tarea" value="BUSCAR"> <input type="hidden" name="dictionaryTerm" value=""> <input type="text" id="kwSearchBox" name="busq_kw"> <input type="submit" value="Buscar"> </form> document.getElementById("kwSearchForm").onsubmit = function() { handleKwSearch(); return false; } function handleKwSearch() { var form = document.getElementById("kwSearchForm"); form.dictionaryTerm.value = ""; var query = document.getElementById("kwSearchBox").value; query = query.replace(/\s+/g," "); query = query.replace(/^\s+|\s+$/g,""); document.getElementById("kwSearchBox").value = query; form.submit(); } El envío de este formulario causa la carga de una nueva página en el iframe. --------------------------------- FORM BUSQUEDA POR MFN --------------------------------- <form id="mfnSearchForm" name="mfnSearchForm" action="[pft]v6001^u[/pft]" method="GET" target="searchResultsIframe"> <input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="tarea" value="MFN_RANGE"> <input type="text" id="mfnSearchBox" name="mfn"> <input type="submit" value="Mostrar"> </form> El envío de este formulario causa la carga de una nueva página en el iframe.
- ——————————–
FORM BUSQUEDA POR COND. BOOLEANA
- ——————————–
<form id=“testConditionSearchForm” name=“testConditionSearchForm” action=“[pft]v6001^u[/pft]” method=“GET” target=“searchResultsIframe”>
<input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="tarea" value="TEST_CONDITION"> <input type="hidden" name="maxMFN" value=""> <input type="text" id="testConditionSearchBox" name="condition"> <input id="grCheckbox" type="checkbox" name="gr" value="On"><label for="grCheckbox">grupo repetible</label> <input type="submit" value="Buscar"> </form> El envío de este formulario causa la carga de una nueva página en el iframe.
Diccionario:
--------------------------------- FORM VER TERMINOS DEL DICCIONARIO --------------------------------- <form id="indexForm" action="[pft]v6001^u[/pft]" method="GET" target="hiddenIFRAME"> <input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="tarea" value="SHOW_KEYS"> <input type="hidden" name="reverse" value=""> <input type="text" id="dictBrowseBox" name="dictkey"> <input type="submit" value="Ver"> </form> document.getElementById("indexForm").onsubmit = function() { updateDictionaryList(this.dictkey.value); return false; } function updateDictionaryList(key1,reverse) { var form = document.getElementById("indexForm"); form.dictkey.value = key1; form.reverse.value = ( reverse ) ? "On" : ""; form.submit(); }
- ——————————–
LINK EN TERMINO DEL DICCIONARIO
- ——————————–
newLink.onclick = function() {
searchFromDictionary(this.title,this._index); return false; } function searchFromDictionary(dictionaryTerm,rowNumber) { var form = document.getElementById("kwSearchForm"); form.dictionaryTerm.value = dictionaryTerm; form.submit(); } --------------------------------- BOTONES 'ANTERIORES' y 'SIGUIENTES' EN EL DICCIONARIO --------------------------------- newButton.onclick = function() { updateDictionaryList(this._term,"reverse"); } newButton.onclick = function() { updateDictionaryList(this._term); }
Listado de registros:
- ——————————–
BOTÓN 'EDITAR' (también se puede editar desde botones en la vista detallada y en el form de edición)
- ——————————–
'<button onclick=parent.editRecord(“',v001,'”,event)','>',v001,'</button>'
function editRecord(recordID,evt) { var form = document.getElementById("hiddenFORM"); form.tarea.value = "EDITAR_REG"; form.recordID.value = recordID; form.method = "GET"; form.target = "hiddenIFRAME"; form.submit(); } --------------------------------- LINK 'VER DETALLES' (también desde botones en la vista detallada) --------------------------------- <a ... onclick=top.viewRecordDetails(null,"',v001,'",top.g_RecordDisplayStyle);return(false)> function viewRecordDetails(evt,recordID,recordDisplayStyle) { var form = document.getElementById("hiddenFORM"); if ( recordID == null ) { recordID = form.recordID.value; } form.tarea.value = ( "etiq" == recordDisplayStyle ) ? "SEND_ETIQUETADO" : "SEND_RECORD"; form.recordID.value = recordID; form.target = "hiddenIFRAME"; form.method = "GET"; form.submit(); } --------------------------------- BOTONES 'ANTERIORES' y 'SIGUIENTES' ---------------------------------
PARA MFNRANGE <form action="[pft]v6001^u[/pft]" method="get" style="display: inline;"> <input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="tarea" value="MFN_RANGE"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="from" value="[pft]f(rmax(f(val(v2006)-20,1,0),x1,'1'),1,0)[/pft]"> <button onclick="this.form.submit()">Anteriores</button> </form>
<form action="[pft]v6001^u[/pft]" method="get" style="display: inline;"> <input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="tarea" value="MFN_RANGE"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="from" value="[pft]f(val(v2006)+20,1,0)[/pft]"> <button onclick="this.form.submit()">Siguientes</button> </form>
PARA BUSQUEDAS <form action="[pft]v6001^u[/pft]" method="get" style="display: inline;"> <input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="tarea" value="BUSCAR"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="busq_kw" value="[pft]v3005[/pft]"> <input type="hidden" name="dictionaryTerm" value="[pft]v3009[/pft]"> <input type="hidden" name="from" value="[pft]f(val(v2006)-20,1,0)[/pft]"> <button onclick="this.form.submit()">Anteriores</button> </form>
<form action="[pft]v6001^u[/pft]" method="get" style="display: inline;"> <input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="tarea" value="BUSCAR"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="busq_kw" value="[pft]v3005[/pft]"> <input type="hidden" name="dictionaryTerm" value="[pft]v3009[/pft]"> <input type="hidden" name="from" value="[pft]f(val(v2006)+20,1,0)[/pft]"> <button onclick="this.form.submit()">Siguientes</button> </form>
Edición de registros:
- ——————————–
BOTÓN 'GRABAR' (toolbar)
- ——————————–
<button id=“btnGrabar”> </button>
document.getElementById("btnGrabar").onclick = function() { saveRecord(); } function saveRecord() { var form = document.getElementById("hiddenFORM"); form.marcFields.value = marcFields; form.recordID.value = document.getElementById("marcEditForm").f001.value; form.tarea.value = "GRABAR_REG"; form.method = "POST"; // method="GET" genera errores, como es de esperar form.target = "hiddenIFRAME"; form.submit(); } --------------------------------- BOTÓN 'BORRAR' (toolbar) --------------------------------- <button id="btnBorrar">X</button> document.getElementById("btnBorrar").onclick = function() { deleteRecord(); } function deleteRecord() { if ( confirm("BORRADO DEL REGISTRO\n\nSi borra este registro, no podrá recuperarlo. ¿Confirma el borrado?") ) { var form = document.getElementById("hiddenFORM"); form.tarea.value = "BORRAR_REG"; form.recordID.value = document.getElementById("marcEditForm").f001.value; form.target = "hiddenIFRAME"; form.method = "GET"; form.submit(); } }
- ——————————–
EDITAR SIGUIENTE/ANTERIOR EN LA LISTA DE RESULTADOS
- ——————————–
document.getElementById(“btnPrevResult”).onclick = function() {
checkModified(this.id); ==> editRecord() } document.getElementById("btnNextResult").onclick = function() { checkModified(this.id); ==> editRecord() }
FORM OCULTO (compartidos por varias tareas):
<form id="hiddenFORM" action="[pft]v6001^u[/pft]" method="" target=""> <input type="hidden" name="IsisScript" value="[pft]v2000[/pft]"> <input type="hidden" name="userid" value="[pft]v2002[/pft]"> <input type="hidden" name="db" value="[pft]v2104[/pft]"> <input type="hidden" name="tarea" value=""> <input type="hidden" name="mfn" value=""> <input type="hidden" name="recordID" value=""> <input type="hidden" name="debug" value=""> <!-- TEXTAREA usado para enviar el registro MARC al servidor --> <textarea name="marcFields" style="display: none;" -id="recordTextarea"></textarea> </form>
<button id="editRecordBtn">Editar</button> document.getElementById("editRecordBtn").onclick = function() { editRecord(null,event); } <button id="aacrDisplayBtn">AACR2</button> document.getElementById("aacrDisplayBtn").onclick = function() { viewRecordDetails(event,null,"aacr"); }
hasta acá imprimí el 7 de marzo
14 de marzo
MVC - Model-View-Controller
Una charla de ayer con Rubén Mansilla me llevó a poner otra vez algo de atención en el MVC.
Hay un ejemplo breve y bastante claro en
JavaScript Model-View-Controller with Dojo toolkit http://www.alexatnet.com/blog/2/2006/08/04/javascript-model-view-controller-with-dojo-toolkit
El modelo es una lista (i.e. un array), con métodos para añadir, quitar y mover ítems; la vista es un elemento select más algunos botones, con métodos para poblar el select con los datos provenientes del modelo, y para asociar cambios en el modelo a cambios en la vista; y el controlador consiste de los “event handlers” asociados a los elementos de la interfaz. Encontré un par de anomalías, las reporté al autor y dijo que hará las modificaciones. Ver anotaciones en la copia impresa del código.
Cita de http://isegserv.itd.rl.ac.uk/blogs/alistair/archives/21 (My First AJAX Application, Alistair Miles, 10 June 2006):
In designing the application, I've tried to follow a strict Model-View-Controller architecture. A set of controller functions are invoked when the appropriate view event is fired. Controller functions execute some functionality (e.g. make an asynchronous HTTP request) and then make appropriate changes to a model object. If properties of the model object are changed, the view is notified and updated as appropriate (i.e. the appropriate view functions are invoked). Everything flows in the direction view (event) > controller > model > view. I've tried make the best of both the functional and the object-oriented features of the Javascript language, for example I use the Controller and View objects simply to collect a set of related functions that can be called statically, however a single instance of the Model object is created and used. E.g. when the “Search” button is pressed, the static method Controller.searchAll() is invoked. This changes the “model” variable by calling model.setProperty(Model.PROP_RESULTS, results), which then causes the view to be updated via the static method View.updateResults(). The static methods of the View object really only provide convenience layer above manipulations on the actual DOM document being viewed.
La aplicación a la que se refiere es: http://isegserv.itd.rl.ac.uk/claddier/search/single/ (guardada como “CLADDIER Discovery Service.html” en Ubuntu Desktop; ver archivo mvc.js) Documentación en http://isegserv.itd.rl.ac.uk/claddier/docs/api/
Estos otros ejemplos podrían ser útiles: http://polyglotinc.com/AJAXscratch/MVC/examples/index.html “Bruce Wallace has developed a library of JavaScript classes that implement a Model-View-Controller framework for Web Applications.”
Aquí hay una descripción más o menos clara del MVC: http://www.phpwact.org/pattern/model_view_controller
En el foro de Ext, este thread puede ser relevante: http://www.yui-ext.com/forum/viewtopic.php?t=3299&highlight=controller Allí Animal escribe:
/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/- The way I handle it, and I imagine most people will is with a singleton app controller using the pattern
var appName = {}; appName.appController = function() { var private = "data"; return { //Create page layout, extract UpdateManagers for later use etc init: function() { }, getPublicData() { return "stuff"; } } }(); Ext.onReady(appName.appController.init, appName.appController, true);
The object returned there knows about your page layout and performs services on it.
Commonly needed items can either be made public by placing them in the “appName” namespace, or kept private within the appController. /-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-
13 de mayo, post similar de Animal acerca de cómo organizar el código de una app: http://extjs.com/forum/showthread.php?p=30527#post30527 En ese thread, jsakalos mentions his policies for application design.
…………………………………. 15 de marzo.
Encontré Freja: “Freja is an Open-Source MVC Javascript Framework”
http://www.csscripting.com/wiki/index.php?title=Freja It stands (tentatively) for 'Framework for REstful Javascript Applications'. Freja takes the traditional web development model upside-down, so before going any further, consider its founding principles: * Build and run client-side. * See the server as a Web Service provider. * Aim for zero-latency. Se ve interesante, al menos como fuente de ideas. Hay sólo un par de ejemplos de uso real.
Un paper básico sobre MVC.
Applications Programming in Smalltalk-80(TM): How to use Model-View-Controller (MVC) by Steve Burbeck, Ph.D. (1992) http://st-www.cs.uiuc.edu/users/smarch/st-docs/mvc.html Lo imprimí. Como habla principalmente sobre Smalltalk, sólo rescaté unas pocas ideas básicas.
Model-View-Controller según Microsoft:
http://msdn2.microsoft.com/en-us/library/ms978748.aspx Printer friendly: http://msdn2.microsoft.com/en-us/library/ms978748(d=printer).aspx
Ajax in Action, cap. 3 habla de Web server MVC, y cap. 4, de browser MVC.
En 13.7 se usa MVC en el refactoring del RSS reader.
Para debugging en IE, probar:
IE WebDeveloper
http://www.ieinspector.com/dominspector/index.html
MS Script Editor?
Companion.JS
21 de marzo de 2007 Uso de objetos para mejor manejo de los campos y subcampos.
Ejemplo:
* Clase DataField
- subclase PersonalName
- subclase CorporateName
- subclase MeetingName
- subclass UniformTitle
- subclass Note
- subclase AddedEntry
- Field700 (hereda de PersonalName y de AddedEntry)
Los métodos de visualización, edición, validación pueden organizarse de acuerdo a esta estructura.
Pero no hay que tomárselo demasiado en serio, porque pueden aparecer inconsistencias en MARC que causen problemas y al final no resulte tan cómodo de aplicar/usar.
catalis desarrollo