- EL CONOCIMIENTO ES Y DEBE SER LIBRE -

domingo, diciembre 26, 2010

PRIMEROS PASOS CON: SMARTGWT LGPL+APP ENGINE

ACLAREMOS EL PANORAMA
SMARTGWT EE (Enterprise Edition) hace uso del protocolo GWT-RPC; por lo tanto,  el Servidor (contenedor de Servlets) debe entender Java.  Sin embargo, existe un proyecto denominado  GWTPPHP que intenta implementar GWT-RPC para usarlo con PHP (lamentablemente la actividad del proyecto es bastante baja, casi nula) .

SMARTGWT LGPL (FREE) puede recuperar los datos en formato JSON o XML vía HTTP (como se ha mostrado a lo largo de este Blog). Sin embargo, en el foro de SmartGWT encontraran una librería llamada GwtRpcDataSource (desarrollada por marbot)  que nos permite utilizar GWT-RPC, pero todavía no está lo bastante madura.

Debido a lo anteriormente mencionado en este post nos comunicaremos con Appengine a través de scripts jsp(java server page), es decir JSON vía HTTP.

Aunque he conseguido realizar  pequeños proyectos con SmartGWT  LGPL con PHP usando  MVP . Seguir con la misma filosofía  con un Servidor Java (contenedor de Servlets) es factible, aunque tendría que construir un framework MVP (similar al construido con PHP),  en el corto plazo no está en mis planes.

EL EJEMPLO
El ejemplo mostrado tiene el mismo funcionamiento de de la entrada: “PRIMEROS PASOS CON: GWT DESIGNER  + SMARTGWT + PHP”. Solo se ha migrado la aplicación, que inicialmente corría sobre el Servidor Web Apache, para que ahora se ejecute sobre el Almacén de Datos de Appengine.
En esta dirección podrás ver la ejecución del ejemplo:  http://gwtdesigner2010.appspot.com  , la librería SmartGwt es bastante pesada, así que, tendrás que tener un poco de paciencia.
La entrada “PRIMEROS PASOS CON: GWT DESIGNER  + SMARTGWT + PHP” genero un proyecto que lo llamamos “gwtdesigner” es conveniente que habrás este proyecto para completar el ejemplo; ya que, haremos uso del código ahí mostrado.

Los pasos que se muestran en el video-tutorial se pueden resumir como sigue:
  1. Creamos un nuevo proyecto nombrándolo como “gwtdesignerAppEngine“.
  2. Borramos el código generado, por el Asistente, convenientemente (ver el video).
  3. Copiamos “DSDesigner.java” y “gwtdesigner.java” del proyecto “gwtdesigner” a nuestro paquete “pck.client”.
  4. Modificamos  el archivo de configuración (gwt.xml) para que el punto de entrada (entry point) del proyecto sea “gwtdesigner.java”; ya que, el asistente creo  por defecto el “gwtdesignerappengine.java” como punto de entrada.
  5. Eliminamos “gwtdesignerappengine.java”.
  6. Creamos los archivo “add.jsp”,” remove.jsp” y “update.jsp”.
  7. Creamos el archivo “Empleados.java” y “PMF.java”, enseguida los añadimos en “pck.server”.
  8. Modificamos “DSDesigner.java” para que se direccionen a:  “add.jsp”,” remove.jsp” y “update.jsp”.
  9. Subimos la proyecto a AppEngine.   
Esta entrada se continuara actualizando… en el breve plazo.

/*
 * Codigo Fuente PMF.java
 */
package pck.server;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}
/*
 * Codigo Fuente Empleado.java
 */
