Arquitectura, buenas prácticas y desarrollo sobre la nueva herramienta de Microsoft SharePoint 2016

[SharePoint] Crear una interfaz de usuario potente con Ractive.JS (I)

Hoy en día el éxito de cualquier aplicación depende en gran medida de tener una interfaz de usuario rápida, intuitiva y atractiva al usuario. Debido a estos requerimientos el auge de un lenguaje como JavaScript ha ido en aumento y se ha incorporado como parte de nuestros conocimientos si o sí.
Ahora bien a la hora de decantarse por un framework JavaScript es donde más dudas hay. El primer motivo es la gran cantidad de frameworks que hay (como hemos visto en anteriores entradas). El segundo motivo principalmente es porque ningún frameworks cubre 100 % nuestros objetivos, nos gusta parte de uno y parte de otro, salvo AngularJS (cuyo principal inconveniente es el conocimiento que debemos de tener de JavaScript y que pertenece a Google).
Para solventar en parte esta problemática hacemos uso de RactiveJS. RactiveJS es otro framework JavaScript, que se caracteriza por tener aspectos destacados de otros frameworks (JQueryUI, Angular, Mustache…) la posibilidad de realizar componentes con lo que nuestros desarrollos pueden ser reutilizables con tan solo cambiar los estilos. Además puede ser extensibles con otros frameworks como Backbone, JQuery UI y hacerlos que formen parte de nuestro ecosistema sin que desentonen, ni que se vean como un pegote o añadido.
Para esto voy a escribir una serie de post, en las semanas próximas:
1.- Introducción a RactiveJS + Integrando con SharePoint
2.- Extendiendo RactiveJS con Backbone
3.- Caso de Uso: Encamina.Enmarcha UI Herramientas utilizadas

1.- Introducción a RactiveJS
RactiveJS fue creado originalmente para usarlo en el periódico ingles The Guardian, y nació con la misión de solucionar el eterno problema con los navegadores, ya que una web de noticias se debe de visualizar en todos los navegadores posibles (tanto de escritorio como aplicaciones móviles). Para ello hay tiene que solventar un problema: el HTML estático. Para ello RactiveJS hace uso de las plantillas de Mustache pero convirtiendo ese HTML en un DOM muy ligero, haciendo uso de estas plantillas, junto con JavaScript y CSS. A este HTML se le añade funcionalidad como eventos, acciones para poder dar un paso más que lo que utilizando solamente Mustache.
Otra de las ventajas que tiene es que permite una buena integración entre los diversos miembros del equipo por un lado el departamento de diseño tiene el HTML tal y como lo ha pensado, y por otra parte tiene una integración con los datos tal y como desean los desarrolladores. Esta comunicación entre los miembros del equipo se hace indispensable para no solaparse el trabajo y no duplicar el trabajo.
Creando un ejemplo de Tareas pendientes
Cualquier proyecto/app que utilice RactiveJS esta dividida en tres partes: Template (HTML extendido), JavaScript y CSS.

  • Nos hace falta un «Template» o plantilla donde vamos a pintar los datos que necesitamos. Esto será un html pero con marcas donde vamos a representar los datos.
  • Una parte de JavaScript, donde dotaremos de la creación de los eventos que tiene esta plantilla
  • CSS, fichero CSS donde alojaremos los estilos de esta plantilla. Esta parte es opcional, los estilos se pueden añadir dentro del template o bien en un fichero aparte. Lo bueno que tiene Ractive es que los estilos solamente se aplican sobre ese Template de esta forma evitamos ir creando archivos de estilos interminables y fijados a los identificadores del html.

