sábado, 29 de diciembre de 2012

Creando vistas en Yii Framework usando Jamboree

Jamboree es una nueva extensión que he creado para crear vistas y en general cualquier contenido HTML, incluso para generar el cuerpo principal del website. Por muy complejo que sea, con Jamboree se vuelve muy simple. Lo mas importante es que permite que al diseño ya establecido se le puedan hacer modificaciones, sin dañar nada, solo moviendo piezas solidas de html.

Por ejemplo, si hiciste un enorme formulario con subformularios dentro de este y un dia no te va bien el subformulario 4 por la razón que sea, pues simplemente lo mueves de sitio con solo mover una variable, en vez de insertar form4 en tal sitio, lo insertas en otro y listo, nadie lloró. Hacer esto último "sin Jamboree" es estrepitoso, causante de bugs y de inconsistencias inter-navegador debido a que al mover piezas crudas de html puedes llevarte por delante un simple "</div>" y todo se daña, debiendo usar a veces muchas horas para detectar el error, a veces al extremo de tener que hacer toda la vista de nuevo, porque son muy costosos de detectar. Hablo de formularios y vistas grandes, en las sencillas no ocurre esto.

Sabes cuales son las principales causas de los errores en IE ?

Las inconsistencias en el diseño de paneles.

Lo que a veces funciona bien en chrome y firefox, explota estrepitosamente en IE, si es cierto, lo considero un navegador a veces prepotente y obtuso, aunque muy rápido eso si.

Usando Jamboree te puedo garantizar que una diseño html va a funcionar en IE, debido a que Jamboree internamente fuerza el buen layout, aquel apto para IE, aunque no siempre será así pero ayudará en la mayoría de los casos en donde el diseño fallará en IE, incluso en otros navegadores debido al hecho de crear un mal encapado de elementos DIV.

Usando Jamboree


En esta dirección web tienes varios casos de ejemplo, con imágenes, que muestran a Jamboree en su totalidad.

Wiki y Repositorio para Descargar

https://github.com/christiansalazar/jamboree/wiki

Descarga Directa

Algunos ejemplos (tomados de la wiki indicada arriba) 

La siguiente imagen pertenece a un E-Commerce, hecho enteramente con Ajax y Jamboree, veras varios paneles, a la izquierda, al centro, a la derecha, arriba, una galería, todo fue hecho con Jamboree, sin escribir en lo absoluto ningun código HTML crudo, salvo cositas como enlaces, y una que otra cosa menor.  También notarás que Jamboree no restringe el uso de CSS, ni de JQueryUI, ya que aqui estoy mostrando exactamente eso.




El siguiente es un ejemplo de código que produce un simple formulario de "login" hecho con Jamboree:

<?php
$main = new JamVertPanel();
$b = $main->add(new JamElement("b",'Please login'));
$b->setMarginBottom('15px');
$main->add("Username:");
$main->add(CHtml::textField('username',''));
$main->add("Password:");
$main->add(CHtml::passwordField('password',''));
// now, horz because it grows horizontally
$hpanel = $main->add(new JamHorzPanel());  
$okbutton = $hpanel->add(CHtml::submitButton('Login'));
$cancelbutton = $hpanel->add(CHtml::submitButton('Cancel',
        array('name'=>'cancel')));
$main->setWidth('200px');
$hpanel->addHtmlOption('style','background-color: #def');
$main->render();

Lo cual al ejecutar la vista produce:

login form

Explico este ejemplo:

se crea un JamVertPanel, eso causa que todos sus elementos añadidos crezcan (grows) hacia abajo,  por incluso se le inserta un JamHorzPanel (el de color azul), el cual al ser horizontal hace que sus elementos añadidos crezcan hacia la derecha.

Si un buen dia decides que el panel de color azul no va ahi, sino a la derecha del form, simplemente haces:
$hpanel = $main->add(new JamHorzPanel());  // QUITAS ESTO

Y PONES:
$hpanel = new JamHorzPanel();
notaras que el boton de login y cancel siguen perteneciendo a este panel...lo unico quehas hecho es sacar el panel azul del formulario principal, lo puedes reinsertar en otrolado facilmente.


