Etiqueta: js

typescript – computed property on interface

You can’t have computed or calculated properties on interfaces since they omit implementation details, that’s the use of interfaces. For example, we can’t do this 🛑

export interface IUser {
  firstName?: string;
  lastName?: string;
  fullName?: string = this.firstName + this.lastName;
}

but we can just declare it in the interface and implement it in a class

export interface IUser {
  firstName?: string;
  lastName?: string;
  fullName?: string;
}
export class User implements IUser {
  constructor(public firstName?: string, public lastName?: string) {}

  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
}

now let's see how we can instantiate those class

User user = new User('Jhon', 'Doe');
console.log(user.fullName); // Jhon Doe

the problem I had was the data was coming from a service

this.userService.query().pipe(
filter((res: HttpResponse) => res.ok),
map((res: HttpResponse) => res.body)
)
.subscribe((data: IUser[])=>{
this.users = data;
})

changing declared type of reponses won't make a difference 🤷

this.userService.query().pipe(
filter((res: HttpResponse) => res.ok),
map((res: HttpResponse) => res.body)
)
.subscribe((data: User[])=>{
this.users = data;
})

neither 'cast' (which in typescript its not casting but just an assertion) response objects to class type

this.userService.query().pipe(
filter((res: HttpResponse) => res.ok),
map((res: HttpResponse) => res.body)
)
.subscribe((data: IUser[])=>{
this.users = data.map(value => (value as User));
})

i tried to did it by copying properties to a new object of the correct class with spread operator syntax

this.userService.query().pipe(
filter((res: HttpResponse) => res.ok),
map((res: HttpResponse) => res.body)
)
.subscribe((data: IUser[])=>{
this.users = data.map(value => {
return {...new User(), ...value} as User;
});
})

it turns out spread syntax produce a plain or literal object so it can't be used to accomplish what we we're trying to do. We need to use the Object.assign method (be aware there could be undesirable or unexpected results).

this.userService.query().pipe(
filter((res: HttpResponse) => res.ok),
map((res: HttpResponse) => res.body)
)
.subscribe((data: IUser[])=>{
this.users = data.map(value => {
return Object.assign(new User(), value);
});
})

great! 😄

UPDATE: 2019-06-24
Although it worked I was assuming it would only be used in just one component, and of course it didn't.
So, it's way easier to just add the computed field in the domain object or DTO on the server, i.e.

UserDTO.java

public String getFullName() {
    return getFirstName() + " " + getLastName();
}

If you can't change the model or just insist on computing at client side, then it's better if you do it at a central location, i.e. the Service

  query(req?: any): Observable {
    return this.http.get('/api/users', { params: options, observe: 'response' })
      .pipe(
        map(res => {
          const users = res.body.map(user => {
            return Object.assign(new User(), user);
          });
          return new HttpResponse({...res, body: users});
        })
      );
  }

sources:
https://stackoverflow.com/questions/53238881/computed-property-in-typescript
https://stackoverflow.com/questions/49937677/how-to-merge-class-instances-in-typescript

Anuncios

angular – Spring GetMapping with JSON parameter throwing HTTP 400 – Bad Request

I had a pretty simple REST service implemented with Spring Web and it was throwing a HTTP 400 – Bad Request Error.

This was the implementation

@GetMapping("titulos/preview")
    public void preview(@RequestParam String titulo, HttpServletResponse response) throws DatatypeConfigurationException, JAXBException, IOException {
        log.debug("REST request to preview Titulo: {}", titulo);
        Titulo tituloParsed = mapper.readValue(titulo, Titulo.class);
        String xml = titulosService.toXML(tituloParsed);
        String url = "http://birtviewer/viewer/run?__report=report/rptTitulo.rptdesign&__format=pdf";
        url += "&payload=" + URLEncoder.encode(xml, "utf-8");
        response.sendRedirect(url);
    }

Nothing wrong with it, it just receives JSON as a parameter and converts it to its XML representation in order to forward it to a BIRT report viewer instance. Still, it was throwing the aforementioned error whenever I was calling it from the client with a JSON payload, opening in a new window or tab.

window.open(`${this.resourceUrl}/preview?titulo=${JSON.stringify(titulo)}`, '_blank');

It turned out that I wasn’t URL encoding the JSON value before sending it to the service. This was causing the error due to the parameter not being recognized and being required by default. I just had to do that to get rid of the error.

window.open(`${this.resourceUrl}/preview?titulo=${encodeURIComponent(JSON.stringify(titulo))}`, '_blank');

sources:
https://stackoverflow.com/questions/29823626/angular-http-post-to-target-blank
https://stackoverflow.com/questions/38372134/how-to-convert-an-object-to-json-correctly-in-angular-2-with-typescript
https://stackoverflow.com/questions/40799609/set-headers-while-submitting-a-form-data
https://developer.mozilla.org/es/docs/Web/HTTP/Status/400

angularjs – bootstrap tabs route conflict

In an old angularjs app where we used bootstrap 2.3.2 we wanted to implement a tab component to separate results from different sources.

We added a tab component as shown in the docs

<ul class="nav nav-tabs">
  <li><a href="#home" data-toggle="tab">Home</a></li>
  <li><a href="#profile" data-toggle="tab">Profile</a></li>
  <li><a href="#messages" data-toggle="tab">Messages</a></li>
  <li><a href="#settings" data-toggle="tab">Settings</a></li>
</ul>

<div class="tab-content">
  <div class="tab-pane active" id="home">...</div>
  <div class="tab-pane" id="profile">...</div>
  <div class="tab-pane" id="messages">...</div>
  <div class="tab-pane" id="settings">...</div>
</div>

but it navigated away from the page (state), instead of switching tabs. The problem is the angularjs routing mechanism, it uses bang hashtag navigation -it means it uses the url address to know which route is active, actual navigation doesn’t happen, remember it is SPA (Single Page Application)-
So, in the previous example when the profile tab is clicked it changed the url to http://myapp/index.html#/profile because of the href we used.

The solution is quite simple and I found it here, just change the href attributes to data-target and add an empty href attribute (to change the cursor as it would do with links).

<ul class="nav nav-tabs">
  <li><a href="" data-target="#home" data-toggle="tab">Home</a></li>
  <li><a href="" data-target="#profile" data-toggle="tab">Profile</a></li>
  <li><a href="" data-target="#messages" data-toggle="tab">Messages</a></li>
  <li><a href="" data-target="#settings" data-toggle="tab">Settings</a></li>
</ul>

<div class="tab-content">
  <div class="tab-pane active" id="home">...</div>
  <div class="tab-pane" id="profile">...</div>
  <div class="tab-pane" id="messages">...</div>
  <div class="tab-pane" id="settings">...</div>
</div>

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