forked from CIT/Vmeda.Online
		
	MOBILE-3281 search: Add history to search boxes
This commit is contained in:
		
							parent
							
								
									67da1cdeba
								
							
						
					
					
						commit
						17448fe010
					
				| @ -19,6 +19,7 @@ import { TranslateModule } from '@ngx-translate/core'; | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CorePipesModule } from '@pipes/pipes.module'; | ||||
| import { CoreSearchComponentsModule } from '@core/search/components/components.module'; | ||||
| import { AddonMessagesDiscussionsComponent } from '../components/discussions/discussions'; | ||||
| import { AddonMessagesConfirmedContactsComponent } from '../components/confirmed-contacts/confirmed-contacts'; | ||||
| import { AddonMessagesContactRequestsComponent } from '../components/contact-requests/contact-requests'; | ||||
| @ -37,7 +38,8 @@ import { AddonMessagesContactsComponent } from '../components/contacts/contacts' | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CorePipesModule | ||||
|         CorePipesModule, | ||||
|         CoreSearchComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|     ], | ||||
|  | ||||
| @ -19,6 +19,7 @@ import { AddonMessagesSearchPage } from './search'; | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CorePipesModule } from '@pipes/pipes.module'; | ||||
| import { CoreSearchComponentsModule } from '@core/search/components/components.module'; | ||||
| import { AddonMessagesComponentsModule } from '../../components/components.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
| @ -29,9 +30,10 @@ import { AddonMessagesComponentsModule } from '../../components/components.modul | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CorePipesModule, | ||||
|         CoreSearchComponentsModule, | ||||
|         AddonMessagesComponentsModule, | ||||
|         IonicPageModule.forChild(AddonMessagesSearchPage), | ||||
|         TranslateModule.forChild() | ||||
|         TranslateModule.forChild(), | ||||
|     ], | ||||
| }) | ||||
| export class AddonMessagesSearchPageModule {} | ||||
|  | ||||
| @ -20,6 +20,7 @@ import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CorePipesModule } from '@pipes/pipes.module'; | ||||
| import { CoreCourseComponentsModule } from '@core/course/components/components.module'; | ||||
| import { CoreSearchComponentsModule } from '@core/search/components/components.module'; | ||||
| import { AddonModGlossaryIndexComponent } from './index/index'; | ||||
| import { AddonModGlossaryModePickerPopoverComponent } from './mode-picker/mode-picker'; | ||||
| 
 | ||||
| @ -35,7 +36,8 @@ import { AddonModGlossaryModePickerPopoverComponent } from './mode-picker/mode-p | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CorePipesModule, | ||||
|         CoreCourseComponentsModule | ||||
|         CoreCourseComponentsModule, | ||||
|         CoreSearchComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|     ], | ||||
|  | ||||
| @ -87,6 +87,7 @@ import { CoreRatingModule } from '@core/rating/rating.module'; | ||||
| import { CoreTagModule } from '@core/tag/tag.module'; | ||||
| import { CoreFilterModule } from '@core/filter/filter.module'; | ||||
| import { CoreH5PModule } from '@core/h5p/h5p.module'; | ||||
| import { CoreSearchModule } from '@core/search/search.module'; | ||||
| 
 | ||||
| // Addon modules.
 | ||||
| import { AddonBadgesModule } from '@addon/badges/badges.module'; | ||||
| @ -235,6 +236,7 @@ export const WP_PROVIDER: any = null; | ||||
|         CoreTagModule, | ||||
|         CoreFilterModule, | ||||
|         CoreH5PModule, | ||||
|         CoreSearchModule, | ||||
|         AddonBadgesModule, | ||||
|         AddonBlogModule, | ||||
|         AddonCalendarModule, | ||||
|  | ||||
| @ -112,13 +112,15 @@ ion-app.app-root { | ||||
|     @include core-selected-item($core-splitview-selected); | ||||
|   } | ||||
| 
 | ||||