Diagrama de Clases de Jamboree

Jamboree Class Diagrams

Diagrama de Objetos de Jamboree

Jamboree Objects Relationship Diagrams














sábado, 22 de diciembre de 2012

Reducir una imágen con PHP y GD

Reducir una imagen con PHP y GD

Este simple script permite procesar una imagen, pudiendo ser usado tanto al subir la imagen para guardar una version reducida de esta, o para emitir una version ajustada a la medida solicitada leyendo desde una imagen mas grande.

Se requiere que la instalación de PHP tenga incorporada la librería GD.

 Efecto Image Zoom:  Una variante de este código podría usarse para hacer "Zoom" en una imagen muy alta resolución, programando adecuadamente el vector de desplazamiento sobre la imagen original y trayendo solo un área específica, esta técnica combinada con Ajax realiza el efecto de Zoom

Ejemplo de uso:

  1. // IMAGEN ORIGINAL..EN ALTA RESOLUCION.
    $fullName = '/anypath/image.jpg';

    // OBTENER INFORMACION DE LA IMAGEN ORIGINAL.
  2. list($ow, $oh, $xmime) = getimagesize($fullName);
  3. $imageSize = filesize($fullName);
  4. $mime = '';
  5. if($xmime == 2) $mime = 'image/jpg';
  6. if($xmime == 3) $mime = 'image/png';

  7. // LEER LA IMAGEN ORIGINAL
  8. $f = fopen($fullName,"r");
  9. $imageData = fread($f, $imageSize);
  10. fclose($f);

  11. // HACER EL RESIZE A 160 x 120 px, con 70% de compresion JPEG    
  12. $r = new ImageResizer();
  13. $newImage = $r->resize($imageData, 160, 120, 70, 'jpg', $ow, $oh);

    // EMITIR LA IMAGEN
  14. header('Content-type: '.$mime);
  15. echo $newImage;


Si el ejemplo anterior estuviese alojado en una pagina PHP llamada:

http://miservidor/imagen.php

podríamos visualizar la imagen en una pagina HTML asi:

<html>
   <img src='http://miservidor/imagen.php'>
</html>

Está demás decir que se pudiera programar el script para recibir argumentos y asi emitir la imagen en un formato especifico:

<html>
   <img src='http://miservidor/imagen.php?w=320&h=200&c=50'>
</html>

El script podria recibir los argumentos pasados aqui y asi emitir la imagen en esa dimensión solicitada.


Por qué "a veces" es incorrecto hacer:  "<img src='...' width=160 height=120  >" ?

Respuesta:  Porque "a veces" la imagen original pesa 4 megas y no vas a querer tener en un website una imagen de 160x120 pixels que pese 4 megas, debido a que aunque estes solicitando esas medidas en tiempo de browsing (es decir al momento de presentarla) esa imagen pesara 4 megas, tardara en bajar al browser para finalmente ser presentada en una medida muy pequeña, aparte de quedar horrible por la fragmentación del pixelado, va a pesar mucho.

