Browse Source

search engine and pagination implemented in view list base

robert2206 8 years ago
parent
commit
083d3c5f75

+ 4 - 0
src/app/app.scss

@@ -15,6 +15,10 @@ ion-title > div, ion-navbar > button, ion-buttons > button {
     color: $header-font-color !important;
 }
 
+ion-buttons {
+    padding-right: 0.8em;
+}
+
 ion-fab > button {
     background: $button-color !important;
 }

+ 345 - 86
src/base/base-list-view.ts

@@ -8,22 +8,235 @@ import { EventsManager } from "../base/events/event-manager";
 export abstract class BaseListView<T> extends BaseView<T>{
 
     items: T[];
-    selectedIndex: number;
+    itemsShown: T[];
+    itemsMatched: T[];
+    selectedItem: T;
     filters: Array<[string, string, any]>;
+    search: boolean;
+    range: { start: number, end: number };
 
     constructor(c: { new (): T; }, ...filters: Array<[string, string, any]>) {
         super(c, PouchService);
 
         this.items = [];
-        this.selectedIndex = -1;
+        this.itemsShown = [];
+        this.itemsMatched = [];
+        this.selectedItem = null;
         this.filters = filters;
-
+        this.search = false;
+        this.range = { start: 0, end: 10 }
+        
         this.initialize();
     }
 
+    //-------------------------------------------------------------------------------
+    //                           BEGIN GETTERS AND SETTERS                          |
+    //-------------------------------------------------------------------------------
+
+    /**
+     * 
+     */
+    getItems(): T[] {
+        return this.items;
+    }
+    
+    /**
+     * 
+     * @param items 
+     */
+    setItems(items: T[]): void {
+        this.items = items;
+        this.setItemsShown(this.getItems().slice(this.getRange().start, this.getRange().end));
+    }
+
+    /**
+     * 
+     */
+    getItemsShown(): T[] {
+        return this.itemsShown;
+    }
+
+    /**
+     * 
+     * @param itemsShown 
+     */
+    setItemsShown(itemsShown: T[]): void {
+        this.itemsShown = itemsShown;
+    } 
+
+    /**
+     * 
+     */
+    getItemsMatched(): T[] {
+        return this.itemsMatched;
+    }
+
+    /**
+     * 
+     * @param itemsMatched 
+     */
+    setItemsMatched(itemsMatched: T[]): void {
+        this.itemsMatched = itemsMatched;
+    }
+
+    /**
+     * 
+     * @param item 
+     */
+    setSelectedItem(item: T): void {
+        this.selectedItem = item;
+    }
+
+    /**
+     * 
+     */
+    getSelectedItem(): T {
+        return this.selectedItem;
+    }
+
     /**
      * 
      */
+    getFilters(): Array<[string, string, any]> {
+        return this.filters;
+    }
+
+    /**
+     * 
+     * @param filters 
+     */
+    setFilters(filters: Array<[string, string, any]>): void {
+        this.filters = filters;
+
+        this.applyFilter(this.getItems());
+    }
+
+    /**
+     * 
+     */
+    isSearch(): boolean {
+        return this.search;
+    }
+    
+    /**
+     * 
+     * @param search 
+     */
+    setSearch(search: boolean): void {
+        this.search = search;
+    }
+
+    /**
+     * 
+     */
+    getRange(): { start: number, end: number } {
+        return this.range;
+    }
+
+    /**
+     * 
+     * @param range 
+     */
+    setRange(range: { start: number, end: number }): void {
+        this.range = range;
+    }
+
+    // ------------------------------- SPECIAL GETTERS -------------------------------
+
+    /**
+     * 
+     */
+    getStartRange(): number {
+        return this.getRange().start;
+    }
+
+    /**
+     * 
+     */
+    getEndRange(): number {
+        return this.getRange().end;
+    }
+
+    /**
+     * 
+     */
+    hasItems(): boolean {
+        return this.getItems().length > 0;
+    }
+
+    /**
+     * 
+     */
+    hasItemsShown(): boolean {
+        return this.getItemsShown().length > 0;
+    }
+
+    /**
+     * 
+     */
+    isExpandableRange(): boolean {
+        return this.getRange().end >= this.getItemsShown().length;
+    }
+
+    /**
+     * 
+     */
+    getSelectedIndexInAll(): number {
+        if (!this.getSelectedItem()) {
+            return -1;
+        }
+
+        return this.getItems().indexOf(this.getSelectedItem());
+    }
+
+    /**
+     * 
+     */
+    getSelectedIndexInShown(): number {
+        if (!this.getSelectedItem()) {
+            return -1;
+        }
+
+        return this.getItemsShown().indexOf(this.getSelectedItem());
+    }
+
+    /**
+     * 
+     */
+    getSelectedIndexInMatched(): number {
+        if (!this.getSelectedItem()) {
+            return -1;
+        }
+
+        return this.getItemsMatched().indexOf(this.getSelectedItem());
+    }
+
+    // ---------------------------- SPECIAL SETTERS -----------------------------
+
+    /**
+     * 
+     * @param start 
+     */
+    setStartRange(start: number): void {
+        this.setRange({ start: start, end: this.getRange().end });
+    }
+
+    /**
+     * 
+     * @param end 
+     */
+    setEndRange(end: number): void {
+        this.setRange({ start: this.getRange().start, end: end });
+    }
+
+    //---------------------------------------------------------------------------
+    //                               BEGIN SERVICES                             |
+    //---------------------------------------------------------------------------
+
+    /**
+     * Inicializa la lista.
+     * Emite una evento al empezar y terminar el proceso.
+     */
     initialize() {
         EventsManager.publish("app:loading", true);
 
@@ -41,28 +254,7 @@ export abstract class BaseListView<T> extends BaseView<T>{
     }
 
     /**
-     * 
-     */
-    private subcribeToEvents(): void {
-        EventsManager.subscribe("app:changed", data => {
-            if (data.action === "add") {
-                this.items.unshift(data.item);
-            }
-
-            if (data.action === "update") {
-                this.items[this.getSelectedIndex()] = data.item;
-            }
-
-            if (data.action === "delete") {
-                this.items.splice(this.getSelectedIndex(), 1);
-            }
-
-            this.setSelectedIndex(-1);
-        });
-    }
-
-    /**
-     * 
+     * Aplica los filtros correspondientes a la lista
      */
     applyFilter(records: T[]): void {
         if (records.length == 0) {
@@ -70,12 +262,12 @@ export abstract class BaseListView<T> extends BaseView<T>{
         }
         
         if (this.getFilters().length == 0) {
-            this.items = records;
+            this.setItems(records);
 
             return;
         }
 
-        this.items = records.filter(record => {
+        this.setItems(records.filter(record => {
             let expressions = this.getFilters().map(filter => {
                 let operator = "===";
                 let leftOperand = "record." + filter[0];
@@ -105,111 +297,178 @@ export abstract class BaseListView<T> extends BaseView<T>{
             });
 
             return eval(expressions.join("&&"));
-        });
+        }));
     }
-    
+
     /**
-     * 
+     * Suscribe a los eventos de la lista
      */
-    performDelete(): void {
-        super.getInjectable(PouchService).remove(this.getSelectedItem()).subscribe(result => { 
-            EventsManager.publish("app:changed", {
-                action: "delete",
-                details: result
-            });
-        }, error => { 
-            EventsManager.publish("app:error", error);
+    private subcribeToEvents(): void {
+        EventsManager.subscribe("app:changed", data => {
+            if (data.action === "add") {
+                this.add(data.item);
+            }
+
+            if (data.action === "update") {
+                this.update(data.item);
+            }
+
+            if (data.action === "delete") {
+                this.remove();
+            }
+
+            this.unselectItem();
         });
     }
 
     /**
-     * 
+     * Adicioana a la lista un nuevo objeto guardado
+     * @param obj 
      */
-    getItems(): T[] {
-        return this.items;
+    add(item: T): void {
+        this.items.unshift(item);
+
+        if (this.getItemsShown().length < this.getEndRange()) {
+            this.itemsShown.unshift(item);
+        }
     }
-    
+
     /**
-     * 
-     * @param items 
+     * Actualiza el objeto modificado
+     * @param item 
      */
-    setItems(items: T[]): void {
-        this.items = items;
+    update(item: T): void {
+        this.itemsShown[this.getSelectedIndexInShown()] = item;
 
-        this.applyFilter(this.getItems());
+        if (this.isSearch()) {
+            this.itemsMatched[this.getSelectedIndexInShown()] = item;
+        }
+
+        this.items[this.getSelectedIndexInAll()] = item;
     }
 
     /**
-     * 
+     * Quita el objeto borrado de la lista
      */
-    getSelectedIndex(): number {
-        return this.selectedIndex;
+    remove(): void {
+        this.itemsShown.splice(this.getSelectedIndexInShown(), 1);
+
+        if (this.isSearch()) {
+            this.itemsMatched.splice(this.getSelectedIndexInShown(), 1);
+        }
+
+        this.items.splice(this.getSelectedIndexInAll(), 1);
     }
 
     /**
-     * 
-     * @param index 
+     * Deselecciona el elemento que haya sido previamente seleccionado.
      */
-    setSelectedIndex(selectedIndex: number): void {
-        this.selectedIndex = selectedIndex;
+    unselectItem(): void {
+        this.setSelectedItem(null);
     }
 
     /**
-     * 
-     * @param item 
+     * Limpia las coincidencias de búsqueda.
      */
-    indexOf(item: T): number {
-        return this.getItems().indexOf(item);
+    clearItemsMatched(): void {
+        this.setItemsMatched([]);
     }
 
     /**
-     * 
+     * Alterna entre el modo de lista y el modo de búsqueda de la lista.
      */
-    getSelectedItem(): T {
-        if (this.getSelectedIndex() === -1) {
-            return null;
-        }
+    toggleSearch(): void {
+        this.setSearch(!this.isSearch());
+        this.setRange({ start: 0, end: 10 });
 
-        return this.getItems()[this.getSelectedIndex()];
+        if (!this.isSearch()) {
+            this.setItemsMatched([]);
+            this.setItemsShown(this.getItems().slice(this.getRange().start, this.getRange().end));
+        }
     }
 
     /**
-     * 
+     * Realiza la expansión del rango para la vista.
      */
-    hasItems(): boolean {
-        return this.getItems().length > 0;
-    }
+    expandRange(): void {
+        this.setRange({ start: this.getRange().end, end: this.getRange().end + 10 });
 
-    /**
-     * 
-     * @param item 
-     */
-    setSelectedItem(item: T): void {
-        let index = this.indexOf(item);
-        this.setSelectedIndex(index);
+        if (this.isSearch()) {
+            this.setItemsShown(this.getItemsShown().concat(this.getItemsMatched().slice(this.getRange().start, this.getRange().end)));
+        } else {
+            this.setItemsShown(this.getItemsShown().concat(this.getItems().slice(this.getRange().start, this.getRange().end)));
+        }
     }
 
+ 
     /**
+     *  Realiza la expasión de la vista de la lista.
      * 
+     * @param event 
      */
-    deselectItem(): void {
-        this.setSelectedIndex(-1);
+    expand(event: any): void {
+        this.expandRange();
+
+        if (this.isExpandableRange()) {
+            event.enable(false);
+        }
+
+        event.complete();
     }
 
     /**
-     * 
+     * Realiza la busqueda cada campo de los objetos en la lista de items mientras este campo sea de tipo string.
+     * Emite un evento cuando comienza y termina el proceso.
+     * @param text 
      */
-    getFilters(): Array<[string, string, any]> {
-        return this.filters;
+    performSearch(event: any): void {
+        let value: string = event.target.value;
+
+        if (!value || value.length < 3) {
+            return;
+        }
+        
+        EventsManager.publish("app:loading", true);
+
+        this.clearItemsMatched();
+        
+        let regExp = new RegExp(value, "g");
+        let item: T = null;
+        let keys: Array<any> = null;
+
+        for (let i = 0; i < this.getItems().length; i++) {
+            item = this.getItems()[i];
+            keys = Object.keys(item);
+
+            for (let j = 0; j < keys.length; j++) {
+                if (typeof item[keys[j]] !== "string") {
+                    continue;
+                }
+
+                if (!!item[keys[j]].toLowerCase().match(regExp)) {
+                    this.getItemsMatched().push(item);
+                    break;
+                }
+            }
+        }
+
+        this.setItemsShown(this.getItemsMatched().slice(this.getRange().start, this.getRange().end));
+        
+        EventsManager.publish("app:loading", false);
     }
 
     /**
-     * 
-     * @param filters 
+     * Realiza el borrado del item seleccionado.
+     * Emite un evento al terminar el proceso.
      */
-    setFilters(filters: Array<[string, string, any]>): void {
-        this.filters = filters;
-
-        this.applyFilter(this.getItems());
+    performDelete(): void {
+        super.getInjectable(PouchService).remove(this.getSelectedItem()).subscribe(result => { 
+            EventsManager.publish("app:changed", {
+                action: "delete",
+                details: result
+            });
+        }, error => { 
+            EventsManager.publish("app:error", error);
+        });
     }
 } 

+ 8 - 6
src/pages/customer/customer.html

@@ -1,6 +1,11 @@
 <ion-header>
     <ion-navbar>
         <ion-title>{{ title }}</ion-title>
+        <ion-buttons end>
+            <button ion-button icon-only type="submit" form="details-form" [disabled]="customerForm.invalid">
+                <i class="fa fa-save fa-lg"></i>
+            </button>
+        </ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>
@@ -30,20 +35,17 @@
             <ion-input type="text" formControlName="email"></ion-input>
         </ion-item>
     </form>
-    <ion-fab right top edge>
-        <button ion-fab mini>
+    <ion-fab right bottom>
+        <button ion-fab>
             <ion-icon name="menu"></ion-icon>
         </button>
-        <ion-fab-list side="bottom">
+        <ion-fab-list side="top">
             <button ion-fab>
                 <i class="fa fa-camera fa-lg"></i>
             </button>
             <button ion-fab>
                 <i class="fa fa-map-marker fa-lg"></i>
             </button>
-            <button ion-fab color="secondary" type="submit" form="details-form" [hidden]="customerForm.invalid">
-                <i class="fa fa-save fa-lg"></i>
-            </button>
         </ion-fab-list>
     </ion-fab>
 </ion-content>

+ 20 - 4
src/pages/customers/customers.html

@@ -4,11 +4,24 @@
             <ion-icon name="menu"></ion-icon>
         </button>
     	<ion-title>Clientes</ion-title>
+		<ion-buttons end>
+			<button ion-button icon-only (click)="toggleSearch()">
+				<i class="fa fa-search fa-lg"></i>
+			</button>
+		</ion-buttons>
   	</ion-navbar>
 </ion-header>
+<ion-toolbar *ngIf="isSearch()">
+    <ion-searchbar placeholder="Buscar" debounce="1000" (ionInput)="performSearch($event)"></ion-searchbar>
+    <ion-buttons end>
+        <button ion-button icon-only (click)="toggleSearch()">
+            <i class="fa fa-undo fa-lg"></i>
+        </button>
+    </ion-buttons>
+</ion-toolbar>
 <ion-content>
 	<ion-list>
-		<ion-item *ngFor="let item of getItems()" (doubleTap)="openOptions(item)" [ngClass]="{ 'selected-item': item === getSelectedItem() }">
+		<ion-item *ngFor="let item of getItemsShown()" (doubleTap)="openOptions(item)" [ngClass]="{ 'selected-item': item === getSelectedItem() }">
 			<ion-thumbnail item-left>
 				<img src="./assets/images/customer.png" *ngIf="!item.image_medium"/>
 				<img [src]="item.image_medium | sanitizeUrl" *ngIf="item.image_medium"/>
@@ -28,11 +41,14 @@
 			</p>
 		</ion-item>
 	</ion-list>
-	<div class="no-items" *ngIf="!hasItems()">
+	<ion-infinite-scroll (ionInfinite)="expand($event)">
+        <ion-infinite-scroll-content></ion-infinite-scroll-content>
+    </ion-infinite-scroll>
+	<div class="no-items" *ngIf="!hasItemsShown()">
         <h2>Sin elementos</h2>
     </div>
-	<ion-fab right top edge>
-        <button ion-fab mini (click)="goToDetails()">
+	<ion-fab right bottom>
+        <button ion-fab (click)="goToDetails()">
             <ion-icon name="add"></ion-icon>
         </button>
     </ion-fab>

+ 2 - 2
src/pages/customers/customers.ts

@@ -31,7 +31,7 @@ export class CustomersPage extends BaseListView<Partner> {
      * 
      */
     ionViewDidEnter() {
-        super.deselectItem();
+        super.unselectItem();
     }
 
     /**
@@ -77,7 +77,7 @@ export class CustomersPage extends BaseListView<Partner> {
                     text: "Cancelar",
                     role: "cancel",
                     handler: () => {
-                        super.deselectItem();
+                        super.unselectItem();
                     }
                 },
             ]

+ 5 - 5
src/pages/lead/lead.html

@@ -1,12 +1,12 @@
 <ion-header>
     <ion-navbar>
         <ion-title>{{ title }}</ion-title>
+        <ion-buttons end>
+            <button ion-button icon-only type="submit" form="details-form">
+                <i class="fa fa-save fa-lg"></i>
+            </button>
+        </ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>
-    <ion-fab right top edge>
-        <button ion-fab mini type="submit" form="details-form">
-            <i class="fa fa-save fa-lg"></i>
-        </button>
-    </ion-fab>
 </ion-content>

+ 7 - 2
src/pages/leads/leads.html

@@ -4,6 +4,11 @@
             <ion-icon name="menu"></ion-icon>
         </button>
         <ion-title>{{ getTitle() }}</ion-title>
+        <ion-buttons end>
+			<button ion-button icon-only>
+				<i class="fa fa-search fa-lg"></i>
+			</button>
+		</ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>
@@ -32,8 +37,8 @@
     <div class="no-items" *ngIf="!hasItems()">
         <h2>Sin elementos</h2>
     </div>
-    <ion-fab right top edge>
-        <button ion-fab mini (click)="goToDetails()">
+    <ion-fab right bottom>
+        <button ion-fab (click)="goToDetails()">
             <ion-icon name="add"></ion-icon>
         </button>
     </ion-fab>

+ 2 - 2
src/pages/leads/leads.ts

@@ -34,7 +34,7 @@ export class LeadsPage extends BaseListView<Lead>  {
      * 
      */
     ionViewDidEnter() {
-        super.deselectItem();
+        super.unselectItem();
     }
 
       /**
@@ -73,7 +73,7 @@ export class LeadsPage extends BaseListView<Lead>  {
                     text: "Cancel",
                     role: "cancel",
                     handler: () => {
-                        super.deselectItem();
+                        super.unselectItem();
                     }
                 },
             ]

+ 8 - 6
src/pages/order/order.html

@@ -1,6 +1,11 @@
 <ion-header>
     <ion-navbar>
         <ion-title>{{ title }}</ion-title>
+        <ion-buttons end>
+            <button ion-button icon-only type="submit" form="details-form">
+                <i class="fa fa-save fa-lg"></i>
+            </button>
+        </ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>
@@ -12,11 +17,11 @@
         <ion-item-divider color="light">Cliente</ion-item-divider>
     </form>
 
-    <ion-fab right top edge>
-        <button ion-fab mini>
+    <ion-fab right bottom>
+        <button ion-fab>
             <ion-icon name="menu"></ion-icon>
         </button>
-        <ion-fab-list side="bottom">
+        <ion-fab-list side="top">
             <button ion-fab (click)="selectCustomer()">
                 <i class="fa fa-user fa-lg"></i>
             </button>
@@ -26,9 +31,6 @@
             <button ion-fab (click)="selectProduct()">
                 <i class="fa fa-cart-plus fa-lg"></i>
             </button>
-            <button ion-fab color="secondary">
-                <i class="fa fa-save fa-lg"></i>
-            </button>
         </ion-fab-list>
     </ion-fab>
 </ion-content>

+ 7 - 2
src/pages/orders/orders.html

@@ -4,6 +4,11 @@
             <ion-icon name="menu"></ion-icon>
         </button>
         <ion-title>{{ getTitle() }}</ion-title>
+        <ion-buttons end>
+			<button ion-button icon-only>
+				<i class="fa fa-search fa-lg"></i>
+			</button>
+		</ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>
@@ -35,8 +40,8 @@
     <div class="no-items" *ngIf="!hasItems()">
         <h2>Sin elementos</h2>
     </div>
-    <ion-fab right top edge>
-        <button ion-fab mini (click)="goToDetails()">
+    <ion-fab right bottom>
+        <button ion-fab (click)="goToDetails()">
             <ion-icon name="add"></ion-icon>
         </button>
     </ion-fab>

+ 2 - 2
src/pages/orders/orders.ts

@@ -34,7 +34,7 @@ export class OrdersPage extends BaseListView<SaleOrder> {
      * 
      */
     ionViewDidEnter() {
-        super.setSelectedIndex(-1);
+        super.unselectItem();
     }
 
     /**
@@ -73,7 +73,7 @@ export class OrdersPage extends BaseListView<SaleOrder> {
                     text: "Cancel",
                     role: "cancel",
                     handler: () => {
-                        super.setSelectedIndex(-1);
+                        super.unselectItem();
                     }
                 },
             ]

+ 5 - 5
src/pages/phonecall/phonecall.html

@@ -1,12 +1,12 @@
 <ion-header>
     <ion-navbar>
         <ion-title>{{ title }}</ion-title>
+        <ion-buttons end>
+            <button ion-button icon-only type="submit" form="details-form">
+                <i class="fa fa-save fa-lg"></i>
+            </button>
+        </ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>
-    <ion-fab right top edge>
-        <button ion-fab mini type="submit" form="details-form">
-            <i class="fa fa-save fa-lg"></i>
-        </button>
-    </ion-fab>
 </ion-content>

+ 7 - 2
src/pages/phonecalls/phonecalls.html

@@ -4,6 +4,11 @@
             <ion-icon name="menu"></ion-icon>
         </button>
         <ion-title>Llamadas</ion-title>
+        <ion-buttons end>
+			<button ion-button icon-only>
+				<i class="fa fa-search fa-lg"></i>
+			</button>
+		</ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>
@@ -24,8 +29,8 @@
     <div class="no-items" *ngIf="!hasItems()">
         <h2>Sin elementos</h2>
     </div>
-    <ion-fab right top edge>
-        <button ion-fab mini (click)="goToDetails()">
+    <ion-fab right bottom>
+        <button ion-fab (click)="goToDetails()">
             <ion-icon name="add"></ion-icon>
         </button>
     </ion-fab>

+ 2 - 2
src/pages/phonecalls/phonecalls.ts

@@ -31,7 +31,7 @@ export class PhonecallsPage extends BaseListView<Phonecall> {
      * 
      */
     ionViewDidEnter() {
-        super.deselectItem();
+        super.unselectItem();
     }
 
     /**
@@ -63,7 +63,7 @@ export class PhonecallsPage extends BaseListView<Phonecall> {
                     text: "Cancelar",
                     role: "cancel",
                     handler: () => {
-                        super.deselectItem();
+                        super.unselectItem();
                     }
                 },
             ]

+ 16 - 3
src/pages/product/product.html

@@ -1,6 +1,11 @@
 <ion-header>
     <ion-navbar>
         <ion-title>{{ title }}</ion-title>
+        <ion-buttons end>
+            <button ion-button icon-only type="submit" form="details-form" [disabled]="productForm.invalid">
+                <i class="fa fa-save fa-lg"></i>
+            </button>
+        </ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>
@@ -18,9 +23,17 @@
             <ion-input type="text" formControlName="defaultCode"></ion-input>
         </ion-item>
     </form>
-    <ion-fab right top edge>
-        <button ion-fab mini type="submit" form="details-form" [hidden]="productForm.invalid">
-            <i class="fa fa-save fa-lg"></i>
+    <ion-fab right bottom>
+        <button ion-fab>
+            <ion-icon name="menu"></ion-icon>
         </button>
+        <ion-fab-list side="top">
+            <button ion-fab>
+                <i class="fa fa-barcode fa-lg"></i>
+            </button>
+            <button ion-fab>
+                <i class="fa fa-camera fa-lg"></i>
+            </button>
+        </ion-fab-list>
     </ion-fab>
 </ion-content>

+ 7 - 2
src/pages/products/products.html

@@ -4,6 +4,11 @@
             <ion-icon name="menu"></ion-icon>
         </button>
         <ion-title>Productos</ion-title>
+		<ion-buttons end>
+			<button ion-button icon-only>
+				<i class="fa fa-search fa-lg"></i>
+			</button>
+		</ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>
@@ -27,8 +32,8 @@
 	<div class="no-items" *ngIf="!hasItems()">
         <h2>Sin elementos</h2>
     </div>
-	<ion-fab right top edge>
-        <button ion-fab mini (click)="goToDetails()">
+	<ion-fab right bottom>
+        <button ion-fab (click)="goToDetails()">
             <ion-icon name="add"></ion-icon>
         </button>
     </ion-fab>

+ 2 - 2
src/pages/products/products.ts

@@ -31,7 +31,7 @@ export class ProductsPage extends BaseListView<ProductTemplate> {
      * 
      */
     ionViewDidEnter() {
-        super.setSelectedIndex(-1);
+        super.unselectItem();
     }
 
     /**
@@ -63,7 +63,7 @@ export class ProductsPage extends BaseListView<ProductTemplate> {
                     text: "Cancel",
                     role: "cancel",
                     handler: () => {
-                        super.setSelectedIndex(-1);
+                        super.unselectItem();
                     }
                 },
             ]

+ 5 - 0
src/pages/variants/variants.html

@@ -4,6 +4,11 @@
             <ion-icon name="menu"></ion-icon>
         </button>
         <ion-title>Variantes</ion-title>
+		<ion-buttons end>
+			<button ion-button icon-only>
+				<i class="fa fa-search fa-lg"></i>
+			</button>
+		</ion-buttons>
     </ion-navbar>
 </ion-header>
 <ion-content>