jhipster – configure elasticsearch analyzer for a field

jhipster-elastic

We have an Entity with an Enum field. This enum field gets saved in the database as string.

enum

public enum TipoContrato {
    ADJUDICACION_DIRECTA,  INVITACION_RESTRINGIDA,  LICITACION_PUBLICA
}

entity

@Entity
@Table(name = "contrato")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Document(indexName = "contrato")
public class Contrato implements Serializable {

    ...

    @Enumerated(EnumType.STRING)
    @Column(name = "tipo")
    private TipoContrato tipo;

the thing is, it includes underscore. Elasticsearch by default uses the standard analyzer which stores it and searches for it as one word. So, i.e. searching for ‘directa’ would not produce any results, but searching for ‘adjudicacion_directa’ would.

First you should enable http access to elasticsearch on your jhipster project to test if you’re getting results with a simple request, i.e using curl

curl -XPOST 'localhost:9200/contrato/_search?pretty' -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool" : {
      "should" : [
        { "match" : { "tipo" : "directa" } }
      ]
    }             
  }                                         
}
'

to get the results we’re expecting we need to change the analyzer that’s being used to index the field. We do this by changing the mapping. You could change it by using the REST api, and then reindexing your data. In development, we can reconfigure our mapping and then just do a mvn clean to regenerate it all.

Let’s change the mapping in our entity (we just need to add an annotation).

    @Enumerated(EnumType.STRING)
    @Column(name = "tipo")
    @Field(type = FieldType.String, analyzer = "simple")
    private TipoContrato tipo;

now, the results that came back from the curl request were fine. But the results from the search box didn’t. We had to include a custom search for it to work, cause the more general search doesn’t do a full text search apparently i.e. if you open in a new tab the url http://localhost:9200/contrato/_search?q=directa it doesn’t work but if you include the field it works http://localhost:9200/contrato/_search?q=tipo:directa

public interface ContratoSearchRepository extends ElasticsearchRepository<Contrato, Long> {

    @Query("{\n" +
        "  \"query\": {\n" +
        "    \"bool\" : {\n" +
        "      \"should\" : [\n" +
        "        { \"match\" : { \"_all\" : \"?0\" } },\n" +
        "        { \"match\" : { \"tipo\" : \"?0\" } }\n" +
        "      ]\n" +
        "    }             \n" +
        "  }                                         \n" +
        "}")
    Page<Contrato> buscarContrato(String query, Pageable page);

}

sources:
http://www.baeldung.com/spring-data-elasticsearch-tutorial
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html
https://stackoverflow.com/questions/31837546/elasticsearch-splitting-words-on-underscore-search-founds-nothing
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html
https://docs.spring.io/spring-data/elasticsearch/docs/3.0.2.RELEASE/api/

Anuncios

jhipster – change ngb-datepicker date format

Screenshot from 2018-01-11 16-16-39

When you generate an entity with jhipster entity and add a LocalDate the UI generated uses ng-bootstrap Datepicker. To change the format you need to add an implementation of the class NgbDateParserFormatter. To change the language you need to provide an implementation of the class NgbDatepickerI18n (as shown in the examples).

First create the class files, i.e.

/webapp/src/main/webapp/app/blocks/config/ng-bootstrap.date-parser-formatter.ts

