birt report drill down url window location base path problem

You can navigate to the details of a certain data category in a BIRT report using the hyperlink property of an element and use the drill down option to configure it.

Selection_007

Hyperlink Options _006

the problem for us was that when deploying the report to an instance of the ReportViewer it added some wrong path prefix to the report path variable ‘__report’.

So we had to construct the path ourselves. First change the drill-trough option to URI, and the Location to javascript syntax.

We tried to access the window object from a script in the report (since the scripts are javascript) and use it to find out the base url and path, but it failed complaining that ‘window’ was not defined. I suppose you can’t access the window object, I couldn’t find one.

importPackage(Packages.java.text);
sdf = new SimpleDateFormat('yyyy-MM-dd');

window.location.origin + window.location.pathname +
'?__format=pdf&__report=report/tsj/DetallesIndicadores/DetalleCarpetasIniciadas.rptdesign' +
'&desde=' + sdf.format(params['desde']) +
'&hasta=' + sdf.format(params['hasta'])

But it turns out, as I discovered through this forum post, you can use the report context object to access the HttpServletRequest which we can use to construct our URL.

Then you just have to format and add your parameters to the URL.

Ours ended up like this

importPackage(Packages.java.text);
sdf = new SimpleDateFormat('yyyy-MM-dd');

reportContext.getHttpServletRequest().getRequestURL().toString() +
'?__format=pdf&__report=report/tsj/DetallesIndicadores/DetalleCarpetasIniciadas.rptdesign' +
'&desde=' + sdf.format(params['desde']) +
'&hasta=' + sdf.format(params['hasta'])

remember the last line is returned as the value for our URL, in this case the concatenation of our values.

sources:
https://www.eclipse.org/forums/index.php/m/1235339/?srch=window.location#msg_1235339
https://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.birt.doc.isv%2Fenginescript%2Fapi%2Forg%2Feclipse%2Fbirt%2Freport%2Fengine%2Fapi%2Fscript%2FIReportContext.html
https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/http/HttpServletRequest.html
https://stackoverflow.com/questions/1629102/root-url-of-the-servlet

Anuncios

Javascript – Sobrecarga de métodos

En Java y C# es muy común utilizar la sobrecarga de operadores para, por ejemplo, tener un método con el mismo nombre pero distintos parámetros.

    public String sayHi(String name){
        return "hi " + name +"!";
    }

    public String sayHi(String first, String last){
        return sayHi(first + " " + last);
    }

    public String sayHi(String first, String last, String city){
        return sayHi(first, last) + "from " + city;
    }

En javascript no existe la sobrecarga de operadores. Pero podemos hacer algo parecido si pasamos un objeto como parámetro a nuestra función, por ejemplo

$scope.sayHi = function(options){
    var greeting = 'hi '
    if(typeof options.first !== 'undefined' && typeof options.last !== 'undefined' && typeof options.city !== 'undefined'){
        greeting += options.first + ' ' + options.last + ' from ' + options.city
    }else if(typeof options.first !== 'undefined' && typeof options.last !== 'undefined'){
        greeting += options.first + ' ' + options.last
    }
    else if(typeof options.name !== 'undefined'){
        greeting += options.name
    }
    return greeting + '!'
}

Fuentes:
http://stackoverflow.com/questions/456177/function-overloading-in-javascript-best-practices
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

AngularJS – Ejecutar una función de javascript en una plantilla (template)

Screenshot from 2014-04-16 12:11:30

Para dar formato a una fecha, y muchas cosas más, existe una librería llamada Moment.js. Para utilizar sus métodos desde una plantilla encontré este post que nos explica cómo crear un filtro para lograrlo.

El filtro quedaría de la siguiente manera

angular.module('myModule').
  filter('fromNow', function() {
    return function(dateString) {
      return moment(dateString).fromNow()
    }
  })

y el template

{{ reply.createdDate | fromNow }}

Funciona muy bien, pero tendríamos que crear un filtro para cada función de la librería que quisieramos utilizar. La ventaja es que la implementación del filtro queda independiente de la vista (template).

Otra opción sería poner el objeto moment disponible en la vista de la siguiente manera

angular.module('myModule', []).
controller('myController', ['$scope', function($scope){
 $scope.moment = moment
}])

y en la plantilla

{{ moment(reply.createdDate).fromNow() }}

Fuentes:

http://www.34m0.com/2012/07/angularjs-user-friendly-date-display.html
http://stackoverflow.com/questions/12466661/using-helper-methods-while-templating-with-angular-js
http://momentjs.com/

backbone js – Incremento del uso de memoria de la aplicación