Código fuente:

  1. <?php
  2. /**
  3.  * ImageResizer
  4.  *      a helper class to handle image resize.
  5.  *
  6.  *      example usage:
  7.  *             
  8.  *  $fullName = '/anypath/image.jpg';
  9.  *      list($ow, $oh, $xmime) = getimagesize($fullName);
  10.  *      $imageSize = filesize($fullName);
  11.  *      $mime = '';
  12.  *      if($xmime == 2) $mime = 'image/jpg';
  13.  *  if($xmime == 3) $mime = 'image/png';
  14.  *       
  15.  *      $f = fopen($fullName,"r");
  16.  *      $imageData = fread($f, $imageSize);
  17.  *      fclose($f);
  18.  *     
  19.  *      $r = new ImageResizer();
  20.  *      $newImage = $r->resize($imageData, 160, 120, 70, 'jpg', $ow, $oh);
  21.  *      header('Content-type: '.$mime);
  22.  *      echo $newImage;
  23.  *
  24.  *
  25.  * @author Christian Salazar <christiansalazarh@gmail.com>
  26.  * @license NEW BSD.
  27.  */
  28. class ImageResizer {
  29.         /**
  30.          * resize
  31.          *      resizes an image making it to fit into a rectangle
  32.          *
  33.          * @param mixed $image  Binary raw image data.
  34.          * @param mixed $dw     destination viewport width
  35.          * @param mixed $dh     destination viewport height
  36.          * @param mixed $q      quality for jpg or png: 1 to 100.
  37.          * @param mixed $imgtype desired output image type 'jpg' or 'png'
  38.          * @param mixed $ow     original image width
  39.          * @param mixed $oh     original image height
  40.          * @return new image. you can echo it or use GD functions to handle it.
  41.          */
  42.         public function resize($image, $dw, $dh, $q, $imgtype, $ow, $oh){
  43.                 $im = imagecreatetruecolor($dw, $dh);
  44.                 $im_src = imagecreatefromstring($image);
  45.                 $_w = 0;
  46.                 $_h = 0;
  47.                 $this->_scaleVector($dw, $dh, 0.95, $ow, $oh, $_w, $_h);
  48.                 // displacement vector, this vector center the image.
  49.                 $dx = ($dw - $_w)/2;
  50.                 $dy = ($dh - $_h)/2;
  51.                 $fillcolor = imagecolorallocate($im,255,255,255);
  52.                 //$xcolor = imagecolorallocate($im, 200,200,200);
  53.                 imagefilledrectangle($im, 0,0,$dw, $dh, $fillcolor);
  54.                 //imagefilledrectangle($im, $dx,$dy, $dx + $_w, $dy + $_h, $xcolor);
  55.                 imagecopyresampled(
  56.                                 $im, $im_src,
  57.                                 $dx, $dy, 0, 0,
  58.                                 $_w, $_h,
  59.                                 $ow, $oh
  60.                 );
  61.                 if($imgtype == 'png')
  62.                         return imagepng($im, null, $q);
  63.                 return imagejpeg($im, null, $q);
  64.         }
  65.         /**
  66.          * _scaleVector
  67.          *     
  68.          *
  69.          * @param mixed $dw             |       destination viewport:
  70.          * @param mixed $dh             |               d = {w, h}
  71.          * @param mixed $delta          |       delta: is a fixture measurement. max 1.
  72.          * @param mixed $ow             |       original viewport to be scaled into "d":
  73.          * @param mixed $oh             |               o = {w, h}
  74.          * @param mixed $out_w         
  75.          * @param mixed $out_h
  76.          * @access private
  77.          * @author Christian Salazar H. <christiansalazarh@gmail.com>  
  78.          * @return void
  79.          */
  80.         private function _scaleVector($dw, $dh, $delta, $ow, $oh, &$out_w, &$out_h){
  81.                 $dR = $dw / $dh;
  82.                 if($dR >= 1){
  83.                         $out_w = $delta * $dw;
  84.                         $out_h = ($out_w * $oh) / $ow;
  85.                 }else{
  86.                         $out_h = $delta * $dh;
  87.                         $out_w = ($out_h * $ow) / $oh;
  88.                 }
  89.         }
  90. }

viernes, 21 de diciembre de 2012

Optimizando CJuiTabs para vistas voluminosas.



Hay casos en que las vistas muy voluminosas tardan en cargarse, y mientras eso sucede el sitio web se ve horrible, sin formato,  luego de unos segundos aparece el maravilloso jQueryUi.tabs (manejado en Yii mediante CJuiTabs).

El truco: Ocultar el componente y mostrarlo cuando el html completo termine de cargarse.

Hay que crear el componente jQuery.tabs() con CJuiTabs pasandole un estilo CSS de ocultación inicial (display: none;) eso causara que no se vea...hasta que le digamos que se vea.

Cuando le diremos que se vea ?
cuando el html cargue completo.