import { Injectable } from '@angular/core';
import { NgbDateParserFormatter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';

function padNumber(value: number) {
    if (isNumber(value)) {
        return `0${value}`.slice(-2);
    } else {
        return "";
    }
}

function isNumber(value: any): boolean {
    return !isNaN(toInteger(value));
}

function toInteger(value: any): number {
    return parseInt(`${value}`, 10);
}


@Injectable()
export class NgbDateParserFormatterEsMX extends NgbDateParserFormatter {
    parse(value: string): NgbDateStruct {
        if (value) {
            const dateParts = value.trim().split('/');
            if (dateParts.length === 1 && isNumber(dateParts[0])) {
                return {year: toInteger(dateParts[0]), month: null, day: null};
            } else if (dateParts.length === 2 && isNumber(dateParts[0]) && isNumber(dateParts[1])) {
                return {year: toInteger(dateParts[1]), month: toInteger(dateParts[0]), day: null};
            } else if (dateParts.length === 3 && isNumber(dateParts[0]) && isNumber(dateParts[1]) && isNumber(dateParts[2])) {
                return {year: toInteger(dateParts[2]), month: toInteger(dateParts[1]), day: toInteger(dateParts[0])};
            }
        }
        return null;
    }

    format(date: NgbDateStruct): string {
        let stringDate: string = "";
        if(date) {
            stringDate += isNumber(date.day) ? padNumber(date.day) + "/" : "";
            stringDate += isNumber(date.month) ? padNumber(date.month) + "/" : "";
            stringDate += date.year;
        }
        return stringDate;
    }
}

<project>/direccion-juridica/webapp/src/main/webapp/app/blocks/config/ng-bootstrap-datepicker-i18n.ts

import {Component, Injectable} from '@angular/core';
import {NgbDatepickerI18n} from '@ng-bootstrap/ng-bootstrap';

const I18N_VALUES = {
    'es-MX': {
        weekdays: ['Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab', 'Dom'],
        months: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
    }
    // other languages you would support
};

// Define a service holding the language. You probably already have one if your app is i18ned. Or you could also
// use the Angular LOCALE_ID value
@Injectable()
export class I18n {
    language = 'es-MX';
}

// Define custom service providing the months and weekdays translations
@Injectable()
export class CustomDatepickerI18n extends NgbDatepickerI18n {

    constructor(private _i18n:I18n) {
        super();
    }

    getWeekdayShortName(weekday:number):string {
        return I18N_VALUES[this._i18n.language].weekdays[weekday - 1];
    }

    getMonthShortName(month:number):string {
        return I18N_VALUES[this._i18n.language].months[month - 1];
    }

    getMonthFullName(month:number):string {
        return this.getMonthShortName(month);
    }
}

finally define the provider in <project>/direccion-juridica/webapp/src/main/webapp/app/app.module.ts

import ...

...
    providers: [
        ProfileService,
        customHttpProvider(),
        PaginationConfig,
        UserRouteAccessService,
        I18n,
        {provide: NgbDatepickerI18n, useClass: CustomDatepickerI18n},
        {provide: NgbDateParserFormatter, useClass: NgbDateParserFormatterEsMX}
    ],
    bootstrap: [ JhiMainComponent ]
})
...

to change the displayed format of dates in tables you can replace in files date:'mediumDate' with date:'dd/MM/yyyy' and date:'medium' with date:'dd/MM/yyyy HH:mm' for example.

for ZonedDateTime it uses a datetime-local input which tries to use browser configuration -if supported-
Due to, and I’m quoting this from Mozilla

Date/time inputs sound convenient at first glance; they provide an easy UI 
for choosing dates and times, and they normalize the data format sent to the 
server, regardless of the user's locale. However, there are issues with 
 because of the **limited browser support.**

So I would recommend breaking DateTimes into a Datepicker and a Timepicker from ng-bootstrap and configuring as above.

sources:
https://stackoverflow.com/questions/40664523/angular2-ngbdatepicker-how-to-format-date-in-inputfield

https://github.com/ng-bootstrap/ng-bootstrap/issues/754#issuecomment-247767027

angular – prevent default submit on click of a button

I had a form with ngSubmit

<form name="editForm" role="form" novalidate (ngSubmit)="save()" #editForm="ngForm">

and inside this form I have 3 buttons like so

<div class="btn-group flex-btn-group-container">

            <span class="fa fa-eye"></span>
            <span class="hidden-md-down">Descargar</span>


            <span class="fa fa-pencil"></span>
            <span class="hidden-md-down">Edit</span>


            <span class="fa fa-remove"></span>
            <span class="hidden-md-down">Delete</span>

</div>

two are of type submit and one has a click handler.

Whenever I clicked the first button (click handler), it was submitting the form. To prevent this you should add the $event parameter

<button (click) = "descargarAdjunto(adjunto, $event)" class="btn btn-info btn-sm">
    <span class="fa fa-eye"></span>
    <span class="hidden-md-down">Descargar</span>
</button>

and call the prevent default

private descargarAdjunto(adjunto, $event){
        $event.preventDefault()
        ...
}

fuentes:
https://coderwall.com/p/hvy1rg/angularjs-how-to-prevent-form-submission-after-click-on-button
https://github.com/angular/angular.js/issues/6017
https://docs.angularjs.org/api/ng/directive/form

angular – debugging an app

Peek 2017-11-24 15-39.gif

There’s an excellent article on ionic blog which talks about how to debug an app. It become really handy now that JS Batarang extension stopped working on my google chrome 😥 (Ubuntu 16.04.3 LTS; Chrome Version 62.0.3202.62 (Official Build) (64-bit)).

Peek 2017-11-24 15-23

You just have to identify an element on your page which is associated with an scope and you would see all the variables on that scope through the use of a simple console.log call.

angular.element(targetNode).scope()

