Tecnologías de Front-end

usadas en Som Energia, para Pythoner@s

David García Garzón

Frontend

Frontend vs Backend

  • Frontend: se ejecutan en el navegador
    • Mithril, ReactJS, Angular…
  • Backend: se ejecutan en el servidor
    • Flask, Django, NodeJS, PHP…

Objetivo

Poner al día de las tecnologías de Frontend que usamos en Som Energía al personal que ya programa en Python temas de Backend.

Se supone una base en programación.

Haré paralelismos con tecnologías Python.

Ecosistema de Frontend

Javascript (Lenguage)
NodeJS
(Paqueteria, Entorno)
Webpack (Builder)
Mithril (Control)
Material (Widgets)

Javascript

Inevitable: El único lenguage incluido en todos los navegadores (en cada uno a su manera)

Sintaxis familiar pero engañosa, no se comporta igual que C++ o Java. Repasaremos algunas trampas.

Es necesario entender algunas construciones que usamos en Mithril.

NodeJS

Tecnologia de backend, sí, pero nos da un entorno de desarrollo (como el que tenemos en python).

  • Definicion de proyecto (setup.py -> package.json)
  • Repositorio de paquetes (pip -> npm)
  • Entorno local aislado (venv -> node_modules)
  • Interprete interactivo (python -> node)
  • Web server para desarrollo

Webpack

Constructor: Prepara el paquete de ficheros que se bajará el navegador. Deja obsoletos a Grunt, Gulp, Requirejs, Bower, Browserify…

  • Aisla los .js en paquetes/namespaces
  • Soluciona dependencias
  • Transcompila lenguajes (scripts, estilos)
  • Optimiza ficheros
  • Empaqueta lo que puede ir junto
  • Separa lo que puede ir separado
  • Modifica el HTML para bajarselo todo

Mithril

Framework para desarrollar aplicaciones de página única que se ejecutan en el navegador.

  • Definir componentes HTML dinámicos
  • Sincronizar vista y modelo
  • Acceder asincronamente a las APIs
  • Pequeño, sencillo, rápido y potente

Alternativas: React, Vue, Angular…

Material Design

Especificación de componentes gráficos. Originalmente para Android.

Define aspecto, comportamiento, variaciones… de botones, menus, radio buttons…

Hay muchas implementaciones (incompletas todas) Usamos la de Google para Web:

Material Design Components for the Web

Agnóstica al framework, la adaptamos a Mithril

Javascript

Falsos amigos

Javascript, tan familiar que pensamos que no hay que aprenderlo.

Se parece a C++ y Java

Esperamos que se comporte como ellos.