|   // Recover borders on items inside cards. | ||||
|   // Recover borders on items inside cards and lists. | ||||
|   .card.with-borders .core-as-item, | ||||
|   .list.with-borders .core-as-item, | ||||
|   .core-as-item { | ||||
|     @include core-as-items(); | ||||
|   } | ||||
| 
 | ||||
|   .card.with-borders .item { | ||||
|   .card.with-borders .item, | ||||
|   .list.with-borders .item { | ||||
|     @include core-items(); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -25,7 +25,6 @@ import { CoreSplitViewComponent } from './split-view/split-view'; | ||||
| import { CoreIframeComponent } from './iframe/iframe'; | ||||
| import { CoreProgressBarComponent } from './progress-bar/progress-bar'; | ||||
| import { CoreEmptyBoxComponent } from './empty-box/empty-box'; | ||||
| import { CoreSearchBoxComponent } from './search-box/search-box'; | ||||
| import { CoreFileComponent } from './file/file'; | ||||
| import { CoreFilesComponent } from './files/files'; | ||||
| import { CoreIconComponent } from './icon/icon'; | ||||
| @ -66,7 +65,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; | ||||
|         CoreIframeComponent, | ||||
|         CoreProgressBarComponent, | ||||
|         CoreEmptyBoxComponent, | ||||
|         CoreSearchBoxComponent, | ||||
|         CoreFileComponent, | ||||
|         CoreFilesComponent, | ||||
|         CoreIconComponent, | ||||
| @ -118,7 +116,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; | ||||
|         CoreIframeComponent, | ||||
|         CoreProgressBarComponent, | ||||
|         CoreEmptyBoxComponent, | ||||
|         CoreSearchBoxComponent, | ||||
|         CoreFileComponent, | ||||
|         CoreFilesComponent, | ||||
|         CoreIconComponent, | ||||
|  | ||||
| @ -1,88 +0,0 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| 
 | ||||
| /** | ||||
|  * Component to display a "search box". | ||||
|  * | ||||
|  * @description | ||||
|  * This component will display a standalone search box with its search button in order to have a better UX. | ||||
|  * | ||||
|  * Example usage: | ||||
|  * <core-search-box (onSubmit)="search($event)" [placeholder]="'core.courses.search' | translate" | ||||
|  *     [searchLabel]="'core.courses.search' | translate" autoFocus="true"></core-search-box> | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'core-search-box', | ||||
|     templateUrl: 'core-search-box.html' | ||||
| }) | ||||
| export class CoreSearchBoxComponent implements OnInit { | ||||
|     @Input() searchLabel?: string; // Label to be used on action button.
 | ||||
|     @Input() placeholder?: string; // Placeholder text for search text input.
 | ||||
|     @Input() autocorrect = 'on'; // Enables/disable Autocorrection on search text input.
 | ||||
|     @Input() spellcheck?: string | boolean = true; // Enables/disable Spellchecker on search text input.
 | ||||
|     @Input() autoFocus?: string | boolean; // Enables/disable Autofocus when entering view.
 | ||||
|     @Input() lengthCheck = 3; // Check value length before submit. If 0, any string will be submitted.
 | ||||
|     @Input() showClear = true; // Show/hide clear button.
 | ||||
|     @Input() disabled = false; // Disables the input text.
 | ||||
|     @Input() initialSearch: string; // Initial search text.
 | ||||
|     @Output() onSubmit: EventEmitter<string>; // Send data when submitting the search form.
 | ||||
|     @Output() onClear: EventEmitter<void>; // Send event when clearing the search form.
 | ||||
| 
 | ||||
|     searched = false; | ||||
|     searchText = ''; | ||||
| 
 | ||||
|     constructor(private translate: TranslateService, private utils: CoreUtilsProvider) { | ||||
|         this.onSubmit = new EventEmitter<string>(); | ||||
|         this.onClear = new EventEmitter<void>(); | ||||
|     } | ||||
| 
 | ||||
|     ngOnInit(): void { | ||||
|         this.searchLabel = this.searchLabel || this.translate.instant('core.search'); | ||||
|         this.placeholder = this.placeholder || this.translate.instant('core.search'); | ||||
|         this.spellcheck = this.utils.isTrueOrOne(this.spellcheck); | ||||
|         this.showClear = this.utils.isTrueOrOne(this.showClear); | ||||
|         this.searchText = this.initialSearch || ''; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Form submitted. | ||||
|      * | ||||
|      * @param e Event. | ||||
|      */ | ||||
|     submitForm(e: Event): void { | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
|         if (this.searchText.length < this.lengthCheck) { | ||||
|             // The view should handle this case, but we check it here too just in case.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.searched = true; | ||||
|         this.onSubmit.emit(this.searchText); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Form submitted. | ||||
|      */ | ||||
|     clearForm(): void { | ||||
|         this.searched = false; | ||||
|         this.searchText = ''; | ||||
|         this.onClear.emit(); | ||||
|     } | ||||
| } | ||||
| @ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; | ||||
| import { CoreCoursesSearchPage } from './search'; | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CoreSearchComponentsModule } from '@core/search/components/components.module'; | ||||
| import { CoreCoursesComponentsModule } from '../../components/components.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
| @ -28,8 +29,9 @@ import { CoreCoursesComponentsModule } from '../../components/components.module' | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CoreCoursesComponentsModule, | ||||
|         CoreSearchComponentsModule, | ||||
|         IonicPageModule.forChild(CoreCoursesSearchPage), | ||||
|         TranslateModule.forChild() | ||||
|         TranslateModule.forChild(), | ||||
|     ], | ||||
| }) | ||||
| export class CoreCoursesSearchPageModule {} | ||||
|  | ||||
							
								
								
									
										43
									
								
								src/core/search/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/core/search/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | ||||
