Balsamiq Mockups, una herramienta efectiva para la creación de prototipos (mockups) de Componentes de Software

September 1, 2009 – 2:04 am
Tags: , , ,

Uno de los grandes problemas que tradicionalmente se presentan durante el desarrollo de software a la medida, es el de ofrecer a los usuarios finales un mecanismo que les permita comprender y evaluar de forma temprana las diferentes funcionalidades que el sistema de software les puede ofrecer.

Entre muchas propuestas, el Diseño Centrado en el Usuario (User-Centered Design) constituye una aproximación al diseño fundamentada en tratar de comprender de forma eficiente la forma en que la información es procesada por los usuarios finales que utilizarán el producto.
En ese sentido tal vez una de las herramientas más simples (y efectivas) de diseño centrado en el usuario es el de la utilización de bocetos gráficos o prototipos de interfaz de usuario (mockups) que visualmente permitan dar una idea de la forma en que lucirá el sistema, así como también de evidenciar las diferentes formas en que el usuario podrá interactuar con la aplicación.

No siempre las herramientas CASE (Computer Aided Software Engineering) tradicionales proveen mecanismos que de forma rápida y eficiente faciliten la creación de dichos mockups. Es en este punto donde Balsamiq Mockups se convierte en un programa que de forma increíblemente rápida y eficiente
permite la especificación de interfaces gráficas de usuario.

Una de las grandes fortalezas de la herramienta es su usabilidad, de tal forma que gracias a su entorno amigable tanto usuarios de software principiantes como avanzados pueden lograr resultados rápidos sin recurrir a la consulta adicional de manuales de usuario o tutoriales externos.

Gracias a su facilidad de uso, Balsamiq Mockups permite el diseño eficiente de interfaces gráficas de usuario de tal forma que los usuarios finales pueden ofrecer una rápida retroalimentación al diseño del software mucho tiempo antes de las primeras etapas de construcción y codificación de los prototipos ejecutables del mismo.

Los usuarios finales pueden interpretar visualmente las funcionalidades que potencialmente les ofrecerá el sistema, ofreciendo así la posibilidad de realizar de forma temprana los ajustes y correcciones necesarios que permitan implementar los requerimientos expresados por el cliente.