package pck.server;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Empleado {
 
 @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;
 @Persistent
 private String nombres;
 @Persistent
 private String dni;
 @Persistent
 private String email;
 
 public Empleado(String Nombres, String Dni,String Email) {
  this.nombres=Nombres;
        this.dni=Dni;
        this.email=Email;
    }

 public void setNombres(String nombres) {
  this.nombres = nombres;
 }

 public String getNombres() {
  return nombres;
 }

 public void setDni(String dni) {
  this.dni = dni;
 }

 public String getDni() {
  return dni;
 }

 public void setEmail(String email) {
  this.email = email;
 }

 public String getEmail() {
  return email;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public Long getId() {
  return id;
 }
}
<%
/*
* Codigo Fuente add.jsp
*/
%>
<%@ page import= "pck.server.*" %>
<%@ page import= "java.util.List" %>
<%@ page import= "javax.jdo.PersistenceManager" %>
<%@ page import= "com.google.appengine.repackaged.org.json.JSONArray" %>
<%@ page import= "com.google.appengine.repackaged.org.json.JSONObject" %>
<%
  String nombres=request.getParameter("nombres");
  String dni=request.getParameter("dni");
  String email=request.getParameter("email");
  
  Empleado employee = new Empleado(nombres, dni,email);
  PersistenceManager pm = PMF.get().getPersistenceManager();

  JSONObject enviar=new JSONObject();
  JSONObject resp=new JSONObject();
  JSONArray data=new JSONArray(); 

  try {   
    employee=pm.makePersistent(employee);
             
    JSONObject reg= new  JSONObject();
    reg.put("id", employee.getId());
    reg.put("nombres", nombres);
    reg.put("dni", dni);
    reg.put("email", email);
    data.put(0, new JSONObject(reg.toString()));
    pm.close();
   resp.put("status",0);
   resp.put("data",new JSONArray(data.toString())); 
   enviar.put("response", new JSONObject(resp.toString()));
   
  } catch (Exception e) {
   pm.close();
   response.setContentType("text/plain");
   response.getWriter().println(e.toString());
  }
  
  response.setContentType("text/plain");
  response.getWriter().println(enviar.toString());
%>
<%
/*
* Codigo Fuente fetch.jsp
*/
%>
<%@ page import= "pck.server.*" %>
<%@ page import= "java.util.List" %>
<%@ page import= "javax.jdo.PersistenceManager" %>
<%@ page import= "com.google.appengine.repackaged.org.json.JSONArray" %>
<%@ page import= "com.google.appengine.repackaged.org.json.JSONObject" %>

<%
  JSONObject enviar=new JSONObject();
  JSONObject resp=new JSONObject();
  JSONArray data=new JSONArray(); 
  
  PersistenceManager pm = PMF.get().getPersistenceManager();
   
  try {
   
   String query = "select from " + Empleado.class.getName();
   
      List<Empleado> employees = (List<Empleado>) pm.newQuery(query).execute();   
   
   for(int i=0;i<employees.size();i++){
    
    JSONObject reg= new  JSONObject();
    reg.put("id", employees.get(i).getId());
    reg.put("nombres", employees.get(i).getNombres());
    reg.put("dni", employees.get(i).getDni());
    reg.put("email", employees.get(i).getEmail());
    
    data.put(i, new JSONObject(reg.toString()));
   }
   pm.close();
   resp.put("status",0);
   resp.put("data",new JSONArray(data.toString())); 
   enviar.put("response", new JSONObject(resp.toString()));
   
  } catch (Exception e) {
   pm.close();
   response.setContentType("text/plain");
   response.getWriter().println(e.toString());
  }
  response.setContentType("text/plain");
  response.getWriter().println(enviar.toString());
  
%>
<%
/*
* Codigo Fuente update.jsp
*/
%><%@ page import= "pck.server.*" %>
<%@ page import= "java.util.List" %>
<%@ page import= "javax.jdo.PersistenceManager" %>
<%@ page import= "com.google.appengine.repackaged.org.json.JSONArray" %>
<%@ page import= "com.google.appengine.repackaged.org.json.JSONObject" %>

<%
  String id=request.getParameter("id");
  String nombres=request.getParameter("nombres");
  String dni=request.getParameter("dni");
  String email=request.getParameter("email");
  
  JSONObject enviar=new JSONObject();
  JSONObject resp=new JSONObject();
  JSONArray data=new JSONArray(); 
  
  PersistenceManager pm = PMF.get().getPersistenceManager();
    
  try {
    Empleado e = pm.getObjectById(Empleado.class, Long.parseLong(id));
    
    e.setDni(dni);
    e.setNombres(nombres);
    e.setEmail(email);
    
    pm.close();
    
    JSONObject reg= new  JSONObject();
    reg.put("id", Integer.parseInt(id));
    reg.put("nombres", nombres);
    reg.put("dni", dni);
    reg.put("email", email);
    data.put(0, new JSONObject(reg.toString()));
   
   resp.put("status",0);
   resp.put("data",new JSONArray(data.toString())); 
   enviar.put("response", new JSONObject(resp.toString()));
   
  } catch (Exception e) {
   
   pm.close();
   response.setContentType("text/plain");
   response.getWriter().println(e.toString());
  }
  response.setContentType("text/plain");
  response.getWriter().println(enviar.toString());
%>

martes, noviembre 02, 2010

PRIMEROS PASOS CON: GWT DESIGNER + SMARTGWT + PHP

GWT Designer todavía no soporta ampliamente a SmartGWT, como consecuencia de esto se siente desesperadamente lento el IDE (contrariamente a GWT que se siente más rápido).
Sin embargo, aun con sus carencias puede ayudar considerablemente en el Diseño de una Interfaz.

El ejemplo simplemente mostrara, añadirá y editara Empleados de una gran Empresa (encargada de construir un framework al estilo de Spring Roo), para conseguir con el objetivo haremos uso de”DynamicForm” una clase de SmartGwt que reducirá significativamente el código de las operaciones.

Hasta la línea 116 del código fuente (gwtDesigner.java) lo genera GWT Designer salvo la línea 49 y 116  que se añaden programáticamente “BeginGrid” y “Begin”, respectivamente. Teniendo en claro lo anterior proceda a realizar el ejemplo como se muestran en los videos-tutoriales sin olvidar las Variables Locales que deben ser convertidos en Atributos de la Clase.(dependiendo de su configuracion, por defecto GWT Designer crea los Widgets como Variables Locales)

Nota: Este ejemplo se ejecuta con: GWT 2.1, GWT Designer 8.1, SmartGWT 2.2 y  PHP 5.2.3




Qué realiza DynamicForm?
DynamicForm nos permite crear y editar un registro muy rápidamente, simplemente hay que asignarle un DataSource  al DynamicForm. DynamicForm automáticamente reconocerá los campos del DataSource asignado y se verán reflejados en un Formulario. En nuestro ejemplo creamos los Formularios “dynamicFormNuevo” y “dynamicFormEditar”
public void Begin(){
    ...
    dynamicFormNuevo.setDataSource(DSDesigner.getInstance());
    dynamicFormEditar.setDataSource(DSDesigner.getInstance());
   }
Cómo creamos un Empleado?
Antes de poder crear un Empleado será necesario validar el ingreso de datos. El método “saveData” de DynamicForm llamara automáticamente a “fetch.php”.
public void OnClickNuevo_Guardar(){
    if(dynamicFormNuevo.validate()){
     dynamicFormNuevo.saveData();
     ...
     ...
    }
   }
Cómo editamos un Empleado?
En nuestro ejemplo para poder editar un Empleado necesitamos implementar el evento “onRecordClick” de la grilla “listgrid”.
public void OnClickRecordGrid(){
    dynamicFormEditar.reset();
    dynamicFormEditar.editSelectedData(listGrid);
   }
Enseguida procedemos a validar el ingreso de datos con el método “validate”, después guardamos los cambios realizados con el método “saveData”que automáticamente llamara a “update.php”.
public void OnClickEditar_Guardar(){
    if(dynamicFormEditar.validate()){
     dynamicFormEditar.saveData();
     ...
     ...
    }
   }



/**
 * Codigo Fuente GWTDesigner
 */
public class gwtdesigner implements EntryPoint {
 private DynamicForm dynamicFormEditar;
 private Tab tabEditar;
 private Tab tabNuevo;
 private DynamicForm dynamicFormNuevo;
 private Tab tabGrilla;
 private ListGrid listGrid;
 private TabSet tabSet;
 
 public void onModuleLoad() {
  RootPanel rootPanel = RootPanel.get();
  
  tabSet = new TabSet();
  tabGrilla=new Tab("Empleados");
  
  VLayout layout = new VLayout();
  
  ToolStrip toolStrip = new ToolStrip();
  toolStrip.setSize("100%", "25px");
  
  ToolStripButton btnGrillaNuevo = new ToolStripButton("Nuevo");
  btnGrillaNuevo.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
   public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
    OnClickGrilla_Nuevo();
   }
  });
  btnGrillaNuevo.setIcon("icons/16/new.gif");
  toolStrip.addButton(btnGrillaNuevo);
  
  ToolStripButton btnGrillaEditar = new ToolStripButton("Editar");
  btnGrillaEditar.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
   public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
    OnClickGrilla_Editar();
   }
  });
  btnGrillaEditar.setIcon("icons/16/edit.png");
  toolStrip.addButton(btnGrillaEditar);
  layout.addMember(toolStrip);
  
  listGrid = new ListGrid();
  listGrid.addRecordClickHandler(new RecordClickHandler() {
   public void onRecordClick(RecordClickEvent event) {
    OnClickRecordGrid();
   }
  });
  BeginGrid();
  layout.addMember(listGrid);
  tabGrilla.setPane(layout);
  tabNuevo=new Tab("Nuevo");
  
  VLayout layout_1 = new VLayout();
  
  ToolStrip toolStrip_1 = new ToolStrip();
  toolStrip_1.setSize("100%", "25px");
  
  ToolStripButton btnNuevoGuardar = new ToolStripButton("Guardar");
  btnNuevoGuardar.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
   public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
    OnClickNuevo_Guardar();
   }
  });
  btnNuevoGuardar.setIcon("icons/16/disk_blue.png");
  toolStrip_1.addButton(btnNuevoGuardar);
  
  ToolStripButton btnNuevoSalir = new ToolStripButton("Salir");
  btnNuevoSalir.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
   public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
    OnClickNuevo_Salir();
   }
  });
  btnNuevoSalir.setIcon("icons/16/salir.png");
  toolStrip_1.addButton(btnNuevoSalir);
  layout_1.addMember(toolStrip_1);
  
  dynamicFormNuevo = new DynamicForm();
  layout_1.addMember(dynamicFormNuevo);
  tabNuevo.setPane(layout_1);
  tabEditar=new Tab("Editar");
  
  VLayout layout_2 = new VLayout();
  
  ToolStrip toolStrip_2 = new ToolStrip();
  toolStrip_2.setSize("100%", "25px");
  
  ToolStripButton btnEditarGuardar = new ToolStripButton("Guardar");
  btnEditarGuardar.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
   public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
    OnClickEditar_Guardar();
   }
  });
  btnEditarGuardar.setIcon("icons/16/disk_blue.png");
  toolStrip_2.addButton(btnEditarGuardar);
  
  ToolStripButton btnEditarSalir = new ToolStripButton("Salir");
  btnEditarSalir.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
   public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
    OnClickEditar_Salir();
   }
  });
  btnEditarSalir.setIcon("icons/16/salir.png");
  toolStrip_2.addButton(btnEditarSalir);
  layout_2.addMember(toolStrip_2);
  
  dynamicFormEditar = new DynamicForm();
  layout_2.addMember(dynamicFormEditar);
  tabEditar.setPane(layout_2);
  tabSet.addTab(tabGrilla);
  tabSet.addTab(tabNuevo);
  tabSet.addTab(tabEditar);
  tabSet.setSize("430px", "299px");
  rootPanel.add(tabSet, 10, 10);
  
  Begin();
 }
 /*
    * Inicio de Eventos
    */
   public void OnClickGrilla_Nuevo(){
    tabNuevo.setDisabled(false);
    tabSet.selectTab(1);
    dynamicFormNuevo.reset();
   }
   public void OnClickGrilla_Editar(){
    tabEditar.setDisabled(false);
    tabSet.selectTab(2);
   }
   public void OnClickNuevo_Guardar(){
    if(dynamicFormNuevo.validate()){
     dynamicFormNuevo.saveData();
     tabNuevo.setDisabled(true);
     tabSet.selectTab(0); 
    }
   }
   public void OnClickNuevo_Salir(){
    tabNuevo.setDisabled(true);
    tabSet.selectTab(0);
   }
   public void OnClickEditar_Guardar(){
    if(dynamicFormEditar.validate()){
     dynamicFormEditar.saveData();
     tabEditar.setDisabled(true);
     tabSet.selectTab(0);
    }
   }
   public void OnClickEditar_Salir(){
    tabEditar.setDisabled(true);
    tabSet.selectTab(0);
   }
   public void OnClickRecordGrid(){
    dynamicFormEditar.reset();
    dynamicFormEditar.editSelectedData(listGrid);
   }
   public void BeginGrid(){
       
    listGrid.setDataSource(DSDesigner.getInstance());
    //$hide>>$
    listGrid.setAutoFetchData(true);
    //$hide<<$
    ListGridField  id = new ListGridField ("id", "ID",20);
    ListGridField  nombres = new ListGridField ("nombres", "NOMBRES",100);
    ListGridField  dni = new ListGridField ("dni", "DNI",60);
    ListGridField  telefono = new ListGridField ("email", "E-MAIL");
    listGrid.setFields(id,nombres,dni,telefono);
   }
   public void Begin(){
    //$hide>>$
    tabNuevo.setDisabled(true);
    tabEditar.setDisabled(true);
    //$hide<<$
    dynamicFormNuevo.setDataSource(DSDesigner.getInstance());
    dynamicFormEditar.setDataSource(DSDesigner.getInstance());
   }
}
/**
 * Codigo Fuente DSDesigner
 */
public class DSDesigner extends RestDataSource{

 private static DSDesigner instance = null;
 
 public static DSDesigner getInstance() {
  if (instance == null) {
   instance = new DSDesigner();
  }
  return instance;
 }//end function
 
