Lista de bookmarks en Dart M3

Acabo de terminar mi primera prueba con Dart M3. Tenía pensado hacerme un widget para automatizar el listado de enlaces en el blog y me pareció buena idea probarlo con el nuevo lenguaje de Google. Ha sido una experiencia bastante positiva: se consigue el objetivo de programar más estructuradamente que con Javascript, es sencillísimo acceder al DOM y también muy fácil mantener separados lo que hace cada elemento de su estilo. Como carencia no dispone de buen soporte para manipular objetos JSON (lo mismo le ocurre a Javascript: me las he visto y deseado para ordenar la lista de enlaces. En esta primera versión la lista de links está embebida en el código, cada vez que se cambie hay que generar el javascript correspondiente y subirlo. He probado a cargar el runtime de Dart pero aunque me ha funcionado en local no lo ha hecho en el blog.
Para instalarlo se precisa también el fichero css correspondiente. Se crear una página estática y se inserta:
En head:



En body:
Es posible que sea necesario modificar algún estilo para adaptarlo a las necesidades requeridas.

import 'dart:html';
import 'dart:uri';
import 'dart:json' as json;

/******
 * Cadena JSON con los datos de los enlaces:
 * "Sección": {
 *  "Subsección":
 *    { "Texto del enlace":
 *      { "url": "url", "comment": "comentario" }
 *    }
 * } 
******/

String BookmarksList = '''{
      "Open Source": {
        "General":
          { "The Architecture of Open Source Applications":
              { "url": "http://www.aosabook.org/en/index.html", "comment": "" }
          }
      },
      "Brainstorming": {
        "General":
          { "TED":
              { "url": "http://www.ted.com", "comment": "Technology, Entertainment, Design" }
          }
      },
      "Programación": {
        "Recursos":
          { "Stack Overflow": 
              { "url": "http://stackoverflow.com/", "comment": "Quizás el mejor foro sobre programación" },
            "Wotsit":
              { "url": "http://www.wotsit.org/", "comment": "Recursos para programadores" }
          },
        "General":
          { "Coding Horror":
              { "url": "http://www.codinghorror.com/blog/", "comment": "" }
          }
      },
      "Proyectos": {
        "General":
          { "Project Euler":
              { "url": "http://projecteuler.net/", "comment": "Desafíos matemáticos y de computación" }
          }
      }
    }
''';

/******
 * Clases para generar los divs de cada sección.
******/

class Seccion
{
  Element div;
  
  Seccion(String nombre)
  {
    this.div = new Element.tag('div');
    this.div.classes.add('cSeccion');
    this.div.innerHtml = nombre;
  }
}

class SubSeccion
{
  Element div;
  
  SubSeccion(String nombre)
  {
    this.div = new Element.tag('div');
    this.div.classes.add('cSubSeccion');
    this.div.innerHtml = nombre;
  }
}

class MargenInferior
{
  Element div;
  
  MargenInferior()
  {
    this.div = new Element.tag('div');
    this.div.classes.add('cMargen');
  }
  
}

class Link
{
  Element div;
  
  Link(String texto, Map data)
  {
    this.div = fDivContainer(texto, data);
  }
  
  Element fDivContainer(String texto, Map data)
  {
    Element eDiv = new Element.tag('div');
    eDiv.classes.add('cContainer');
    eDiv.children.add(fDivLink(texto, data));
    eDiv.children.add(fDivComment(data['comment']));
    return eDiv;
  }
  
  Element fDivLink(String texto, Map data)
  {
    Element eDiv = new Element.tag('div');
    eDiv.classes.add('cLink');
    eDiv.classes.add('tabLink');
    eDiv.children.add(fImage(data['url']));
    eDiv.children.add(fAHRef(data['url'], texto));
    return eDiv;
  }
  
  Element fDivComment(String comment)
  {
    Element eDiv = new Element.tag('div');
    eDiv.classes.add('cComment');
    eDiv.innerHtml = comment;
    return eDiv;
  }
  
  Element fAHRef(String url, String txt)
  {
    AnchorElement eAHRef = new AnchorElement();
    eAHRef.classes.add('cHRef');
    eAHRef.href = url;
    eAHRef.text = txt;
    eAHRef.target = '_blank';
    return eAHRef;
  }
  
  Element fImage(String url)
  {
    Uri src = new Uri(url);
    
    ImageElement image = new ImageElement();
    image.classes.add('cImage');
    image.src = 'http://'.concat(src.domain).concat('/favicon.ico');
    Element imageLink = fAHRef(url, '');
    imageLink.children.add(image);
    return imageLink;
  }
}

/*****
 * Se ordena un Map convirtiéndolo primero a string junto con su valor y almacenándolo en un array de strings (Lista).
 * Para convertirlo en string se usa json.stringify para conservar las comillas dobles de los campos, asimismo se añaden los dos puntos (:) que también se perderían
 * Se crea una tira con el nombre del campo concatenado con su valor para mantenerlos apareados cuando se ordenen por el nombre del campo.
 * Se devuelve un objeto JSON con los campos ordenados. Antes se prepara la cadena a convertir quitándole el primer y último carácter (corchetes [], formato lista) y se
 * sustituyen por llaves ({}, formato JSON). 
******/

Map OrdenaJSON(Map data)
{
  var Lista = <string>[];
  
  data.forEach((k, v) { Lista.add(json.stringify(k).concat(':').concat(json.stringify(v))); });
  Lista.sort((a, b) => a.compareTo(b));
  String s = Lista.toString().substring(1, Lista.toString().length - 1);
  return json.parse('{'.concat(s).concat('}'));
}

/*****
 * Se crea un bucle para cada categoría de la cadena JSON: Sección, Subsección y Texto del link. Para cada categoría se llama al procedimiento de ordenación. 
******/

Map OrdenaBookmarks(String data)
{
  Map Lista = OrdenaJSON(json.parse(data));
  Lista.forEach((kSeccion, v)
      {
        Lista[kSeccion] = OrdenaJSON(Lista[kSeccion]);
        Lista[kSeccion].forEach((kSubSeccion, v)
            {
                Lista[kSeccion][kSubSeccion] = OrdenaJSON(Lista[kSeccion][kSubSeccion]);
            });
      });
  return Lista;
}

void main()
{
  Map JSONBookmarks = OrdenaBookmarks(BookmarksList);
  Seccion sRef;
  SubSeccion sbRef;
  Link lRef;
  MargenInferior sMI;
  Element container = query('#BLContainer');
  JSONBookmarks.forEach((kSeccion, v) 
      {
        sRef = new Seccion(kSeccion);
        container.children.add(sRef.div);
        JSONBookmarks[kSeccion].forEach((kSubSeccion, v)
            {
              sbRef = new SubSeccion(kSubSeccion);
              container.children.add(sbRef.div);
              JSONBookmarks[kSeccion][kSubSeccion].forEach((kTxtLink, v)
                  {
                    lRef = new Link(kTxtLink, v);
                    container.children.add(lRef.div);
                  });
                  sMI = new MargenInferior();
                  container.children.add(sMI.div);
            });
      });        
}

El resultado final:


Descargas: BookmarksList

P.D.: Estuve mirando cómo modificar el código para poder cargar la lista de bookmarks desde un fichero externo pero es demasiado lío, Dart impone una restricción total a la carga desde otros dominios (Access-Control-Allow-Origin) que sólo puede eludirse instalando una especie de mini sevidor cruzado o usando una antigua librería javascript. Por si fuera poco, el código necesario para poder acceder a los parámetros de la URL para conocer el nombre del fichero también es excesivo. Total, no vale la pena el esfuerzo. Muy bonito pero poco más.

No hay comentarios:

Publicar un comentario