Cuando no lo hace, 😠 😠 😠 😠 \(`O´)/

Falsos amigos: == y !=

  • == y != intentan convertir tipos antes de comparar
  • === y !== comparan sin conversion
1=='1' // true    ''=='    ' // false  null == undefined // true
1==='1' // false  false=='' // true    null === undefined // false
0=='' // true     false==0 // true     undefined==false // false
0==='' // false                        [1,2]=='1,2' // true

La regla: Usar el de tres signos al menos que realmente quieras la conversión (y no, no la quieres)

Falsos amigos: +

3 + 2 // 5
'a' + 'b' // 'ab'
'2' - 3 // -1 !!
'2' + 3 // 23 !!
'a' + 3 // 'a3'
3 + true // 4 !!
3 + false // 3 !!
'3' + true // '3true' !!
3 + undefined // NaN
'a' + undefined  // 'aundefined'
3 + {} // '3[object Object]'

Falsos amigos: bool cast

En Python estamos muy acostumbrados a usarlas en condiciones y funcionan muy distinto.

  • Las estructuras vacias {} y [] son true!!
  • Un string vacio '' es false, pero tambien uno con solo espacios: ' '
  • El entero 0 es falso pero, tambien el string '0'!!
  • De hecho un string con varios ceros pero con espacios delante y detras tambien es false: ' 000 \t\n '

Variables no declaradas

Típico: me equivoco en el nombre de la variable

Javascript no se queja, devuelve undefined.

Se propaga en silencio como false o el numero NaN o la cadena 'undefined'… hasta que peta!!!

Añadir 'use strict'; al principio de los ficheros hace que se queje si usas una variable no declarada.

¡Ojo! Avisará con las variables, pero no con las propiedades de los objetos que no existen.

Objetos

Los objetos de Javascript son “diccionarios”

Acceso dual con . y [] como nuestro yamlns.

var name = 'd';
var o1 = { // literal
    a: 1,
    'b-at': 2,  // 'b-at' no es identificador valido, comillas
    name: 19,   // coge la clave 'name', no 'd'
    name+'': 3, // truco para coger 'd' de la variable
};

// Updating: en Python: o1.update({'a':4, 'c':5})
Object.assign(o1, {a: 4, c: 5});

// Cloning: en Python: o2 = dict(o1)
var o2 = Object.assign({}, o1);

Singletons

Métodos: atributos que apuntan a funciones

var MySingleton = {
    _param1: 'param1value',
    method1: function() {
        // body
    },
    method2: outerfunction,
};
MySingleton.method3 = function() {};

No son clases, son Singletons, objetos únicos. Pero sirven de prototipo para otros.

En Mithril los usamos para definir componentes.

Las clases son funciones

function MyClass(param1) {  // funcion 'Factoria'
    this._param1 = param1; // Attributo
    function premethod1() {
        // body
    } // <- Sin punto y coma, es una declaracion
    this.method1 = premethod1;
    this.method2 = function() {
        // body
    }; // <- Ojo el punto y coma! es una asignacion
}
var myinstance = new MyClass('param1value'); // No olvides el new

// Ampliar una clase a posteriori. No olvides el 'prototype'!
MyClass.prototype.method3 = function() { ... };

Falsos amigos: this

Su valor en una funcion f depende de como se llame, no de donde se defina

// La misma funcion todo el rato
function f (a,b) { console.debug(this); }

f(a,b); // Seria `undefined`
new f(a,b); // Seria un nuevo objeto `{}` vacio
o.f=f; o.f(a,b); // Seria `o`
var f2 = o.f; f2(a,b); // 'undefined', f2 pierde el binding
var f3 = f.bind(o); f3(a,b); // Seria `o`
// A bajo nivel
f.call(o, a, b); // Seria `o`
f.apply(o, [a,b]); // Seria `o`

Falsos amigos: this

Ojo con las lambdas y las inner:

function mymethod(b) {
    var a = 3;
    var self = this;
    this.onEvent(function() {
        // a y b se pueden usar aqui dentro
        // pero this esta redefinida como undefined
        // por eso usamos self como variable intermedia
        this.otrafuncion(a,b); // Error, this es undefined
        self.otrafuncion(a,b); // Bien
    }
}

Promesas

Facilitan la programación asíncrona.

function funcionAsincrona() {
    return new Promise(function (resolve, reject) {
        // do you async stuff here
        if (ok) { resolve(result); }  // makes the promise succed
        else { reject(error); }  // makes the promise fail
    });
}
var promesa = funcionAsincrona();
promesa.then(function(result) {
    // Codigo a ejecutar cuando acabe
}).catch(function(error) {
    // Codigo a ejecutar si falla
});

NodeJS

Definición del proyecto

La definición del proyecto se guarda en package.json.

Se crea con el comando npm init a partir de preguntas interactivas.

Repositorio de paquetes

npm es un gestor de paquetes parecido a pip

npm search, npm install

Se baja los paquetes de un repositorio online y los instala en local, en la carpeta node_modules.

Cada proyecto tiene su node_modules propio y aislado como un virtualenv en Python.

Dependencias del proyecto

Dependencias (de run-time): de uso del paquete

Dependencias de desarrollo: de construcción del paquete

Para nosotros, que no hacemos paquete (aun), las de run-time son las que se usan en el navegador.

Se definen en el package.json y se instalan con npm install sin especificar paquete.

Añadiendo dependencias

Se añaden con npm install --save paquete o con --save-dev si es de desarrollo.

Con las opciones save quedan guardadas en las claves dependencies y devDependencies

Si no, no se actualiza (los marca straneous)

Queda guardada la versión. Para actualizarlas: npm update --save paquete

Comandos extra

Podemos añadir comandos personalizados para el desarrolo de nuestro proyecto.

El package.json contiene la clave scripts con comandos personalizados de npm.

    "scripts": {
        "server": "webpack-dev-server --open --watch -d",
    },
$ npm run server

Chuleta

npm init # Crea el package.json a base de preguntas

npm install # Instal·la les dependencies del package.js

npm search <words> # busca paquetes con words en la descripción

npm install --save <package> # añade la dependencia al proyecto

npm install --save-dev package # añade dependencia de desarrollo

npm update --save/--save-dev # actualiza versión (menores) de los paquetes

npm run test # Ejecuta el script `test` definido en `package.json`

Webpack

¿Qué aporta?

A partir del codigo fuente, genera los ficheros que se bajará el navegador.

Vamos que compila, ¿pero Javascript no era interpretado?

A medida que los proyectos se hacen grandes, es necesario modularizar, descartar modulos no usados, agregar el resto, optimizar, preprocesar…

Ejemplo real

Repositorio webforms-mithril (link)

La configuración de webpack en webpack-config.js

Comandos de webpack en los scripts del package.json

Modulos en Javascript

A pelo, Javascript ES5 no sabe de modulos.

Se incluye cada .js en el html con <script>.

Tambien las dependencias!

Todo va al scope global. No hay namespaces.

Se usan funciones auto-llamadas para aislar.

¿Cómo funciona?

Explora las dependencias entre los módulos y genera código para:

  • aislar el espacio de nombres de cada módulo
  • indexar los módulos por el path
  • implementar require para cargarlos

Tambien modifica el html para incluir los assets finales

Definición de módulos

// mymodule.js
'use strict'; // Activa errors per variables no declarades

function myfunction() {
    return 'hello world';
}

module.exports = myfunction;
// main.js
'use strict'; // Activa errors per variables no declarades

var imported = require('./mymodule');

console.debug(imported()); // should show 'hello world'

El bundle (envoltorio)

(function(modules) { // funcion auto-llamada
    var installedModules = {}; // The module cache
    function require(moduleId) {
        // Check if module is in cache
        if(installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // Create a new module (and put it into the cache)
        var module = installedModules[moduleId] = {
            i: moduleId,
            exports: {}
        };
        // Execute the module function
        modules[moduleId].call(module, module.exports, require);
        // Return the exports of the module
        return module.exports;
    }
    require('./main'); // el entry point del bundle
}) ({
    './mymodule': function(module, exports, require) {
        eval('string con el contenido del fichero');
    },
    './main': function(module, exports, require) {
        eval('string con el contenido del fichero');
    },
});

Entry points y bundles

Entry point: Punto de partida (js) de donde estirar las dependencias. Puede haber varios (diferentes páginas)

Los navegadores cargan más rápido un fichero mediano que muchos pequeños.

Bundle: Un fichero que junta las dependencias de un punto de partida.

Configuración

var config = {
    context: path.resolve(__dirname, 'src'), // our code's root
    entry: {
        main: './main',   // bundle name -> src/main.js
    },
    output:
        // Apends a chunkhash to force reloading
        filename: 'bundle-[name]-[chunkhash].js',
    },
    plugins:[
        // webpack deals with js. html by plugins
        // Generaes html to insert generated css and js
        new HtmlWebpackPlugin({
            filename: 'index.html',
            }),

Chunks

Un fichero grande también es peor que varios medianos. Fragmentemos.

Los assets se generan con un hash en el nombre para forzar recarga de cache si hay cambios.

Las dependencias vendor son menos proclives a cambiar, si las separamos tendran mas hits de cache.

Con varios entry points, habrá cosas comunes entre los bundles. Separando lo común y lo particular, se optimiza la carga de múltiples páginas.

Múltiples entradas

    ...
    entry: {
        main: './main',
        contact: './contact', // added: second entry point
    },
    output:
        filename: 'bundle-[name]-[chunkhash].js',
        // added: id would be 'main~contract` for the shared one
        chunkFilename: 'chunk-[id]-[chunkhash].js', // added
    },
    plugins:[
        new HtmlWebpackPlugin({filename: 'index.html'}),
        // Each page should have its html plugin
        new HtmlWebpackPlugin({filename: 'contact.html'}),

Otros recursos (assets)

Datos (json, xml, yaml), estilos (css, sass, less, stylus), imagenes (png, jpg, svg) …

Se requieren como si fueran javascripts.

Una cadena de loaders se encarga de que esten disponibles en el navegador.

Transcripción

Los loaders nos permiten traducir recursos en formatos no soportados por los navegadores.

  • CSS: Sass, Less, Stylus
  • Javascript: ES6, CoffeScript, TypeScript
  • HTML: Markdown, RST, Jade
  • Datos: XML, JSON, YAML
  • Imagenes, videos, fuentes…

También se usan para la optimización. Minifiers, uglifiers, imagenes multiresolución…

Disposición

Los loaders determinan en que forma estará disponible el recurso y lo hacen accesible de forma transparente

Como ficheros independientes

Como strings en el bundle javascript

Extraidos en su propio bundle

Insertados en otro recurso

Loaders

config.module.rules como cargar cada recurso.

rules: [
    // yaml-loader: yaml data -> json data
    // json-loader: json data -> js object
    // you get it with 'require'
    { test: /\.yaml$/, use: ["json-loader", "yaml-loader" ] },

    // stylus-loader:  stylus -> css
    // css-loader: css -> js code that adds the style
    // MiniCssExtractPlugin: extracts into a css bundle
    // 'require' ensures that the css is loaded
    // as any dependant assets (images, includes...)
    { test: /\.styl$/, use: [ MiniCssExtractPlugin.loader,
            "css-loader", "stylus-loader"]},
    // The css bundle is configured in the plugin section

Plugins

Los loaders son solo tuberias.

Los plugins aportan loaders nuevos pero tambien otra funcionalidad.

HtmlWebPackPlugin: Genera un html a partir de un template incluyendo uno o varios chunks (js, css…)

plugins: [
    new HtmlWebPackPlugin({
        filename: 'index.html',
        template: 'mithriltemplate.html',
        chunks: ['example', 'common'],
    }),
]

Integración Back-end

Problema: el HTML lo genera la aplicacion de backend (flask, django, php), no webpack.

Hay plugins de webpack que generan manifiestos con la lista de assets.

Hay extensiones para Flask y Django que integran esa lista en las páginas.

Estrategia: en backend pocas páginas y mucha API.

Servidor de desarrollo

webpack-dev-server --open -d

o npm run server

Lanza un webserver NodeJS local con los assets

Apunta el navegador a la página principal.

Detecta cambios en los ficheros, regenera los assets del servidor y recarga el navegador.

Agiliza mucho el ciclo de desarrollo.

Source Maps

Con tanta transformación, ¿cómo relacionamos un error en el navegador con el código fuente?

En modo desarrollo webpack genera source maps. Comentarios especiales que referencian al código original, fichero y linia.

Los navegadores modernos los entienden y generan stack traces usables.

Mithril

Frameworks

HTML y CSS permiten algunas animaciones y respuesta interactiva, pero son en esencia estàticos. JavaScript permite modificar el HTML en el navegador.

La librería estándar no es muy potente. Hay librerías que la complementan: JQuery, Underscore, Sugar…

No es suficiente abstracción para construir aplicaciones complejas. Ahí entran los frameworks: Vue, React, Angular, Mithril…

Componentización

Los frameworks suelen dar una forma de definir componentes gráficos o widgets.

En HTML sería un tag propio, que controlamos vía atributos, y que abstrae un HTML más complejo.

Se pueden replicar y juntar con otros para armar nuevos componentes.

Interacción Modelo-Vista

Cada framework llama vista, modelo o controlador a cosas distintas. El patron MVC original no era práctico y nadie lo usa tal cual. Pero quedó la idea: Separar la presentación de los datos.

El framework define:

Como el modelo (datos js) altera la vista (html)

Como lo que pasa en la vista modifica el modelo

Ventajas de Mithril

  • Ligero: muy pocos k’s
  • Rápido: Usa DOM virtual
  • Simple: API minimalista
  • Potente: A pesar de la simplicidad

Hola mundo

...
<div id='mithril-target' class='main'>
    <div class='loading'><b>Loading...</b></div>
</div>
...
var m = require('mithril');

window.onload = function() {
    var element = document.getElementById('mithril-target');
    m.render(element, m('h1', 'Hola mundo'));
};

Hola mundo, comentado

m.xxxx - acceso a funciones de la api Mithril.

m(...) - como función genera nodos virtuales.

Los nodos virtuales (vnodes) representan HTML sin usar el DOM directamente, que es lento.

m.render convierte nodos virtuales en HTML.

Renderiza solo una vez, nos vale para ejemplos. Veremos adelante como actualizar.

Generando vnodes

// Sintaxis general
m(tag, attrs, children, children2, ...);

// tag: sintaxis css
'h1'                // <h1>
'.sidebar.black'    // <div class="sidebar black">
'#mytag'            // <div id="mytag">
'[title="tooltip"]' // <div title="tooltip">
// Combinable:
'input.mdc-input#name[type="text"]'

// attrs: (opcional) diccionario con más attributos
// children: (opcional, multiple) string, vnodes o lista childrens

Hyperscript

var backuri = 'http://example.com';
return m('section#section1',[ // <section id="section1>
    m('h2', 'Titulo'),             //   <h2>Titulo</h2>
    m('p.first','yes'),            //   <p class="first">yes</p>
    m('p','no'),                   //   <p>no</p>
    m('nav.backlink', [            //   <nav class="backlink">
        m('a[target="_blank"]', {  //     <a target="_blank"
            'href': backuri,       //       href="http://examp..."
        },                         //     >
            'Volver atras'         //       Volver atras
        ),                         //     </a>
    ]),                            //   </nav> 
]);                                // </section>

Control flujo

Las estructuras de control if y for rompen la estructura visual del hyperscript.

Mejor usar expresiones ternarias o cortocircuitos booleanos y, para los loops, map.

var verduras = ['judias', 'acelgas', 'espinacas'];
var selected = 'judias';
return m('ul',
    verduras.map(function(verdura, i) {
        return m('li', i+1, ' - ', verdura,
            verdura==selected?" selected":null),
        );
    })
);

vnodes por dentro

Es un objeto/diccionario con:

  • tag, attrs y children: los parámetros del m
  • text: si solo hay un children y es texto
  • dom: elemento DOM renderizado (si lo está)
  • state, key: los veremos

Manipular el DOM dispara el redibujado. Los vnodes son baratos de crear y comparar. Vale la pena generarlos a menudo a cambio de juntar cambios en un solo redibujado.

Primer componente

// Nuestro primer componente!
var Hello = {
    // render llama al método view pasandole el vnode original
    // Retornamos el vnode que se renderizará en su lugar
    view: function(vn) {
        // podemos acceder a los attributos del vnode original
        return m('h1', 'Hola '+vn.attrs.name||'mundo');
    },
};

window.onload = function() {
    var element = document.getElementById('mithril-target');
    // Usamos el componente como tag
    m.render(element, m(Hello, {name: 'Voki'}));
};

Montando la app

La funcion m.mount activa el sistema de actualización. Después de cada evento se dispara un renderizado, si hay cambios se redibuja el DOM.

var App = {};
App.view = function(vn) { return m('h1', 'Hola mundo'); },

window.onload = function() {
    var element = document.getElementById('mithril-target');
    m.mount(element, App); // mount, no render!
};

Ojo! Al mount le pasamos un componente, App, no un vnode, m(App), como al render.

Componente contenedor

Igual que usábamos vn.attrs, podemos propagar vn.children al virtual node resultante.

var RedBox = {};
RedBox.view = function(vn) {
    return m('', {style: 'border:3pt solid red'}, vn.children);
};

var App = {};
App.view = function(vn) {
    return m(RedBox,
        m('h1', 'Dress in red'),
        m('.content', 'bla bla'),
    );
};

Controlando un modelo

var Person = { name: 'anonymous' };  // the model

var PersonEditor = {};
PersonEditor.view = function(vn) {
    return m('',[
        m('input', {
            value: Person.name, // Model -> View
            oninput: function(ev) {
                Person.name = ev.target.value; // View -> Model
            },
        }),
        m('', 'hello ' + Person.name), // Model -> View (again)
    ]);
};

Estado interno

vn.state: objeto que mantiene el estado del widget

PersonEditor.var1 = 1; // inicializa vn.state.var1

// Inicializa el estado en oninit
// Se llama antes de inicializar el DOM
PersonEditor.oninit = function(vn) {
    vn.state.var2 = 'value2';
    // vn.state se pasa como this a los metodos del componente
    this.var3 = 'value3'; // igual que vn.state.var3
};

¡Ojo! Distingue entre estado interno y modelo

Acceso al dom

Hay que evitar manipular el DOM directamente.

A veces es necesario por el uso de otras librerías. (Como MDC4W).

vn.dom: apunta al DOM renderizado

El componente puede implementar hooks que se llaman en diferentes momentos del ciclo de vida.

En vn.oninit() no está disponible porque no se ha ejecutado aún ningún render.

Ciclo de vida (I)

oninit: Antes de llamar al view la primera vez. Para inicializar vn.state a partir de vn.attrs. Cosas que no necesiten el DOM.

oncreate/onupdate: Tras insertar/actualizar el DOM después de un render. Para llamar librerias que necesitan el DOM o consultar layout final.

¡Ojo! Cambios en el modelo aquí, no disparan render.

Ciclo de vida (II)

onremove: Para tareas de limpieza. Se llama justo antes de eliminar el nodo.

onbeforeremove: Para transiciones de salida. Retorna un Promise. Se retrasa el onremove y la eliminación del nodo hasta que el Promise resuelva.

Conciliar con claves

Es difícil saber que nodo virtual corresponde si reordenamos los nodos o si cambian demasiado.

Mithril permite asociar al virtual node una clave, vn.key, para asegurar que el mapeo es correcto.

Consultas a APIs

m.request({
    url: 'https://example.com/api/persona',
    method: 'POST',
    data: {
        name: Persona.name,
    },
}).then(function(response) {
    
}, function(error) {

});

Se refresca la interfaz despues de recibir la respuesta.

Por defecto JSON, personalizable.

Personalizar la response

Diferentes niveles de personalizacion partiendo de:

extract(xhr, options): acceso a toda la respuesta, por defecto pasa xhr.responseText

deserialize(responseText): a partir del responseText, por defecto parser JSON

type(object): objeto JSON parseado, por defecto identidad

Personalizar la request

data: datos que serializan en el body o en la request

headers: añade cabeceras

config: permite modificar las cabeceras de la request

serialize: aplicado a data, por defecto JSON.serialize

Material

Librerias componentes

Bibliotecas que definen elementos de la interfaz reusables (widgets)

  • Bootstrap (Twitter)
  • Material (Google)
  • JQuery-UI

Material Design

http://material.io

Especificación de cómo han de ser las interfaces en Android a partir de Lollypop

Generalizado a otros soportes como el web.

Multiples implementaciones.

Component List

Button

Slider

Switch

TextField

Selects

Checkbox

RadioButton

LayoutGrid

List

GridList

ImageList

Tabs

Chip

Progress

AppBar

Drawer

Card

SnackBar

Banner

Dialog

Sheet

Comunicación

Dialog

Persistente

Bloqueante

Banner

Persistente

No bloqueante

SnackBar

Temporal

No bloqueante

Sistema de Color

Tema de Color

Primary, Secondary y sus variantes light y dark.

Las variantes para destacar de forma harmoniosa.

Secondary para dar acento especial.

Background para el fondo estatico.

Surface para las cosas que se elevan sobre el fondo.

OnX: El color de texto cuando se usa X como fondo

Tipografía

Valores por defecto, customizables y criterios.

Fuente, tamaño, espaciado, mayúsculas…

  • Headlines 1-6: Textos cortos prominentes
  • Subtitles 1-2: Textos cortos enfatizados
  • Body 1-2: Textos largos
  • Caption: Pies de imagen
  • Overline: Contextualizar un titulo
  • Button: Texto para botones

¿Material para Mithril?

Polythene: Lo usamos en el Tomàtic. Calcula los estilos en el navegador, y pierde lo que ganas con Mithril.

Mithril MDL: wrapper para Mithril de Material Design Lite de Google. Menos completo, mucho más rápido. Estilos precompilados.

MDL fue discontinuado en favor de Material Components 4 Web que aún no tiene wrapper Mithril.

MD Components 4 Web

Implementación Web de Google

Incompleta como todas, en progreso rápido.

En vez de concentrarse en un framework da herramientas para usarlo en cualquiera.

Nosotros haremos el wrapping para Mithril: src/mdc/ (link)

Demo en vivo

Estructura

Estilos:

Implementados con Sass.

Customizables (¡precalculados!)

Javascript:

Cuando necesitan inicializacion

Cuando Ofrecen API.

Aplicar tipografía

require('@material/typography/dist/mdc.typography.css');

En la raiz aplicar la clase mdc-typography.

  • mdc-typography--headline1 a 6
  • mdc-typography--subtitles1 a 2
  • mdc-typography--body1 a 2
  • mdc-typography--caption
  • mdc-typography--overline
  • mdc-typography--button

Redefinibles en el CSS.

Usando el tema

En nuestro css, antes de cargar el de MDC4W

* {
    --mdc-theme-primary: red;
    --mdc-theme-secondary: yellow;
    --mdc-theme-background: white;
    --mdc-theme-surface: #ffe;
    /* cuando ponemos un color de tema de fondo,
        estos colores para el texto */
    --mdc-theme-on-primary: white;
    --mdc-theme-on-secondary: black;
    --mdc-theme-on-surface: black;
}

Usables como color var(--mdc-theme-primary, #faf)

MDC Button en Mithril

Sin Javascript, basado solo en estilos

require('@material/button/dist/mdc.button.css');
var Button = {
    view: function(vn) {
        return  m('button.mdc-button'
            +(vn.attrs.raised ? '.mdc-button--raised':'')
            +(vn.attrs.unelevated ? '.mdc-button--unelevated':'')
            +(vn.attrs.outlined ? '.mdc-button--outlined':'')
            +(+vn.attrs.dense ? '.mdc-button--dense':'')
            , attrs, [
            (vn.attrs.faicon ? m(
                'i.mdc-button__icon.fa.fa-'+vn.attrs.faicon):''),
            vn.children
        ]);
    },
};

Uso del MDC Button

var Button = require('./mdc/button');

var App = {
    clicked: false,
    view: function(vn) {
        return  m(Button, {
            raised: true,
            faicon: this.clicked?'spinner.fa-spin':'paper-plane',
            onclick: function(ev) {
                vn.state.clicked = true;
            },
        }, 'Send');
    },
};

Como pinta

MDC Dialog en Mithril

Inicializando y con API.

const mdcDialog = require('@material/dialog');
const MDCDialog = mdcDialog.MDCDialog;
var Dialog = {};
Dialog.oninit = function(vn) {
    // Para poder acceder desde fuera a la API
    vn.state.model = vn.attrs.model || {};
    // Api publica del componente Mithril
    vn.state.model.open = function() {
        vn.state.widget.show();
    };
};
Dialog.oncreate = function(vn) {
    vn.state.widget = MDCDialog.attachTo(vn.dom);
    vn.state.widget.listen('MDCDialog:accept', function() {
        vn.attrs.onaccept && vn.attrs.onaccept();
    });
    vn.state.widget.listen('MDCDialog:cancel', function() {
        vn.attrs.oncancel && vn.attrs.oncancel();
    });
};
Dialog.onremove = function(vn) {
    vn.state.widget.destroy();
};

Uso de Dialog

Acceso al API via objeto injectado

const Dialog = require('./mdc/dialog');
var mydialog = {};
m(Dialog, {
    oncancel: function() { }, // Whatever to do on cancel
    onaccept: function() { }, // Whatever to do on accept
    model: mydialog, // inject object
    buttons: [
        { text: "Help", onclick: showhelp }, // Custom action
        { text: "No", cancel: true }, // Default cancel action
        { text: "Si", accept: true }, // Default accept action
    },
}, m('¿Quieres proceder?)),
m(Buttton, {
    // open is accessible via mydialog
    onclick: function() { mydialog.open(); },
}, "Open Dialog");