chrome memory profiler
chrome memory profiler

Después de tener una aplicación web abierta por un tiempo prolongado, empecé a notar que esta incrementaba mucho el uso de memoria. Este problema comenzó a presentarse cuando introduje un ‘timer’ para cambiar de vistas cada cierto tiempo. Al parecer no se estaba liberando la memoria en algún lado.
Crear y desplegar vistas en backbone es algo aparentemente sencillo, pero –como ya fue analizado– realmente no lo es.

Al principio recreaba las vistas de la siguiente manera

app.Router = Backbone.Router.extend({
    routes: {
        "": "audiencias",
        "audiencias": "audiencias",
        "screensaver": "screensaver"
    },
    initialize: function(){
        this.$content = $('#content')
    },

audiencias: function(){
    app.views.audiencias = new app.views.Audiencias({model: new app.models.AudienciaCollection()})
    this.$content.html(app.views.audiencias.render().el)
},

screensaver: function(){
    app.views.screensaver = new app.views.Screensaver()
    this.$content.html(app.views.screensaver.render().el)
}
})

Para evitar recrearlas y sólo hacer un ‘render’ cada que cambiaba de vista, lo modifiqué de la siguiente manera y la fuga de memoria ya no se presentó

app.Router = Backbone.Router.extend({
    routes: {
        "": "audiencias",
        "audiencias": "audiencias",
        "screensaver": "screensaver"
    },

initialize: function(){
    this.$content = $('#content')
    app.views.audiencias = new app.views.Audiencias({model: new app.models.AudienciaCollection()})
    app.views.screensaver = new app.views.Screensaver()
},

audiencias: function(){
    app.views.audiencias.setElement(this.$content).render()
},

screensaver: function(){

    app.views.screensaver.setElement(this.$content).render()
}
})

Fuentes:
http://ianstormtaylor.com/rendering-views-in-backbonejs-isnt-always-simple/
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/

web – agregar un protector de pantalla a una aplicación

screensaver
screensaver

Ahora que las Smart TV’s son más comunes, existe un número mayor de aplicaciones y usos que les podemos dar. Desde hace ya bastante tiempo, se han utilizado pantallas para desplegar la información de los vuelos en los aeropuertos.

Desarrollé una aplicación parecida al tablero del aeropuerto y estaba preocupado por el efecto “burn-in”, así que decidí utilizar un salvapantallas (screensaver). Afortunadamente un equipo de Adobe desarrolló un proyecto (git repo) que hace precisamente esto, y lo hace muy bien.

Para agregarlo en mi proyecto, solo copie los archivos en una carpeta ‘screensaver’ e inserte un iframe en la página que lo quería mostrar

<iframe id="saver" src="screensaver/saver.html"></ iframe>

y para que iniciara inmediatamente, sin la necesidad de la intervención del usuario

$(document).ready(function(){
this.$('#saver').load(function(){
this.contentWindow.eve('menu.instagram')
this.contentWindow.eve('go')
})
})

con esto es como si el usuario hubiera dado clic en el botón de instagram y luego en el botón de play 🙂

Fuentes:
http://stackoverflow.com/questions/251420/invoking-javascript-code-in-an-iframe-from-the-parent-page
http://stackoverflow.com/questions/12199797/why-is-iframe-contentwindow-null

Backbone – Acceder a los objetos de una colección en una plantilla de Handlebars

Para acceder a los objetos de una colección en una plantilla de Handlebars tienes que pasar dichos objetos como un parámetro a la función template. Si utilizas Backbone y tu colección de objetos es una colección de modelos, puedes utilizar la función toJSON para convertirlos en objetos javascript y que puedan ser interpretados por Handlebars.

Supongamos que tenemos los siguientes objetos (modelos)

app.models.Equipo = Backbone.Model.extend({

    initialize: function () {

    },

    sync: function (method, model, options) {
    if (method === 'read') {
        app.adapters.equipos.findById(parseInt(this.id)).done(function (data) {
            options.success(data)
        })
    }
    }
})

app.models.EquipoCollection = Backbone.Collection.extend({

    model: app.models.Equipo,

    sync: function (method, model, options) {
    if (method === 'read') {
        app.adapters.equipos.findAll().done(function(data){
            options.success(data)
        })
    }
    }
})