 private DSDesigner()
 {
  OperationBinding fetch = new OperationBinding();
  fetch.setOperationType(DSOperationType.FETCH);
  fetch.setDataProtocol(DSProtocol.POSTPARAMS);

  OperationBinding add = new OperationBinding();
  add.setOperationType(DSOperationType.ADD);
  add.setDataProtocol(DSProtocol.POSTPARAMS);

  OperationBinding remove = new OperationBinding();
  remove.setOperationType(DSOperationType.REMOVE);
  remove.setDataProtocol(DSProtocol.POSTPARAMS);

  OperationBinding update = new OperationBinding();
  update.setOperationType(DSOperationType.UPDATE);
  update.setDataProtocol(DSProtocol.POSTPARAMS);

  this.setOperationBindings(fetch, add, remove, update);
  this.setDataFormat(DSDataFormat.JSON);
  
  this.setFetchDataURL("fetch.php");
  this.setAddDataURL("add.php");
  this.setUpdateDataURL("update.php");
  
  DataSourceIntegerField id = new DataSourceIntegerField("id");
  id.setPrimaryKey(true); 
 
  DataSourceTextField nombres = new DataSourceTextField("nombres");
  
  RegExpValidator maskDni=new RegExpValidator();
  maskDni.setErrorMessage("dni es invalido");  
  maskDni.setExpression("[0-9]{8}");  
        
  DataSourceTextField dni = new DataSourceTextField("dni");
  dni.setValidators(maskDni);
  dni.setLength(8);
  
  RegExpValidator maskEmail=new RegExpValidator();
  maskEmail.setErrorMessage("e-mail es invalido");  
     maskEmail.setExpression("^([a-zA-Z0-9_.\-+])+@(([a-zA-Z0-9\-])+\.)+[a-zA-Z0-9]{2,4}$");  
           
  DataSourceTextField email = new DataSourceTextField("email");
  email.setValidators(maskEmail);
  
  this.setFields(id,nombres,dni,email);
  
 }//end functiono
 
}//end class
<?php
/*
* Codigo fuente de add.php
*/
$data=array();
$data[0]["id"]=$_POST["id"];
$data[0]["nombres"]=$_POST["nombres"];
$data[0]["dni"]=$_POST["dni"];
$data[0]["email"]=$_POST["email"];
 
$mensaje=array();
$mensaje["response"]["status"]=0;
$mensaje["response"]["data"]=$data;
 
echo json_encode($mensaje);
?>
<?php
/*
* Codigo fuente de fetch.php
*/
$data=array();
$data[0]["id"]=1;
$data[0]["nombres"]="Sanjiv Jivans";
$data[0]["dni"]="86763232";
$data[0]["email"]="sanjiv@smartgwt.com";
 
$data[1]["id"]=2;
$data[1]["nombres"]="Eric Clayberg";
$data[1]["dni"]="89328745";
$data[1]["email"]="eric@gwtdesigner.com";
 
$data[2]["id"]=3;
$data[2]["nombres"]="Fabien Potencier";
$data[2]["dni"]="84563245";
$data[2]["email"]="fabien@symfony.com";
 
$mensaje=array();
$mensaje["response"]["status"]=0;
$mensaje["response"]["data"]=$data;
 
echo json_encode($mensaje);
?>
<?php
/*
* Codigo fuente de update.php
*/
$data=array();
$data[0]["id"]=$_POST["id"];
$data[0]["nombres"]=$_POST["nombres"];
$data[0]["dni"]=$_POST["dni"];
$data[0]["email"]=$_POST["email"];
 
$mensaje=array();
$mensaje["response"]["status"]=0;
$mensaje["response"]["data"]=$data;
 
echo json_encode($mensaje);
?>

miércoles, octubre 13, 2010

MI PRIMER WEB-SITE CONSTRUIDO CON GOOGLE WEB TOOLKIT

Acá le muestro el web-site que realicé con Google Web Toolkit, aunque obviamente tiene sus deficiencias sentía la necesidad de realizar un web-site con este extraordinario framework y aunque con Symfony lo hubiera terminado en la decima parte del tiempo (debido a la curva de aprendizaje) con la ayuda de un experimentado diseñador, maqueteador, etc.

Este web-site está construido casi en su totalidad con GWT (la cabecera de presentación se hizo con flash).  Se utiliza la Api de datos de Youtube , la Api de datos de Picasa,  Google Ajax Feed (RSS), corriendo sobre el Servidor Web Apache con Php.

Inicialmente funcionaba la Api de Google Maps; sin embargo, la última vez que actualice el web-site me sale el molestoso mensaje: “Este sitio web requiere una clave diferente del API de Google Maps. Puedes generar una clave nueva en http://code.google.com/apis/maps”, después de darle vueltas al asunto decide poner una simple imagen, y si hay alguien que me pueda dar una mano le agradecería infinitamente.

Para los aun escépticos aqui el site, tambien vean este de aqui ambos se realizaron practicamente al mismo tiempo.

Actualizado:Después de depurar, me percato que a pesar que le envió un Key valido este no lo reconoce, entonces opte por colocar en el "index" lo siguiente:
<script type="text/javascript" src="http://www.google.com/jsapi?key=TUCLAVE"></script>
Es extraño porque en ningún momento se menciona en la guía de inicio de Google Maps Api para GWT la necesidad de lo anterior.

Saludos

viernes, agosto 13, 2010

Primeros Pasos con la API de datos de Picasa con Google Web Toolkit

Este post debió ser el doble del tamaño que se muestra, pero debido a que me presentaron a la clase JsonpRequestBuilder se disminuyo considerablemente.

Qué puedo hacer con  la Api de Datos de Picasa?
La Api de datos de Picasa te permite modificar, añadir o eliminar álbumes o fotos de Picasa utilizando el Google Data Protocol.
Google Data Protocol, es utilizado  por muchas Apis de Google (Youtube, Reader, Calendar,Picasa etc ). 
El esfuerzo que imprimas en aprender Google Data Protocol te permitirá utilizar los múltiples servicios que Google pone a tu disposición.

Cómo realizo consultas con la Api de Datos de Picasa?
Las consultas las realizaremos con requerimientos HTTP, dichos requerimientos devolverán la respuesta en formato XML o JSON según indiquemos. Para probar su funcionamiento puedes usar cualquier navegador y enviar el siguiente requerimiento:
http://picasaweb.google.com/data/feed/api/user/tombrad2/album
/LaLisera?prettyprint=true&alt=json
“tombrad2” es el usuario de Picasa.
“LaLisera” es el álbum del usuario sobre el cual realizamos la consulta.
 “prettyprint=true” nos muestra la consulta en un formato legible.
“alt=json” le decimos que la respuesta sea en formato JSON.

Algunos ejemplos de consultas a la Api de Datos de Picasa
No está de más hacerles recordar que las consultas que se muestran las pueden realizar en cualquier navegador de su preferencia.
Teniendo como referencia el documento de acá, aquí brindo algunas consultas de las múltiples que se pueden realizar.
http://picasaweb.google.com/data/feed/api/user/tombrad2/album/ParquePeru?prettyprint=true
&alt=json&fields=title,subtitle,icon
Muestra  los campos del album "ParquePeru":”title”,”subtitle” y “icon”.
http://picasaweb.google.com/data/feed/api/user/
tombrad2?prettyprint=true
Muestra los datos de los álbumes del usuario “tombrad2”
http://picasaweb.google.com/data/feed/api/user/
tombrad2/album/ParquePeru?alt=json
&fields=entry/media:group/media:description,entry/media:group/media:thumbnail&kind=photo&thumbsize=640
Muestra las fotos asociadas al álbum “ParquePeru”, con los campos “description” y “thumbanail”. Para una mejor visualización en tu navegador, no te olvides agregar “prettyprint=true”.

Cómo utilizar la Api de Datos de Picasa con Google Web Toolkit?
Antes de responder la pregunta permítanme presentarles a la Clase JavaScriptObject. La Clase  JavaScriptObject es normalmente declarado como el tipo de retorno en un método JSNI (los métodos JSNI pueden declarar tipo de dato retornado a diferencia de los métodos JavaScript nativos). Lamentablemente a la instancia de la Clase (Objeto) JavaScriptObject no podemos utilizarlo directamente en GWT. Para acceder a la data de un Objeto JavascriptObject tenemos dos alternativas. La primera alternativa es descrita en este artículo, la segunda alternativa es utilizar el Objeto JSONObject que recibe en su constructor al Objeto JavaScriptObject. En este post usaremos JSONObject.
GWT permite acceder a requerimientos cross domain mediante la clase JsonpRequestBuilder del siquiente modo:
public void onModuleLoad() {
  
  String url ="http://picasaweb.google.com/data/feed/api/user/tombrad2/album/ParquePeru?alt=json-in-script&fields=entry/media:group/media:description,entry/media:group/media:thumbnail&kind=photo&thumbsize=640";  
  JsonpRequestBuilder jsonp = new JsonpRequestBuilder();
   jsonp.requestObject(url,  new AsyncCallback<JavaScriptObject>(){

    @Override
    public void onFailure(Throwable caught) {
   
     Window.alert(caught.getMessage());
    }
    @Override
    public void onSuccess(JavaScriptObject object) {

     JSONObject obj=new JSONObject(object);
     
     Ejecutar(obj);
     
    }});
  
 }//end function
Mostrando el Ejemplo
En la línea 35 pueden observar cómo se declara la instancia de la clase JSONObject y como se le pasa en el constructor la instancia de la clase JavaScriptObject.
En la líneas  10 y 11 se puede observar la recuperación de los datos almacenados en la instancia de la clase JSONObject. No te dejes intimidar por la aparente complejidad de recuperación de datos!.
public class Apipicasa implements EntryPoint {
 