sources:
http://blog.ionicframework.com/angularjs-console/

angular – replace character on displayed values of select list array without filter directive

I have an enum in one of our entities like so

private enum ConduccionAlProceso {
        POR_COMPARECENCIA, POR_CITACION, CON_CONTROL_DE_DETENCION, EN_CUMPLIMIENTO_DE_ORDEN_DE_APREHENSION
    }

I wanted to display the values on client side without underscores. There are a lot of articles showing how to do so by using a filter directive like

App.filter('underscoreless', function () {
  return function (input) {
      return input.replace(/_/g, ' ');
  };
});

and then in html

{{ addText | underscoreless }}

but I didn’t want to create a directive for this since i’m only using it once.

So, I ended up adding a function in the controller

$scope.mediosConduccionAlProceso = [
    'POR_COMPARECENCIA', 'POR_CITACION', 'CON_CONTROL_DE_DETENCION', 'EN_CUMPLIMIENTO_DE_ORDEN_DE_APREHENSION'
]
$scope.replaceUnderscores = function(item){
    return item.replace(new RegExp('_','g'), ' ');
}

and calling it in the html like

    <select class="form-control"
            ng-model="carpeta.conduccionAlProceso"
            ng-options="medio as replaceUnderscores(medio) for medio in mediosConduccionAlProceso 
                        | orderBy: 'toString()'"
            required>
        <option value=""></option>
    </select>

sources:
https://stackoverflow.com/questions/31939288/angular-filter-to-replace-all-underscores-to-spaces
https://stackoverflow.com/questions/1144783/how-to-replace-all-occurrences-of-a-string-in-javascript
https://stackoverflow.com/questions/21785021/can-ngoptions-call-a-function-to-get-display-text
https://docs.angularjs.org/api/ng/directive/ngOptions

fuelux – use a select list inside a placard

Peek 2017-11-23 08-51

Fuel UX extends Bootstrap with additional lightweight JavaScript controls for your web applications. It has a placard control, which adds a pop-up element to edit inputs/textareas inline on focus with additional options for explicit accept/cancel actions.

I was trying to display a select list -in angular- when the placard is shown. I achieved it with the following code. I had to put the select list inside the placard-popup div and give it a margin so it didn’t overlap with the header. I don’t know if that’s what has been addressed since version 3.11.4 and that’s the purpose of the placard-popup div, it doesn’t say in the docs or at least i couldn’t found it.

<div id="tipoAudiencia" class="placard">
    <div class="placard-popup">
        <div class="form-group" style="margin-top:30px;">



        </div>
    </div>
    <div class="placard-header">Selecciona el tipo de audiencia</div>
    <div class="form-control placard-field glass">{{audiencia.tipoAudiencia.nombre}}</div>
    <div class="placard-footer">
        <a class="placard-cancel" href="#">Cancelar</a>
        Aceptar
    </div>
</div>

then in my controller I update the value if it gets accepted

$('#tipoAudiencia').on('accepted.fu.placard', function(){
    console.log('actualizando el tipo de audiencia a ' + $scope.tipoAudienciaEditada);
    $scope.audiencia.tipoAudiencia = $scope.tipoAudienciaEditada;
    Audiencia.update($scope.audiencia);
});

I had some minor issues with sizing but they’re not showstoppers

Peek 2017-11-23 08-52

sources:
https://github.com/ExactTarget/fuelux/issues/1392

Netbeans – Enable symbolic links in Payara (GlassFish) for your application

Screenshot from 2017-11-09 13-08-37

To enable symbolic links in Payara you just have to go to the admin console (http://localhost:4848 or inside netbeans open it in the Services window | Servers | Payara right click it and select ‘open admin console’) and click Configuration | server-config | Virtual Servers | server, then under Additional Properties, click Add Property and enter allowLinking as name and true as value as shown in the image above.

Symbolic links do not work with war deployments, they only work with exploded wars (if you add a symbolic link inside a war, the files from the target dir will be copied inside the war -compressed file-)
The good news is that Netbeans runs your app as an exploded war (folder). The folder path is usually {project}/build/web, to find out the folder which it uses for your app just open the Payara admin console and go to the Applications section and click your app, then look for the attribute ‘Location’
Go to that location and create whatever symbolic link your app needs, for example

cd {project}/build/web
ln -s /usr/share/docs docs

then you can access all the resources inside that folder like so

http://localhost:8080/project/docs

In production, you’ll have to deploy your app as an exploded war and have the same symbolic link created in the server

sources:
http://www.stpe.se/2008/07/enable-symbolic-links-in-glassfish/