Cuando sabemos eso ? mediante otra belleza de Yii: el CClientScript, al cual le pediremos que inserte un script javascript al final cuando la página haya finalizado de cargarse usando la opción: CClientScript::POS_LOAD

  1. <?php
  2. $this->widget('zii.widgets.jui.CJuiTabs',array(
  3.         'id'=>'article_tab',
  4.         'htmlOptions'=>array('style'=>'display: none;'),  // INVISIBLE..
  5.         'tabs'=>array(
  6.                 'Datos del Articulo'=>$this->renderPartial('_form',
  7.                         array('model'=>$model),true),
  8.                 'Imagenes'=>$this->renderPartial('_form-images',
  9.                         array('model'=>$model),true),
  10.                 'Opciones'=>$this->renderPartial('_form-options',
  11.                         array('model'=>$model),true),
  12.                 'Stock'=>$this->renderPartial('_form-stock',
  13.                         array('model'=>$model),true),
  14.         ),
  15.         'options'=>array(
  16.                 'collapsible'=>false,
  17.                 'cookie'=>9000,
  18.         ),
  19. ));
  20. // VISIBLE solo cuando cargue todo..
  21. Yii::app()->getClientScript()->registerScript("articletab", // un ID unico tambien
  22. "$('#article_tab').show();" ,CClientScript::POS_LOAD);      // el ID del tab
  23. ?>








miércoles, 19 de diciembre de 2012

SCRIPT VS CClientScript


SCRIPT VS CClientScript (Yii Framework)

En Yii Framework es posible insertar <SCRIPT> en cualquier parte, no hay limitantes, pero viene a la ayuda CClientScript, la cual trae extremos beneficios, aunque a veces sea mas rápido solo insertar <script>...bla...</script>, no siempre será esta la mejor opcion, aqui es donde entra CClientScript.

Pongo un ejemplo, CListView.

Bien sabes que CListView recibe un array de datos (de "registros" para ser mas familiar), en forma de array de arrays para poder hacer paginación, esto no viene al caso del ejemplo, asi que limitemonos a pensar en que CListView toma un array y por cada elemento llamara a una vista en la cual "renderizará" al registro de ese array.

// file:   ..protected/views/car/index.php
<div class='cars'>
$this->widget('zii.widgets.CListView',
  array('dataProvider'=>Car::model()->search(), 'view'=>'applications.view.car._car'),

);
</div>

Cada "Car"  sera renderizado por una vista: "applications.views.car._car" (../protected/views/car/_car.php) la cual dice:

// file:   ../protected/views/car/_car.php
<?php
  // $data:  instancia de Car

  echo "<b>".$data->name."</b>";
?>


Como resultado tendrás una pagina html que dira:

<html>
   <div class='cars'>

   <b>chevrolet aveo</b><b>chevrolet malibu</b><b>chevrolet spark</b>
   </div>

</html>

Hasta aqui vamos bien, no pretendo explicar a CListView, sino mostrar como CClientScript se va a portar aqui en contraparte con el uso de <SCRIPT>.

Insertando una pieza de javascript usando: <SCRIPT> VS CClientScript.
Supongamos que queremos agregar un evento click a cada <B> usando jQuery, tenemos dos opciones, o usamos SCRIPT o usamos CClientScript. aqui verás la gran diferencia:

Usando SCRIPT en index.php:  (incorrecto desde el punto de vista de modelado, correcto si quieres hacer un desastre)

// file:   ../protected/views/car/index.php
<div class='cars'>
$this->widget('zii.widgets.CListView',
  array('dataProvider'=>Car::model()->search(), 'view'=>'applications.view.car._car'),

);
</div>
<script>
   $('.cars b').each(function(){  

        $(this).click(function(){ 
             alert("hola "+$(this).html()); 
         }); 
    });
</script>


Si decides usar <SCRIPT> como muestro arriba, tendrás que ponerlo en "protected/views/car/index.php" ya que de lo contrario si lo pones en _car.php el script se repetirá N veces (segun N registros tengas), causando problemas, incluso dejando de funcionar, pero este no es el sitio mas correcto porque:
¿ que le importa a index.php acerca de cómo _car.php pinta al carro ?...no le importa...no es su negocio.