 VerticalPanel vp=new VerticalPanel();
 
 public void Ejecutar(JSONObject obj){
  int size=(int)obj.get("feed").isObject().get("entry").isArray().size();
  
  for(int i=0;i<size;i++)
  {
   String url=obj.get("feed").isObject().get("entry").isArray().get(i).isObject().get("media$group").isObject().get("media$thumbnail").isArray().get(0).isObject().get("url").isString().stringValue();
   String descripcion=obj.get("feed").isObject().get("entry").isArray().get(i).isObject().get("media$group").isObject().get("media$description").isObject().get("$t").isString().stringValue();
   
   vp.add(new Image(url));
   vp.add(new HTML(descripcion));
   
  }
 
  RootPanel.get().add(vp);
 }//end function
 
 public void onModuleLoad() {
  
  String url ="http://picasaweb.google.com/data/feed/api/user/tombrad2/album/ParquePeru?alt=json-in-script&fields=entry/media:group/media:description,entry/media:group/media:thumbnail&kind=photo&thumbsize=640";  
  JsonpRequestBuilder jsonp = new JsonpRequestBuilder();
   jsonp.requestObject(url,  new AsyncCallback<JavaScriptObject>(){

    @Override
    public void onFailure(Throwable caught) {
   
     Window.alert(caught.getMessage());
    }
    @Override
    public void onSuccess(JavaScriptObject object) {

     JSONObject obj=new JSONObject(object);
     
     Ejecutar(obj);
     
    }});
  
 }//end function
}

viernes, julio 23, 2010

Conociendo la Grilla de SmartGWT (PARTE I)

Uno de los motivos por la que use este framework fue su Grilla (ListGrid y TreeGrid) su potencia lo pueden ver en el showcase pero hay una característica que no se muestra muy claro en el showcase de ejemplos (debido a que los ejemplos corren solo en el lado del cliente) que es lo que se denomina: “live grid”. El término “live grid” hace referencia a la capacidad que tiene la grilla de capturar datos del servidor conforme deslizamos la barra de desplazamiento.

En el ejemplo que muestro a continuación la Grilla (listgrid) captura 15 registros cada vez que deslizamos la barra de desplazamiento. El total de registros a capturar será de 5000, debes de notar que puedes desplazarte a los últimos registros sin necesidad de mostrar todos los anteriores (te sugiero que veas el video al final del post para mayor entendimiento)

ListGrid solicita los primeros 15 registros al Servidor mediante las variables “startRow” y “endRow” con los valores 0 y 15 respectivamente (la asignación de estas variables se realizan automáticamente y sirven para controlar el envió de registros del lado del Servidor), una vez recibidos los registros solicitados por ListGrid este procede a solicitar los siguiente 15 registros modificando las variables “startRow=15” y “endRow=30” y así sucesivamente, esto se ve claramente en el video-guía.

Ahora explicaremos algo del código fuente: “Blogspot.java”, en la línea  27 se le dice a la Grilla que capture 15 registros por requerimiento, además en la línea 29 le decimos a la grilla que  auto capture la data conforme deslizamos la barra de desplazamiento.
/*
*Codigo Fuente Blogspot.java
*/
public class Blogspot implements EntryPoint {
 
 public void onModuleLoad() {
  
 DataSourceIntegerField id = new DataSourceIntegerField("id","id",20);
    id.setPrimaryKey(true); 
    DataSourceTextField descripcion = new DataSourceTextField("descripcion");
   
    //preparamos para la captura de la data
    OperationBinding fetch = new OperationBinding();
    fetch.setOperationType(DSOperationType.FETCH);
    fetch.setDataProtocol(DSProtocol.POSTPARAMS);
    
    RestDataSource data=new RestDataSource();  
    data.setOperationBindings(fetch);
    data.setDataFormat(DSDataFormat.JSON);
    data.setFetchDataURL("fetch.php");
    data.setFields(id,descripcion);
    
        ListGrid lg=new ListGrid();
     lg.setCanFreezeFields(true);
     lg.setHeight(200);
     //capturamos 15 registros por pagina
     lg.setDataPageSize(15);
   
     lg.setAutoFetchData(true);
     lg.setDataSource(data);
     lg.setShowAllRecords(false);
     
     RootPanel.get("idImgVertical").add(lg);
 }
}
/*
* Codigo fuente de fetch.php
*/
$st=$_POST["_startRow"];
$en=$_POST["_endRow"];

$data=array();

for($i=0;$i<15;$i++)
{
 $data[$i]['id']=$st+$i;
 $data[$i]['descripcion']='descricion de :'.$i; 
}
$mensaje=array();
$mensaje['response']['status']=0;
$mensaje['response']['startRow']=$st;
$mensaje['response']['endRow']=$en;
$mensaje['response']['totalRows']=5000;
$mensaje['response']['data']=$data;

echo json_encode($mensaje);

lunes, julio 12, 2010

Integrando jQuery, Gwt y Php

La idea de este Post no es realizar un  tutorial, sino una guía de introducción que permitan al programador de una manera rápida ver de qué trata el asunto. Al final se muestra un pequeño ejemplo junto con su respectivo video-guía de implementación para que cualquiera con algo de entusiasmo pueda realizarlo.

La integración  de jQuery a GWT se consigue a través de JSNI(JavaScript Native Interfaze) y la integración de GWT con PHP lo realizaremos a través de RestDataSource (librería de SmartGWT).
Lectura Obligada: GWT Style, Configuration and JSNI Reference

Introducción
El JSNI nos brinda la posibilidad de utilizar métodos Java que contengan código JavaScript, estos métodos de Java deberán de tener el modificador  “native”, con este modificador le comunicamos al compilador que no valide su contenido.

Llamado métodos JavaScript de Java
Llamar métodos JavaScript de Java es bastante sencillo, solo tienes que tener en cuenta los alias de los objetos JavaScript:  “window” y “document”,  que son:  “$wnd” y “$doc” respectivamente.
//Capturamos la imagen snapshot del video
 public native String getImageUrl(String cad)/*-{ 
  //aca llamamos a JavaScript de Java
  return $wnd.$.jYoutube(cad);  
 }-*/;


Llamando métodos Java de JavaScript
Llamar métodos Java de JavaScript implica el manejo de una sintaxis especial.
[instance-expr.]@class-name::method-name(param-signature)(arguments)
La explicación de esta sintaxis la encuentran  acá. (Para encontrar el “class-name” use el autocompletado  de Eclipse, y no se hagan tanto rollo con eso de “fully qualified name”).
//Mostramos la imagen sin efecto 
 public void ShowImage(String url)
 {
  ImgPrincipal.setUrl(url);
 }
  
   //Ocultamos la imagen con efecto SLOW 
 public native void HideImage(String url)/*-{ 
  var tempThis=this;  
  $wnd.$("#idImgPrincipal").hide('slow',function(){          
    //aca llamamos a Java de JavaScript
    tempThis.@jsni.pck.client.Jsni::ShowImage(Ljava/lang/String;)(url);
    $wnd.$("#idImgPrincipal").show('fast');
  });    
 }-*/;
No está de más mencionar que “ShowImage” y “HideImage” pueden ser llamados desde cualquier parte de nuestra aplicación Java.


Pasando valores entre Java y JavaScript y viceversa
Este tema no es complicado, pero en el documento del inicio del Post hay una muy buena guía sobre este tema.

Integración con PHP
Se puede utilizar la integración con php a través de RequestBuilder, pero nosotros utilizaremos RestDataSource que como verán en el ejemplo nos brinda la data ya formateada, de modo tal que su utilización se hace mucho más fácil; ya que, no tenemos que lidear con el formato JSON directamente.
//preparamos para la captura de la data
  OperationBinding fetch = new OperationBinding();
  fetch.setOperationType(DSOperationType.FETCH);
  fetch.setDataProtocol(DSProtocol.POSTPARAMS);
   
  RestDataSource data=new RestDataSource();  
  data.setOperationBindings(fetch);
  data.setDataFormat(DSDataFormat.JSON);
  data.setFetchDataURL("fetch.php");
  //capturamos la data asincronamente
  data.fetchData(null, new DSCallback(){
   @Override
   public void execute(DSResponse response, Object rawData,
     DSRequest request) {
     
     OnExecuteFetch(response.getData());  
   } 
  }); 
Integración con JQUERY
La integración es bastante sencilla sola haga referencia a los scripts deseados

    
    
El Ejemplo
Normalmente los métodos de Jquery  tienen una función de Callback(Retrollamada) al concluir alguna operación :
$('.tag').hide(“slow”,function(){
  
  alert(‘el tag fue ocultado’)

 });
La idea del ejemplo es mostrar como mediante JSNI podemos llamar al método “hide” y como implementar la función de Callback (Retrollamada) todo dentro de GWT.

Ahora explicaremos algo del código fuente "Jsni.java". En la línea 9 el método “ShowImage” muestra una imagen dada una url, en la línea 16 el método “HideImage” oculta una imagen dada una url , para cumplir con su objetivo hace uso de Jquery con su método “hide” (creando el efecto de ocultamiento)que a su vez realiza una retrollamada (Callback) al ocultar la imagen, al ocultar la imagen lanza la nueva imagen por mostrar llamando al método “ShowImage”.