Balsamiq Mockups tiene varias versiones: Demo WebDesktop y Plugin (integrable con herramientas tales como Jira , XWiki, Confluence o FogBugz.

Técnicamente hablando, Balsamiq Mockups es una herramienta que se creó utilizando Adobe Flex ,
y que para su versión Desktop utiliza la plataforma de Adobe Air.


Intro to Balsamiq Mockups

Balsamiq Mockups provee una librería inicial de 75 componentes que pueden ser extendidos a través de la comunidad de Mockups To Go.

Otro hecho de bastante importancia para resaltar es su precio bastante accesible para cualquier empresa o individuo: $79 dólares (versión desktop)

Referencias:

  • Share/Bookmark

Crear un Lector de Podcasts con Adobe Flex 3

August 29, 2009 – 3:04 pm
Tags: ,

El código fuente publicado en esta entrada muestra una aplicación flex que permite la consulta y descarga de archivos podcast publicados en internet. A continuación una imagen de muestra del aplicativo:


Básicamente el usuario selecciona un canal del ComboBox superior y las entradas de podcast se cargan en la lista que tiene un botón de reproducción y descarga para cada uno de los items.
Cuando el usuario selecciona el botón de reproducción de una de las entradas, el componente de audio carga el mp3 relacionado en el componente del “player” que se encuentra en la parte inferior de la pantalla.

Ver aplicación en Línea (*)

Descargar Código Fuente

Notas (*) :

  • La carga de algunos RSS y el analizador de espectro solo funcionan ejecutando el aplicativo en forma local.
  • La descarga de archivos funciona únicamente si el usuario tiene instalado el  Flash Player versión 10

Código Fuente:

Mp3Player.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
	creationComplete="{init();}" width="100%" height="100%"
	xmlns:fx="com.fusiox.ui.*" 
	layout="vertical">
<mx:Script>
	<![CDATA[
		import mx.rpc.events.ResultEvent;
		import mx.collections.ArrayCollection;
		import mx.controls.Alert;
		import mx.utils.ObjectUtil;
        // Play & Pause Icons
		[Embed(source="assets/Pause.png")] 	[Bindable] public var iconPause:Class;
		[Embed(source="assets/Play.png")] 	[Bindable] public var iconPlay:Class;
		[Embed(source="assets/Next.png")] 	[Bindable] public var iconNext:Class;
 
 
		private var sound:Sound;						// Mp3 File 
		private var soundChannel:SoundChannel;			// Reference to playing channel 
		private var pausePosition:Number;				// Current play position (time)
		private var percent:Number;						// Current played percentage
		private var isPlaying:Boolean = false;			// Is the mp3 playing?
		private var isLoaded:Boolean = false;			// Is the mp3 loaded?
		private var updateSeek:Timer = new Timer(500);	// Timer for updating the seek bar
 
		[Bindable]
        public var podcastChannels:ArrayCollection = new ArrayCollection(
            [ {label:"Music - Free Music Everywhere Podcast", data:'http://feeds2.feedburner.com/FreeMusicEverywhere'}, 
              {label:"Music - Last.fm Free Mp3 - Electronic ", data:'http://ws.audioscrobbler.com/2.0/tag/Electronic/podcast.rss'},
              {label:"Music - Last.fm Free Mp3 - Metal ", data:'http://ws.audioscrobbler.com/2.0/tag/Metal/podcast.rss'},
              {label:"Music - Last.fm Free Mp3 - Jazz ", data:'http://ws.audioscrobbler.com/2.0/tag/Jazz/podcast.rss'},
              {label:"Music - Last.fm Free Mp3 - Rock ", data:'http://ws.audioscrobbler.com/2.0/tag/Rock/podcast.rss'},
              {label:"Music - Last.fm Free Mp3 - Classical ", data:'http://ws.audioscrobbler.com/2.0/tag/Classical/podcast.rss'},
              {label:"News - Guardian Daily", data:'http://www.guardian.co.uk/news/series/guardiandaily/podcast.xml'},
              {label:"News - Tech Weekly", data:'http://www.guardian.co.uk/technology/series/techweekly/podcast.xml'},
              {label:"News - Business Weekly", data:'http://downloads.bbc.co.uk/podcasts/worldservice/bizweekly/rss.xml'},
              {label:"News - BBC Global News", data:'http://downloads.bbc.co.uk/podcasts/worldservice/globalnews/rss.xml'},
              {label:"News - La Historia del Mundo con Diana Uribe", data:'http://www.caracol.com.co/feed.aspx?id=PROG_320899'}, 
              {label:"News - Hora de Negocios con Paola Ochoa", data:'http://www.caracol.com.co/feed.aspx?id=PROG_555605'} 
            ]);
 
		private function init():void { 
			// Add event listener for seek bar updater & start timer							
            updateSeek.addEventListener(TimerEvent.TIMER, updatePosition);
            updateSeek.start();
            // Add mouse click listener for the seek bar
            seekAudioBar.addEventListener(MouseEvent.CLICK, seek);
			// Add event listener that blurs the visualization
			viz.addEventListener("beforeVisualization", vizFade);
			selectChannel();
			//Add Events that come from each renderer. 
			rssList.addEventListener('playPodcast', playPodcast);
		}
 
		private function selectChannel():void {
            rssService.url = channelsCb.selectedItem.data;
			rssService.send();
        } 
 
        protected function onServiceResult(event:ResultEvent):void {
			rssList.dataProvider = event.result.rss.channel.item;
			next();
		}
 
		protected function playPodcast(event:Event):void {
			var selection:Object = rssList.selectedItem ; 
			loadSound(selection.enclosure.url);
		}
 
		private function loadSound(mp3Url:String):void{
			// If the song isn't loaded yet, set up a new sound load request
			sound = new Sound();
			sound.load(new URLRequest(mp3Url));
			// Add an event listener to check for song load complete event
			sound.addEventListener(Event.COMPLETE, songLoaded);
			// Show text for loading song and add event listener for updating
			sound.addEventListener(ProgressEvent.PROGRESS, updateStatus);
			sound.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			loadProgressBar.visible=true;
			loadProgressBar.text = "0 %"
		}
		private function updateStatus(e:ProgressEvent):void {
			var loadPercent:Number = Math.round((e.bytesLoaded / e.bytesTotal) * 100);
			loadProgressBar.text = loadPercent+" %";
		}
		private function onIOError(event:IOErrorEvent):void{
		    Alert.show("The sound could not be loaded: " + event.text, "Error");
		}
 
		// Once the song is loaded...
		private function songLoaded(e:Event):void {
			if(soundChannel){
				soundChannel.stop()
			}
 
			var selection:Object = rssList.selectedItem ; 
			podTitle.text = selection.title;
 
			// Hide status text
			loadProgressBar.visible = false;
			// Remove load event listener
			sound.removeEventListener(Event.COMPLETE, songLoaded);
			// Turn on seek bar
			enableSeek();
			// Play the song
			soundChannel = sound.play(0);
            // Pull the title/artist/album from the ID3 tags and display
            theArtistAlbum.text  = "";
            if(sound.id3.TIT2){
            	theArtistAlbum.text += sound.id3.TIT2;
            }
            if(sound.id3.artist){
            	theArtistAlbum.text += ", "+ sound.id3.artist;
            }
			// Song is loaded
			isLoaded = true;
			seekAudioBar.visible = isLoaded;
			isPlaying = true;
		}
 
		// Play or Pause button has been clicked
		private function playPause(e:Event = null):void  {
			// Song playing?
			if(isPlaying) {
				// Save the current position in the track, stop playback, change button icon
				pausePosition = soundChannel.position;
				soundChannel.stop();
				playPauseBtn.setStyle("icon", iconPlay);
			// Song is not playing?
			} else {
				// The song IS loaded, so play it
				soundChannel = sound.play(pausePosition);
				// Change the button icon to the pause style
				playPauseBtn.setStyle("icon", iconPause);
			}
			// Regardless of playing state, change it now to the opposite
			isPlaying = !isPlaying;
		}
 
		// Enabled the seek (progress) bar
		public function enableSeek():void {
			seekAudioBar.enabled = true;
			seekAudioBar.visible = true;
		}			
 
		// Add a color transform to the visulization to create the fade effect
		private function vizFade(e:Event):void {
			e.target.bitmapData.colorTransform(
				e.target.bitmapData.rect, new ColorTransform(0.75,0.75,0.75,.75));
		}
 
		// Called when the seek (progress) bar needs updating
		private function updatePosition(e:Event):void {
 			// Is a song playing?
 			if(isPlaying && soundChannel) { 
 				// If the position is beyond the song start, calculate & set the percentage
 				percent = soundChannel.position > 0 ? (soundChannel.position/sound.length)*100 : 0;
				seekAudioBar.setProgress(percent,100);
	   			// Tell the seekbar to redraw itself
	   			seekAudioBar.validateNow();
	   		}
		}
 
 
 
 
 
		// Mouse click event on the seekbar calls this function
		private function seek(e:MouseEvent):void {
			// Is the song playing AND is the seekbar enabled and visible?
			if(isPlaying && seekAudioBar.enabled) { 
				// Stop playing at current position, change to new location, continue playing
				soundChannel.stop();
				pausePosition = (seekAudioBar.contentMouseX/seekAudioBar.width)*sound.length;
				soundChannel = sound.play(pausePosition);
				setVolume(this.volumeControl.value)
			}
		}
 
		private function setVolume(volume:Number):void {
            if(soundChannel){
	            var transform:SoundTransform = soundChannel.soundTransform;
	            transform.volume = volume;
            	soundChannel.soundTransform = transform;
            }
        }
 
        private function next():void{
        	var selectedIdx:int = rssList.selectedIndex;
        	if(selectedIdx < rssList.dataProvider.length){
        		rssList.selectedIndex = ++selectedIdx;
        		playPodcast(null);
        	}
        }
 
	]]>
</mx:Script>
	<mx:Style source="style.css" />
	<mx:HTTPService id="rssService" result="{onServiceResult(event)}" resultFormat="object" />
	<mx:Fade id="FadeIn" duration="3000" alphaFrom="0.0" alphaTo="1.0"/>	
	<mx:Fade id="FadeOut" duration="3000" alphaFrom="1.0" alphaTo="0.0"/>
 
	<mx:VDividedBox width="100%" height="100%">
		<mx:VBox width="100%" height="50%"  >
			<mx:Label text="My Flex Based Podcast Reader" fontWeight="bold" fontSize="16" />
			<mx:HBox width="100%">
				<mx:Label text="Available Podcasts:"/>
				<mx:ComboBox id="channelsCb" dataProvider="{podcastChannels}" width="90%" close="selectChannel();"/>	
			</mx:HBox>
			<mx:List id="rssList" width="100%" height="100%" itemRenderer="RSSRenderer" labelField="title"  />
		</mx:VBox>
		<mx:VBox id="podBox" width="100%" height="50%" horizontalAlign="center"  >
			<mx:Text id="podTitle"  fontSize="15" fontWeight="bold" maxWidth="{podBox.width}" />
			<mx:Text id="theArtistAlbum" text="or enter a new URL to load below" fontStyle="italic"/>
			<mx:HBox width="100%" height="100" >
				<fx:Visualization id="viz" type="bars" bars="96" channel="stereo" 
					width="95%" height="{volumeControl.height}" 
					audioFillColor="0xFFFFFF" audioLineColor="0xFFFFFF"/>	
				<mx:VSlider id="volumeControl" allowTrackClick="false" toolTip="{}" liveDragging="true" 
					thumbDrag="setVolume(this.volumeControl.value)" 
					height="100" value=".5" maximum="1" minimum="0"/>						
			</mx:HBox>
 
			<mx:ControlBar width="100%" >
				<mx:Button id="playPauseBtn" click="{playPause(event)}"  icon="{iconPlay}" />
				<mx:Button icon="{iconNext}" click="{next();}"/>
				<mx:ProgressBar id="seekAudioBar"  showEffect="{FadeIn}" height="16" width="100%" 
					enabled="false" visible="false" 
					mode="manual" label="" indeterminate="false" labelPlacement="center"/>	
			</mx:ControlBar>
			<mx:Label id="loadProgressBar" showEffect="{FadeIn}" hideEffect="{FadeOut}" width="100%" height="20"
					enabled="false" visible="false" 
					text="" fontWeight="bold" />
		</mx:VBox>
	</mx:VDividedBox>
</mx:Application>

RSSRenderer.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" 
	width="100%" height="60" 
	verticalGap="0" verticalAlign="middle">
<mx:Script>
<![CDATA[
import flash.net.navigateToURL;
[Embed(source="assets/Play.png")] 	[Bindable] public var iconPlay:Class;
[Embed(source="assets/Save.png")] 	[Bindable] public var iconSave:Class;
//procesa el dato para ser configurado por el renderer	
override protected function commitProperties():void {
	super.commitProperties();
	if (data == null) { return; }
	lbl.label = data.title;
	lbl.toolTip = data.description;
	downloadProgress.text = "";
}
//
protected function playPodcast():void {
	dispatchEvent(new Event('playPodcast', true));
}
//abre la página html original donde se publicó el podcast
private function viewPost():void{
	navigateToURL(new URLRequest(data.link), "_blank");
}
//inicia el proceso de descarga del mp3
private function downloadMp3():void{
	var urlReq:URLRequest = new URLRequest(data.enclosure.url);
	var fileRef:FileReference = new FileReference();
	fileRef.addEventListener(ProgressEvent.PROGRESS, progressHandler);  
   	fileRef.addEventListener(Event.COMPLETE, completeHandler);  
	fileRef.download(urlReq);
}
//Progreso de la descarga del archivo mp3
private function progressHandler(e:ProgressEvent):void {  
     var loadPercent:Number = Math.round((e.bytesLoaded / e.bytesTotal) * 100);
	downloadProgress.text = loadPercent+" %";
} 
private function completeHandler(event:Event):void {  
     downloadProgress.text = "Download Complete";  
}  
	]]>
</mx:Script>
	<mx:LinkButton id="lbl" width="260" color="0x0" fontWeight="bold" click="{viewPost()}"  />
	<mx:Button toolTip="Play" icon="{iconPlay}" click="playPodcast()" height="32" />
	<mx:Button toolTip="Download" icon="{iconSave}" click="downloadMp3()" height="32" />
	<mx:Label id="downloadProgress" text=""  />
</mx:HBox>

Referencias:

  • Share/Bookmark

Desarrollo de aplicaciones colaborativas utilizando Flex 3, Facebook, BlazeDs, Spring y Java

August 22, 2009 – 8:40 am
Tags: , , ,

En el siguiente enlace pueden encontrar la aplicación, código fuente y demos que Kudos Ltda. presenta durante el FlashCampBogotá 2008:
Desarrollo de aplicaciones colaborativas utilizando Flex 3, Facebook, BlazeDs, Spring y Java

A continuación la presentación en SlideShare:

  • Share/Bookmark

Kudos Ltda. en el Flash Camp Bogotá 2009

August 13, 2009 – 12:52 pm
Tags: , , ,

Kudos Ltda. estará presente en el Flash Camp Bogotá 2009 con la conferencia: Desarrollo de aplicaciones colaborativas utilizando Flex 3, Facebook, BlazeDs, Spring y Java.   El evento se realizará el 22 de agosto del 2009 en la Universidad de Bogota Jorge Tadeo Lozano, Bogotá, Colombia.

Qué es el Flash Camp?:

Flash Camp Bogotá es un evento dirigido a estudiantes y profesionales de la industria del diseño, la programacion, ingenieria, publicidad y disciplinas afines que quieran aumentar sus conocimientos en Adobe Flash.

Durante 11 horas los mejores desarrolladores y diseñadores de Colombia, asi como gente del medio de la publicidad digital y un speaker directamente de Adobe Systems estaran brindandonos sus mejores conocimientos y presentaciones.


Entre otros temas del evento tenemos: Adobe Flash®, Adobe Flex®, Adobe AIR®, ActionScript, Flash Catalyst, Flash Media Server, Flash Mobile, , Adobe Creative Suite®, flujos de trabajo entre desarrolladores y diseñadores y el desarrollo de aplicaciones ricas de Internet (RIA) para empresas.

Otras Conferencias:

  • Realidad Aumentada en Flash con AS3
  • Papervision 3D: Puente entre Maya y Flash
  • Juegos en Actionscript 3 y Box2d
  • Introducción a Flex y Actionscript

Más Información:

  • Share/Bookmark

Groovy: Ejemplos Básicos de Programación

July 1, 2009 – 11:52 am
Tags: ,

En el siguiente archivo Groovy Examples pueden encontrarse algunos ejemplos básicos de programación utilizando el lenguaje Groovy.
Entre otros pueden encontrarse los siguientes ejemplos:

  • En el archivo _1_Hello hay una introduccion a la definicion de variables, rangos, listas y mapas y a la definicion de closures
  • En el archivo _2_Estructuras_Logicas hay una descripcion de la forma en que trabajan las estructuras de control basicas y los loops particulares de groovy
  • En el archivo _3_Metodos hay una explicacion del llamado a métodos y las sentencias try_catch_finally

Descargar Archivo Groovy Examples

Ejemplo de Loops con Groovy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 
public static void loops(){
 
//la variable it es el iterador
 
//al igual que todas los loop y closures, las sentencias del bucle se declaran entre corchetes
 
		// inicia en 0
 
		5.times {println "Times + $it "}
 
		1.upto(3) {println "Up + $it "}
 
		4.downto(1) {println "Down + $it "}
 
 
 
		def sum = 0
 
		1.upto(100) {sum += 1}
 
		println sum
 
 
 
		(1..6).each {println "Range $it"}
 
	}
  • Share/Bookmark

Geo Marketing, ¿Herramienta o Gadget?

June 25, 2009 – 12:31 pm
Tags: , , , , ,

El Geomarketing es una herramienta que cuenta con los SIG (Sistemas de Información Geográfica) para relacionar el marketing con aspectos sociodemográficos; existen diversos modelos de investigación  que permiten visualizar distribuciones geográficas que facilitan la toma  de decisiones relevantes a la proyección y concentración de negocios.

Esta herramienta  valora factores representativos para la empresa como la ubicación, la competencia, las sucursales, entre otros.  Además colabora en la identificación de consumidores potenciales teniendo en cuenta aspectos como  el estrato, el nivel sociocultural, las necesidades etc.

Como elementos vitales para la aplicación del Geomarketing se identifican la calidad de la información, la definición de variables a analizar y la definición de objetivos; la información es filtrada, procesada y analizada mediante modelos estadísticos, matemáticos y espaciales obteniendo como resultado mapas temáticos digitales o impresos, en los que son representados los factores anteriormente relacionados respecto a la empresa y los consumidores.  Sin dejar de lado los aspectos cuantitativos y cualitativos que hacen parte de un análisis de este tipo.

Geo Marketing, ¿Herramienta o Gadget?: A continuación se publica la presentación realizada por el economista Javier Carranza en el auditori de la Universidad Nacional de Colombia el Miercoles 24 de Junio de 2009 sobre Geomarketing:

  • Share/Bookmark

gr8conf: Conferencia dedicada a Groovy, Grails y Griffon

June 25, 2009 – 12:00 am
Tags: ,


En días pasados se realizó la conferencia gr8conf dedicada por completo a los temas de Groovy, Grails y Griffon.
Griffon es un nuevo framework destinado al desarrollo de interfaces gráficas:

Although several options exist for interface development in Java, even popular UI toolkits like Swing have been notoriously complex and difficult to use. Griffon, an agile framework that uses Groovy to simplify Swing, makes UI development dramatically faster and easier. In many respects, Griffon is for desktop development what Grails is for web development. While it’s based on Swing, its declarative style and approachable level of abstraction is instantly familiar to developers familiar with other technologies such as Flex or JavaFX.

A continuación algunas de las presentaciones disponibles en Slideshare:

  • Share/Bookmark

Recopilación de Enlaces con Documentación sobre Groovy y Grails

June 4, 2009 – 12:20 pm
Tags: ,

Documentación sobre Groovy y Grails

  • Share/Bookmark

Introducción al Desarrollo de Aplicaciones con Groovy y Grails (Parte 6)

June 3, 2009 – 11:51 am
Tags: , ,

Generación de RSS con Grails

En esta sección del tutorial utilizaremos un plugin para la generación de RSS utilizando grails.  (Puede encontrar una lista de los plugins existentes en la siguiente ubicación: http://grails.org/plugin/home

El plugin que utilizaremos para la generación de rss se llama Feeds.  Para instalarlo ejecutamos el siguiente comando (tenga en cuenta estar ubicado en la carpeta de su proyecto)

> F:\dev\issuetracker>grails install-plugin feeds

Cuando aparezca en la consola el mensaje “Plugin feeds-1.4 installed” podemos seguir al siguiente paso del tutorial.

Vamos a crear un feed rss con los issues creados en el sistema. Primero procedemos a ubicar el archivo /issuetracker/grails-app/controllers/IssueController.groovy y adicionamos el siguiente método:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def feed = {
        render(feedType:"rss", feedVersion:"2.0") {
            title = "Mi feed de prueba "
            link = "http://localhost:8080/issuetracker/issue/feed"
            description = "Issues Grails news feed"
 
            Issue.list().each() { 
            	issue -> entry(issue.submitter) { 
            		link = "http://your.test.server/article/${issue.id}" 
            		issue.description // return the content 
            	} 
            } 
        } 
    }

Si digitamos la dirección http://localhost:8080/issuetracker/issue/feed podremos ver generado el siguiente feed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>Mi feed de prueba</title>
    <link>http://localhost:8080/issuetracker/issue/feed</link>
    <description>Issues Grails news feed</description>
    <item>
      <title>User 1</title>
      <link>http://your.test.server/article/5</link>
      <content:encoded>My report from error</content:encoded>
      <guid>http://your.test.server/article/5</guid>
    </item>
    <item>
      <title>User 2</title>
      <link>http://your.test.server/article/6</link>
      <content:encoded>This component does not work in firefox</content:encoded>
      <guid>http://your.test.server/article/6</guid>
    </item>
  </channel>
</rss>

Si queremos incluir el enlace para la visualización del RSS en nuestra página de listado de Issues podemos adicionar el siguiente código al archivo /issuetracker/grails-app/views/issue/list.gsp

1
2
3
   <div >
	<a href="${createLinkTo(dir:'issue/feed', file:'')}">View RSS</a>
   </div>
  • Share/Bookmark

Introducción al Desarrollo de Aplicaciones con Groovy y Grails (Parte 5)

June 3, 2009 – 11:20 am
Tags: , ,

Modificación de las Vistas Generadas por Grails

En muchas ocasiones no es suficiente la funcionalidad que genera automáticamente el marco de trabajo Grails.  En la presente sección realizaremos algunas modificaciones a las pantallas generadas por la herramienta.

Vamos a la carpeta ‘grails-app/views‘, notese que hay una carpeta por cada tabla creada, en estas carpetas estará en código seudo-HTML para cada una de nuestras vistas en la aplicación.

El siguiente paso es modificar algunas de las vistas para ver su funcionamiento, para nuestro ejemplo vamos a modificar la vista que muestra la lista de “issues”, abrimos el archivo ‘grails-app/views/issue/list.gsp‘, pero, primero vamos a crear un nuevo tag dinamico para nuestra aplicación (ud puede ver mas de los tags en http://grails.org/Dynamic+Tag+Libraries).

Teniendo como base la carpeta de nuestro proyecto ejecutamos el comando:

> grails create-tag-lib Misc

Una vez ejecutado este comando se creara el archivo ‘grails-app/taglib/MiscTagLib.groovy ‘ y a este le vamos a añadir las siguientes líneas de código:

Descargamos la siguiente imagen: http://openclipart.org/people/hrum/hrum_cocktail.png y la copiamos en la carpeta issuetracker\web-app\images (es recomendable reducir el tamaño de la imagen para una mejor visualización).

Ahora modificamos   el archivo ‘grails-app/views/issue/list.gsp ‘ de la siguiente forma: (Nótense las líneas 27, y 39 a la 43.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <meta name="layout" content="main" />
        <title>Issue List</title>
    </head>
    <body>
        <div class="nav">
            <span class="menuButton"><a class="home" href="${resource(dir:'')}">Home</a></span>
            <span class="menuButton"><g:link class="create" action="create">New Issue</g:link></span>
        </div>
        <div class="body">
            <h1>Issue List</h1>
            <g:if test="${flash.message}">
            <div class="message">${flash.message}</div>
            </g:if>
            <div class="list">
                <table>
                    <thead>
                        <tr>
                   	        <g:sortableColumn property="id" title="Id" />
                   	        <th>Component</th>
                   	        <g:sortableColumn property="type" title="Type" />
                   	        <g:sortableColumn property="submitter" title="Submitter" />
                   	        <g:sortableColumn property="description" title="Description" />
                   	        <g:sortableColumn property="status" title="Status" />
                   	        <g:sortableColumn property="bounty" title="Bounty" />
                        </tr>
                    </thead>
                    <tbody>
                    <g:each in="${issueInstanceList}" status="i" var="issueInstance">
                        <tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
                            <td><g:link action="show" id="${issueInstance.id}">${fieldValue(bean:issueInstance, field:'id')}</g:link></td>
                            <td>${fieldValue(bean:issueInstance, field:'component')}</td>
                            <td>${fieldValue(bean:issueInstance, field:'type')}</td>
                            <td>${fieldValue(bean:issueInstance, field:'submitter')}</td>
                            <td>${fieldValue(bean:issueInstance, field:'description')}</td>
                            <td>${fieldValue(bean:issueInstance, field:'status')}</td>
                            <td>
                            	<g:repeat times="${issueInstance.bounty}">  
   									<img src="${createLinkTo(dir:'images', file:'hrum_cocktail.png')}" alt="${issueInstance.bounty} beers"/>  
   								</g:repeat>  
                            </td>
                        </tr>
                    </g:each>
                    </tbody>
                </table>
            </div>
            <div class="paginateButtons">
                <g:paginate total="${issueInstanceTotal}" />
            </div>
        </div>
    </body>
</html>

Ahora nuestro listado de Issues se verá de la siguiente forma:

  • Share/Bookmark