Etiqueta: angular

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

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

angular – PrimeNG checkbox in reactive forms with default value

If you don’t know what reactive forms are in angular, you should read this first.

The most similar example I could found was this stackoverflow question

How to set Prime NG radio button default value in reactive forms

With this I was able to create my form and bind the checkbox input to the corresponding FormGroup, also set the default value of the checkbox to true -actually add the option to a checked list-

html

<p-checkbox (onChange)="onCumpleServicioSocial($event)" class="form-control" 
formControlName="cumplioServicioSocial" 
id="fldCumplioServicioSocial" 
name="cumplioServicioSocial" 
value="cumplioServicioSocial">
</p-checkbox>

typescript

@Component({
  selector: 'jhi-expedicion',
  templateUrl: './expedicion.component.html',
  styleUrls: ['expedicion.scss']
})
export class ExpedicionComponent implements OnInit {
  instituciones: IInstitucion[];

  formGroupCarrera = this.fb.group({
    institucion: [null, Validators.required],
    fechaInicio: [null, Validators.required],
    ...
    cumplioServicioSocial: [['cumplioServicioSocial']],
  });

  constructor(
    private fb: FormBuilder,
    protected jhiAlertService: JhiAlertService,
    protected institucionService: InstitucionService,
    ...
  ) {}

  onCumpleServicioSocial(checked) {
    console.log('servicio updated', checked);
    console.log(this.formGroupCarrera.get('cumplioServicioSocial').value);
  }

the key to understand is that it returns the value as a FormArray with the values of enabled controls as an array as specified here.

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 – fontawesome icons

I was trying to use another a lock icon to show when a row is locked, but when trying to use the following code

<fa-icon [icon]="'lock'"></fa-icon>

it threw the following exception

angular-fontawesome.js?1dff:89 FontAwesome: Could not find icon with iconName=lock and prefix=fas

well, you just have to import the icon in the src/main/webapp/app/vendor.ts file.
It should be noted in the docs somewhere. At least, it is pointed out in the jhipster mini book.

import {
    // other imports
    faFileSignature,
    faLock
} from '@fortawesome/free-solid-svg-icons';

// other imports
library.add(faFileSignature);
library.add(faLock);

angular – jhipster disable grid row

You always have catalogs in a system. In our case, we wanted to preserve the initial records of them because they were required to be immutable (i.e. they should not change) in order to interchange information with another vendor.

First we added a field to our tables to know if they’re locked or not.

        <addColumn tableName="tc_institucion">
            <column name="blocked" type="varchar(1)"/>
        </addColumn>

then updated the entities extending from an abstract class (you can do it in many different ways)

@MappedSuperclass
public abstract class AbstractLockedEntity {

    @Column(name = "blocked")
    @Convert(converter = BooleanConverter.class)
    private Boolean locked;

    // getters and setters
}

note that we used a converter to convert between Boolean and String. We store ‘S’ for true and ‘N’ for false (idiomatic bias, could be ‘T’ and ‘F’).

@Converter
public class BooleanConverter implements AttributeConverter<Boolean, String> {
    @Override
    public String convertToDatabaseColumn(Boolean value) {
        if(value == null){
            return "N";
        }
        return value? "S": "N";
    }

    @Override
    public Boolean convertToEntityAttribute(String s) {
        if(s == null){
            return false;
        }
        return s.equals("S") ? true: false;
    }
}

we need to update our client entities as well. Since jhispter generates an interface for each of them, we extended them from a new interface called IEntityLocked

export interface IEntityLocked {
    locked?: boolean;
}

then we had

export interface IInstitucion extends IEntityLocked {
    id?: number;
    nombre?: string;
    clave?: string;
}

finally we updated our components (pages, views, you name them) by just adding the ‘table-secondary’ class to every row that is locked, displaying a locked icon instead of the id, and disabling buttons.

            <tr *ngFor="let institucion of institucions ;trackBy: trackId" [ngClass]="{'table-secondary': institucion.locked}">
                <td [ngSwitch]="institucion.locked">
                    <a *ngSwitchCase="false" [routerLink]="['/institucion', institucion.id, 'view' ]">{{institucion.id}}</a>
                    <fa-icon *ngSwitchCase="true" [icon]="'lock'"></fa-icon>
                </td>
                <td>{{institucion.clave}}</td>
                <td>{{institucion.nombre}}</td>
                <td class="text-right">
                    <div class="btn-group flex-btn-group-container">


                            <span class="d-none d-md-inline">View</span>



                            <span class="d-none d-md-inline">Edit</span>



                            <span class="d-none d-md-inline">Delete</span>