Beneficios usando CClientScript:

Desde el punto de vista de "Modelado" es sano y  limpio que la funcionalidad del carro este en _car.php y no en index.php.   El widget que presenta la lista de carros es una cosa, y el carro otra.

Primero dejemos a index.php como estaba al inicio:

// file:   ../protected/views/car/index.php
<div class='cars'>
$this->widget('zii.widgets.CListView',
  array('dataProvider'=>Car::model()->search(), 'view'=>'applications.view.car._car'),

);
</div>

ahora, vayamos a _car.php y demosle la funcionalidad:


CASO ERRONEO: (Usando SCRIPT)
razón:  <script> se repetirá N veces, dejando de funcionar, y no vamos a perder la maravilla de jQuery por andar reparando esto al estilo de los años 80....dios. Simplemente asi no se usa, y pasamos a CClientScript.


// file:   ../protected/views/car/_car.php
<?php
  // $data:  instancia de Car

  echo "<b>".$data->name."</b>";
?>

<script>
   $('.cars b').each(function(){  

        $(this).click(function(){ 
             alert("hola "+$(this).html()); 
         }); 
    });
</script>


CASO CORRECTO: (usando CClientScript)
// file:   ../protected/views/car/_car.php
<?php
  // $data:  instancia de Car

  echo "<b>".$data->name."</b>";
?>

<?php

Yii::app()->getClientScript()->registerScript( "car_script",
 "
   $('.cars b').each(function(){  
        $(this).click(function(){ 
             alert("hola "+$(this).html()); 
         }); 
    });
"
,CClientScript::POS_END);
?>


¿ Por qué esta forma de insertar el script es correcta ?, porque CClientScript se preocupa de NO REPETIR el script si su id ya fue incrustado: "car_script".  Verás en consecuencia que la pagina obtenida incrustará el script al final debido a la opcion CClientScript::POS_END.

Esto causará que donde quiera que uses la vista _car.php esta traiga la funcionalidad indicada, no importando si fue lanzada por index.php (el widget de CListView que lista todos los carros) o si estas mostrando un carro en particular usando:

Podrías invocar la vista _car.php no solo desde el widget (index.php) sino desde cualquier vista sin que pierda la funcionalidad !!

$mycar = Car::model()->findByPk('ABC-123');
$this->renderPartial(
'application.views.car._car.php', array('data'=>$mycar));

resutado ? siempre que muestre es carro, el mensajito "hola xxxx" saldrá, sin importar como se listo el carro.

jueves, 6 de diciembre de 2012

Diseñando una Aplicación Web


Diseñando una Aplicación Web
(con Cruge e EYui)

Modelando con Cruge e EYui

En este tema quiero mostrar cómo integrar dos piezas importantes de Software para crear una aplicación web eficiente, tanto conceptualmente como a nivel de modelado UML.
En primer lugar usaré UML como lenguaje de modelado ya que te servirá para familiarizarte. Las cajas negras de punta cuadrada (por ejemplo "Empresa") son "Clases", no las confundas con las "Tablas" aunque muchas de ellas o quiza todas se almacenen luego en archivos (se llaman Relaciones..no "tablas") de un motor de datos. Una "tabla" es simplemente una forma de almacenar (o persistir) una Clase (sea en mysql, en un archivo de texto, etc).

Primera Etapa - Concepto Simple

REGLA DE NEGOCIO MINIMA:
Queremos que una empresa tenga departamentos, puestos y empleados. La regla aqui es que un empleado labore en varias empresas dentro del mismo sistema, además queremos usar Cruge como sistema de gestión de usuarios.
Como he repetido en varias oportunidades, no vamos a alterar a Cruge para insertar un modelo de negocios por mas que insistas en que: "hay que ponerle un idempresa a CrugeUser".
Relacion: Empresa-Empleado-CrugeUser
CrugeUser representa a una persona...las personas no son de una empresa, son libres, son seres libres, ahora...que "tengan una relacion" con una "empresa" eso es otra cosa, por tanto sería necesario crear una nueva "Relacion" (una nueva clase) para asociar a una persona (CrugeUser) con una Empresa, esa relacion se es representada por la clase "Empleado" que tiene dos atributos clave: idempleado e iduser. Esta clase puede ser almacenada en una relacion en mysql llamada "Empleado" con los campos igual a los atributos de la clase.
La relacion Empresa-Empleado-CrugeUser resuelve el problema de que un usuario de cruge labore en empresas. (sin necesidad en lo absoluto de crear un issue para insertarle el idempresa a crugeuser).

