Etiqueta: javascript

jhipster – bootstrap dropdown doesn’t work

I wanted to display a dropdown button to provide extra options to the default action. I thought using the bootstrap dropdown options would be a good fit. So, i just grab a sample from the docs and pasted it to the component.

surprisingly to me it wasn’t working, it was showing the main button but nothing happened when I clicked the button to see the options. I went to the dev console and the dropdown menu items were there, so i thought maybe a I had something missing. Check the docs again and thought I should add popper.js as dependency for it to work.

Dropdowns are built on a third party library, Popper.js, which provides dynamic positioning and viewport detection. Be sure to include popper.min.js before Bootstrap’s JavaScript or…

So, I did npm install popper.js but it kept failing to show the options.

Later I found out that jhipster doesn’t use plain old bootstrap but ng-bootstrap instead.

You can see a sample of how to use the dropdown in the entities menu from the navbar.component.html file, where they’ve used ngbDropdown, ngbDropdownToggle and ngbDropdownMenu directives. Or, you can use any sample provided in the ng-bootstrap dropdown documentation, i.e.

now it’s working great! 🔥

Screenshot from 2019-06-20 10-36-41

Anuncios

jhipster – use description in enums instead of key names

I used jhipster to generate an entity which have a property of type enum.

jhipster entity tipoEstudio
What are the values of your enumeration (separated by comma, no spaces)? SUPERIOR,MEDIA_SUPERIOR,BASICA

because i couldn’t use spaces or special characters (diacritics i.e. accents) in enum names I had to shorten the description a little bit.

It generated the following code

export const enum TipoEducativo {
    SUPERIOR = 'SUPERIOR',
    MEDIA_SUPERIOR = 'MEDIA_SUPERIOR',
    BASICA = 'BASICA'
}

export interface ITipoEstudio {
    id?: number;
    nombre?: string;
    tipoEducativo?: TipoEducativo;
}

export class TipoEstudio implements ITipoEstudio {
    constructor(public id?: number, public nombre?: string, public tipoEducativo?: TipoEducativo) {}
}

The problem was that the values stored in the database came as (note the spaces and diacritics)

    'EDUCACIÓN SUPERIOR',
    'EDUCACIÓN MEDIA SUPERIOR',
    'EDUCACIÓN BÁSICA'

In order to be able to use those values I had to first add a description property in the enum

public enum TipoEducativo {
    SUPERIOR("EDUCACIÓN SUPERIOR"), MEDIA_SUPERIOR("EDUCACIÓN MEDIA SUPERIOR"), BASICA("EDUCACIÓN BÁSICA");

    private String desc;

    TipoEducativo(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    public static TipoEducativo getTipo(String desc){
        if(desc == null){
            return null;
        }

        for(TipoEducativo tipoEducativo: TipoEducativo.values()){
            if(desc.equals(tipoEducativo.desc)){
                return tipoEducativo;
            }
        }

        throw new IllegalArgumentException("No existe el TipoEducativo para " + desc);
    }

}

And then change the Java class for the Entity by removing the @Enumerated annotation and adding a converter for the enum field.

@Entity
@Table(name = "tipo_estudio")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class TipoEstudio implements Serializable {

    // id, constructor, etc

    @NotNull
    @Convert(converter = TipoEducativoConverter.class)
    @Column(name = "stipo_educativo", nullable = false)
    private TipoEducativo tipoEducativo;

    // getters, setters, etc

}

TipoEducativoConverter.java

@Converter
public class TipoEducativoConverter implements AttributeConverter<TipoEducativo, String> {


    @Override
    public String convertToDatabaseColumn(TipoEducativo tipoEducativo) {
        return tipoEducativo.getDesc();
    }

    @Override
    public TipoEducativo convertToEntityAttribute(String desc) {
        return TipoEducativo.getTipo(desc);
    }
}

With these changes the app was reading correctly the enum type field and was storing the correct description for its value. The only grip I had then was that in the web client UI the underscored descriptions were being showed instead of the long descriptions.

I thought that if I changed the description in the enum type it would use those in the UI, so i changed it like so

export const enum TipoEducativo {
    SUPERIOR = 'EDUCACIÓN SUPERIOR',
    MEDIA_SUPERIOR = 'EDUCACIÓN MEDIA SUPERIOR',
    BASICA = 'EDUCACIÓN BÁSICA'
}

but it changed nothing. I had to change the html and component as follows

<tr *ngFor="let tipoEstudio of tipoEstudios ;trackBy: trackId">
                <td><a [routerLink]="['/tipo-estudio', tipoEstudio.id, 'view' ]">{{tipoEstudio.id}}</a></td>
                <td>{{tipoEstudio.nombre}}</td>
                <td>{{TipoEducativo[tipoEstudio.tipoEducativo]}}</td>
                <!-- button columns --> 
            </tr>

tipo-estudio.component.ts

// other imports
import { ITipoEstudio, TipoEducativo } from 'app/shared/model/tipo-estudio.model';

@Component({
    selector: 'jhi-tipo-estudio',
    templateUrl: './tipo-estudio.component.html'
})
export class TipoEstudioComponent implements OnInit, OnDestroy {
    // other fields
    TipoEducativo = TipoEducativo;

    constructor() {}

    // other methods
}

which led to this error

error TS2476: A const enum member can only be accessed using a string literal.

the key thing is

const in an enum means the enum is fully erased, which means you can’t index it by an arbitrary value. Just remove the const modifier.

as explained here

Finally I removed the const keyword and was able to use the descriptions in the enum instead of key names.

Note however that you still have to replace the code generated by jhipster on select items since they’re hard coded like so.

<select class="form-control" name="tipoEducativo" [(ngModel)]="tipoEstudio.tipoEducativo" id="field_tipoEducativo"  required>
                        <option value="SUPERIOR"> EDUCACIÓN SUPERIOR</option>
                        <option value="MEDIA_SUPERIOR">EDUCACIÓN MEDIA SUPERIOR</option>
                        <option value="BASICA">EDUCACIÓN BÁSICA</option>
                    </select>

sources:
Best approach to JPA enumerated types mapping
Mapping enums with a fixed ID in JPA – An alternative to String and Ordinal
Mapping enums done right with @Convert in JPA 2.1
Get TypeScript enum name from instance
const enum in Typescript
How do the different enum variants work in TypeScript?

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

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