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!

Anuncios

SQL – Limitar el número de resultados en Oracle

Al realizar una consulta que debía regresarme un sólo resultado, me regresaba 2 e incluso 4. Esto se debía a que existen registros ‘duplicados’ -la tabla no tiene una llave primaria, huh? no pregunten-

Bueno, entonces me interesaba obtener sólo el registro más actual, por suerte, la tabla si tiene un campo con la fecha que se creó el registro.

Para limitar el número de resultados varía de acuerdo a la base de deatos que estes utilizando, por ejemplo, en MySQL es con la palabra LIMIT, en SQL Server con TOP y en Oracle con ROWNUM

Así que, en Oracle, la consulta más simple que se me ocurrió fue la siguiente

SELECT *
FROM USERS
WHERE ID = :ID
AND ROWNUM <= 1
ORDER BY FECHA_ALTA DESC

Pero no me arrojó los resultados esperados, de hecho, siempre me regresaba el mismo registro aunque creará un registro más actual. Esto se debe al orden en que Oracle ejecuta los comandos de la sentencia -primero ejecuta la claúsula WHERE que regresa un sólo registro y luego lo ordena de acuerdo a ORDER BY (si, ordena un sólo registro)-

La solución es realizar una subconsulta de la siguiente manera

SELECT * FROM (
SELECT *
FROM USERS
WHERE ID = :ID
ORDER BY FECHA_ALTA DESC
) WHERE ROWNUM <= 1

Fuentes:
http://www.w3schools.com/sql/sql_top.asp
http://www.oracle.com/technetwork/issue-archive/2006/06-sep/o56asktom-086197.html

ionic – Navegar a otra página pasando parámetros en la url

A diferencia de AngularJS, ionic utiliza el módulo ui-router en vez de ngRoute para el mecanismo de enrutamiento de las páginas.
En nuestra configuración tendremos estados en vez de rutas. Por ejemplo,

phonecatApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html',
        controller: 'PhoneListCtrl'
      }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html',
        controller: 'PhoneDetailCtrl'
      }).
      otherwise({
        redirectTo: '/phones'
      });
  }]);

se convertiría en

    phonecatApp.config(function ($stateProvider, $urlRouterProvider) {
        $stateProvider

            .state('phones', {
                url: '/phones',
                templateUrl: 'partials/phone-list.html',
                controller: 'PhoneListCtrl'
            })

            .state('phones.detail', {
                url: '/:phoneId',
                templateUrl: 'partials/phone-detail.html',
                controller: 'PhoneDetailCtrl'
            })

        $urlRouterProvider.otherwise('/phones')
    })

Y para navegar a otra página, supongamos que tenemos un botón en nuestra plantilla

<button class="button button-block button-positive" ng-click="goTo(phoneId)">
                Detail
</button>

nuestro controlador quedaría

    .controller('PhoneListCtrl', ['$scope', '$state', function($scope, $state){
        $scope.goTo = function(phoneId){
            $state.go('phones.detail', {phoneId: phoneId})
        }
    }])

notesé el paso del parámetro ‘phoneId’

Fuentes:

AngularJS – Invocar un servicio REST y adaptar la respuesta a nuestro modelo

Desarrollé un servicio web que regresa una respuesta al estilo de SmartGWT

{
"response": {
"status": 0,
"startRow": 0,
"endRow": 76,
"totalRows": 546,
"data": [
{"field1": "value", "field2": "value"},
{"field1": "value", "field2": "value"},
… 76 total records …
]
}
}

Queremos utilizar estos datos en nuestra plantilla

distritos.html

...
<label class="item item-input item-select">
                <div class="input-label">
                    Distrito
                </div>
                <select>
                    <option ng-repeat="distrito in distritos">
                        {{ distrito.descripcion }}
                    </option>
                </select>
            </label>
...

Hay dos formas para poder consumir esta respuesta en nuestra plantilla -a través de un servicio de AngularJS-

Una es utilizar una función transformResponse en la configuración del servicio de la siguiente manera

services.js

'use strict'
angular.module('Equinox.services', ['ngResource'])

    .factory('Distritos', ['$resource', function ($resource) {
        return $resource('http://localhost:8080/equinox/catalogos/distritos', {}, {
            'query': {method: 'GET', isArray: true, transformResponse: function(data){
                return angular.fromJson(data).response.data
            }}
        })
    }])

controllers.js

angular.module('Equinox.controllers', [])

    .controller('DistritosCtrl', ['$scope', 'Distritos', function ($scope, Distritos) {
        $scope.distritos = Distritos.query()

    }])

y la segunda forma es utilizar la respuesta tal y como viene del servidor

services.js

'use strict'
angular.module('Equinox.services', ['ngResource'])

    .factory('Distritos', ['$resource', function ($resource) {
        return $resource('http://localhost:8080/equinox/catalogos/distritos', {}, {
            'query': {method: 'GET', isArray: false}
        })
    }])

controllers.js

angular.module('Equinox.controllers', [])

    .controller('DistritosCtrl', ['$scope', 'Distritos', function ($scope, Distritos) {
        Distritos.query().$promise.then(function(data){
            $scope.distritos = data.response.data
        })
    }])

Fuentes:

SQL – Obtener el año actual