La integración con PHP podemos verlo en la línea 83, donde se le brinda la ubicación de: “fetch.php”. La data se captura asíncronamente, una vez que la data llega al cliente se lanza el evento “OnExecuteFetch” (en la línea 90).


//Codigo fuente Jsni.java
 
public class Jsni implements EntryPoint {

 private  Image ImgPrincipal=null;
 private VerticalPanel ImgVertical=null;
 
 
   //Mostramos la imagen sin efecto 
 public void ShowImage(String url)
 {
  ImgPrincipal.setUrl(url);
 }
 
   //Ocultamos la imagen con efecto SLOW 
 public native void HideImage(String url)/*-{ 
  var tempThis=this;  
  $wnd.$("#idImgPrincipal").hide('slow',function(){          
    //aca llamamos a Java de JavaScript
    tempThis.@jsni.pck.client.Jsni::ShowImage(Ljava/lang/String;)(url);
    $wnd.$("#idImgPrincipal").show('fast');
  });    
 }-*/;
 
   //Capturamos la imagen snapshot del video
 public native String getImageUrl(String cad)/*-{ 
  //aca llamamos a JavaScript de Java
  return $wnd.$.jYoutube(cad);  
 }-*/;

   //Evento lanzado cuando se ejecuta la Fetch
 public void OnExecuteFetch(Record[] row)
 {
  for(Record tempRow:row){
   
   final String url=tempRow.getAttributeAsString("url");
   String titulo=tempRow.getAttributeAsString("titulo");
   
   HorizontalPanel hp=new HorizontalPanel();
   Image img=new Image(getImageUrl(url));
   img.setSize("50px", "50px");
   
   HTML label=new HTML(titulo);
    label.addClickHandler(new ClickHandler(){ 
     @Override
     public void onClick(ClickEvent event) {
     
      HideImage(getImageUrl(url));
      
     }});
  
   hp.add(img);
   hp.add(label);
   
   ImgVertical.add(hp);
  } 
 }
 
  // Enlaza los Widgets a la Interfaz 
 public void EnlazaInterfaz()
 {
  ImgPrincipal=new Image();
  ImgPrincipal.setSize("250px", "250px");
  
  ImgVertical=new VerticalPanel();
  
  RootPanel.get("idImgPrincipal").add(ImgPrincipal);
  RootPanel.get("idImgVertical").add(ImgVertical);
 }
 public void onModuleLoad() {
  
  //Enlazamos al HTML
  EnlazaInterfaz();
  
  //preparamos para la captura de la data
  OperationBinding fetch = new OperationBinding();
  fetch.setOperationType(DSOperationType.FETCH);
  fetch.setDataProtocol(DSProtocol.POSTPARAMS);
  
  RestDataSource data=new RestDataSource();  
  data.setOperationBindings(fetch);
  data.setDataFormat(DSDataFormat.JSON);
  data.setFetchDataURL("fetch.php");
  //capturamos la data asincronamente
  data.fetchData(null, new DSCallback(){
   @Override
   public void execute(DSResponse response, Object rawData,
     DSRequest request) {
    
     OnExecuteFetch(response.getData());  
   } 
  }); 
 
 }//end function
}//end class
/*
* Codigo fuente de fetch.php
*/
$data=array();
$data[0]['id']=1;
$data[0]['descripcion']='inaguracion en el año 3000';
$data[0]['url']='http://www.youtube.com/watch?v=dz1GpyU6YKQ';
$data[0]['titulo']='INAGURACION SAN FRANK';

$data[1]['id']=2;
$data[1]['descripcion']='inaguracion en el año 2009';
$data[1]['url']='http://www.youtube.com/watch?v=9-FQly3ETP8';
$data[1]['titulo']='INAGURACION SAN JOSE';

$data[2]['id']=3;
$data[2]['descripcion']='inaguracion en el año 2010';
$data[2]['url']='http://www.youtube.com/watch?v=wqE4SdrIu1E';
$data[2]['titulo']='INAGURACION SAN JUMANOR';

$mensaje=array();
$mensaje['response']['status']=0;
$mensaje['response']['data']=$data;

echo json_encode($mensaje);
Los Widgets serán enlazados a los ids: “idImgVertical” y “idImgPrincipal”, como se muestra en el código fuente “Jsni.html”
<!--Codigo fuente Jsni.html-->
<h1>Aplicando JSNI con PHP</h1><table style="width: 605px;" border="1" height="343"><tbody>
<tr><td height="160" width="166"><div id="idImgVertical" style="height: 250px;"></div></td><td width="127"></td>      <td width="290"><div id="idImgPrincipal" style="height: 250px; width: 250px;"></div></td>   </tr>
</tbody> </table>
En este video-guía  se asume que ha creado un proyecto con el nombre “jsni” y paquete “jsni.pck”. Adicionalmente debe de tener instalado GWT 2.0.3 y SmartGWT 2.2. En la carpeta war del proyecto tendrá que crear una carpeta nombrándola como “Scripts” en donde deberá de colocar los archivos “JQuery  1.4.2.js” y “JYouTube.js”.

sábado, junio 26, 2010

Primeros Pasos con Google Maps Api para GWT

Hoy tratare de mostrar un poco del Api de Google Maps para GWT, en realidad los ejemplos que vienen con la Api son bastante explicativos, pero acá mostrare un pequeño vistazo. El ejemplo que muestro lo realice íntegramente con GWT nativo.

¿Cómo muestro un mapa con Google Maps Api para GWT?
 Para insertar un mapa de Google Maps tienes que solicitar un API Key (solo si la aplicación se encuentra en un servidor público); sin embargo, podrás usar la Google Maps Api  sin necesidad de la API Key, siempre que ejecutes localmente (localhost).
/**** Codigo fuente GoogleMapsExample.java *****/
public class GoogleMapsExample implements EntryPoint {

 public void onModuleLoad() {
  /*
   * En el primer parametro se debe de colocar
   * el Api Key en caso sea necesario  
  */
 Maps.loadMapsApi("", "2", false, new Runnable() {
  public void run() {
          buildUi();
        }
  });      
 }
 private void buildUi()
 {  
  final MapWidget map = new MapWidget();
 map.setWidth("500px");
  map.setHeight("500px");
 final LatLng coordenadas = LatLng.newInstance(-18.0376, -70.2506);
 //mostramos el mapa centrado con las coordenas (-18.0376, -70.2506)
 map.setCenter(coordenadas);
 //establecemos en nivel de zoom
 map.setZoomLevel(18);      
 RootPanel.get().add(map);    
 }
}   
¿Cómo añadir controles?
 Se pueden añadir muchos tipos de controles de zoom y navegación aquí la documentación.
private void buildUi()
 {
  final MapWidget map = new MapWidget();
  map.setWidth("500px");
  map.setHeight("500px");
  final LatLng coordenadas = LatLng.newInstance(-18.0376, -70.2506);
  //mostramos el mapa centrado con las coordenas (-18.0376, -70.2506)
  map.setCenter(coordenadas);
  //establecemos en nivel de zoom
  map.setZoomLevel(18);
  //añadimos control selector de tipo de mapa
  map.addControl(new MapTypeControl());
  //añadimos control de desplazmiento con zoom
  map.addControl(new LargeMapControl());
  //añadimos control escala de mapa
  map.addControl(new ScaleControl());
  RootPanel.get().add(map);        
 }
¿Cómo cambiar el tipo de mapa y añadir marcas?
 Podemos escoger el tipo de mapa que deseamos (satelital, hibrido, normal, etc.), además de poder añadir marcar a nuestro mapa.
private void buildUi()
 {
  final MapWidget map = new MapWidget();
  map.setWidth("500px");
  map.setHeight("500px");
  final LatLng coordenadas = LatLng.newInstance(-18.0376, -70.2506);
  //mostramos el mapa centrado con las coordenas (-18.0376, -70.2506)
  map.setCenter(coordenadas);
  //establecemos en nivel de zoom
  map.setZoomLevel(18);
  //añadimos control selector de tipo de mapa
  map.addControl(new MapTypeControl());
  //añadimos control de desplazamiento con zoom
  map.addControl(new LargeMapControl());
  //añadimos control escala de mapa
  map.addControl(new ScaleControl());
  //cambiamos a vista mapa satelital
  map.setCurrentMapType(MapType.getSatelliteMap());
  //creamos un marcador en la coordenadas (-18.0376, -70.2506)
  Marker mrk=new Marker(coordenadas);
  map.addOverlay(mrk);
  RootPanel.get().add(map);        
 }
¿Cómo lanzar una Ventana de Información (InfoWindow) al hacer un click sobre un Marcador?
 Al hacer un click sobre un Marcador se puede levantar una Ventana de Información que viene por defecto; sin embargo, esta Ventana de Información puede contener contenido html como Widgets.