                    </div>
                </td>
            </tr>

sources:
https://toddmotto.com/ng-class-angular-classes#property-binding-with-classname
https://getbootstrap.com/docs/4.0/utilities/colors/
https://getbootstrap.com/docs/4.0/content/tables/
https://fontawesome.com/icons?d=gallery&q=lock

angular – passing a complex (object or array) parameter to a popup dialog

In a jhipster generated app We created a component to confirm an action that is to be done on a batch of selected documents.

You can pass parameters as path params or query params, but most examples only tell you how to pass an id and then resolve it to an object. We already had an array of objects which we want to pass as parameters.
I suppose you could stringify them and pass them as one of the options above, but that would increase the length of the URL; with the advantage it can be shared and state would be restored. Nonetheless we decided to opt for the fourth option described in this answer to the stackoverflow question ‘Send data through routing paths in Angular’.

First we created our component to select the documents that are going to be processed, when the button is pressed we should confirm the action.
We used a PrimeNG table to allow the user to select the documents which we store in our ‘documentosSeleccionados’ variable.
The routerLink specifies it should navigate to a popup dialog, the trick here is we also specified a click action where we tell it to update the value on a Service property -as explained in the answer above-.

lote-firmar.component.html

        <button class="btn btn-primary float-right" (click)="documentoService.documentosSeleccionados = documentosSeleccionados"
                [routerLink]="['/', { outlets: { popup: 'lote/'+ lote.id + '/firmar/confirma'} }]"
                replaceUrl="true"
                queryParamsHandling="merge">
            <fa-icon [icon]="'file-signature'"></fa-icon>
            <span>
            Firmar
            </span>
        </button>
    <br/>
    <p-table #dt [value]="documentos" dataKey="id" selectionMode="multiple" [(selection)]="documentosSeleccionados">
        <ng-template pTemplate="header">
            <tr>
                <th style="width: 3em">
                    <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
                </th>
                <th>Archivo</th>
                <th>
                    <p-dropdown [options]="estatusDocumentos" [style]="{'width':'100%'}" (onChange)="dt.filter($event.value, 'estatus', 'equals')"></p-dropdown>
                </th>
            </tr>
        </ng-template>
        <ng-template pTemplate="body" let-documento>
            <tr>
                <td>
                    <p-tableCheckbox [value]="documento"></p-tableCheckbox>
                </td>
                <td>{{documento.archivo}}</td>
                <td>{{documento.estatus}}</td>
            </tr>
        </ng-template>
    </p-table>

lote-firmar.component.ts

@Component({
    selector: 'jhi-lote-firmar',
    templateUrl: './lote-firmar.component.html'
})
export class LoteFirmarComponent implements OnInit {
    lote: ILote;
    documentos: IDocumento[];
    documentosSeleccionados: IDocumento[];
    estatusDocumentos: SelectItem[];

    ... we inject the Service
    constructor(private activatedRoute: ActivatedRoute, private documentoService: DocumentoService) {}

The service is pretty straightforward, we just need to add the property where we’d like to pass to the popup dialog. We could even specified the type, but we didn’t in this case. Remember services are singletons, so we have just one instance of this class.

documento.service

@Injectable({ providedIn: 'root' })
export class DocumentoService {
    ... other properties
    public documentosSeleccionados;

    constructor(private http: HttpClient) {}

    ... we also have the method to send the objects to process to the server
    firmar(documentos: IDocumento[], keyPassword: string): Observable<EntityArrayResponseType> {
        const options = createRequestOption({ keyPassword });
        return this.http.post<IDocumento[]>(this.resourceUrl + '/firmar', documentos, {
            params: options,
            observe: 'response'
        });
    }
}

Finally, the popup dialog

lote-firmar-dialog.component.html

<form name="loteFirmarForm" autocomplete="off" (ngSubmit)="firmarDocumentos()">
    <div class="modal-header">
        <h4 class="modal-title">Se requiere contraseña</h4>
        ×
    </div>
    <div class="modal-body">

        <p id="jhi-delete-documento-heading">Para firmar los documentos seleccionados, ingresa la contraseña de tu archivo .key</p>
        <div class="col-md-8">
            <div class="form-group">

            </div>
        </div>
    </div>
    <div class="modal-footer">

             <span>Cancelar</span>


             <span>Firmar</span>

    </div>
</form>

since we inject the Documentos service already in this component, we can access the value passed as parameter trough the service and initialize our model in the NgOnInit method.

lote-firmar-dialog.component.ts

@Component({
    selector: 'jhi-lote-firmar-dialog',
    templateUrl: './lote-firmar-dialog.component.html'
})
export class LoteFirmarDialogComponent implements OnInit {
    ... other properties
    documentosSeleccionados: IDocumento[];

    ... this is where we inject the service
    constructor(private documentoService: DocumentoService, public activeModal: NgbActiveModal, private eventManager: JhiEventManager) {}

    ... this is where we read the array passed through the service
    ngOnInit() {
        this.documentosSeleccionados = this.documentoService.documentosSeleccionados;
    }

    ... then we can send this data to the server to be processed in this service
    firmarDocumentos() {
        this.documentoService.firmar(this.documentosSeleccionados, this.keyfilePassword).subscribe(
            response => {
                console.log('se enviaron los documentos a firmar, response:', response);
                this.eventManager.broadcast({
                    name: 'documentosListModification',
                    content: 'Los documentos se firmaron correctamente'
                });
                this.activeModal.dismiss(true);
            },
            error => {
                console.log('ocurrio un error al enviar los documentos a firmar', error);
                this.eventManager.broadcast({
                    name: 'documentosListModification',
                    content: 'Ocurrio un problema al firmar los documentos [' + error + ']'
                });
            }
        );
    }
}

done!