Construya un reproductor con lista de reproducción de YouTube personalizado
Extienda el reproductor integrado básico para que incluya las mismas funciones que el reproductor de YouTube.com nativo
Lanzado en el 2005, YouTube ha evolucionado hacia el sitio de web dominante para compartir videos. Las listas de reproducción son una de las funciones más ampliamente utilizadas de YouTube. Puede desarrollar listas de reproducción propias y los videos subidos de otros usuarios y compartir sus listas de reproducción en YouTube. También puede compartir sus listas de reproducción integrándolas en su sitio de web, blog o página de medios sociales. Sin embargo, al reproductor de listas de reproducción integrado carece de la plena funcionalidad del reproductor nativo en YouTube.com.
La Figura 1 muestra el reproductor de la lista de reproducción nativa de youtube.com.
Figura 1. Reproductor de lista de reproducción nativo
La Figura 2 muestra el reproductor integrado predeterminado.
Figura 2. Reproductor integrado de listas de reproducción
En la lista de reproducción integrada:
- La lista de videos incluidos está oculta de manera predeterminada y sólo aparece como una forma sobrepuesta, no a un lado del reproductor como lo hace en YouTube.com.
- Se elimina la habilidad para que la lista de reproducción sea aleatoria.
- Se eliminan las notas agregadas a la lista de reproducción por este autor.
Yo encontré estas limitaciones cuando decidí integrar una lista de reproducción conteniendo algunos de los mejores goles en los juegos de calificación para la Copa Mundial FIFA 2014 en mi blog. Con la ayuda de YouTube API, jQuery, máquinas de plantillas JsRender y el framework de front-end de Bootstrap, extendí y mejoré el reproductor integrado predeterminado para crear una versión equivalente al reproductor nativo. Yo los llevaré a través del mismo proceso en este artículo. Construirá una lista de reproducción en YouTube, agregará notas con los tiempos de los momentos sobresalientes (como los goles), después utilizará las mismas herramientas para construir un reproductor integrado que restaure la funcionalidad faltante y mejore la experiencia del usuario.
El código completo del proyecto se guarda en DevOps Services. He desplegado la aplicación a IBM Cloud™ para que pueda verlo en operación. Puede usar cualquier sitio de host, incluyendo a IBM Cloud, para desplegar su código.
Nota: Para realizar la bifurcación del código para el proyecto de este artículo, haga un clic en el botón EDIT CODE en la esquina superior derecha (introduzca sus credenciales DevOps Services si no ha iniciado la sesión todavía) y haga un clic en el botón FORK del menú para crear un nuevo proyecto.
El primer paso es el de obtener una llave para acceder a su API de YouTube.
Obtenga una llave del API de YouTube
Para acceder al API para cualquier servicio de Google, incluyendo a YouTube, primero deberá registrar un proyecto en Google Developers Console y crear una llave API. El acceso a los diferentes APIs es gratuito hasta para un cierto número de solicitudes por día, que varían de servicio en servicio y estará disponible a cualquiera con una cuenta de Google.
Inicie una sesión en Google Developers Console con sus credenciales de Google y haga un clic en Create Project. De manera predeterminada, los cuadros de texto del nombre del proyecto y de la identificación del proyecto contienen valores aleatorios. Introduzca el nombre de su proyecto e identificación en su lugar y haga un clic en Create.
Figura 3. Creando un nuevo proyecto de API de Google
En el tablero del proyecto, haga un clic en APIs & auth para abrir la lista de APIs disponibles. Desplácese hacia abajo a YouTube Data API v3 y haga un clic en el botón asociado etiquetado como Off para permitir el acceso para su proyecto. Seleccione APIs & Auth > Credentials y seleccione Create New Key bajo Public API Access. Elija Browser key.
En el diálogo Create a browser key and configure allowed referers, se puede restringir el acceso a la llave de API a las solicitudes de ciertos dominios, tales como el propio sitio o IBM Cloud.net. A no ser que se restrinja el acceso, su llave de API estará visible a todos en la fuente de HTML de su aplicación. Durante el desarrollo este no es un tema, así que deje el diálogo en blanco y haga clic en Create. Pero cuando se despliegue el proyecto, regrese a este paso y restrinja el acceso a las solicitudes del dominio de sus aplicaciones para que los terceros no puedan usar la llave en otras aplicaciones.
Si se clonara el proyecto de DevOps Services, puede insertar la llave ahora donde se indique en el archivo index.html para permitir que el código opere exitosamente.
Configure la biblioteca del cliente Google JavaScript para acceder al API de YouTube
Google provee bibliotecas del cliente para varios idiomas, incluyendo JavaScript. Importe el cliente de JavaScript a su documento HTML incluyendo la etiqueta del HTML en la etiqueta del documento
<body>
(no en la etiqueta <head>
):
1
| <script src="https://apis.google.com/js/client.js?onload=function"></script> |
La mejor práctica recomendada es colocar esta etiqueta
<script>
al final de la etiqueta <body>
.
Después de que es cargue la biblioteca del cliente, el objeto
gapi
(Google API) está disponible en el alcance de la ventana en su página. El parámetro onload
en la etiqueta <script>
que acaba de agregar se refiere a una función callback que se solicitó inmediatamente después de las cargas de la biblioteca. La definición de esa función deberá preceder la etiqueta <script>
que carga el cliente. La función llama a el método gapi.client.load(api name, version, callback function)
para cargar los APIs que usará el cliente. En este caso, cargue el API de YouTube con gapi.client.load('youtube', 'v3', onYouTubeApiLoad)
. onYouTubeApiLoad
que es una función de una línea que llama al método setAPIKey
. En setAPIKey
, coloque la llave en el valor de su llave de navegador Google.
El cliente provee los métodos para acceder a las varias llamadas de API para los servicios de Google. Para obtener la lista de partidas en una lista de reproducción, usted podrá tratar la respuesta del método de
playlistItems.list
en el API de YouTube en una función asíncrona y guardar los atributos relevantes en una instancia de un objeto de JavaScript denominado YouTubePlayList
. Usted desarrollará el objeto YouTubePlaylist
a través de este artículo. El constructor del objeto se define en la función de JavaScript que se muestra abajo.Listado 1. YouTubePlaylist.js
1
| function YouTubePlaylist(id, entries) { this.id = id; this.entries = entries; this.currently_playing = 0; this.randomizer = false; } |
id
que es la ID de la lista de reproducción.entries
es un arreglo JSON de los videos en la lista de reproducción.currently_playing
es el índice del video actualmente en reproducción en el arregloentries
.randomizer
indica si la reproducción es aleatoria.
Cree un objeto JSON con parámetros de respuesta
Ahora cree un objeto JSON con los parámetros necesarios en la respuesta. Sólo necesita un pequeño subconjunto de la lista completa de parámetros disponibles.
El parámetro
part
es una lista de atributos de valores separados por comas (CSV) que devolverá la llamada. Use los atributos contentDetails
y snippet
. El atributo snippet
contiene información básica sobre cada video. contentDetails
contiene la ID de video y cualesquiera notas que agregue el autor de la lista de reproducción. contentDetails
será importante después cuando identifique los puntos sobresalientes en el video. El parámetro playListId
es la ID de la lista de reproducción que usará. Para propósitos de este artículo, coloqué una lista de reproducción con los puntos sobresalientes cuya ID es PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe
. En la lista de reproducción, note que se agregan las horas de los goles como notas. El objeto JSON requestOptions
ahora se ve así:
1
| var requestOptions = { playlistId: playlist_id, part: 'contentDetails,snippet' }; |
Al recurrir al método
gapi.client.youtube.playlistItems.list()
con este objeto JSON como parámetro regresa un objeto con dos métodos: execute
y subscribe
. Recurra al método execute
con una función asíncrona, con la respuesta como un parámetro.
1
| request.execute(function(response) {}); |
El arreglo
items
en la respuesta contiene la lista de videos en la lista de reproducción. Usará el método jQuery each()
para iterar a través de las partidas. Guardará la ID de video, la miniatura mediana del video, el título y la nota en un objeto JSON, después agregue eso a un arreglo.
Listado 2. Agregando el objeto JSON al arreglo entries
1
| var entries = []; $.each( response.items, function( key, val ) { var entry = {}; entry.video_id = val.snippet.resourceId.videoId; entry.image_src = val.snippet.thumbnails.medium.url; entry.title = val.snippet.title; entry.note = val.contentDetails.note; entries.push(entry); }); |
Cree el objeto YouTubePlayLlist
Cree el nuevo objeto
YouTubePlaylist
llamando al constructor (consulte Listado 1) con la ID de la lista de reproducción y el arreglo entries
y guarde el objeto en el alcance de la ventana como una nueva variable designada con la ID de la lista de reproducción.
1
| window[playlist_id] = new YouTubePlaylist(playlistId, entries); |
Ahora puede acceder al objeto
YouTubePlaylist
usando window[playlist_id]
. Posteriormente, usará window[playlist_id]
para llamar la funcionalidad adicional del objeto YouTubePlaylist
.Use una plantilla para formatear el reproductor con JsRender
El esquema de su reproductor será parecido al de la Figura 4, con la miniatura, título y lista de notas a la derecha conformada por registros en su lista de reproducción.
Figura 4. Esquema del nuevo reproductor
De manera predeterminada, el conjunto de caracteres HTML no incluye iconos similares a los iconos siguientes, previos y aleatorios en el reproductor nativo de YouTube. Su aplicación usará los iconos suministrados por el framework front-end Bootstrap. Para importar la hoja del estilo Bootstrap, agregue el siguiente snippet a la etiqueta
<head>
.
1
| <link rel="stylesheet" type="text/css" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css"> |
Entregará el objeto
YouTubePlaylist
usando el motor de la plantilla JsRender. Se define una plantilla JsRender en una etiqueta <script>
con el conjunto de atributos type
en text/x-jsrender
. Genere el HTML recurriendo al método render()
de la plantilla con el objeto x-jsrender
que se entregará. Se entregan variables en la plantilla usando la anotación de corchetes angulares dobles {{:}}
. Por ejemplo, {{:id}}
entrega el atributo id
del objeto pasado a la plantilla.
La plantilla que se muestra en el Listado 3 integra un reproductor YouTube en una página web.
Listado 3. Plantilla para integrar un reproductor YouTube
1
| <object width="640" height="360" data="http://www.youtube.com/v/video id?version=3&amp;enablejsapi=1&amp;playerapiid=id" id="player id" type="application/x-shockwave-flash"> <param value="always" name="allowScriptAccess"> <param value="true" name="allowFullScreen"> </object> |
En el Listado 3,
video id
está la ID del video que se reproducirá, y player id es la ID del propio objeto del reproductor. En la inicialización se hará la cola del primer video en el arreglo de entries
, así que introduzca {{:entries[0].video_id}}
como la ID de video en la plantilla del reproductor integrado. Para la ID del reproductor, use la ID de la lista de reproducción, digamos {{:id}}
.
A la inicialización de un objeto del reproductor de YouTube, el reproductor llamará automáticamente la función
onYouTubePlayerReady()
con la ID del reproductor como un parámetro para personalizar su conducta cuando cambie su estado. Los estados del reproductor son no iniciado, terminado, reproduciendo, en pausa, en memoria intermedia y en cola de video; estas se enumeraron como -1, 0, 1, 2, 3, y 4. En la versión actual del API, no puede personalizarse la función callback. Por ahora, definirá una función en el alcance de la ventana para descargar el siguiente video en la lista de reproducción (definirá esta función en el objeto YouTubePlaylist
posteriormente) y agregarlo como un evento para escuchar en el reproductor.Listado 4. La función para cargar el siguiente video en la lista de reproducción
1
| function onYouTubePlayerReady(playerApiId) { var player = document.getElementById(playerApiId); window["onStateChange" + playerApiId] = function(state) { switch(state) { case 0: var video_player = document.getElementById(player_id); video_player.loadVideoById(window[player_id].getNextVideo(), 0, "large"); < br > break; } }; player.addEventListener("onStateChange", "onStateChange" + playerApiId); } |
Para iterar a través del arreglo de videos, usará la etiqueta JsRender
{{for}}
. Creará cada registro de lista de reproducción a la derecha de la Figura 4 con la plantilla en el Listado 5.Listado 5. Plantilla para crear cada registro en la lista de reproducción en la lista
1
| {{for entries}} <div class="playListEntry {{if #index == 0}}nowPlaying{{/if}}" id="{{:video_id}}"> <div class="playListEntryThumbnail"> <img src="{{:image_src}}"/> </div> <div class="playListEntryDescription"> <div class="playListEntryTitle">{{:title}}</div> <div class="playListEntryNote">{{:note}}</div> </div> </div> {{/for}} |
En el Listado 5,
entries
está el atributo entries
en el objeto YouTubePlaylist
. El índice del objeto en el arreglo se guarda en la variable #index
. Dado que el primer registro en la lista es el video que se carga en el reproductor a su creación, aplicará la clase CSS nowPlaying
a la primera clase playListEntry
usando la etiqueta {{if}}
para identificarlo en el bucle for
.
Los controles en la parte inferior del reproductor se crean aplicando la clase Bootstrap
glyphicon
a un span
, usando las clases glyphicon-backward
, glyphicon-forward
y glyphicon-random
para los iconos.
1
| <div class="playListControls"> <span class="playListControl disabled glyphicon glyphicon-backward"/> <span class="playListControl glyphicon glyphicon-forward"/> <span class="playListControl glyphicon glyphicon-random"/> </div> |
Note que inicialmente se deshabilita el icono "previous" porque cuando se carga primero el reproductor, se preestablece al primer registro en la lista de reproducción, por lo cual no hay un video previo por reproducir.
Agregará el HTML entregado a un
div
vacío en la página con la ID playlist
con este snippet de código (recordando que window[player_id]
se refiere al objeto que se creó en la sección "Create the YouTubePlaylist".
1
| $('#' + player_id).html($('#playListPlayerTemplate').render(window[player_id])); |
Para hacer este código reutilizable, muévalo a una función con la firma
addPlaylistToElement(playlist_id, element_id)
Después puede llamarse con addPlaylistToElement('PLLzJfby7cTLTbusOgXca-yIpVOImC1mWe', 'playlist')
.Agregue controles
Regrese el objeto
YouTubePlaylist
y empiece a agregar funcionalidad. Posteriormente, usará esta versión mejorada del objeto para completar el reproductor en la página web.
Agregue seis funciones al objeto:
previous()
, next()
, getCurrentlyPlaying()
, setCurrentlyPlaying()
, randomize()
y isRandomized()
. Las funciones previous
y next
se mueven a los videos relevantes en la lista de reproducción y regresan true
si la acción tiene éxito o “false” si no lo hacen (eso es, si el usuario hace clic en "previous" en el primer registro en la lista de reproducción o "next" en el último registro). getCurrentlyPlaying()
regresa la ID del video que se encuentra actualmente en reproducción en la lista de reproducción. randomize()
establece o deja de establecer el atributo random
en el objeto, y isRandomizer()
regresa el valor del atributo aleatorio.
El listado 6 muestra la función
Next()
.
Listado 6. La función Next()
1
| next: function() { var retVal = false; if(this.randomizer) { retVal = true; this.currently_playing = Math.floor((Math.random() * this.entries.length)); } else if(this.currently_playing <= this.entries.length) { retVal = true; this.currently_playing++; } return retVal; |
En la función
Next()
, verifique primero si está establecido el atributo random
y si lo está, establezca el índice currently_playing
a un valor aleatorio en el arreglo entries
. Si no se establece el atributo random
y el índice currently_playing
es inferior al número de videos en el arreglo (eso es, no ha pasado el último video en la lista de reproducción), incremente el valor del índice en uno para moverse al siguiente video y regresar true
para indicar que la operación tuvo éxito. Si no tuvo éxito, regrese false
.
El listado 7 muestra la función
previous()
. Si el índice currently_playing
es mayor a cero (eso es, el usuario está viendo cualquier video a excepción del primer video en la lista de reproducción), disminuya el índice en uno y regrese true
para indicar que la operación tuvo éxito; de lo contrario utilice false
.
Listado 7. La función previous()
1
| previous: function() { var retVal = false; if(this.currently_playing > 0) { retVal = true; this.currently_playing--; } return retVal; } |
En la función
getCurrentlyPlaying()
, utilice la ID de video del índice que se esté reproduciendo actualmente en el arreglo de registros.
1
| getCurrentlyPlaying: function() { return this.entries[this.currently_playing].video_id; } |
El listado 8 muestra la función
setCurrentlyPlaying()
. Una vez que obtenga la video_id
de la lista de reproducción actual, establezca currently_playing
al índice del elemento en el arreglo entries array
con ese valor.
Listado 8. La función setCurrentlyPlaying()
1
| setCurrentlyPlaying: function(video_id) { for(var index = 0; index < this.entries.length; index++) { if (this.entries[index].video_id === video_id) { this.currently_playing = index; break; } } } |
En la función
randomize()
, invierta el valor del atributorandomizer
— de verdadero a falso y viceversa — e introduzca el nuevo valor.
1
| randomize: function() { this.randomizer = !(this.randomizer); return this.randomizer; } |
La función
isRandomized()
da el valor del atributo randomizer
de la lista de reproducción — eso es, si la lista de reproducción esta en reproducción aleatoria:
1
| isRandomized: function() { return this.randomizer; } |
Use la funcionalidad agregada
Ahora agregue funciones para usar la funcionalidad agregada del objeto JavaScript.
Primero, agregue la función del asistente para arreglar los controles de una lista de reproducción. Si el reproductor está en reproducción aleatoria:
- Se resalta el icono "random".
- Siempre se deshabilita el icono "previous" porque ya no se está grabando el video reproducido anteriormente en ninguna parte.
- Siempre está habilitado el icono "next" porque la lista de reproducción nunca puede reproducir el video "last" de manera aleatoria. (Piénselo: Cuando su reproductor MP3 esté en aleatorio ¿dejará de reproducirse alguna vez?)
Si la lista de reproducción no está en reproducción aleatoria:
- El icono "previous" sólo si se está reproduciendo el primer registro en la lista de reproducción.
- Sólo se deshabilita el icono "next" si se está reproduciendo el último registro en la lista de reproducción.
- Se deshabilita el icono "random" hasta volver a hacer un nuevo clic.
El listado 9 muestra la función del asistente.
Listado 9. La función del asistente para arreglar los controles de la lista de reproducción
1
| function arrangePlayerControls(player_id) { var playListPlayer = $('#' + player_id + 'playListPlayer'); if(window[player_id].isRandomized()) { $('#' + player_id + 'Backward').addClass('disabled'); $('#' + player_id + 'Forward').removeClass('disabled'); $('#' + player_id + 'Random').addClass('randomizeActive'); } else { $('#' + player_id + 'Random').removeClass('randomizeActive'); var playListEntries = $('#' + player_id + 'playListEntries'); if(playListEntries.children(":first").hasClass('nowPlaying')) { $('#' + player_id + 'Backward').addClass('disabled'); } else { $('#' + player_id + 'Backward').removeClass('disabled'); } if(playListEntries.children(":last").hasClass('nowPlaying')) { $('#' + player_id + 'Forward').addClass('disabled'); } else { $('#' + player_id + 'Forward').removeClass('disabled'); } } } |
Después, agregue una función para cargar un video al reproductor para una ID de lista de reproducción dada y el índice de tiempo para empezarlo. Recuerde quitar la clase
nowPlaying
del div
para el video que se está reproduciendo actualmente y agregarlo al div
para el nuevo video. Después llame la función del asistente en el Listado 9 para arreglar los iconos de la lista de reproducción. El Listado 10 muestra una función de cargado de video.Listado 10. Función para cargar un video al reproductor
1
| function loadVideoForPlayer(currently_playing_video_id, player_id, time) { time = time || 0; var video_id = window[player_id].getCurrentlyPlaying(); $('#' + currently_playing_video_id).removeClass('nowPlaying') $('#' + video_id).addClass('nowPlaying'); $('#' + player_id + 'playListEntries').scrollTop($('#' + video_id).index() * 80); document.getElementById(player_id).loadVideoById(video_id, time, "large"); arrangePlayerControls(player_id); } |
Finalmente, agregue una función para cargar el siguiente video en una lista de reproducción dada, pero sólo si hay otro video en la lista de reproducción (eso es, no está en reproducción aleatoria y el video actual no es el último video).
Listado 11. Función para cargar el video si existe el siguiente video
1
| function loadNextVideo(player_id) { var currently_playing_video_id = window[player_id].getCurrentlyPlaying(); if(window[player_id].next()) { loadVideoForPlayer(currently_playing_video_id, player_id); } } |
Esta función es similar a la función anónima que declaró en
onYouTubePlayerReady()
(consulte el Listado 4), así que refactorice el bloque case 0
en onYouTubePlayerReady()
para mejor utilizar loadNextVideo()
.
Puede haberse dado cuenta de que agregué los tiempos de los goles en cada video en la lista de reproducción. Con estas funciones, puede usar los tiempos de los goles como zonas de vínculos para saltar directamente al gol en el video, en lugar de necesitar ver absolutamente todo. En el bucle de
$.each()
en addPlaylistToElement()
, guarde el valor de la nota para cada objeto playlistItem
en una variable local, después use la función JavaScript match()
para obtener un arreglo de horas a partir de la nota, usando la expresión regular /[0-9]*:[0-5][0-9]/g
para encontrar cada tiempo. Después se podrá hacer un bucle en este arreglo y reemplazar el valor de cada tiempo en la variable con un vínculo para llamar la función cueThisVideo()
con la ID del reproductor, el video que se reproducirá y el índice de tiempo donde empezará. Recuerde que la llamada del API de YouTube loadVideoById()
tome el tiempo en segundos, así que divida el tiempo en un arreglo, usando dos puntos (:) en la cadena como delimitador. Multiplique el valor en el primer índice (los minutos) por 60 para convertirlo en segundos y después súmelo a los segundos en el índice de segundos para obtener el número total de segundos. Por ejemplo, 1:30 se convierte en un arreglo de [1, 30] (1 * 60) + 30 = 90 segundos. Finalmente, reemplace el tiempo en la nota con el nuevo vínculo. Al haberse procesado cada hora en la nota, guarde la cadena concluida como una nota para el registro en el arreglo entries
, como se muestra en el Listado 12.Listado 12. Reemplazando las horas en la nota con un vínculo
1
| var note = val.contentDetails.note; var times = note.match(/[0-9]*:[0-5][0-9]/g); times.forEach(function(value, index, array) { var time = value.split(":"); var seconds = parseInt(time[0]) * 60; seconds += parseInt(time[1]); note = note.replace(value, "<span class='timeLink' onclick='cueThisVideo(\"" + player_id + "\", \"" + video_id + "\", " + seconds + ");'>" + value + "</span>"); }); entry.note = note; |
Todo lo que resta es revisitar la plantilla y agregar las llamadas a estas nuevas funciones. Quiere que el usuario sea capaz de crear una cola de video haciendo clic en el título o en la miniatura, hacer una cola en los videos previos o siguientes en la lista de reproducción y hacer que la lista de reproducción sea aleatoria con los botones relevantes en el panel de control. El Listado 13 muestra los cambios concluidos en la plantilla para usar la funcionalidad agregada.
Listado 13. Código refactorizado de la plantilla
1
| <div onclick="cueThisVideo('{{:~player_id}}', '{{:video_id}}');" class="playListEntryThumbnail"> <img src="{{:image_src}}"/> </div> <div onclick="cueThisVideo('{{:~player_id}}', '{{:video_id}}');" class="playListEntryTitle"> {{:title}} </div> <span id="{{:id}}Backward" class="playListControl disabled glyphicon glyphicon-backward" onclick="if(!$(this).hasClass('disabled')) { loadPreviousVideo('{{:id}}') }"> </span> <span id="{{:id}}Forward" class="playListControl glyphicon glyphicon-forward" onclick="if(!$(this).hasClass('disabled')) { loadNextVideo('{{:id}}') }"> </span> <span id="{{:id}}Random" class="playListControl glyphicon glyphicon-random" onclick="window['{{:id}}'].randomize();arrangePlayerControls('{{:id}}');"> </span> |
¡Ahora su reproductor a la medida está listo para el rock!
Conclusión
Este artículo ha demostrado la forma de usar el API de YouTube API y cierto JavaScript simple y la estilización para obtener una lista de reproducción de YouTube integrada con una funcionalidad equivalente a la lista de reproducción nativa en YouTube.com. Consulte el archivo README en mi página DevOps Services para ver el proyecto y para obtener algunas sugerencias para mejorar aún más el reproductor. Siéntase en la libertad de bifurcar el código del proyecto para implementar cualesquiera o todas esas sugerencias.
Recursos para Descargar
Temas relacionados
- Consulte la documentación de YouTube API :
- jQuery: Aprenda más sobre esta biblioteca JavaScript.
- JsRender: Eche un ojo a la biblioteca de la plantilla JsRender.
- Bootstrap: Profundice más en el framework front-end de Bootstrap.
- Empezando con los Servicios IBM Cloud y DevOps: Consulte la forma de usar los servicios DevOps para su código de aplicación de web y desplegarlo directamente a IBM Cloud.
- IBM Cloud en developerWorks: Encuentre los recursos técnicos sobre los servicios IBM Cloud, tiempos de ejecución e infraestructura.
0 comentarios:
Publicar un comentario