private void buildUi()

 {

  final MapWidget map = new MapWidget();

  map.setWidth("500px");

  map.setHeight("500px");

  final LatLng coordenadas = LatLng.newInstance(-18.0376, -70.2506);

  //mostramos el mapa centrado con las coordenas (-18.0376, -70.2506)

  map.setCenter(coordenadas);

  //establecemos en nivel de zoom

  map.setZoomLevel(18);

  //añadimos control selector de tipo de mapa

  map.addControl(new MapTypeControl());

  //añadimos control de desplazamiento con zoom

  map.addControl(new LargeMapControl());

  //añadimos control escala de mapa

  map.addControl(new ScaleControl());

  //cambiamos a vista mapa satelital

  map.setCurrentMapType(MapType.getSatelliteMap());

  //creamos un marcador en la coordenadas (-18.0376, -70.2506)

  final Marker mrk=new Marker(coordenadas);

  map.addOverlay(mrk);  

  mrk.addMarkerClickHandler(new MarkerClickHandler(){

   @Override

   public void onClick(MarkerClickEvent event) {

    //Widget en InfoWindow Normal

    Image img=new Image();

    img.setUrl("http://lh6.ggpht.com/_oxEB1W000Zc/S5sCOsecjdI/AAAAAAAAAGg/_CDb-vUE7gs/s800/13%20DE%20SET.%20%20PLAZA%20ATMAT.JPG");

    img.setWidth("235px");

    img.setHeight("267px");

    //Widget en InfoWindow Maximizado

    HTML video= new HTML("<object width='180' height='160'><param name='movie' value='http://www.youtube.com/v/8qp_VSmV17I&hl=es_ES&fs=1&rel=0'></param><param name='allowFullScreen' value='true'></param><param name='allowscriptaccess' value='always'></param><embed src='http://www.youtube.com/v/8qp_VSmV17I&hl=es_ES&fs=1&rel=0' type='application/x-shockwave-flash' allowscriptaccess='always' allowfullscreen='false' width='180' height='160'></embed></object>");

    HTML album= new HTML("<embed type='application/x-shockwave-flash' src='http://picasaweb.google.com/s/c/bin/slideshow.swf' width='180' height='160' flashvars='host=picasaweb.google.com&hl=es&feat=flashalbum&RGB=0x000000&feed=http%3A%2F%2Fpicasaweb.google.com%2Fdata%2Ffeed%2Fapi%2Fuser%2FVictorCabreraZolla%2Falbumid%2F5447950544182068785%3Falt%3Drss%26kind%3Dphoto%26hl%3Des' pluginspage='http://www.macromedia.com/go/getflashplayer'></embed>");    

    HorizontalPanel hp=new HorizontalPanel();

    hp.setSpacing(20);

    VerticalPanel vp=new VerticalPanel();

    vp.add(new HTML("video"));

    vp.add(video);

    vp.add(new HTML("diapositiva"));

    vp.add(album);

    hp.add(new HTML("<p style=\"text-align: justify;\">Este parque se construyo como un esfuerzo mancomunado del Sr. Alcalde  Jorge Jumanor  con los vecinos, haciendo realidad un sueño muchas veces  postergado por las anteriores autoridadede de Turno</p><p style=\"text-align: justify;\">La Obra se empezo  a contruir a inicios del año 2009 culminandose satisfactoriamente a  mediados del  2009 en el aniversario del Distrito Gregorio  Albarracion  Lanchipa</p>"));

    hp.add(vp);

    //creamos la Ventana de Informacion

    InfoWindowContent info=new InfoWindowContent(img);    

    info.setMaxTitle("Parque Perez Gamboa");

    info.setMaxContent(hp);

    map.getInfoWindow().open(mrk.getLatLng(),info);

   }

  }); 

  RootPanel.get().add(map);        

 } 
 En en siguiente video se muestra la ejecución del ejemplo:

sábado, junio 19, 2010

Cómo integrar SmartGWT con PHP usando DataSource?

SmartGwt no es otro tradicional framework Ajax que solo presenta y muestra datos via XML/Json u otro mecanismo (de estos hay bastantes!!!).  SmartGwt fue concebido para la administración del estado del lado del cliente y la propagación de cambios en el servidor. Por ejemplo, cuando tenemos varias grillas mostrando datos de una tabla y hemos añadido un registro por medio de un formulario, en ese instante deben de actualizarse las grillas automáticamente. Lo anterior se consigue con un mecanismo denominado databinding, lo cual significa que todas las grillas(widgets) se han enlazado al DataSource. Todas las operaciones CRUD se reflejaran automáticamente a todos los widgets enlazados.
A continuación implementaremos un ejemplo bastante básico de como SmartGwt se integra con PHP:

/*
* Codigo fuente de DataSourceCountry.java
*/
public class DataSourceCountry extends RestDataSource{

 private static DataSourceCountry instance = null;    
 
 public static DataSourceCountry getInstance() {  
       
     if (instance == null) {  
            
            instance = new DataSourceCountry();
            
        }  
        return instance;  
    }//fin de funcion
 static void AddData(Record record)
    {
     instance.addData(record);   
    }//fin de funcion 
    static void RemoveData(Record record)
    {
     instance.removeData(record);     
    }//fin de funcion
//public DataSourceCountry() Corregido por el amigo anonimo
    private DataSourceCountry()
    {
      DataSourceIntegerField id = new DataSourceIntegerField("id", "CATEGORIA ID"); 
   id.setPrimaryKey(true);  
   
   DataSourceTextField idcategoria = new DataSourceTextField("idcategoria", "PERTENECE A");  
   idcategoria.setForeignKey("id");  
   idcategoria.setRootValue("");  
 
   DataSourceTextField descripcion = new DataSourceTextField("descripcion", "DESCRIPCION");   
   DataSourceTextField categoria = new DataSourceTextField("categoria", "CATEGORIA");

   this.setFields(id,idcategoria,descripcion,categoria);  
   
   OperationBinding fetch = new OperationBinding();
   fetch.setOperationType(DSOperationType.FETCH);
   fetch.setDataProtocol(DSProtocol.POSTPARAMS);
   
   OperationBinding add = new OperationBinding();
   add.setOperationType(DSOperationType.ADD);
   add.setDataProtocol(DSProtocol.POSTPARAMS);
           
   OperationBinding remove = new OperationBinding();
   remove.setOperationType(DSOperationType.REMOVE);
   remove.setDataProtocol(DSProtocol.POSTPARAMS);
            
   this.setOperationBindings(fetch,add,remove);
   this.setDataFormat(DSDataFormat.JSON); 
  
   this.setFetchDataURL("fetch.php");  //integracion con PHP
   this.setAddDataURL("add.php");      //integracion con PHP
   this.setRemoveDataURL("remove.php");//integracion con PHP
    }
}
<?php
/*
* Codigo fuente de fetch.php
*/
$data=array();
$data[0]['id']=1;
$data[0]['descripcion']='descripcin plazas';
$data[0]['categoria']='plazas';
$data[0]['idcategoria']='';

$data[1]['id']=2;
$data[1]['descripcion']='descripcin comedores';
$data[1]['categoria']='comedores';
$data[1]['idcategoria']='';

$mensaje=array();
$mensaje['response']['status']=0;
$mensaje['response']['data']=$data;

echo json_encode($mensaje);
?>
En el archivo "remove.php" podemos notar que al remover un registro el DataSource envía el "id" del registro a eliminar, entonces el Servidor debe de responder con el "id" y un "status=0".
<?php
/*
* Codigo fuente de remove.php
*/
$data=array();
$data[0]['id']=$_POST['id'];

$mensaje=array();
$mensaje['response']['status']=0;
$mensaje['response']['data']=$data;

echo json_encode($mensaje);
?>
En el archivo "add.php" el DataSource envía el campo "descripción" y "categoria" enseguida el Servidor le devuelve los campos pero aumentando los campos "id" y "idcategoria".
<?php
/*
* Codigo fuente de add.php
*/
$data=array();
$data[0]['id']=3;
$data[0]['descripcion']=$_POST['descripcion'];
$data[0]['categoria']=$_POST['categoria'];
$data[0]['idcategoria']='';

$mensaje=array();
$mensaje['response']['status']=0;
$mensaje['response']['data']=$data;

echo json_encode($mensaje);
?>
Explicare un poco el archivo "Probandatasource.java". En la linea 21,37,41 realizamos el databinding con el DataSource con los Widgets TreeGrid,ListGrid y SelectItem respectivamente. En la linea 50 creamos un registro y lo enviamos al DataSource (al pulsar el botón añadir). En la linea 65 capturamos el "id" del registro seleccionado y lo enviamos al DataSource(al pulsar el botón eliminar)
/*
* Codigo fuente de Probandodatasource.java
*/
public class Probandodatasource implements EntryPoint {