y nuestra plantilla es la siguiente

    <div class="row">
        {{#each equipos}}
        <div class="col-6">
            <a href="equipos/#{{name}}" class="thumbnail text-center">
                <img src="{{logoUrl}}"/>
            </a>
        </div>
        {{/each}}
    </div>

Entonces para pasar nuestros objetos a la plantilla, lo haríamos de la siguiente manera

app.views.EquiposListView = Backbone.View.extend({
    initialize: function(){
        this.equipos = new app.models.EquipoCollection()
        this.equipos.on('reset', this.render, this)
        this.equipos.fetch({reset: true})
    },

    render: function(){
        this.$el.html(this.template({equipos: this.equipos.toJSON()}))
        return this
    }

})

Notesé el uso de la función toJSON

Fuentes:
http://stackoverflow.com/questions/12439471/rendering-handlebars-template-with-backbone
http://stackoverflow.com/questions/14170298/how-to-access-backbone-model-properties-in-a-handlebar-template

Eclipse – Titulo dinámico en el encabezado (master page)

Cuando tienes varios reportes, es buena idea crear una librería para reutilizar elementos en los reportes. Un ejemplo común es cuando necesitamos usar el mismo encabezado y pie de página para los reportes.

BIRT nos permite agregar algunos elementos dinámicos -tales como el número de páginas, la fecha e incluso un valor obtenido mediante código javascript en un DynamicText- El problema es que esto dependerá de cada reporte, así que ¿cómo agregamos elementos dinámicos desde una librería a nuestro reporte?

Pues habrá que crear una “master page” en la librería donde pongamos elementos “DynamicText” en el encabezado y que estos desplieguen el valor de un parámetro. Después podemos modificar el valor del parámetro en cada reporte, y así, tener un título personalizado pero manteniendo el formato.

Si además, queremos obtener el título mediante una consulta a la base de datos. Lo que tenemos que hacer es crear el DataSource a la base de datos y un DataSet con la consulta que obtiene el título, por ejemplo


SELECT DESCRIPCION
FROM OFICINAS
WHERE ID=:OFICINA

Luego, debemos poner el siguiente código en el evento “beforeFactory” de nuestro reporte


//public final static int MODE_GENERATION = 1; //This mode is for generate the report document. Typically used in Report Engine run task
//public final static int MODE_PRESENTATION = 2; //This mode is for present data that saved in report document, without further data manipulation operation. Typically used in report engine render task
//public final static int DIRECT_PRESENTATION = 3;//This mode is to execute the data query without create report document. It is used in report engine runAndRender task.
//public final static int MODE_UPDATE = 4; //This mode is used to update the existing report document with new queries. Typicially used in IV.
importPackage( Packages.org.eclipse.birt.report.model.api );
importPackage(Packages.java.lang);
importPackage(Packages.java.util);
importPackage(Packages.org.eclipse.birt.report.data.adapter.api);
importPackage(Packages.org.eclipse.birt.report.model.api);
importPackage(Packages.org.eclipse.birt.data.engine.api.querydefn);
var myconfig = reportContext.getReportRunnable().getReportEngine().getConfig();
var des = DataRequestSession.newSession(myconfig, new DataSessionContext(3));
var dsrc = reportContext.getDesignHandle().findDataSource("dsrcOficina");
var dset = reportContext.getDesignHandle().findDataSet("dsetOficina");
des.defineDataSource(des.getModelAdaptor().adaptDataSource(dsrc));
des.defineDataSet(des.getModelAdaptor().adaptDataSet(dset));
var paramBinding = new InputParameterBinding("param_1", new ScriptExpression("\""+ params['IDOFICINA'].value +"\"") );
queryDefinition = new QueryDefinition( );
queryDefinition.setDataSetName( "dsetOficina" );
queryDefinition.addInputParamBinding( paramBinding );
queryDefinition.setAutoBinding(true);
var pq = des.prepare( queryDefinition );
var qr = pq.execute( null );
var ri = qr.getResultIterator( );
if ( ri.next( ) )
{
params["Titulo"].value = ri.getString("DESCRIPCION");
}
ri.close( );
qr.close( );
des.shutdown( );

Es importante poner los nombres exactamente igual que en el diseñador.
En nuestro caso, el parámetro “IDOFICINA” es de tipo String, así que lo ingresamos de la siguiente manera


var paramBinding = new InputParameterBinding( "param_1",new ScriptExpression("\""+ params['IDOFICINA'].value +"\"") );

si fuera un Integer tendría que ser de la siguiente manera


var paramBinding = new InputParameterBinding( "param_1",new ScriptExpression(params['IDOFICINA'].value) );

El valor de ScriptExpression debe ser un bloque de javascript válido, cómo si lo ingresaramos en la ventana “ExpressionBuilder” 🙂

Fuentes:
eclipse forums
birt exchange