Relaciones: Departamento y Puesto
Una "Empresa" esta dividida en Departamentos y ofrece Puestos de trabajo. Ya lo adiviné: le vas insertar un IDEMPRESA a Departamento y Puesto...otra vez: NO!!no se le agrega un IDEMPRESA al puesto o departamento
¿ Por qué no se debe insertar un IDEMPRESA a un Departamento o Puesto. ?
Principalmente la razon es que un sistema podría tener una lista global de departamentos o puestos, los cuales un administrador de sistemas agrega a una empresa.
Siempre hay un puesto de "ingeniero de sistemas" o "administrador" o "analista" (como ejemplos de puestos), no vamos a anotar los 30 puestos de una empresa a cada empresa que hagamos en el sistema, será mejor seleccionar los puestos y agregarselos a una empresa en particular, si un puesto es especifico para una empresa se agrega a la lista mayor y se asocia de vuelta quedando disponible para otras empresas del mismo sistema, mismo caso con los departamentos.
EYuiRelation como componente para manejar las asociaciones de Departamento, Puesto y demás.
Aqui es donde entra EYui, con su componente EYuiRelation. El cual permitiría asociar puestos, departamentos y demás a una empresa, de forma abstracta, EYuiRelation presenta un widget para manejar la asociación, te evita hacer el mismo codigo una y otra vez. Así quedaría el manejo de relaciones de Departamento, Puestos con EYuiRelation.

Segunda Etapa - Clases de Asociación

En el siguiente grafico verás dos clases nuevas: PuestoEmpresa y DeptoEmpresa. Estas son las "clases de asociacion". Una Clase de Asociación es aquella que permite que una clase X se asocie con una Y, siendo la clase de asociación otra nueva llamada XY, de ahi el nombre PuestoEmpresa....y no otro invento raro.
La clase de asociación se grafica con una "linea punteada" que apunta a la relación entre X y Y, es decir, la rayita que va desde Empresa a Departamento (o a Puesto). Esa "rayita punteada" indica que es necesaria una clase llamada "PuestoEmpresa" la cual guardará la asociación, dejando INTACTA a Puesto y a Empresa.
Lo mismo sucedió con la clase EMPLEADO, esta no es sino una "clase de asociación" entre Empresa y CrugeUser, pero no quise graficarla de otra forma para evitar confusiones de inicio. Podríamos perfectamente graficar a Emplado-Empresa-CrugeUser de esta forma:
En esta imagen (arriba de este texto) ves cómo se convierte el gráfico (el primero de esta wiki) en un modelo mas refinado a nivel de UML, diciendote que Empleado es una Clase de Asociación entre Empresa y CrugeUser.
Aqui además ves que CrugeUser tiene atributos propios de una persona, Cedula, Nombre, Apellido, y otros...aqui no van campos de negocio...!! solo van campos de persona. (ahora me entiendes la razón de existir de un CrugeField, espero que si. ?)
En la clase de asociación "Empleado" van atributos propios de la relacion entre Juan Perez y "Empresa XYZ"...por ejemplo, el numero de contrato, la fecha de su relacion laboral etc....estos datos NO VAN EN Cruge !!...van en tu modelo.
Los dos asteriscos *------* que van de CrugeUser a Empresa dicen que: "Un usuario puede estar asociado a varias empresas a la vez, y una empresa puede tener varios usuarios". (En matemática básica de conjuntos tiene un nombre específico, que ahora no recuerdo, pero es algo de 5to grado de escuela básica, la cual aun recuerdo a mi papa enseñandome eso de chico, pero soy malo recordando nombres, recuerdo mejor hechos y dibujos.)