 public void onModuleLoad() {
  final TreeGrid  countryGrid = new TreeGrid ();
        countryGrid.setCanFreezeFields(true);         
        countryGrid.setWidth("100%");  
        countryGrid.setHeight("100%");  
        countryGrid.setShowAllRecords(true);
        countryGrid.setLoadDataOnDemand(false);
       
        TreeGridField  descripcion = new TreeGridField ("descripcion", "Descripcion", 200);  
        TreeGridField  categoria = new TreeGridField ("categoria", "Categoria",200);  
  
        countryGrid.setFields(categoria, descripcion);  
        countryGrid.setCanResizeFields(true); 
        countryGrid.setAutoFetchData(true);

        countryGrid.setDataSource(DataSourceCountry.getInstance());
         
        final ListGrid  countryGridList = new ListGrid ();
        countryGridList.setCanFreezeFields(true);         
        countryGridList.setWidth("100%");  
        countryGridList.setHeight("100%");  
        countryGridList.setShowAllRecords(true);
        //countryGridList.setLoadDataOnDemand(false);
       
        ListGridField  descripcionList = new ListGridField ("descripcion", "Descripcion", 200);  
        ListGridField  categoriaList = new ListGridField ("categoria", "Categoria",200);  
  
        countryGridList.setFields(categoriaList, descripcionList);  
        countryGridList.setCanResizeFields(true); 
        countryGridList.setAutoFetchData(true);

        countryGridList.setDataSource(DataSourceCountry.getInstance());
        
        final SelectItem combobox = new SelectItem("ComboBox");  
    combobox.setWidth(240);  
    combobox.setOptionDataSource(DataSourceCountry.getInstance());  
    combobox.setDisplayField("categoria");  
    combobox.setPickListWidth(450);  
    combobox.setPickListFields(categoriaList, descripcionList); 
        
 
        IButton btnAnadir=new IButton("Añadir");
        btnAnadir.addClickHandler(new ClickHandler(){

   @Override
   public void onClick(ClickEvent event) {
    ListGridRecord rec = new ListGridRecord();    
    rec.setAttribute("idcategoria","");                          
                                rec.setAttribute("descripcion","descripcion de jardines");  
                                rec.setAttribute("categoria","jardines");  
    
    DataSourceCountry.AddData(rec);
   }
         
        });
        
        IButton btnRemove=new IButton("Remover");
        btnRemove.addClickHandler(new ClickHandler(){

   @Override
   public void onClick(ClickEvent event) {
    ListGridRecord id=countryGrid.getSelectedRecord();
    
    DataSourceCountry.RemoveData(id);
    }
         
        });
        
        VLayout vl=new VLayout();
        vl.setWidth100();
        vl.setHeight100();
        vl.addMember(countryGrid);
        vl.addMember(countryGridList);
        
        DynamicForm df=new DynamicForm();
        df.setItems(combobox);
        vl.addMember(df);
        
        vl.addMember(btnAnadir);
        vl.addMember(btnRemove);
        vl.draw();
        
 }
}
La implementación de este pequeñísimo ejemplo lo pueden ver en el video, cabe mencionar que copio y pego el código fuente de este blog a Eclipse; tal que, cualquiera con algo de entusiasmo y ganas lo pueda ejecutar.





Nota: Tengo la terrible costumbre de enfocarme en el funcionamiento, en lo próximos posts tratare de hacer ejemplos mucho mas estéticos!

sábado, mayo 29, 2010

Ejecutar y Depurar GWT sobre el Servidor Web Apache (PHP) con Eclipse

Compilar GWT 2.0.3 toma un tiempo que no es despreciable (bastante tiempo, para los que venimos de aplicaciones de escritorio), además de no poder depurar la aplicación (del lado del cliente) .GWT permite depurar tu aplicación del lado del cliente aun cuando la aplicación del lado del servidor este corriente sobre el Servidor Web Apache.
  1. Debes crear un host virtual (aca esta el como) en mi caso cree “http://prublogspot” que direcciona a la carpeta ”war” que tiene como ruta “D:\workspace_jumanor\prublogspot\war”, esta carpeta es la que me genera por defecto al crear un proyecto GWT con Eclipse.
  2. En la carpeta “war” deberan de ubicarse los archivos php en mi caso: "data.php".
  3. En Eclipse no dirigimos a “Run->Run Configuration” luego a la opción “Arguments” y modificamos “-startupUrl” y agregamos “-noserver” quedando asi:
  4. -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" 
    -startupUrl http://prublogspot/Prublogspot.html
    -logLevel INFO 
    -war D:\workspace_jumanor\prublogspot\war prublogspot.pck.Prublogspot 
    -noserver prublogspot.pck.Prublogspot
    
  5. Del mismo modo “Run->Debug Configuration” luego a la opción “Arguments” y modificamos como en el paso anterior.
Para probar el funcionamiento ejecutamos un codigo similar al siguiente:
try{ 
  RequestBuilder req= new RequestBuilder(RequestBuilder.POST, URL.encode("http://prublogspost/data.php");
  req.setHeader("Content-Type", "application/x-www-form-urlencoded"); 
  req.sendRequest(null, new RequestCallback()
  {
   @Override
   public void onError(Request request, Throwable exception)
   {
    Window.alert("Error en Requerimiento");
   }
   @Override
   public void onResponseReceived(Request request, Response response)
   {
    Window.alert(response.getText());
   }
  }
 }
catch(RequestException e)
{ 
 Window.alert("Exception");
} 

Ahora solo queda depurar la Aplicación!!!

martes, mayo 25, 2010

GWT, un primer vistazo con SmartGWT

En este Post mostrare un sencillo(básico) ejemplo utilizando GWT (Google Web Toolkit) específicamente SmartGWT todo sobre el IDE de Eclipse. SmartGWT  me ha dejado hasta el momento satisfecho pero aun no lo ponga al límite. GWT es una idea maravillosa que aquí muestro para que se animen a probarlo. La curva de aprendizaje toma su tiempo, pero tenemos una extensa y sustanciosa documentación.



La idea del video es hacer la migración de DHTMLX a GWT. Quisiera que noten como se separa la lógica de cada opción del menú en una forma decente, osea en Clases (PrimeroMenu, SegundoMenu, TercerMenu), todos los que viene de programar Aplicaciones de Escritorio saltaran de alegría, hacer eso con Javascript suponía una complejidad enorme(no soy experto en Javascript ), y desde luego acá si podrán hacer las Pruebas Unitarias con todo las consecuencias positivas de su uso. 
En los siguientes Posts iré contando como GWT se fusiona con PHP y desde luego con Doctrine ORM.

Nota: Edite el archivo "Prublogspot.html" para que desaparezca esa aparente marca de agua. Acá les dejo el código fuente de la aplicación

viernes, abril 09, 2010

Windows Communication Foundation una Vision Practica

En este pequeño ejemplo mostraremos como se consume un servicio WCF el libro de referencia que estoy utilizando es “Microsoft  .NET Framework 3.5 Windows Communication Foundation” del kit de entrenamiento, hay bastante teoría sobre este tema pero mi intención es mostrar la parte practica del asunto (mas que practica diria incentivadora!!! ).
  1. Primero crearemos una clase en c# le agregaremos algunas atributos ([servicecontract],[operationcontract], entre otros) y esta clase ya es un servicio.
  2.  Este servicio debe estar alojado en algún lugar, para que el cliente pueda consumir el servicio.  Hay varias formas de hospedar un servicio, yo usare la más sencilla que es utilizando una aplicación de consola (normalmente utilizado en la fase de desarrollo), acá caí la primera vez y se explicara con mayor detenimiento en las próximas entradas!!!
  3.  El Cliente tiene que consumir el servicio (mediante clases proxyes) .NET hace el consumo de un servicio extremadamente sencillo sola haciendo uso de una sencilla referencia al servicio y listo utilizamos un servicio como de una clase se tratara.
Debido que explicar esto en modo texto sería bastante complicado; ya que,  hago uso intensivo de las herramientas que me proporciona Visual Studio es que he decido colocarlo en un video este primer ejemplo.




Bueno tengo en mi libreria digital los siguiente libros electronicos WCF:
  1. "Inside Microsoft Windows Communication Foundation", Justin Smith, Ed. Microsoft Press.
  2. "Microsoft .NET Framework 3.5 Windows Communication Foundation (MCTS 70-503 Training Kit)", Ed. Microsoft Press. 
  3.  "Essential Windows Communication Foundation", Steve Resnick,Richard Crane, Ed. Addison Wesley
  4. "Pro WCF: Practical Microsoft SOA Implementation", Chris Peiris and Dennis Mulder, Ed. Apress
  5. "Microsoft Windows Communication Foundation Step by Step", Jhon Sharp, Ed. Microsoft Press.
  6. "Windows Communication Foundation Unleashed", Craig McMurty, Marc Mercuri, Nigel Waitling Matt Winklet, Ed. Sams Publishing.
  7. "Programming WCF Services". Juval Lowy, Ed. O'Really.
  8. "Professional WCF Programming: .NET Development with the Windows Communication Foundation", Scott Klein, Ed. Wiley Publishing, Inc.
  9. "Microsoft Windows Communication Foundation Hand On Beta Edition", Craig McMurty, Marc Mercuri, Nigel Watling, Ed. Sams
Desde luego que si necesitan alguno solo enviar a mi e-mail el respectivo requerimiento.

miércoles, febrero 03, 2010

Cargar múltiples modelos con Doctrine

Doctrine nos brinda la posibilidad de partir nuestro “modelo” en varios “sub-modelos” consiguiente con esto separar la lógica del negocio.
/******** Archivo bootstrap.php **********************/
require_once(dirname(__FILE__) . '/doctrine-1.2.1/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));

$pathmodels=array();
array_push($pathmodels,dirname(__FILE__)."/models/cayetano");
array_push($pathmodels,dirname(__FILE__)."/models/plantillas");

Doctrine::loadModels($pathmodels);

$dsn = 'pgsql:dbname=cayetano;host=127.0.0.1';
$user='postgres';
$password='123456';

//$dbh = new PDO($dsn, $user, $password);
$conn = Doctrine_Manager::connection(array($dsn, $user, $password));

En el ejemplo anterior dentro de la carpeta "models” se ha creado la carpeta “cayetano” y la carpeta “plantillas”.

martes, febrero 02, 2010

Multiples Base de Datos con Doctrine

Cuando queremos intercambiar la conexión entre dos o más base de datos en la misma aplicación, lo podemos realizar modificando el archivo bootstrap.php:
/************** Archivo bootstrap.php **********************/
require_once(dirname(__FILE__) . '/doctrine-1.2.1/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));

Doctrine::loadModels('models');

$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL); 
$manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);

$dsn = 'pgsql:dbname=cayetano;host=127.0.0.1';
$user='postgres';
$password='123456';
//nombramos a esta conexion como 'cayetano'
$conn = Doctrine_Manager::connection(array($dsn, $user, $password),'cayetano');

$dsn = 'pgsql:dbname=documentacion;host=127.0.0.1';
$user='postgres';
$password='123456';
//nombramos a esta conexion como 'documentacion'
$conn = Doctrine_Manager::connection(array($dsn, $user, $password)),'documentacion');

Para probar el uso lo podemos hacer del siguiento modo:

/************** archivo prueba.php **********************/
require_once('bootstrap.php');

$manager = Doctrine_Manager::getInstance();
$manager->setCurrentConnection('cayetano');
 //Accedemos a la BD cayetano
 $q = Doctrine_Query::create()
  ->select('t.*,u.log_usu,u.pass_usu')
  ->from('Trabajador t')
  ->innerJoin('t.UsuIntranet u');
    
 print_r($q->execute(array(),Doctrine::HYDRATE_ARRAY));
 
$manager = Doctrine_Manager::getInstance();
$manager->setCurrentConnection('documentacion');
 //Accedemos a la BD documentacion
 $q = Doctrine_Query::create()
    ->from('Empleado d')
    ->leftJoin('d.EmpleadoCargo g')    
    ->leftJoin('g.Cargo h')
    ->leftJoin('h.Oficina w')    
    ->where('d.idusuario = ?', $idUsuario)
    ->orderBy('w.descripcion,h.descripcion,g.id');
 
 print_r($q->execute(array(),Doctrine::HYDRATE_ARRAY));

jueves, enero 28, 2010

Modelando con Doctrine parte III

Teniendo el modelo de clases del post anterior procederemos a construir los métodos modificando el bootstrap.php y quedando del siguiente modo:
/***** bootstrap.php **********/
require_once(dirname(__FILE__) . '/doctrine-1.2.1/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));

Doctrine::loadModels('models');

$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL); //ACTIVAMOS LAS VALIDACIONES DE DOCTRINE
$manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);//PARA LA CREACION DE LOS METODOS