| import { IonicModule } from 'ionic-angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| import { CoreSearchBoxComponent } from './search-box/search-box'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|         CoreSearchBoxComponent, | ||||
|     ], | ||||
|     imports: [ | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         CoreDirectivesModule, | ||||
|         CoreComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|     ], | ||||
|     exports: [ | ||||
|         CoreSearchBoxComponent, | ||||
|     ], | ||||
|     entryComponents: [ | ||||
|         CoreSearchBoxComponent, | ||||
|     ] | ||||
| }) | ||||
| export class CoreSearchComponentsModule {} | ||||
| @ -1,13 +1,21 @@ | ||||
| <ion-card> | ||||
|     <form #f="ngForm" (ngSubmit)="submitForm($event)" role="search"> | ||||
|         <ion-item> | ||||
|             <ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox"></ion-input> | ||||
|             <ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox" (focus)="showHistory()" (blur)="hideHistory()"></ion-input> | ||||
|             <button item-end ion-button clear icon-only type="submit" class="button-small" [attr.aria-label]="searchLabel" [disabled]="disabled || !searchText || (searchText.length < lengthCheck)"> | ||||
|                 <ion-icon name="search"></ion-icon> | ||||
|             </button> | ||||
|             <button *ngIf="showClear" item-end ion-button clear icon-only class="button-small" [attr.aria-label]="'core.clearsearch' | translate" [disabled]="!searched || disabled" (click)="clearForm()"> | ||||
|             <button *ngIf="showClear" item-end ion-button clear icon-only class="button-small" [attr.aria-label]="'core.clearsearch' | translate" [disabled]="searched == '' || disabled" (click)="clearForm()"> | ||||
|                 <ion-icon name="close"></ion-icon> | ||||
|             </button> | ||||
|         </ion-item> | ||||
|         <div class="core-search-history" *ngIf="historyShown && history.length"> | ||||
|             <ion-list class="with-borders"> | ||||
|                 <ion-item text-wrap *ngFor="let item of history" (mousedown)="itemMouseDown($event, item.searchedtext)" (mouseup)="itemMouseUp($event, item.searchedtext)" class="core-clickable"> | ||||
|                     <core-icon name="fa-history" item-start></core-icon> | ||||
|                     {{item.searchedtext}} | ||||
|                 </ion-item> | ||||
|             </ion-list> | ||||
|         </div> | ||||
|     </form> | ||||
| </ion-card> | ||||
| @ -1,4 +1,12 @@ | ||||
| ion-app.app-root core-search-box { | ||||
|     height: 65px; | ||||
|     display: block; | ||||
| 
 | ||||
|     ion-card { | ||||
|         position: absolute; | ||||
|         z-index: 4; | ||||
|     } | ||||
| 
 | ||||
|     .button.item-button[icon-only] { | ||||
|         margin: 0; | ||||
|         padding: ($content-padding / 2) $content-padding; | ||||
| @ -18,4 +26,17 @@ ion-app.app-root core-search-box { | ||||
|         border: 0; | ||||
|         margin: 0; | ||||
|     } | ||||
| 
 | ||||
|     .core-search-history { | ||||
|         max-height: calc(-120px + 80vh); | ||||
|         overflow-y: auto; | ||||
| 
 | ||||
|         .item:hover { | ||||
|             background-color: $gray-lighter; | ||||
|         } | ||||
| 
 | ||||
|         .list .item.item-block:last-child > .item-inner { | ||||
|             border-bottom: 0; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										211
									
								
								src/core/search/components/search-box/search-box.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								src/core/search/components/search-box/search-box.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,211 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Component, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreSearchHistoryProvider, CoreSearchHistoryItem } from '../../providers/search-history'; | ||||
| 
 | ||||
| /** | ||||
|  * Component to display a "search box". | ||||
|  * | ||||
|  * @description | ||||
|  * This component will display a standalone search box with its search button in order to have a better UX. | ||||
|  * | ||||
|  * Example usage: | ||||
|  * <core-search-box (onSubmit)="search($event)" [placeholder]="'core.courses.search' | translate" | ||||
|  *     [searchLabel]="'core.courses.search' | translate" autoFocus="true"></core-search-box> | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'core-search-box', | ||||
|     templateUrl: 'core-search-box.html' | ||||
| }) | ||||
| export class CoreSearchBoxComponent implements OnInit, OnDestroy { | ||||
|     @Input() searchLabel?: string; // Label to be used on action button.
 | ||||
|     @Input() placeholder?: string; // Placeholder text for search text input.
 | ||||
|     @Input() autocorrect = 'on'; // Enables/disable Autocorrection on search text input.
 | ||||
|     @Input() spellcheck?: string | boolean = true; // Enables/disable Spellchecker on search text input.
 | ||||
|     @Input() autoFocus?: string | boolean; // Enables/disable Autofocus when entering view.
 | ||||
|     @Input() lengthCheck = 3; // Check value length before submit. If 0, any string will be submitted.
 | ||||
|     @Input() showClear = true; // Show/hide clear button.
 | ||||
|     @Input() disabled = false; // Disables the input text.
 | ||||
|     @Input() protected initialSearch: string; // Initial search text.
 | ||||
|     @Input() protected searchArea?: string; // If provided. It will save and display a history of searches for this particular Id.
 | ||||
|                                   // To use different history lists, place different Id.
 | ||||
|                                   // I.e. AddonMessagesContacts or CoreUserParticipants-6 (using the course Id).
 | ||||
|     @Output() onSubmit: EventEmitter<string>; // Send data when submitting the search form.
 | ||||
|     @Output() onClear: EventEmitter<void>; // Send event when clearing the search form.
 | ||||
| 
 | ||||
|     searched = ''; // Last search emitted.
 | ||||
|     searchText = ''; | ||||
|     history: CoreSearchHistoryItem[]; | ||||
|     historyShown = false; | ||||
| 
 | ||||
|     protected elementClicked = ''; | ||||
|     protected backdropMouseUpFunc; | ||||
| 
 | ||||
|     constructor(protected translate: TranslateService, | ||||
|             protected utils: CoreUtilsProvider, | ||||
|             protected searchHistoryProvider: CoreSearchHistoryProvider, | ||||
|     ) { | ||||
|         this.onSubmit = new EventEmitter<string>(); | ||||
|         this.onClear = new EventEmitter<void>(); | ||||
|     } | ||||
| 
 | ||||
|     ngOnInit(): void { | ||||
|         this.searchLabel = this.searchLabel || this.translate.instant('core.search'); | ||||
|         this.placeholder = this.placeholder || this.translate.instant('core.search'); | ||||
|         this.spellcheck = this.utils.isTrueOrOne(this.spellcheck); | ||||
|         this.showClear = this.utils.isTrueOrOne(this.showClear); | ||||
|         this.searchText = this.initialSearch || ''; | ||||
| 
 | ||||
|         if (this.searchArea) { | ||||
|             this.loadHistory(); | ||||
| 
 | ||||
|             this.backdropMouseUpFunc = this.backdropMouseUp.bind(this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Form submitted. | ||||
|      * | ||||
|      * @param e Event. | ||||
|      */ | ||||
|     submitForm(e?: Event): void { | ||||
|         e && e.preventDefault(); | ||||
|         e && e.stopPropagation(); | ||||
| 
 | ||||
|         if (this.searchText.length < this.lengthCheck) { | ||||
|             // The view should handle this case, but we check it here too just in case.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (this.searchArea) { | ||||
|             this.saveSearchToHistory(this.searchText); | ||||
|         } | ||||
| 
 | ||||
|         this.searched = this.searchText; | ||||
|         this.onSubmit.emit(this.searchText); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Saves the search term onto the history. | ||||
|      * | ||||
|      * @param text Text to save. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async saveSearchToHistory(text: string): Promise<void> { | ||||
|         try { | ||||
|             await this.searchHistoryProvider.insertOrUpdateSearchText(this.searchArea, text); | ||||
|         } finally { | ||||
|             this.loadHistory(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Loads search history. | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async loadHistory(): Promise<void> { | ||||
|         this.history = await this.searchHistoryProvider.getSearchHistory(this.searchArea); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show search history. | ||||
|      */ | ||||
|     showHistory(): void { | ||||
|         if (this.searchArea) { | ||||
|             this.historyShown = true; | ||||
|             this.elementClicked = ''; | ||||
|             document.body.addEventListener('mouseup', this.backdropMouseUpFunc); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Hide search history. | ||||
|      * | ||||
|      * @param force: If force hidding the history without checking the clicked element. | ||||
|      */ | ||||
|     hideHistory(force: boolean = false): void { | ||||
|         if (this.searchArea && (force || this.elementClicked == '')) { | ||||
|             this.historyShown = false; | ||||
|             this.elementClicked = ''; | ||||
| 
 | ||||
|             document.body.removeEventListener('mouseup', this.backdropMouseUpFunc); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Saves the item where you moused down. | ||||
|      * It uses mouseup/down because blur will block the click event. | ||||
|      * | ||||
|      * @param e Event. | ||||
|      * @param text Selected text. | ||||
|      */ | ||||
|     itemMouseDown(e: Event, text: string): void { | ||||
|         this.elementClicked = text; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Select an item and use it for search text. | ||||
|      * It uses mouseup/down because blur will block the click event. | ||||
|      * | ||||
|      * @param e Event. | ||||
|      * @param text Selected text. | ||||
|      */ | ||||
|     itemMouseUp(e: Event, text: string): void { | ||||
|         if (this.elementClicked == text) { | ||||
|             this.hideHistory(true); | ||||
|             if (this.searched != text) { | ||||
|                 this.searchText = text; | ||||
|                 this.submitForm(e); | ||||
|             } | ||||
|         } | ||||
|         this.elementClicked = ''; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Manages mouseup out of the history. | ||||
|      * | ||||
|      * @param e Event. | ||||
|      */ | ||||
|     backdropMouseUp(e: Event): void { | ||||
|         // Do not hide if the search box is focused.
 | ||||
|         if (document.activeElement['type'] && document.activeElement['type'] == 'search') { | ||||
|             return; | ||||
|         } | ||||
|         this.hideHistory(); | ||||
|         this.elementClicked = ''; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Form submitted. | ||||
|      */ | ||||
|     clearForm(): void { | ||||
|         this.searched = ''; | ||||
|         this.searchText = ''; | ||||
|         this.hideHistory(true); | ||||
|         this.onClear.emit(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * On destroy of the component, clear up any listeners. | ||||
|      */ | ||||
|     ngOnDestroy(): void { | ||||
|         if (this.backdropMouseUpFunc) { | ||||
|             document.body.removeEventListener('mouseup', this.backdropMouseUpFunc); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										176
									
								
								src/core/search/providers/search-history.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								src/core/search/providers/search-history.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,176 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; | ||||
| import { SQLiteDB } from '@classes/sqlitedb'; | ||||
| 
 | ||||
| /** | ||||
|  * Search history item definition. | ||||
|  */ | ||||
| export interface CoreSearchHistoryItem { | ||||
|     searcharea: string; // Search area where the search has been performed.
 | ||||
|     lastused?: number; // Timestamp of the last search.
 | ||||
|     searchedtext: string; // Text of the performed search.
 | ||||
|     times?: number; // Times search has been performed (if previously in history).
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Service that enables adding a history to a search box. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class CoreSearchHistoryProvider { | ||||
| 
 | ||||
|     protected static HISTORY_TABLE = 'seach_history'; | ||||
|     protected static HISTORY_LIMIT = 10; | ||||
| 
 | ||||
|     protected siteSchema: CoreSiteSchema = { | ||||
|         name: 'CoreSearchHistoryProvider', | ||||
|         version: 1, | ||||
|         tables: [ | ||||
|             { | ||||
|                 name: CoreSearchHistoryProvider.HISTORY_TABLE, | ||||
|                 columns: [ | ||||
|                     { | ||||
|                         name: 'searcharea', | ||||
|                         type: 'TEXT', | ||||
|                         notNull: true, | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'lastused', | ||||
|                         type: 'INTEGER', | ||||
|                         notNull: true, | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'times', | ||||
|                         type: 'INTEGER', | ||||
|                         notNull: true, | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'searchedtext', | ||||
|                         type: 'TEXT', | ||||
|                         notNull: true, | ||||
|                     }, | ||||
|                 ], | ||||
|                 primaryKeys: ['searcharea', 'searchedtext'], | ||||
|             }, | ||||
|         ], | ||||
|     }; | ||||
| 
 | ||||
|     constructor(protected sitesProvider: CoreSitesProvider) { | ||||
| 
 | ||||
|         this.sitesProvider.registerSiteSchema(this.siteSchema); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a search area history sorted by use. | ||||
|      * | ||||
|      * @param searchArea Search Area Name. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the list of items when done. | ||||
|      */ | ||||
|     async getSearchHistory(searchArea: string, siteId?: string): Promise<CoreSearchHistoryItem[]> { | ||||
|         const site = await this.sitesProvider.getSite(siteId); | ||||
|         const conditions: any = { | ||||
|             searcharea: searchArea, | ||||
|         }; | ||||
| 
 | ||||
|         const history = await site.getDb().getRecords(CoreSearchHistoryProvider.HISTORY_TABLE, conditions); | ||||
| 
 | ||||
|         // Sorting by last used DESC.
 | ||||
|         return history.sort((a, b) => { | ||||
|             return b.lastused - a.lastused; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Controls search limit and removes the last item if overflows. | ||||
|      * | ||||
|      * @param  searchArea Search area to control | ||||
|      * @param  db SQLite DB where to perform the search. | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     protected async controlSearchLimit(searchArea: string, db: SQLiteDB): Promise<void> { | ||||
|         const items = await this.getSearchHistory(searchArea); | ||||
|         if (items.length > CoreSearchHistoryProvider.HISTORY_LIMIT) { | ||||
|             // Over the limit. Remove the last.
 | ||||
|             const lastItem = items.pop(); | ||||
| 
 | ||||
|             const searchItem: CoreSearchHistoryItem = { | ||||
|                 searcharea: lastItem.searcharea, | ||||
|                 searchedtext: lastItem.searchedtext, | ||||
|             }; | ||||
| 
 | ||||
|             await db.deleteRecords(CoreSearchHistoryProvider.HISTORY_TABLE, searchItem); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Updates the search history item if exists. | ||||
|      * | ||||
|      * @param  searchArea Area where the search has been performed. | ||||
|      * @param  text Text of the performed text. | ||||
|      * @param  db SQLite DB where to perform the search. | ||||
|      * @return True if exists, false otherwise. | ||||
|      */ | ||||
|     protected async updateExistingItem(searchArea: string, text: string, db: SQLiteDB): Promise<boolean> { | ||||
|         const searchItem: CoreSearchHistoryItem = { | ||||
|             searcharea: searchArea, | ||||
|             searchedtext: text, | ||||
|         }; | ||||
| 
 | ||||
|         try { | ||||
|             const existingItem = await db.getRecord(CoreSearchHistoryProvider.HISTORY_TABLE, searchItem); | ||||
| 
 | ||||
|             // If item exist, update time and number of times searched.
 | ||||
|             existingItem.lastused = Date.now(); | ||||
|             existingItem.times++; | ||||
| 
 | ||||
|             await db.updateRecords(CoreSearchHistoryProvider.HISTORY_TABLE, existingItem, searchItem); | ||||
| 
 | ||||
|             return true; | ||||
|         } catch (e) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Inserts a searched term on the history. | ||||
|      * | ||||
|      * @param  searchArea Area where the search has been performed. | ||||
|      * @param  text Text of the performed text. | ||||
|      * @param  siteId Site ID. If not defined, current site. | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     async insertOrUpdateSearchText(searchArea: string, text: string, siteId?: string): Promise<void> { | ||||
|         const site = await this.sitesProvider.getSite(siteId); | ||||
|         const db = site.getDb(); | ||||
| 
 | ||||
|         const exists = await this.updateExistingItem(searchArea, text, db); | ||||
| 
 | ||||
|         if (!exists) { | ||||
|             // If item is new, control the history does not goes over the limit.
 | ||||
|             const searchItem: CoreSearchHistoryItem = { | ||||
|                 searcharea: searchArea, | ||||
|                 searchedtext: text, | ||||
|                 lastused: Date.now(), | ||||
|                 times: 1, | ||||
|             }; | ||||
| 
 | ||||
|             await site.getDb().insertRecord(CoreSearchHistoryProvider.HISTORY_TABLE, searchItem); | ||||
| 
 | ||||
|             await this.controlSearchLimit(searchArea, db); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/core/search/search.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/core/search/search.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CoreSearchComponentsModule } from './components/components.module'; | ||||
| import { CoreSearchHistoryProvider } from './providers/search-history'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|     ], | ||||
|     imports: [ | ||||
|         CoreSearchComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|         CoreSearchComponentsModule, | ||||
|         CoreSearchHistoryProvider, | ||||
|     ], | ||||
| }) | ||||
| export class CoreSearchModule {} | ||||
| @ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; | ||||
| import { CoreTagSearchPage } from './search'; | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CoreSearchComponentsModule } from '@core/search/components/components.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
| @ -26,6 +27,7 @@ import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
|     imports: [ | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CoreSearchComponentsModule, | ||||
|         IonicPageModule.forChild(CoreTagSearchPage), | ||||
|         TranslateModule.forChild() | ||||
|     ], | ||||
|  | ||||
| @ -22,6 +22,7 @@ import { CoreUserTagAreaComponent } from './tag-area/tag-area'; | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CorePipesModule } from '@pipes/pipes.module'; | ||||
| import { CoreSearchComponentsModule } from '@core/search/components/components.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
| @ -35,7 +36,8 @@ import { CorePipesModule } from '@pipes/pipes.module'; | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CorePipesModule | ||||
|         CorePipesModule, | ||||
|         CoreSearchComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|     ], | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user