Reconociendo los PIVOTES y el concepto de PIVOTE

Quisiera introducir la idea del PIVOTE (Es un concepto transmitido por mi Papa, quien no conocía UML pero si fue un gran modelador de software complejo).
Un PIVOTE es aquella pieza de software alrededor de la cual todo ronda. En este sistema no es CrugeUser..el negocio no esta orientado hacia un usuario, sino a la relacion de este con una empresa, por tanto tenemos dos pivotes: Empresa y Empleado.
¿ Por qué hago referencia a pivotes ? , porque hay que reconocerlos a la hora de profundizar en el diseño, para orientar el diseño hacia estos elementos. Como ejemplo de esto, lee la tercera etapa en este mismo documento, a continuación.

Tercera Etapa - Insertando un widget para manejar relaciones

Aqui entra al juego un paquete llamado EYui, el cual fabriqué hace un tiempo para darle a Yii una capa de modelado de negocio mas alta que lo provisto por defecto (lo cual no es tarea de yii..sino de un componente como EYui).
Queremos que un widget nos maneje las relaciones como Departamento y Puesto, las cuales son mas simples y vulgares en comparación con Empleado, que si bien son lo mismo conceptualmente, no es igual a nivel de negocio porque el Empleado es una pieza PIVOTE, tal cual como lo es la EMPRESA. (lee sobre PIVOTE en el tema anterior).

EYuiRelation puedes hallarlo en este repositorio de bitbucket, es parte del paquete EYui.
EYuiRelation usa la inyección de dependencia para pedirle a tu modelo que lo alimente con datos en una forma mínima de fácil comprensión. Mucha gente se intimida con EYui, pero lo cierto es que es simple, lo duro esta oculto...(denle gracias a la Encapsulación).
Lo que se hará será decirle a EYuiRelacion quien es el maestro (la empresa, la parte X de la relacion XY), decirle además dónde estan las opciones seleccionables (la Y de una relacion XY. por ejemplo, la lista general de departamentos o puestos disponibles), y finalmente decirle dónde guardar la relacion (la XY, el departamento asociado a una empresa, o mejor dicho DeptoEmpleado, siguiendo el ejemplo).

Fíjate en las interfaces. Estan simbolizadas por una linea azul que sale (por ejemplo) de Puesto y termina en una pelotica azul. Eso significa que: Puesto REALIZA la interfaz EYuiRelationIOptions, la cual alimenta al widget con las OPCIONES disponibles para seleccionar. Cuando digo "REALIZA" no es un invento mio, es un concepto UML "Realization", lo cual significa que algo lleva a cabo la tarea, debido a que las interfaces son ABSTRACTAS, aqui la realización dice:
 "Yo, el Puesto, voy a darle vida a esta interfaz para que tal cosa me consuma".
Lo que hace PUESTO es Realizar la interfaz EYuiRelationOptions, proveyendole al Widget EYuiRelation los datos necesarios para presentar el DropDownList del cual el usuario va a seleccionar un Puesto para agregarselo a una empresa. Mismo caso con el Departamento.
EYuiRelation también le exige a PuestoEmpresa que le provea de una interfaz llamada "EYuiRelationIRelation", la cual le pide a la clase PuestoEmpresa que provea un mecanismo para "almacenar" (olvidate de mysql, sacatelo de la cabeza) a aquella OPCION tomada de EYuiRelationIOptions (el puesto seleccionado).
Finalmente, EYuiRelation le pide a Empresa una interfaz para poderla reconocer como un objeto al cual asociar. Solo le pide su PRIMARYKEY, para aclarate la idea, pero lo hace bajo una interfaz, es muy simple tecnicamente.
Con todo eso, EYuiRelation ahora puede: Listar los puestos, asociar a un puesto seleccionado a una empresa y guardar eso en una "tabla" llamada "PuestoEmpresa".
Christian Salazar H.
tema escrito para Yii Framework en Español.