$dsn = 'mysql:dbname=comercial;host=127.0.0.1';
$user='root';
$password='123456';

$conn = Doctrine_Manager::connection(array($dsn, $user, $password));

La línea 9 le dice a Doctrine que cargué unas clases que vamos a crear en donde implementaremos los métodos. Estas clases deben de tener la siguiente forma [nombreClase]Table y las colocaremos dentro de la carpeta “models”.
En nuestro ejemplo en particular crearemos la clase UsuarioTable.
/**** UsuarioTable.php *******/
class UsuarioTable extends Doctrine_Table
{
public function GetAllUsuarios()
{
$q=Doctrine_Query::create()
->from('Usuario u')
->execute();
return $q->toArray();
}
public function GetAllCargosByOficina($idUsuario,$idOficina)
{
$q=Doctrine_Query::create()

->from('Oficina o')
->leftJoin('o.Cargo c')
->leftJoin('c.Asignacion a')
->leftJoin('a.Empleado e')
->leftJoin('e.Usuario u')
->where('o.id=?',$idOficina)
->andWhere('u.id=?',$idUsuario) 
->execute();

return $q->toArray(true); 
}
public function GetAllCargos($idUsuario)
{
$q=Doctrine_Query::create()

->from('Cargo c')
->leftJoin('c.Asignacion a')
->leftJoin('a.Empleado e')
->leftJoin('e.Usuario u')
->where('u.id=?',$idUsuario)

->execute();
return $q->toArray(true); 
}
}

Para probar el funcionamiento crearemos un archivo pruebaUsuario.php
/******** pruebaUsuario.php***********/
require_once("bootstrap.php");

$obj= Doctrine::getTable("Usuario");

print_r($obj->GetAllCargos(1));

print_r($obj->GetAllCargosByOficina(1,1));

Haciendo uso de DQL podemos ver que la consultas son bastante sencillas y demandan poco esfuerzo, la clave aquí es el método “toArray()” este método es una forma de serializar (Hydration, en realidad hay muchas formas de hydration) los datos en forma de arrays(objetos). El método “toArray()” puede recibir un parámetro booleano si este es false, nos mostrara el resultado teniendo en cuenta los “join” realizados en la consulta DQL. En caso sea true ocurrirá lo contrario.
Si es que estas trabajando por capas estos resultados hidratados son los que fluyen entre capas.
En mi particular experiencia si estas utilizando el patrón MVP (Model View Presenter) a estos datos hidratados le pasas la función “encode_json()” y se lo envías a la Vista (DHTMLX, GWT,FLASH, etc)
figura 1: MVC vs MVP

Quiero contarles que ahora esto muy metido en GWT (Google Web Toolkit) espero empezar a postear acerca de esta maravillosa idea de la gente de Google.

jueves, enero 07, 2010

Modelando con Doctrine parte II

figura 1
El modelo de clases de la figura 1 debemos de traducirlo a un formato YAML (un esquema que te permite administrar tus modelos de mapeo) para que doctrine genere el modelo de mapeo respectivo a partir de este. Pero como la mayoría de las personas (me incluyo) inmediatamente creamos nuestro modelo E/R y lo reflejamos en la BD elegida, a partir de ahí realizamos la ingeniería inversa que nos generara el mapeo respectivo.
Si Uds. se fijaron en el modelo de clases hay una relación de uno a uno (entre Usuario y Empleado) eso evidentemente va a generar proble mas en algunas BD, nosotros queremos que nuestra aplicación sea independiente de la BD, además que también podríamos tener una relación de mucho a muchos. Entonces la solución está en crear un YAML y partir de este generar el modelo de mapeo y como último paso crear la BD que hayamos elegido.

Para continuar necesitamos definir un estructura de proyecto del siguiente modo:

figura 2

En "data" se encontrara la data a llenar a la BD ( te la bajas de aquí ), en "models" se encuentra el modelo a mapear, en "yaml" se encuentra el esquema(te lo bajas de aquí). El archivo cli.php debe de lucir así:
/***** ARCHIVO CLI.PHP *****/
require_once(dirname(__FILE__) . '/doctrine-1.2.1/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));

$dsn = 'mysql:dbname=comercial;host=127.0.0.1';
$user='root';
$password='123456';

$conn = Doctrine_Manager::connection(array($dsn,$user,$password));

$config = array('data_fixtures_path' => 'data',
'models_path' => 'models',
'migrations_path' => '',
'sql_path' => '',
'yaml_schema_path' => 'yaml');

$cli = new Doctrine_Cli($config);
$cli->run($_SERVER['argv']);
Doctrine brinda una interfaz de línea de comandos (CLI) que nos va a facilitar la creación de la BD, así como la generación del esquema y también los modelos a mapear.
  • Generamos el modelo: "c:\appserv\www\blogspot\php cli.php generate-models-yaml"
  • Creamos la BD: "c:\appserv\www\blogspot\php cli.php create-db"
  • Creamos las Tablas: "c:\appserv\www\blogspot\php cli.php create-tables"
  • Llenamos con Data: "c:\appserv\www\blogspot\php cli.php load-data"
OJO: Cuando generes el modelo no te olvides de añadir los "require_once" correspondientes.
/*** ARCHIVO BOOTSTRAP.PHP *****/
require_once(dirname(__FILE__) . '/doctrine-1.2.1/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));

$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL); //ACTIVAMOS LAS VALIDACIONES DE DOCTRINE
$manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);//PARA LA CREACION DE LOS METODOS

$dsn = 'mysql:dbname=comercial;host=127.0.0.1';
$user='root';
$password='123456';

$dbh = new PDO($dsn, $user, $password);
$conn = Doctrine_Manager::connection($dbh);

Los beneficios de generar la BD a partir de YAML son los siguientes:

  • Independencia de la BD.
  • Se esquematiza las relaciones uno a uno y mucho a muchos
  • Se esquematiza la herencia (podemos escoger como queremos que la herencia, de las múltiples formar que hay, sea implementada en la BD)
  • Se esquematiza la jerarquía de datos.


Debo de confesar que me da pereza aprender YAML ya me acostumbre a tratar el modelo mapeado directamente (clases php) yo normalmente hago lo siguiente:

  • Genero el Modelo E/R a partir del modelo de clases.
  • Creo la BD con sus respectivas relaciones y restricciones.
  • Creo el modelo de mapeado a partir de la BD.
  • Modifico a mano el modelo mapeado(relaciones one to one, many to many entre otros)

En el siguiente post implementaremos los métodos del modelo de clases