Para este ejemplo podemos tener un template como el siguiente:

 <script type="text/ractive" id="example-template">
 <div class='todo-app'>

  <header id='header'>
    <!-- we bind to the custom 'enter' event as well as 'change', for the benefit of IE -->
    <input id='new-todo' on-enter-change='new_todo' placeholder='What needs to be done?' autofocus>
  </header>

  {{#items.length}} <!-- only show when there are one or more items -->
    <section id='main'>

      <!-- 'toggle all' button -->
      <label for='toggle-all'>Mark all as complete</label>
      <input
        id='toggle-all'
        type='checkbox'
        on-click-change='toggle_all'
        checked='{{ items.length === completedTodos() }}'
        intro='fade'
        outro='fade'
      >

      <!-- the actual list -->
      <ul id='todo-list'>
        {{#items:i}}
          {{>item}}
        {{/items}}
      </ul>
    </section>

    <div id='footer' intro='fade' outro='slide'>
      <span id='todo-count'>
        <strong>{{ activeTodos() }}</strong> {{ activeTodos() === 1 ? 'item' : 'items' }} left
      </span>

      <!-- switch filters -->
      <ul id='filters'>
        <li class='{{ currentFilter === "all" ? "selected" : "" }}' on-tap='set_filter:all'>All</li>
        <li class='{{ currentFilter === "active" ? "selected" : "" }}' on-tap='set_filter:active'>Active</li>
        <li class='{{ currentFilter === "completed" ? "selected" : "" }}' on-tap='set_filter:completed'>Completed</li>
      </ul>

      <!-- hidden if no completed items are left -->
      {{# completedTodos() }}
        <button id='clear-completed' on-tap='clear_completed'>
          Clear completed ({{ completedTodos() }})
        </button>
      {{/ completedTodos() }}
    </div>
  {{/items.length}}

</div>
<!-- {{>item}} -->
{{# filter( this ) }}
<li intro='slide:fast' outro='slide:fast' class='{{ .completed ? "completed" : "" }} {{ .editing ? "editing" : "" }}'>
  <div class='view'>
    <input class='toggle' type='checkbox' checked='{{.completed}}'>
    <label on-dblclick='edit'>{{description}}</label>
    <button on-tap='remove:{{i}}' class='destroy'></button>
  </div>

  {{#.editing}}
    <div class='edit-container'>
      <input intro='select' class='edit' value='{{description}}' on-blur-enter='submit'>
    </div>
  {{/.editing}}
</li>
{{/ end of todo item }}
<!-- {{/item}} -->
     </script>

Este html/avanzado es muy semejante por una parte al html que generábamos con Angular por ejemplo tenemos varios funciones a eventos dbclick, onblu-enter,checked, como aplicamos filtros de una forma muy simple. Y por otro lado tenemos una sintaxis muy similar a Mustache donde pondremos el símbolo Mustache «{» donde queremos reemplazar este valor por un valor de nuestros datos o de una función.
Una vez tenemos generado el HTML, el siguiente paso es dotarle de funcionalidad para ello implementaremos la variable Ractive donde por un lado asignaremos el template que vamos a utilizar, por otro lado el sitio donde lo vamos a renderizar y por último le asignaremos los datos que vamos a representar.

  var template = $('#example-template').html();
    var example = $('#example');
   
    var items, sampleItems, todoList, filters;

    // our model is a normal array - Ractive will intercept calls to mutator methods
    // like push and splice, so we don't need to inherit from a custom class or anything
    sampleItems = [
      { description: 'Add a todo' },
      { description: 'Add some more todos' },
      { description: 'Build something with Ractive.js' }
    ];

    // Load data from localStorage. FF will throw a SecurityError if you try
    // to access localStorage with cookies disabled, so we try-catch
    try {
        if (window.localStorage) {
            items = JSON.parse(window.localStorage.getItem('ractive-todos'));
        }

        if (!items || !items.length) {
            items = sampleItems;
        }
    } catch (err) {
        // overwrite localStorage so we don't need to try-catch later
        window.localStorage = {
            setItem: function () { } // noop
        };

        items = sampleItems;
    }
    // set up some filters
    filters = {
        completed: function (item) { return item.completed; },
        active: function (item) { return !item.completed; },
        all: function () { return true; }
    };

    // create our app view
    todoList = new Ractive({
        el: example,
        template: template,
        noIntro: true, // disable transitions during initial render

        data: {
            items: items,
            filter: function (item) {
                var filter = filters[this.get('currentFilter')];
                return filter(item);
            },
            currentFilter: 'all',

            // These computed values are aware of their dependency on
            // `items` because of `this.get( 'items' )` - and will
            // automatically recompute and update the view when
            // `items` changes.
            completedTodos: function () {
                return this.get('items').filter(filters.completed).length;
            },

            activeTodos: function () {
                return this.get('items').filter(filters.active).length;
            }
        },

        // We can define 'transitions', which are applied to elements on intro
        // or outro. This is normally used for animation, but we can use it for
        // other purposes, such as autoselecting an input's contents
        transitions: {
            select: function (t) {
                setTimeout(function () {
                    t.node.select();
                    t.complete();
                }, 0);
            }
        }
    });

    // Various user mouse and keyboard actions, defined in the template, will
    // fire 'proxy events' that trigger behaviours and state changes
    todoList.on({
        remove: function (event, index) {
            items.splice(index, 1);
        },
        new_todo: function (event) {
            items.push({
                description: event.node.value,
                completed: false
            });

            event.node.value = '';
        },
        edit: function (event) {
            this.set(event.keypath + '.editing', true);
        },
        submit: function (event) {
            this.set(event.keypath + '.editing', false);
        },
        clear_completed: function () {
            var i = items.length;

            while (i--) {
                if (items[i].completed) {
                    items.splice(i, 1);
                }
            }
        },
        toggle_all: function (event) {
            var i = items.length, completed = event.node.checked;

            while (i--) {
                this.set('items[' + i + '].completed', completed);
            }
        },
        set_filter: function (event, filter) {
            this.set('currentFilter', filter);
        }
    });
    // When the model changes, persist to localStorage if possible
    todoList.observe('items', function (items) {
        // persist to localStorage
        if (window.localStorage) {
            localStorage.setItem('ractive-todos', JSON.stringify(items));
        }
    });

Este código no es muy dificil de entender. varios métodos para añadir, filtrar elementos y de una forma muy sencilla. Si ejecutamos este código en una App AutoHosted visualizamos la siguiente pantalla:
RactiveJS

Añadiendo SharePoint
Una vez tenemos el ejemplo básico con datos ficticios, ahora vamos a incluirlo que consulte la lista de tareas de SharePoint.
El primer paso vamos a rearlizar una consulta la la lista de SharePoint «Tareas» y posteriormente asignarle estos datos a nuestro objeto Ractive.
Para ello solamente con el siguiente código es suficiente:

 var web = context.get_web();
    context.load(web);
    var spList = web.get_lists().getByTitle('Tarea');
    var camlQuery = SP.CamlQuery.createAllItemsQuery();
    this.value = spList.getItems(camlQuery);
    context.load(value);
    context.executeQueryAsync(Function.createDelegate(this, this.mySuccessFunction),
        Function.createDelegate(this, this.myFailFunction));

function mySuccessFunction() {
    var template = $('#example-template').html();
    var example = $('#example');
    var listItems = this.value.getEnumerator();
    items = [];
    var i = 0;
    while (listItems.moveNext()) {
        var oListItem = listItems.get_current();
        items[i] = { description: oListItem.get_item('Title') };
        i++;
    }
}

Para completar el ejemplo tendremos que añadir en cada evento de añadir/modificar/eliminar su correspondiente acción en el SSOM de JavaScript. Para este primer ejemplo no los voy a implementar, dado que es un ejemplo base y podemos encontrar esta información en la ayuda de Technet.
El añadir esta funcionalidad solamente seria valida para este ejemplo «demo» en un caso real vamos a extender Ractive para hacer uso Backbone y tener una integración total con los servicios REST de SharePoint/WebApi para la parte de comunicación con el servidor y por otro lado vamos a modificar la colección de datos, de tal forma que por un lado comprobemos que los datos se renderizan de una forma muy rápida y eficaz y por otro lado vamos enviando la información al servidor sin interferir en la ejecución de la aplicación. Estos aspectos los visualizaremos en el próximo post 🙂
Este ejemplo lo podéis descargar desde el siguiente link.

mm

Sobre Adrián Díaz

Adrián Díaz es Ingeniero Informático por la Universidad Politécnica de Valencia. Es MVP de Microsoft en la categoría Office Development desde 2014, MCPD de SharePoint 2010, Microsoft Active Profesional y Microsoft Comunity Contribuitor 2012. Cofundador del grupo de usuarios de SharePoint de Levante LevaPoint. Lleva desarrollando con tecnologías Microsoft más de 10 años y desde hace 3 años está centrado en el desarrollo sobre SharePoint. Actualmente es Software & Cloud Architect Lead en ENCAMINA.
Esta entrada ha sido publicada en buenas practicas, javascript y etiquetada como , , . Enlace permanente .
Suscríbete a Desarrollando sobre SharePoint

Suscríbete a Desarrollando sobre SharePoint

Recibe todas las actualizaciones semanalmente de nuestro blog

You have Successfully Subscribed!

ENCAMINA, piensa en colores