Creando un widget para compartirlo entre webs…

14/02/2022

En estos días he estado trabajando en un widget de un Chat, de esos como el “Messenger” de Facebook que integra en las webs a las que le incrustes un fragmento de código…y se me ocurrió compartir la base del proceso para que cualquiera pueda crear estos widgets fácilmente.

Primero, lo primero…el código a integrar.

(function (d, s) {
    var js, fjs = d.getElementsByTagName(s)[0];
    js = d.createElement(s);
    js.src = "http://miweb.com/widget/widget.min.js";
    js.type = "text/javascript";
    fjs.parentNode.insertBefore(js, fjs);
}(document, 'script'));
window.onload = () => MiWidget.init();

¿Qué demonios es este código?, bueno, vamos por partes…

Muchos códigos como el de Facebook, lo que hacen es buscar una tag X, en el documento, y después le anexan un llamado de un archivo, una vez que lo integra, necesitamos esperar a que esté lista la web y entonces sí, ahora sí llamar el servicio.

Viendo ya el código, primero, auto-ejecutamos la función, creamos un elemento nuevo HTML anidado a una tag llamada “script”, debido a que es “obligatorio” que haya un tag <script></script> porque es donde integramos este código es que el segundo parámetro así lo recibe.

En otras palabras, esto irá en las tags <script>/* El código de arriba</script> por lo que sería seguro que nuestro widget anexe el JS y al ser insertBefore (es decir agrégalo antes de esta etiqueta) siempre se llama antes de poder ejecutar el código y no después.

Sin embargo si un usuario decidiera que se agregue antes del cierrehead podría cambiar la condicional, o antes del </body> o así, otro dato es que se puede agregar antes de una tag <script> superior. Ya que buscará la primera y la agrega previa a… podemos también modificar eso, pero por ahora, así se va.

Cabe destacar la URL que pongo, esta es de donde se va a “jalar” nuestro código, aquí estén al tanto que es una forma que nuestro widget se pueda usar en todos lados y nosotros controlemos cachés, versiones y demás sin que los clientes tengan que hacer algo, solo recuerden, cuidado con esos “breaking-changes” ;).

Entonces, ya que nuestra ventana esté cargada, mandamos llamar al Widget con su método init .

Vamos al código ahora…

En este caso, vamos a ver 2 secciones:

const MiWidget = {
opciones: {
nombre: 'Juan Pérez',
},
init: function(opciones) {
alert('Inicié');
}
}

opciones e init

El primero nos permitirá hacer que nuestro widget pueda recibir opciones, lo que haremos que este Widget haga, será imprimir un nombre en pantalla.

Entonces, vamos a validar dónde queremos eso, a través de una segunda opción id y a través de validaciones, así como la posibilidad de cambiar uno u otro sin ocupar escribir todas las opciones, así, como agregar la posibilidad de una hoja de estilos para hacer “menos feo” el nombre.

Nuestras opciones quedarán entonces:

opciones: {
id: 'miwidget',
nombre: 'Juan Pérez',
},

Y nuestro init hasta este punto de la siguiente forma:

init: function(opciones) {
const widgetId = document.getElementById(`${this.opciones.id}`);
if(!widgetId) {
alert('Oops no encontré el id, no puedo continuar');
return;
}

widgetId.innerHTML = `<h1 class="miwidget-h1">¡Hola ${this.opciones.nombre}!</h1>`;
}

Aquí, como veremos si no detectamos el ID indicado, entonces, ¡error!, porque, si no le decimos al usuario que algo anda mal, va a sufrir… y pues como no encontramos el ID, no tiene sentido que siga haciendo algo el código.

Si lo encontramos va a salirnos un <h1> con el mensaje de “¡Hola [nombre]!”.

Así que, ahora en el HTML donde agregamos el <script> debemos agregar un <div> con el ID que indiquemos. Por ejemplo en este caso miwidget :

<div id="miwidget"></div>
<script>/* el script de arriba */<script>

Y entonces veríamos en pantalla algo así:

¡Genial!, ¿no?… bueno, ahora haremos que podamos cambiar el nombre y agregar una hoja de estilos. Vamos con las opciones, para ello agregamos un nuevo método:

extenderObjeto: function (a, b) {
for (const key in b)
if (b.hasOwnProperty(key))
a[key] = b[key];
return a;
},

Después, vamos a agregar algo en nuestro init :

if (typeof opciones !== 'undefined') {
this.opciones= this.extenderObjeto(this.opciones, opciones);
}

Lo que hará aquí es que va a revisar si el init tiene parámetros, si los tiene va a revisar cuáles existen en el objeto de opciones y las va a reemplazar SOLO con las opciones que se indicaron.

Así, entonces si por ejemplo quieren cambiar el nombre, pueden pasar los parámetros:

MiWidget.init({ nombre: 'María González' });

Y veremos que cambia el nombre, pero no cambia ninguna otra opción como el ID.

¡Ya casi terminamos!, solo nos falta hacer que nuestro Widget se vea más bonito. Normalmente, los scripts o widgets luego te hacen integrar un CSS a mano, pero, ¿por qué no hacerlo automático?.

Así que al init vamos a agregar el siguiente código (o, como veremos después en una función interna):

const head = document.getElementsByTagName('head')[0];
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = `http://ejemplo.com/widget/miestilo.css`;
link.media = 'all';
head.appendChild(link);

Y lo que hará este código es idéntico al superior, agregar en el head el CSS pero “hasta arriba” y esto no puede cambiar dado que así es como HTML solicita las hojas de estilo. Entonces hay que crear el estilo “miestilo.css” y asegurarnos que la URL ejemplo apunta a nuestro local (o en producción pues a la URL absoluta)

Una vez creado, vamos a crear las clases para el H1, en este caso miwidget-h1 SIEMPRE intenten utilizar prefijos correspondientes a sus widgets para que no vayan a provocar errores en los diseños de sus usuarios:

.miwidget-h1 {
font-family: Arial, sans-serif;
color: red;
}

Así, con esto ya queda listo nuestro sistema, les dejo el código final apuntando a localhost:8080 recordando que ustedes deben cambiar las URLs:

index.html (con parámetro para ejemplificar mejor):

<div id="miwidget"></div>
<script>
(function (d, s) {
var js, fjs = d.getElementsByTagName(s)[0];
js = d.createElement(s);
js.src = "http://localhost:8080/widget.js";
js.type = "text/javascript";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script'));
window.onload = () => MiWidget.init({ nombre: 'María Gonzáles'} );
</script>

widget.js:

const MiWidget = {
opciones: {
id: 'miwidget',
nombre: 'Juan Pérez',
},
init: function(opciones) {
if (typeof opciones !== 'undefined') {
this.opciones = this.extenderObjeto(this.opciones, opciones);
}
this.agregarCss();
const widgetId = document.getElementById(`${this.opciones.id}`);
if(!widgetId) {
alert('Oops no encontré el id, no puedo continuar');
return;
}

widgetId.innerHTML = `<h1 class="miwidget-h1">¡Hola ${this.opciones.nombre}!</h1>`;
},
extenderObjeto: function (a, b) {
for (const key in b)
if (b.hasOwnProperty(key))
a[key] = b[key];
return a;
},
agregarCss: function() {
const head = document.getElementsByTagName('head')[0];
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = `http://localhost:8080/estilo.css`;
link.media = 'all';
head.appendChild(link);
}
}

estilo.css:

.miwidget-h1 {
font-family: Arial, sans-serif;
color: red;
}

Y el resultado final:

A partir de aquí, ustedes pueden ir implementando más opciones, más posibilidades e ir mejorando su Widget :).

Como extra, en algunas ocasiones se usan iframes debido a que de esta forma, por ejemplo con chats (usando Intercom como ejemplo) ellos pueden manipular el HTML, en este caso nosotros agregamos HTML al <div>, pero imaginen que es muchísimo HTML, entonces por eso prefieren agregar un iframe y ahí cargar el CSS, todas las variables y hacer widgets dinámicos que su contenido pueda cambiar, pero al final, la idea es que el Widget tenga opciones y pueda ser llamado fácilmente.es.

¡Suerte!