Ya que en entradas anteriores hemos ido conociendo LungoJS desde el punto de vista de la semántica y el marcado. Ahora vamos a ir avanzando para empezar a programar algo. Javier Jimenez Villar (@soyjavi) ha ido colgando unos interesantes vídeos en la red sobre diversos temas: Prototipado, SQLite Cache, Templating,., etc. que os recomiendo. Antes de presentar un poco la aplicación ‘buscadora de tweets’ que nos ocupa diré dos cosas: no hay un único camino para explicar un concepto y, dos, leeros la descripción de la API de LungoJS. En pocos días van a cambiar de versión a la 1.2, pero los que llevamos leyendo API’s algo de tiempo os recomendamos que conozcáis las diferencias (aunque no es necesario). Otro concepto importante que la gente confunde: QuoJs es la libreria encargada de manejar DOM para LungoJS desde la versión 1.1. Es importante porque para entornos de desarrollo conviene no usar la versión packed.js. Tendrás que agregar QuoJs antes de lungo-1.1.2.js.
Construyendo el Layout de «BuscaTweets»
Tenemos un section con id=»formulario_busqueda» que presenta la primera pantalla con un campo de texto y un pequeño footer que tiene una lupa, para buscar el término en Twitter. El footer con la lupa para buscar sería conveniente mejorarlo, pero como es un ejemplo donde no vamos a hablar de prototipado, da lo mismo. Aqui tenemos el marcado de la primera section:
<section id="formulario_busqueda"> <header data-title="BuscaTweets 0.1"> </header> <article id="input_search"> <div class="indented"> <label>Término: <input type="search" id="text_search_input" placeholder="" class="text"/> </label> </div> </article> <footer class="toolbar"> <nav> <a href="#tweets" id="search_button" data-target="section" data-icon="search" class="current"></a> </nav> </footer> </section>
El segundo section con id=»tweets» no tiene mucho que comentar, observamos un article con id=»tweet_container» y class=»list» que no tiene nada dentro porque vamos a rellenarlo a traves de «templating» en nuestro fichero view.js
<section id="tweets"> <header data-title="Resultados"> <a href="#back" data-target="section" data-icon="left" class="button darker onright"></a> </header> <article id="tweet_container" class="list"></article> <footer> </footer> </section>
Ahora toca ver las partes que realizan el trabajo. Antes de nada comentar que en este ejemplo tuve la cabezonería de implementar un sistema pulldown-to-refresh. Este sistema permite recargar un listado arrastrando hacia abajo durante unos segundos. LungoJS no lo tenía implementado en la versión 1.1.2 y retoqué con dos lineas el lungo-1.1.2.js. Luego paso a explicar cómo lo hice.
Fichero view.js
Tenemos una variable de marcado o markup que contiene todo el interior de nuestro article de segunda sección que antes comentábamos. Por otra parte el método para crear el template apuntando como referencia con su template_id con el marcado y por último un método propio para pintar la lista que tiene un argumento que es tweets, para data.
App.View = (function(lng, app, undefined) { var tweet_template_markup = "<li class='selectable' data-icon='user'>\n\ <img src='{{profile_image_url}}' />\ {{tweet_touser}}<strong>{{tweet_text}} </strong> <small> {{tweet_author}} {{tweet_date}} {{tweet_url}}</small> </li>"; lng.View.Template.create('tweet_template', tweet_template_markup); // Este método lo creamos para llamarlo posteriormente desde services.js. Aquí no tenemos los datos, por eso le pasamos a data un argumento tweets. var render_list = function(tweets){ lng.View.Template.List.create ({ container_id : 'tweet_container', template_id: 'tweet_template', data: tweets }); } return{ render_list:render_list } })(LUNGO, App);
Fichero data.js
Aquí lo que hacemos es crear un setter y un getter (getSearchTerm() y setSearchTerm() ) para el término de búsqueda. Luego es usado por events.js para la consulta a Twitter.
App.Data = (function(lng, app, undefined) { var term = "<nothing>"; var setSearchTerm = function(par_term) { term = par_term; } var getSearchTerm = function (){ return term; } return { setSearchTerm : setSearchTerm, getSearchTerm : getSearchTerm, } })(LUNGO, App);
Fichero services.js
Este es el fichero encargado de consumir el servicio de Twitter con nuestra consulta. Aquí construímos la parte del listado a través de la llamada al método App.View.render_list(data_to_bind); que monta la lista con el template.
App.Services = (function(lng, app, undefined) { var getSearch = function(search_term){ var twitter_params = { q : encodeURIComponent(search_term), result_type : 'recent', rpp : '100', // include_entities: 'false', callback : '?' }; var twitter_search_url = "http://search.twitter.com/search.json"; // Growl caragando ... lng.Sugar.Growl.show ('Espere', '', 'loading', true, 0); $$.get(twitter_search_url, twitter_params, function(response){ // solo queremos tweets data = response.results; var data_to_bind = []; var to_user = ''; var url = ''; var vacio = false; if (data.length == 0){ var vacio = true; data_to_bind.push({ tweet_text : "¡ No hay resultados !", tweet_author: '<a href="#back"" data-target="section" data-icon="target">Volver al término de búsqueda</a>', tweet_touser: '', // tweet_url: url, profile_image_url: 'assets/images/icon-72.png' }); } for (var i = 0; i < data.length; i++){ if (data[i]['to_user']){ to_user = "<div class='bubble red onright'>Tweet con: @" + data[i]['to_user'] +"</div>"; }else{ to_user = ''; } data_to_bind.push({ tweet_date: data[i]['created_at'].replace("+0000",""), tweet_text : "<a href='https://twitter.com/#!/"+ data[i]['from_user']+"/status/"+ data[i]['id_str'] + "' target='_self'>"+data[i]['text']+"</a>", tweet_author: data[i]['from_user'], tweet_touser: to_user, // tweet_url: url, profile_image_url: data[i]['profile_image_url'] }); } App.View.render_list(data_to_bind); //ocultamos loading ... lng.Sugar.Growl.hide(); // quitamos pulldown-to-refresh si no hay tweets. if (vacio === true){ // quitar pulldown. $$("#pullDown").remove(); } } ); } return { getSearch: getSearch } })(LUNGO, App);
Fichero events.js
En este ficheros tenemos el comportamiento a nivel eventos de nuestra webapp. ¿Que hace la aplicación al pulsar o hacer tap sobre un botón? Aquí se define también la implementación de pull-to-refresh que he asociado al evento longTap que preguntará por el método: makePullDown(). ASi pues, tenemos makeSearch() y makePullDown()
App.Events = (function(lng, app, undefined) { var makeSearch = function(){ app.Services.getSearch(app.Data.getSearchTerm()); } var makePullDown = function(){ //calculamos la distancia del pullDown hacia arriba y la del scroller con respecto a éste. var pulldown_offset_top = (lng.dom("#pullDown").offset().top); var container_top = (lng.dom("#tweet_container").offset().top); // Tenemos las condiciones en la lista para hacer update ? bajamos flechita, espere, actualizamos ... if (pulldown_offset_top >= container_top && !pulldown_offset_top < container_top){ lng.dom("#pullDown").toggleClass('loading'); $$('.pullDownLabel').html('Actualizando ...'); // update the request App.Data.setSearchTerm($$("#text_search_input").val()); makeSearch(); } }; // al pulsar sobre refresh lng.dom('#refresh_button').tap(function(event) { if($$("#text_search_input").val() != ''){ App.Data.setSearchTerm($$("#text_search_input").val()); makeSearch(); } }) // al pulsar sobre la lupa lng.dom('#search_button').tap(function(event) { if($$("#text_search_input").val() != ''){ App.Data.setSearchTerm($$("#text_search_input").val()); makeSearch(); } }) // al arrastrar hacemos pull-to-refresh lng.dom('#tweet_container').on('longTap', function(){ makePullDown(); }); return { makeSearch:makeSearch, makePullDown:makePullDown } })(LUNGO, App);
Bola extra ;-)
Como ya comentaba en algún momento de esta entrada, tuve que añadir dos lineas a lungo-1.1.2.js que implementaban el pull-to-refresh en mi «listado scrollable». Fueron éstas (@ToDo >> method to pulldown):
var _createScroll = function() { var container_id_for_scroll = lng.dom('#' + _config.container_id).parent().attr('id'); var list_config = { snap: 'li' }; lng.View.Scroll.create(container_id_for_scroll, list_config); // @ToDo >> method to pulldown lng.dom("#"+_config.container_id).prepend('<div id="pullDown"><span class="pullDownIcon"></span><span class="pullDownLabel"></span></div>'); onTouchEnd: $$('.pullDownLabel').html('Arrastra hacia abajo para actualizar ...'); }; return { create: create };
Tenéis el código disponible en mi repositorio de GitHub: https://github.com/tunelko/BuscaTweets.-
Os dejo con el video, hasta la próxima!