Introducción
En este framework, basado en la arquitectura Comet, tenemos dos clases principales para hacer un aplicativo, SWTClientApplication y SWTServerApplication. Para comenzar vamos a realizar un pequeño ejemplo sobre lo que sería una aplicación de Exámenes en línea. Donde se muestra una pregunta a la vez, y varias posibles soluciones. Al final de contestar las preguntas da el puntaje obtenido. Este tutorial se dividirá en dos partes, en la primer parte haremos este aplicativo con un modelo en el servidor más inactivo y el cliente web con más mucha más actividad y funcionalidad. En la segunda parte, haremos uso del MVC distribuido que ofrece el SWT, donde mostraremos como mejorar considerablemente la cantidad de mensajes y la forma ineficiente con respecto a la primer parte.
Ejemplo: Exámenes en línea
El modelo
Para comenzar vamos a desarrollar un modelo simple del aplicativo. Un examen se compone de una pregunta y múltiples respuestas, donde cada respuesta puede ser la más acertada para responder o simplemente es incorrecta. Por estándar vamos a tener cinco respuestas por cada pregunta, y cada respuesta tiene un valor. Para hacer los cálculos más simples, vamos a poner un rango de puntuación entre 0 y 10 puntos.
La respuesta es un objeto simple que tiene la respuesta que contiene con su puntaje asociado, y una pregunta es un objeto con una pregunta y una colección de respuestas.
Para ello, vamos a crear un workspace con las expresiones que queremos que funcionen:
| question answers |
answers := OrderedCollection new.
answers
add: (SWTAnswerModel answer: ‘Object’ score: 0);
add: (SWTAnswerModel answer: ‘Collection’ score: 0);
add: (SWTAnswerModel answer: ‘Set’ score: 10);
add: (SWTAnswerModel answer: ‘Bag’ score: 0);
add: (SWTAnswerModel answer: ‘SWTModel’ score: 0);
yourself.
question := SWTQuestionModel question: ‘¿De quién hereda Dictionary?’ answers: answers.
Como vemos en workspace anterior ya tenemos un pequeño borrador de como sería nuestro modelo. Ahora, cada vez que un usuario responde vamos a agregarle que la pregunta sepa la respuesta que eligió el usuario, para ello, agregamos una variable de instancia que nos permita recordar que respondió a esa pregunta. La variable la vamos a denominar answerChoosed.
| question answers |
answers := OrderedCollection new.
answers
add: (SWTAnswerModel answer: ‘Object’ score: 0);
add: (SWTAnswerModel answer: ‘Collection’ score: 0);
add: (SWTAnswerModel answer: ‘Set’ score: 10);
add: (SWTAnswerModel answer: ‘Bag’ score: 0);
add: (SWTAnswerModel answer: ‘SWTModel’ score: 0);
yourself.
question := SWTQuestionModel question: ‘¿De quien hereda Dictionary?’ answers: answers.
question answerChoosed: (answers at: 3).
Si el usuario no sabe contestar la pregunta, pondremos una respuesta por defecto, que valdrá cero puntos, y es No sabe y/o No contesta. Esto lo vamos a hacer en el getter de #answerChoosed
SWTQuestionModel>>answerChoosed ” Returns the answer choosed by the user to the receiver.”
answerChoosed isNil ifTrue: [ self answerChoosed: SWTAnswerModel notAnswered ].
^answerChoosed
Ahora bien, en nuestro modelo ya tenemos preguntas, posibles respuestas y la respuesta que se elige. Esto funciona para solo una pregunta, pero un examen, consta de muchas preguntas, y dado, que generalmente una certificación, tiene al menos unas 40 preguntas, vamos a modelar nuestro objeto examen. Este objeto tiene una colección de preguntas, y sabe calcular el puntaje final. Vamos a denominar SWTExamModel.
Para calcular el puntaje final lo hacemos así:
En el examen
SWTExamModel>>score ” Returns the score of the receiver.”
^self questions inject: 0 into: [ :finalScore :question | question score + finalScore ]
En la pregunta
SWTQuestionModel>>score ” Returns the score of the receiver.”
^self answerChoosed score
Hasta aquí, ya tenemos un modelo funcional, tenemos un examen, que se compone de preguntas, a su vez, las preguntas tiene posibles respuestas, y el usuario por cada pregunta elige su respuesta. El objeto respuesta sabe que puntaje tiene. Entonces el examen solo delega en sus preguntas saber los puntajes obtenidos, y estas a su vez, usan de colaborador a nuestras respuestas.
Nuestro examen aún le falta algo más, si ya respondí una pregunta, tengo que esperar la siguiente, así que vamos a decirle al examen, que nos de una pregunta más, para hacerlo más interesante aún, que sea al azar, y lógicamente, como es azar, no quiero responder más de una vez una pregunta, por lo tanto, el examen deberá quitar las que ya se respondieron. Para ello, agregamos este comportamiento a examen. Vamos a tener variables de instancia a questions, que son las preguntas totales, y otra colección con las preguntas ya usadas, questionsAnswered.
SWTExamModel>>questionsNotAnswered ” Returns the collection of questions not answered yet of the receiver.”
| newQuestions |
newQuestions := self questions copy.
newQuestions removeAll: self questionsAnswered.
^newQuestions
Obtener una nueva pregunta:
SWTExamModel>>newQuestion ” Returns a new question for the exam.”
| newQuestion randomIndex maximumInteger |
randomIndex := Random seed: 2345678901.
[ maximumInteger := self questionsNotAnswered size.
maximumInteger = 0 ifTrue: [ ^nil ].
newQuestion := self questionsNotAnswered at: (randomIndex nextInt: maximumInteger).
self questionsAnswered includes: newQuestion] whileTrue: [].
self currentQuestion: newQuestion.
self questionsAnswered add: self currentQuestion.
^self currentQuestion
Para probar lo que hemos hecho hasta el momento, vamos a hacer un workspace donde tengamos un examen con 3 preguntas y las contestaremos. Por ultimo, evaluaremos el puntaje final.
| exam questions answers questionToAnswer |
questions := OrderedCollection new.
answers := OrderedCollection new.
answers
add: (SWTAnswerModel answer: ‘Object’ score: 0);
add: (SWTAnswerModel answer: ‘Collection’ score: 0);
add: (SWTAnswerModel answer: ‘Set’ score: 10);
add: (SWTAnswerModel answer: ‘Bag’ score: 0);
add: (SWTAnswerModel answer: ‘SWTModel’ score: 0);
yourself.
questions add: (SWTQuestionModel question: ‘¿De quien hereda Dictionary?’ answers: answers).
answers := OrderedCollection new.
answers
add: (SWTAnswerModel answer: ‘un objeto’ score: 0);
add: (SWTAnswerModel answer: ‘una clase’ score: 0);
add: (SWTAnswerModel answer: ‘una variable global’ score: 10);
add: (SWTAnswerModel answer: ‘El nombre del producto’ score: 0);
add: (SWTAnswerModel answer: ‘una nueva distro de Linux’ score: 0);
yourself.
questions add: (SWTQuestionModel question: ‘Que es Smalltalk?’ answers: answers).
answers := OrderedCollection new.
answers
add: (SWTAnswerModel answer: ‘la clase del 3′ score: 0);
add: (SWTAnswerModel answer: ‘la clase Class’ score: 0);
add: (SWTAnswerModel answer: ‘Integer class’ score: 10);
add: (SWTAnswerModel answer: ‘un error’ score: 0);
add: (SWTAnswerModel answer: ‘nil’ score: 0);
yourself.
questions add: (SWTQuestionModel question: ‘que devuelve 3 class class?’ answers: answers).
exam := SWTExamModel questions: questions.
questionToAnswer := exam newQuestion.
questionToAnswer answerChoosed: (questionToAnswer answers at: 1).
questionToAnswer := exam newQuestion.
questionToAnswer answerChoosed: (questionToAnswer answers at: 3).
questionToAnswer := exam newQuestion.
questionToAnswer answerChoosed: (questionToAnswer answers at: 3).
exam score
El resultado obtenido nos debería devolver 20 puntos. Pues la primer pregunta la contestamos incorrectamente, y las dos subsecuentes correctamente.
La interfaz web con SWT sin MVC distribuido
La interfaz de nuestro examen, será bien simple también, solo tenemos que mostrar nuestra pregunta, y las posibles respuestas para que las pueda elegir el usuario. La forma de elegir será a través de un botón por ahora. Cada vez que responde una pregunta, tenemos que continuar a la siguiente, hasta que termine y mostrar el puntaje final obtenido. Para lograr esto, vamos a modelar nuestra página a través de una clase que hereda de SWTClientApplication. La vamos a denominar SWTClientExamApplication, y desde el servidor vamos a tener a su controlador, llamado SWTServerExamApplication.
En el cliente, vamos a tener la pregunta como modelo, y en el server al examen.
SWTServerExamApplication>>exam ” Returns the exam of the receiver.”
exam isNil ifTrue: [ self initializeExam ].
^exam
SWTServerExamApplication>>initializeExam ” Initialize the exam.”
| questions answers |
questions := OrderedCollection new.
answers := OrderedCollection new.
answers
add: (SWTAnswerModel answer: ‘Object’ score: 0);
add: (SWTAnswerModel answer: ‘Collection’ score: 0);
add: (SWTAnswerModel answer: ‘Set’ score: 10);
add: (SWTAnswerModel answer: ‘Bag’ score: 0);
add: (SWTAnswerModel answer: ‘SWTModel’ score: 0);
yourself.
questions add: (SWTQuestionModel question: ‘De quien hereda Dictionary?’ answers: answers).
answers := OrderedCollection new.
answers
add: (SWTAnswerModel answer: ‘un objeto’ score: 0);
add: (SWTAnswerModel answer: ‘una clase’ score: 0);
add: (SWTAnswerModel answer: ‘una variable global’ score: 10);
add: (SWTAnswerModel answer: ‘El nombre del producto’ score: 0);
add: (SWTAnswerModel answer: ‘una nueva distro de Linux’ score: 0);
yourself.
questions add: (SWTQuestionModel question: ‘Que es Smalltalk?’ answers: answers).
answers := OrderedCollection new.
answers
add: (SWTAnswerModel answer: ‘la clase del 3′ score: 0);
add: (SWTAnswerModel answer: ‘la clase Class’ score: 0);
add: (SWTAnswerModel answer: ‘Integer class’ score: 10);
add: (SWTAnswerModel answer: ‘un error’ score: 0);
add: (SWTAnswerModel answer: ‘nil’ score: 0);
yourself.
questions add: (SWTQuestionModel question: ‘que devuelve 3 class class?’ answers: answers).
self exam: (SWTExamModel questions: questions).
SWTServerExamApplication>>newQuestion ” Returns a new question of the receiver.”
^self exam newQuestion.
Una parte muy importante, dado que el cliente, es un mini-Smalltalk, pues todo lo que estamos escribiendo en nuestra clase cliente, a la hora de funcionar, esto se traduce a Javascript. El framework posee un traductor de Smalltalk a Javascript bastante complejo, y como Smalltalk y Javascript son asimétricos, es decir, no todo el potencial del Smalltalk puede ser traducido a funcionalidad en Javascript, debemos tenerlo en cuenta a la hora de poner comportamiento en el cliente.
Todas las clases de nuestro modelo que sean usadas en el cliente, debemos enumerarlas en un mensaje de clase #jsClassesToInclude. Para que el traductor, genere estas clases del modelo en nuestro mini-Smalltalk en Javascript. Para nuestro ejemplo, vamos a hacer uso de las preguntas y las respuestas, por consiguiente, redefinimos este mensaje:
SWTClientExamApplication class>>jsClassesToInclude ^ {SWTQuestionModel. SWTAnswerModel }
Ahora en el cliente, haremos nuestra vista
SWTClientExamApplication>>newQuestion ” Returns the a new question of the receiver and updates the questions panel of the receiver.”
self isolatedAsynchronousRPCMethods:
[ self question: self serverSide newQuestion.
self updateQuestionsPanel ]
Ahora, lo que haremos son los widgets de la vista, esto se hace redefiniendo un método #initializeWidgets.
SWTClientExamApplication>>initializeWidgets ” Initialize the widgets of the receiver.”
| root |
root := self rootWidget.
root
addWidget: (questionsPanel := SWTPanel new).
En este método hemos definido una variable de instancia que guardara un SWTPanel, que es quien contendrá las preguntas, y a medida que el usuario las contesta, entonces, usaremos este panel para actualizar los datos en el navegador.
SWTClientExamApplication>>updateQuestionsPanel ” Update the questions panel of the receiver.”
questionsPanel clearWidgets.
self question isNil
ifTrue:
[ ^questionsPanel
addWidget: (SWTHeader contents: 'Puntaje final: ' , self finalScore)].
questionsPanel
addWidget: (SWTText
contents: ‘Pregunta: ‘ , (self question perform: #question));
addHorizontalRule.
(self question perform: #answers) do:
[:answer |
questionsPanel addWidget: (SWTButton caption: (answer perform: #answer)
onClick: [:event | self answerChoosed: answer])]
Un detalle que debemos notar de este #updateQuestionsPanel, es que si la pregunta es nula, es decir, ya no tenemos más preguntas para mostrar, entonces mostramos el puntaje final obtenido del usuario.
Para ello definimos el mensaje:
SWTClientExamApplication>>finalScore ” Returns the final score of the receiver.”
^self serverSide score
A la hora que se conecta el cliente con el server, vamos a hacer que este se inicialize con la primer pregunta para que la muestre, para engancharnos de este evento, redefiniremos un mensaje que nos provee el framework, el #justConnected. De esta manera nos aseguramos que solo la pregunta será nula, cuando el examen haya finalizado, dado que cuando se conecta por primera vez el cliente, se inicializará con la primer pregunta del examen.
SWTClientExamApplication>>justConnected ” On just connected the receiver.”
super justConnected.
self newQuestion.
Lo que hemos hecho es decirle, una vez que te conectás con el servidor, envíame una pregunta, e inicializamos el MainPanel, que es nuestro panel donde mostramos las preguntas.
¡Muy bien! Ahora ya tenemos nuestro examen listo ¡Es hora de probarlo!

Y la pantalla final del puntaje:

Resumen
Este ejemplo es muy improductivo e ineficiente, porque, como se puede observar en la ultima imagen, usa demasiados mensajes al servidor, y no usa el MVC distribuido que nos proporciona el framework. En nuestra segunda sección, haremos que este mismo ejemplo, sea hecho con el MVC distribuido, mejorando considerablemente la cantidad de mensajes que usan el servidor a través de eventos.
¡La cantidad de peticiones que realizó fueron 30! En la siguiente sección vamos a explorar y refactorizar ciertos mensajes haciendo uso no solo del MVC distribuido sino también de un modelo en el servidor más activo que en la primer parte.
La interfaz web con SWT con MVC distribuido
Una vez que sabemos como podemos hacerlo funcionar, ahora, vamos a hacerlo funcionar bien.
Vamos a usar el mismo modelo, con algunas modificaciones, pero usando el Model-View-Controller distribuido que provee el framework, el cual, está optimizado para Internet, dado que un mal uso del servidor, con muchos usuarios, sería muy caótico, además de desaprovechar el framework.
Creando nuevas clases
Para empezar vamos a redefinir todo la parte web en otras clases. Las vamos a denominar SWTClientMVCExamApplication y SWTServerMVCExamApplication a las nuevas vistas con MVC.
SWTClientApplication subclass: #SWTClientMVCExamApplication instanceVariableNames: ‘exam questionsPanel’
classVariableNames: ”
poolDictionaries: ”
category: ‘TutorialSWT-SWT’
Y el servidor
SWTServerApplication subclass: #SWTServerMVCExamApplication instanceVariableNames: ‘exam’
classVariableNames: ”
poolDictionaries: ”
category: ‘TutorialSWT-SWT’
Modificando el modelo
A nuestro modelo anterior, tenemos que agregarle la parte escencial del MVC, que es que nos avise cuando un evento se produce en él. Las dos únicas situaciones importante en el modelo, que son de interés para nuestra vista, son cuando se gestiona una nueva pregunta (y esto es porque el usuario ya respondió la anterior o porque comienza el examen) y cuando se termina el examen. Estas son las dos situaciones donde necesitamos que nos avise el modelo, y a las cuales, la vista se suscribirá para tomar acción en ellas.
Para ello modificamos el modelo haciendo que la pregunta actual que está siendo mostrada por el sistema al usuario la guardemos en una variable de instancia del examen, cosa que antes no hacíamos, entonces nuestra clase de examen quedaría de esta manera:
SWTModel subclass: #SWTExamModel instanceVariableNames: ‘questions questionsAnswered currentQuestion’
classVariableNames: ”
poolDictionaries: ”
category: ‘TutorialSWT-Core’
y los mensajes que modificaremos serán #newQuestion y
SWTExamModel>>newQuestion ” Returns a new question for the exam.”
| newQuestion randomIndex maximumInteger |
randomIndex := Random seed: 2345678901.
[ maximumInteger := self questionsNotAnswered size.
maximumInteger = 0 ifTrue: [ self triggerEvent: #examFinished. ^nil ].
newQuestion := self questionsNotAnswered at: (randomIndex nextInt: maximumInteger).
self questionsAnswered includes: newQuestion] whileTrue: [].
self currentQuestion: newQuestion.
self questionsAnswered add: self currentQuestion.
self triggerEvent: #newQuestion.
^self currentQuestion
Como se puede observar en el código del método, lo que hemos agregado es el uso de la variable de instancia #currentQuestion a través de sus mensajes de acceso, y hemos puesto los eventos, el primero denominado #examFinished, que se dispara cuando ya terminaron las preguntas, y el otro #newQuestion, que se dispara cuando hay una nueva pregunta disponible.
Vamos a agregar el mensaje al examen #answerChoosed:, donde se la pasa la respuesta seleccionada y genera una nueva pregunta.
SWTExamModel>>answerChoosed: anAnswer self currentQuestion answerChoosed: anAnswer.
self newQuestion.
Se puede apreciar que ya en este nuevo modelo, levemente modificado, un circuito, donde una vez que se contesta una pregunta, se inicia otra, si se termino, avisa, y si está comenzando, con solo llamar a generar una nueva pregunta damos comienzo al examen. Con este pequeño flujo de trabajo, vamos a rediseñar nuestro Servidor y nuestro Cliente para bajar las 30 peticiones que hace el cliente al servidor.
La vista con MVC
Esta nueva vista, al momento que se conecta con el servidor, le pedirá su modelo (un exámen), registrará los eventos y luego dará comienzo al examen.
SWTClientMVCExamApplication>>justConnected ” On just connected the receiver.”
super justConnected.
self exam: self serverSide exam.
self registerEvents.
self serverSide startExam
La suscripción a los eventos del modelo
SWTClientMVCExamApplication>>registerEvents ” Register the events with the model of the receiver.”
self exam
when: #examFinished send: #showFinalScore to: self;
when: #newQuestion send: #showQuestion to: self.
Y en el lado de la clase, vamos a decirle al framework que solo vamos a pasar el examen hacia la vista como modelo.
SWTClientMVCExamApplication class>>jsClassesToInclude ^ {SWTExamModel}
En este método, cuando se produce un evento u otro, llama a dos mensajes distintos dentro de la vista: #showFinalScore y #showQuestion.
Si terminó el examen, entonces se ejecuta:
SWTClientMVCExamApplication>>showFinalScore ” Shows the final score in the questions panel of the receiver.”
self isolatedAsynchronousRPCMethods:
[ questionsPanel
clearWidgets;
addWidget: (SWTHeader contents: 'Puntaje final: ' , self serverSide score printString) ]
En este método, estamos haciendo que esta operación del cliente sea ejecutada en modo batch de una sola vez contra el servidor. Esto es unas de las características que brinda el framework para reducir el tráfico de datos.
Cada vez que hay una nueva pregunta se ejecuta:
SWTClientMVCExamApplication>>showQuestion ” Shows the questions panel of the receiver.”
questionsPanel clearWidgets.
questionsPanel
addWidget: (SWTText contents: ‘Pregunta: ‘ , (self serverSide currentQuestionString));
addHorizontalRule.
(self serverSide currentQuestionAnswersString) inlineDo:
[:answerString |
questionsPanel addWidget: ((SWTButton caption: answerString)
onClick: [:event | self serverSide answerChoosed: event source caption ])]
En esta método, lo que está realizando es que cada vez que se ejecuta, pide al servidor la pregunta, las posibles respuestas y arma la web.
Armando el servidor con su respectivo examen
En el servidor, vamos a definir #startExam, que lo unico que hará es decirle al modelo que comienze el examen por medio de la generación de una nueva pregunta
SWTServerMVCExamApplication>>startExam ” Start the current exam.”
self exam newQuestion
Los dos siguientes mensajes retornan los textos necesarios para la pregunta y sus posibles respuestas
SWTServerMVCExamApplication>>currentQuestionString ” Returns the current question strings.”
^self exam currentQuestion question
El cliente enviará al servidor el siguiente mensaje cuando la respuesta se haya seleccionado:
SWTServerMVCExamApplication>>answerChoosed: anAnswerString
” Set the answer choosed from the button caption clicked.”
self exam
answerChoosed: (self exam currentQuestion answers detect: [ :answer | answer answer = anAnswerString ])
¡Muy bien! Ahora ya tenemos nuestro nuevo examen listo ¡Es hora de probarlo!

Como podemos analizar de ver en la imagen anterior, la cantidad de pedidos al servidor se vieron reducidos en un 60%, lo que es un gran ahorro de tráfico para cualquier tipo de aplicativo.
Resumen
Como hemos visto a través de este ejemplo, el framework permite hacer aplicativos web en forma muy sencilla, sin ningún uso otras tecnologias más que Smalltalk y usando objetos. Y como se puede comprobar, el uso de un MVC distribuido es eficaz y nos permite tener muchas más ventajas, como multiples vista (N cantidad de navegadores sobre un mismo modelo), optimización de tráfico de datos a través de la red y una actualización sin necesidad de que el cliente lo tenga que peticionar al servidor.