En Oracle, si necesitas utilizar el año actual (de la base de datos) para el valor de alguna columna de tipo Number, lo podríamos hacer de la siguiente manera

    INSERT INTO TEXP
            (FOLIO,
             NUMERO,
             ANYO
            )
         VALUES (SEQ_TEXP.NEXTVAL,
             (SELECT MAX (NUMERO) + 1
                FROM TEXP
               WHERE ANYO = TO_NUMBER(TO_CHAR (SYSDATE, 'YYYY'))
             ),
             TO_NUMBER(TO_CHAR (SYSDATE, 'YYYY'))
            );

En este caso la tabla TEXP tiene 3 columnas de tipo Number (FOLIO, NUMERO Y ANYO).
Para obtener el FOLIO utilizamos una secuencia, para obtener el NUMERO hacemos una subconsulta para saber cúal es el valor máximo para el año actual y sumamos 1, para el ANYO utilizamos el año actual.
Pues resulta que no es necesario utilizar la función TO_NUMBER, la base de datos hace la conversión automática de un valor de tipo CHAR (o VARCHAR) a NUMBER y visceversa. De hecho la conversión que hace la base de datos es más eficiente que utilizar la función TO_NUMBER (el doble, 20ms contra 10ms para esta consulta sencilla).

    INSERT INTO TEXP
            (FOLIO,
             NUMERO,
             ANYO
            )
         VALUES (SEQ_TEXP.NEXTVAL,
             (SELECT MAX (NUMERO) + 1
                FROM TEXP
               WHERE ANYO = TO_CHAR (SYSDATE, 'YYYY')
             ),
             TO_CHAR (SYSDATE, 'YYYY')
            );

Fuentes:
http://stackoverflow.com/questions/1119710/how-do-i-get-the-current-year-using-sql-on-oracle

Datanucleus (JDO) – Relaciones y propiedades de tipo objeto

En datanucleus existe un mecanismo llamado attach/detach que sirve para acceder a las propiedades de los objetos fuera del contexto de una transacción. Esto es muy útil, por ejemplo, cuando estructuras tu aplicación en librerías o capas.

Supongamos que tenemos la siguiente clase

    @PersistenceCapable
    public class Audiencia {</p><pre><code>    @PrimaryKey
    @Persistent
    Integer id;
    @Persistent
    Juez juez;
    @Persistent
    Date inicio;
    @Persistent
    Date fin;
    @Persistent
    TipoAudiencia tipo;

    public Audiencia(){
    }

    public static List&lt;Audiencia&gt; getAudiencias(Integer idJuez) {
        List&lt;Audiencia&gt; audiencias;
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        try{
            tx.begin();
            String filter = "juez.id == :idJuez";
            Query query = pm.newQuery(Audiencia.class, filter);
            audiencias = (List&lt;Audiencia&gt;) pm.detachCopyAll((List&lt;Audiencia&gt;)query.executeWithArray(idJuez));
            tx.commit();
        }finally{
            if(tx.isActive()){
                tx.rollback();
            }
            pm.close();
        }
        return audiencias;
    }

}
</code></pre><p>

cabe destacar que dentro del contexto de la transacción podríamos leer las propiedades Juez y TipoAudiencia, datanucleus cargaría los objetos relacionados de forma “lazy loading”. Pero si trataramos de leer alguna propiedad fuera de este contexto -es el caso cuando ejecutamos el método getAudiencias desde otra clase- está tendría el valor null. Datanucleus sólo hace el detach de las propiedades en el Default Fetch Group. Para poder utilizar las demás propiedades, podemos especificar un Fetch Group dinámico de la siguiente manera

    public static List getAudiencias(Integer idJuez, String[] fields) {
        List audiencias;
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        try{
            tx.begin();
            FetchGroup fetchGroup = PMF.get().getFetchGroup(Audiencia.class, "fields");
            fetchGroup.addMembers(fields);
            pm.getFetchPlan().addGroup("fields");
            String filter = "juez.id == :idJuez";
            Query query = pm.newQuery(Audiencia.class, filter);
            audiencias = (List) pm.detachCopyAll((List)query.executeWithArray(idJuez));
            tx.commit();
        }finally{
            if(tx.isActive()){
                tx.rollback();
            }
            pm.close();
        }
        return audiencias;
    }

Fuentes:
http://www.datanucleus.org/products/accessplatform/jdo/fetchgroup.html
http://www.datanucleus.org/products/datanucleus/jdo/attachdetach.html
http://www.datanucleus.org/products/datanucleus/jdo/object
lifecycle.html

Eclipse – Consulta dinámica en BIRT

Para modificar la consulta de un DataSet puedes utilizar la sección “Property Binding”

en mi caso necesitaba reemplazar en la conulta el valor de un parámetro.

Para reemplazar una cadena con otra lo puedes hacer en el “Expression Builder” de la siguiente manera



var q=new Packages.java.lang.String("SELECT COUNT(*) AS TOTAL \
FROM @OFICINA.EMPLEADOS A, @OFICINA.PUESTOS R, \
@OFICINA.SALARIOS D, @OFICINA.DEPTOS O \
WHERE A.NID_ACO_EXP= R.NID_ACO \
R.NID_DMD=D.NID_DMD AND R.NORDEN=O.NID_ORDEN \
AND TRUNC(R.FECHA) BETWEEN :INICIO AND :FIN");
q.replaceAll("@OFICINA",params["OFICINA"].value);

el código es javascript, pero podemos utilizar clases Java también. En este caso utilizamos la clase String de Java en vez de Javascript, porque el método de javascript no reemplaza todas las coincidencias.

Fuentes:
birt exchange