I have a SERVICE that emits the ID, TITLE and MESSAGE for a modal, hence using one modal for many faces; polymorphism, actually.
I can SEND the emitted message but getting it to show up in the generic.modal.component.ts is not happening as expected.
Here's my code (abridged version for brevity)
this is a snippet from my component.ts file
// Top of the code
#Output() setAddress: EventEmitter<any> = new EventEmitter();
#Output() setModalID: EventEmitter<any> = new EventEmitter();
#Output() setModalTitle: EventEmitter<any> = new EventEmitter();
#Output() setModalContent: EventEmitter<any> = new EventEmitter();
#Output() messageEvent = new EventEmitter<object>();
message = {
id: '',
title: '',
msg: '',
msgObj: {}
};
// ... some more code...
openModal(id: string, title: string): void {
console.log('Modal will open...' + id);
this.modalService.setID(id);
this.modalService.setTitle(id, title);
this.modalService.setMsg(id, "This is the Example Modal Body");
this.setModalAction(id, title, this.modalService.getMsg());
this.commonService.showHideGenericModal('button');
}
setModalAction(id: string, title: any, msg: any): void {
this.message.id = id;
this.message.title = title;
this.message.msg = msg;
// Send Message to emit
this.sendMessage();
}
// Send Message Object to be listened for
sendMessage(): void {
console.log('Sending Message: ', this.message);
this.messageEvent.emit(this.message);
}
common-svcs.service.ts
import { Injectable, OnInit } from '#angular/core';
import { SessionStorageService } from 'angular-web-storage';
import { Subject } from 'rxjs';
import { ModalService } from './modal.service';
import { VaForm21elementService } from './vaForm21.element.service';
declare global {
interface Window {
initAutocomplete: () => void;
}
}
#Injectable({
providedIn: 'root'
})
export class CommonSvcsService implements OnInit {
modalMsg: string = '';
modalID: string = '';
modalTitle: string = '';
private _messageDetails: Subject<any> = new Subject<any>();
public messageDetailsObs = this._messageDetails.asObservable();
... some more code
constructor(
private sessionStorageService: SessionStorageService,
private modalService: ModalService,
private vaform21Service: VaForm21elementService
) { }
ngOnInit() {
window.initAutocomplete = this.initAutocomplete;
if (this.isGenericModalOpen) {
this.isGenericModalOpen = false;
this.sessionStorageService.set('isGenericModalOpen', this.isGenericModalOpen);
}
}
ngOnDestroy() {
this.sessionStorageService.remove('isGenericModalOpen');
}
ngAfterViewInit() {
if (this.isGenericModalOpen) {
this.isGenericModalOpen = false;
this.sessionStorageService.set('isGenericModalOpen', this.isGenericModalOpen);
}
}
showHideGenericModal(fromwhere: string): void {
switch (fromwhere) {
case "voice":
case "button":
this.openModalDialog();
break;
case "close":
this.closeModalDialog();
break;
}
}
/**
* Open Generic Modal
*/
public openModalDialog(): void {
this.genericModal = document.getElementsByClassName('drag-block')[0] as HTMLElement;
this.isGenericModalOpen = this.sessionStorageService.get('isGenericModalOpen');
if (!this.isGenericModalOpen) {
this.isGenericModalOpen = true;
this.sessionStorageService.set('isGenericModalOpen', this.isGenericModalOpen);
console.log('Inside openGenericModal: ', this.genericModal);
this.id = this.modalService.getID();
this.modalTitle = this.modalService.getTitle();
this.modalMsg = this.modalService.getMsg();
this.genericModal.classList.add('show')
this.isGenericModalOpen = false;
this.sessionStorageService.set('isGenericModalOpen', this.isGenericModalOpen);
} else {
// ('The modal is already opened');
}
}
}
modal.service.ts
export class ModalService {
modals: any[] = [];
modalTitle!: string;
modalMsg!: string;
id!: string;
showModal: boolean = false;
theModal!: HTMLElement;
add(modal: any) {
// add modal to array of active modals
this.modals.push(modal);
}
remove(id: string) {
// remove modal from array of active modals
this.modals = this.modals.filter(x => x.id !== id);
}
open(id: string): boolean {
// open modal specified by id
const modal: any = this.modals.filter(x => x.id === id)[0];
return this.showModal = false;
}
close(id: string): boolean {
// close modal specified by id
const modal: any = this.modals.filter(x => x.id === id)[0];
return this.showModal = true;
}
setTitle(id: string, title: string) {
// Sets the title
const modal: any = this.modals.filter(x => x.id === id)[0];
this.modalTitle = title;
// modal.title = title;
}
setMsg(id: string, msg: string) {
// Sets the title
const modal: any = this.modals.filter(x => x.id === id)[0];
this.modalMsg = msg;
// modal.msg = msg;
}
setID(id: string): void {
this.id = id;
}
getID(): string {
return this.id;
}
// Get Modal Title
getTitle(): string {
return this.modalTitle;
}
// Get Modal Msg/Body
getMsg(): string {
return this.modalMsg;
}
}
Finally, the generic.modal.component.ts and html files respectively
import { Component, OnInit, Input, ViewChild, ElementRef } from '#angular/core';
import { SessionStorageService } from 'angular-web-storage';
import { CommonSvcsService } from 'src/app/services/common-svcs.service';
import { ModalService } from 'src/app/services/modal.service';
#Component({
selector: 'app-mymodal',
templateUrl: './generic-modal.component.html',
styleUrls: ['./generic-modal.component.css']
})
export class GenericModalComponent implements OnInit {
#Input() my_modal_title!: string;
#Input() my_modal_content!: string;
#Input() id: string = '';
#ViewChild('messageEvent') messageAction: any;
showModal: boolean = false;
#ViewChild('el') el: ElementRef | undefined;
private element: any;
private isGenericModalOpen: boolean = false;
private message!: object;
constructor(
private modalService: ModalService,
private sessionStorageService: SessionStorageService,
private commonService: CommonSvcsService
) { }
ngOnInit() {
const modal = this;
this.element = this.el?.nativeElement;
this.modalService.add(this);
if (this.isGenericModalOpen) {
this.isGenericModalOpen = false;
this.sessionStorageService.remove('isGenericModalOpen');
}
}
ngAfterViewInit() {
this.id = this.commonService.modalID;
this.my_modal_title = this.commonService.modalTitle;
this.my_modal_content = this.commonService.modalMsg;
}
ngAfterViewChecked() {
this.commonService.messageDetailsObs.subscribe((messageDetails) => {
console.log('Message Details: ', messageDetails)
});
}
ngOnDestroy() {
this.modalService.remove(this.id);
this.element.remove();
this.sessionStorageService.remove('isGenericModalOpen');
}
// open modal
public open(): void {
// this.element.style.display = 'block';
// document.body.classList.add('modal-open');
this.showModal = this.modalService.open(this.id);
}
// close modal
public close(): void {
// this.element.style.display = 'none';
// document.body.classList.remove('modal-open');
this.showModal = false;
this.commonService.showHideGenericModal('close');
}
// Set Modal Title
public setTitle(title: string): void {
this.my_modal_title = this.modalService.getTitle();
this.modalService.setTitle(this.id, title);
}
// Set Modal Message
public setMsg(msg: string): void {
this.my_modal_content = this.modalService.getMsg();
this.modalService.setMsg(this.id, msg);
}
// Set the ID of the modal dynamically
public setID(id: string): void {
this.id = id;
}
// Get the ID of the modal set
public getID(): string {
return this.id;
}
public receiveMessage($event: any): void {
this.message = $event;
console.log('Received Message: ', this.message);
}
}
The HTML
<div class="modal fade drag-block" (messageAction)="receiveMessage($event)" id="{{id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
aria-hidden="true" [ngClass]="{'show': showModal}">
<div class="vertical-alignment-helper">
<div class="modal-dialog vertical-align-center" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">{{my_modal_title}}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true" (click)="close()">×</span>
</button>
</div>
<div class="modal-body">
{{my_modal_content}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
What I'm doing is dynamically feeding the ID, TITLE and MSG into a modal so I can send into it anything and use only one modal component. I getting is: private message!: object; is UNDEFINED
BTW: the modal OPENS and the buttons and "x" are there. Nothing is in the TITLE, MSG or ID
Thank you
UPDATE:
So, here's my minor issue after #Krenom helped me.
I'm going to reduce this for simplicity:
main-form.component.ts
main-form.component.html <-- this will fire 1 to "n" number of modals
generic-modal.component.ts
generic-modal.component.html <-- this will RECEIVE the msgObj which is outline both in my question and #Krenom's solution.
common-svc.service.ts <-- talks to any component that imports this
All of these components and service live under src/app/
The components are under /components and the services live under /services under app/
Now that that's done.
The Problem
the msgObj gets populated nicely. BUT even with me sending the messageObject via an Observable or the updateModal function that #Krenom created the fact remains, that I need to be able to change the {{title}} and {{msg}} in the generic-modal.component.ts from the common-svc.service.ts when the msgObj changes.
That's all.
I am STILL keeping #Krenoms solution, I probably just am missing something
Here is the full generic-modal.component.ts
import { Component, OnInit, Input, ViewChild, ElementRef, ChangeDetectorRef, ViewRef } from '#angular/core';
import { SessionStorageService } from 'angular-web-storage';
import { CommonSvcsService } from 'src/app/services/common-svcs.service';
import { ModalService } from 'src/app/services/modal.service';
import { GenericModalObj } from '../va-form-21/vaForm21.interface';
#Component({
selector: 'app-mymodal',
templateUrl: './generic-modal.component.html',
styleUrls: ['./generic-modal.component.css']
})
export class GenericModalComponent implements OnInit {
#Input() my_modal_title!: string;
#Input() my_modal_content!: string;
#Input() id: string = '';
#ViewChild('messageEvent') messageAction: any;
showModal: boolean = false;
#Input() public set data(value: any) {
this.message = value;
this.data();
}
#ViewChild('el') el: ElementRef | undefined;
private element: any;
private isGenericModalOpen: boolean = false;
public message!: object;
constructor(
private modalService: ModalService,
private sessionStorageService: SessionStorageService,
private commonService: CommonSvcsService,
private cdref: ChangeDetectorRef
) { }
msgObj!: GenericModalObj;
ngOnInit() {
const modal = this;
this.element = this.el?.nativeElement;
this.modalService.add(this);
if (this.isGenericModalOpen) {
this.isGenericModalOpen = false;
this.sessionStorageService.remove('isGenericModalOpen');
}
this.commonService.messageDetailsObs.subscribe(message => {
if (message !== this.message) {
this.message = message;
this.setMsgObj();
}
});
}
ngAfterViewInit() {
this.id = this.commonService.modalID;
this.my_modal_title = this.commonService.modalTitle;
this.my_modal_content = this.commonService.modalMsg;
}
ngAfterViewChecked() {
this.commonService.messageDetailsObs.subscribe((messageDetails) => {
console.log('Message Details: ', messageDetails)
});
}
ngAfterContentChecked() {
this.cdref.detectChanges();
}
ngOnDestroy() {
this.modalService.remove(this.id);
this.element.remove();
this.sessionStorageService.remove('isGenericModalOpen');
this.cdref.detach();
}
public checkRefDetectChanged(): void {
if(!(<ViewRef> this.cdref).destroyed) {
this.cdref.detectChanges();
}
}
// open modal
public open(): void {
// this.element.style.display = 'block';
// document.body.classList.add('modal-open');
this.showModal = this.modalService.open(this.id);
}
// close modal
public close(): void {
// this.element.style.display = 'none';
// document.body.classList.remove('modal-open');
this.showModal = false;
this.commonService.showHideGenericModal('close');
}
// Set Modal Title
public setTitle(title: string): void {
this.my_modal_title = this.modalService.getTitle();
this.modalService.setTitle(this.id, title);
this.msgObj.title = this.my_modal_title;
this.cdref.detectChanges();
}
// Set Modal Message
public setMsg(msg: string): void {
this.my_modal_content = this.modalService.getMsg();
this.modalService.setMsg(this.id, msg);
this.msgObj.msg = this.my_modal_content;
this.cdref.detectChanges();
}
// Set the ID of the modal dynamically
public setID(id: string): void {
this.id = id;
this.msgObj.id = this.id;
this.cdref.detectChanges();
}
// Get the ID of the modal set
public getID(): string {
return this.id;
}
public receiveMessage(event: any): void {
this.message = event;
console.log('Received Message: ', this.message);
this.my_modal_title = this.commonService.modalTitle;
this.my_modal_content = this.commonService.modalMsg;
this.msgObj.id = this.commonService.id;
this.msgObj.title = this.my_modal_title;
this.msgObj.msg = this.my_modal_content;
this.modalService.updateModal(this.msgObj);
this.cdref.detectChanges();
}
public get data(): any {
return this.msgObj;
}
public setMsgObj(): void {
this.message = this.msgObj;
}
}
Here's the GenericModalObj from the interface file that #Krenom explained
export interface GenericModalObj {
id: string;
title: string;
msg: string;
msgObj: {}
}
Finally, this is my modal.service.ts
export class ModalService {
modals: any[] = [];
modalTitle!: string;
modalMsg!: string;
id!: string;
showModal: boolean = false;
theModal!: HTMLElement;
add(modal: any) {
// add modal to array of active modals
this.modals.push(modal);
}
remove(id: string) {
// remove modal from array of active modals
this.modals = this.modals.filter(x => x.id !== id);
}
open(id: string): boolean {
// open modal specified by id
const modal: any = this.modals.filter(x => x.id === id)[0];
return this.showModal = false;
}
close(id: string): boolean {
// close modal specified by id
const modal: any = this.modals.filter(x => x.id === id)[0];
return this.showModal = true;
}
setTitle(id: string, title: string) {
// Sets the title
const modal: any = this.modals.filter(x => x.id === id)[0];
this.modalTitle = title;
// modal.title = title;
}
setMsg(id: string, msg: string) {
// Sets the title
const modal: any = this.modals.filter(x => x.id === id)[0];
this.modalMsg = msg;
// modal.msg = msg;
}
setID(id: string): void {
this.id = id;
}
getID(): string {
return this.id;
}
// Get Modal Title
getTitle(): string {
return this.modalTitle;
}
// Get Modal Msg/Body
getMsg(): string {
return this.modalMsg;
}
// In the Stackoverflow solution the options is options:
updateModal(options: GenericModalObj): void {
const modal = this.modals.find(x => x.id === options.id);
if (!modal) return;
if (options.title) modal.title = options.title;
if (options.message) modal.message = options.message;
}
}
Here's where I send the modal msgObj from the FORM COMPONENT
setModalAction(id: string, title: any, msg: any): void {
// Send Message to emit
this.messageOBJ.id = id;
this.messageOBJ.title = title;
this.messageOBJ.msg = msg;
this.sendMessage(this.messageOBJ);
}
// Send Message Object to be listened for
sendMessage(msgObj: Object): void {
console.log('Sending Message: ', msgObj);
this.messageEvent.emit(msgObj);
}
and my settings at the top of this component
#Output() setModalID: EventEmitter<any> = new EventEmitter();
#Output() setModalTitle: EventEmitter<any> = new EventEmitter();
#Output() setModalContent: EventEmitter<any> = new EventEmitter();
#Output() messageEvent = new EventEmitter<object>();
and the HTML for generic-modal.component.html
<div class="modal fade drag-block" (messageAction)="receiveMessage($event)" id="genericmodal" tabindex="-1"
role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" [ngClass]="{'show': showModal}">
<div class="vertical-alignment-helper">
<div class="modal-dialog vertical-align-center" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fxModalTitle">{{my_modal_title}}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="close()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body fxModalBody">{{my_modal_content}}</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" (click)="close()">Close</button>
</div>
</div>
</div>
</div>
</div>
Ok, not nearly enough time in a day to try and figure out how exactly that is all hooked up (you should consider refactoring and simplifying), but I suspect it's due to your inputs.
In a typical #Input() usage, you have some values set and ready, and then you show your component. The values are ready and you have them available at ngOnInit() time.
When dealing with opening dialogs, and using services and observables and all kinds of things that make things non-synchronous, it's quite possible that you don't have them at component init time.
The most basic fix is to wrap all your template parts with *ngIf="someData", that way, if they're undefined to start with, nothing will show (and nothing will attempt to use the un-set variable) and you'll get no errors.
Once the value comes in, it'll show and all shall be good.
A slightly more advanced method, for use with the above, because you still need to prevent the template from accessing properties that aren't yet set - is to use setters.
private _data: any;
#Input() public set data(value: any) {
this._data = value;
this.onDataReceived();
}
public get data(): any { return this._data; }
private onDataReceived(): void {
//... anything you might need to do once you get some input - sorting, processing, etc.
}
Typically though, only really need this if you plan on doing something with the value when it comes in, but always handy to know you can do this.
On the topic of refactoring, I think the simplest thing you can do is remove the split of id/title/message. It's tripling the amount of code that's there making it much harder to read and understand.
All three of those things are closely coupled and related to making one of your dialogs work. Keep them together, then. You already do, in your component.ts:
message = {
id: '',
title: '',
msg: '',
msgObj: {}
};
All methods can be reduced to one update method with this one object as a param instead of several, and you only need to handle the update in one fashion.
public updateModal(options: ModalDataDto): void {
const modal = this.modals.find(x => x.id === options.id);
if (!modal) return;
modal.title = options.title;
modal.message = options.message;
}
If you don't keep track of that options object and anything could be making arbitrary guesswork updates to individual properties, then you can do checks before assigning so you don't accidentally overwrite:
public updateModal(options: ModalDataDto): void {
const modal = this.modals.find(x => x.id === options.id);
if (!modal) return;
if (options.title) modal.title = options.title;
if (options.message) modal.message = options.message;
}
That way, anything could update only a part of it:
...
this.service.updateModal({id: 'asdf', title: 'New title'});
...
I notice that you can dynamically re-set the id as well. Not a fan of changing IDs because they're identifiers, and changing them is weird and not generally done, but if you are going to, I'd then suggest an additional property in your new options:
if (options.newId) modal.id = options.newId;
Update for your update:
One thing I can see is, wrong, ish, but mostly pointless:
#Input() public set data(value: any) {
this.message = value;
this.data();
}
It's setting the message, huzzah, but then calling this.data() - which I can't see as a method in there anywhere. I can see a getter, but then usage would be this.data rather than as a method call. And it's just a getter, so that line wouldn't be doing anything.
I'll say the same thing as before - it's still too complicated to follow, especially at this time of the morning, so I'll guess.
It looks like it works this way, from what I can see:
The modals proper in ModalService is a list of your modals, right? Hard to tell without types. I assume by 'modal', we also mean actual GenericModalComponent instances? It looks like it.
You have the method to affect updates against these modals:
updateModal(options: GenericModalObj): void {
const modal = this.modals.find(x => x.id === options.id);
if (!modal) return;
if (options.title) modal.title = options.title;
if (options.message) modal.message = options.message;
}
Where it does modal.title = .... However, I can't see any public properties for title or message in GenericModalComponent.
You have a my_modal_title as an input, and you have setTitle(...) as a method to do such, but you are using neither.
Presumably, if you used if (options.title) modal.setTitle(options.title); it would work.
Though maybe not... given the weird steps that method is doing.
If I'm reading it right, when you call setTitle it does the following:
Assume the service title is currently 'Service Title', and the modal title is currently 'Modal Title' - and we're going to call the modal's setTitle('New Title') method.
Sets its #Input title (my_modal_title ) to whatever value is currently stored in the ModalService - so that is now 'Service Title'.
It calls the ModalService's setTitle method, which finds the modal, ignores it, and sets its own stored title to be the one given it - the service title is now 'New Title'.
It then sets the msgObj title to be the new one - ok, fine, but that msgObj doesn't look like it's used anywhere?
Then does detect changes - ok, fine, but we'll get to that.
So you've got a few compounding structural issues here that need to be sorted out.
Lets roll with change detection to start with, because I don't think you should ever generally need to worry about change detection unless you're doing something specific, which this doesn't feel like it is.
Assume a basic setup:
thing.component.ts
export class ThingComponent {
public valueValue: string;
public refValue: SomeObject;
public arrayValue: SomeObject[];
}
and a basic template:
<div>Value type: {{valueValue}}</div>
<div>Reference type: {{refValue}} - {{refValue.someProperty}}</div>
<div>
Array type: {{arrayValue}}
<ul>
<li *ngFor="let item of arrayValue">{{item.someProperty}}</li>
</ul>
</div>
When filled, you should see something like:
Value type: Whatever value was entered
Reference type: [Object object] - Whatever ref value was entered
Array type: [Object object, maybe Object array or whatever, I don't remember]
- Whatever ref value 1 was entered
- Whatever ref value 2 was entered
Now, any updates valueValue should cause the view to update. Value types are immutable and Angular handles checking these nps.
Any updates to refValue is different, depending on how you use it. Angular won't, generally, be checking the internals of your object, so any change to refValue.someProperty may not actually be picked up. Guaranteed to be picked up is if refValue itself is changed.
If you had a getter, e.g. public getRefValue(): string { return this.refValue.someProperty; } then changes to someProperty should be picked up if that's used in the view instead, e.g. <div>Value type: {{getRefValue}}</div>.
This is because Angular generally hits the getters during its update cycles and at that point, it's just a string, not an object.
For anything referencing that object, however (e.g. any component #Input()s that you pass it in to), they will be looking at the object reference.
In that case, you can cheat and just create a new object (beware of shallow/deep clone issues) by using the shorthand: this.refValue = {...this.refValue}; - now refValue is a copy of itself, but in a new object, so Angular's change detection will re-evaluate it.
This will trigger setter hits to your #Inputs whereas doing this.refValue.someProperty = 'asdf'; wouldn't have.
Arrays are more fun, because they're more or less the same as objects. You can change the contents of an array, but that doesn't change the array object itself. Similar to objects, you can short-hand for copying, e.g. this.arrayValue = [...this.arrayValue]; or, I think you can do this.arrayValue = this.arrayValue.splice() which also returns a copy.
Finally, after that essay, it my intended usage of the GenericModalObj object. It should be used in place of the multiple lines.
You can replace:
public id: string;
public title: string;
public message: string;
with:
public data: GenericModalObj;
And you don't need all those setId, setTitle, setMessage, and reduce down to the one update method that you, sort of, have in place. Also, any method that deals with more than one of those doesn't need multiple params, just the one. It's about reducing the code.
Id, title, message - all of these are closely coupled; you can't have a modal without them all so it suggests that, structurally, they belong together. So put them together in a type, and make use of the typing available with TS.
private doSomething(): string {...}
Tells everyone so much more than
doSomething() {...}
It's private, so I know it's not available to the template (if this is a component), or generally not available/used outside whatever class it's in.
I can see it's going to return a string, so I know it's not just a random processor method that will (possibly) alter the state of something. I mean, it might, but hopefully the naming will suggest that, but ideally it won't (one method does one thing...).
Making proper use of context like this helps to understand and follow what is happening with your code. Never write code that you understand because you wrote it, always write code so that idiots like me can read it. Others in the future, and Future You, will thank you for it.
I was able to finally get the SUBSCRIBE and SEND MESSAGE to work! This solution is a combination of what Krenom did and what I was relentless in doing.
I'm keeping what Krenom has as a solution but I'm expanding on it.
Here's what I did.
I kept the HTML for generic modal
I've modified the Generic Modal Component (SEE BELOW)
I've modified the Common Service (SEE BELOW)
I've modified the call in the FORM component slightly (SEE BELOW)
Generic Modal Component
import { Component, OnInit, Input, ViewChild, ElementRef, ChangeDetectorRef, ViewRef } from '#angular/core';
import { SessionStorageService } from 'angular-web-storage';
import { CommonSvcsService } from 'src/app/services/common-svcs.service';
import { ModalService } from 'src/app/services/modal.service';
import { GenericModalObj } from '../va-form-21/vaForm21.interface';
#Component({
selector: 'app-mymodal',
templateUrl: './generic-modal.component.html',
styleUrls: ['./generic-modal.component.css']
})
export class GenericModalComponent implements OnInit {
#Input() my_modal_title!: string;
#Input() my_modal_content!: string;
#Input() id: string = '';
#ViewChild('messageEvent') messageAction: any;
showModal: boolean = false;
#Input() public set data(value: any) {
this.message = value;
this.data();
}
#ViewChild('el') el: ElementRef | undefined;
private element: any;
private isGenericModalOpen: boolean = false;
public message!: object;
constructor(
private modalService: ModalService,
private sessionStorageService: SessionStorageService,
private commonService: CommonSvcsService,
private cdref: ChangeDetectorRef
) { }
msgObj!: GenericModalObj;
ngOnInit() {
const modal = this;
this.element = this.el?.nativeElement;
this.modalService.add(this);
if (this.my_modal_content === undefined && this.my_modal_title === undefined) {
this.my_modal_content = '';
this.my_modal_title = '';
} else {
this.my_modal_title = this.modalService.getTitle();
this.my_modal_content = this.modalService.getMsg();
}
if (this.isGenericModalOpen) {
this.isGenericModalOpen = false;
this.sessionStorageService.remove('isGenericModalOpen');
}
this.commonService.messageDetailsObs.subscribe(message => {
if (message !== this.message) {
this.message = message;
this.setMsgObj();
}
});
this.checkRefDetectChanged();
}
ngAfterViewInit() {
this.id = this.commonService.modalID;
if (this.my_modal_title && this.my_modal_title.length > 0) {
this.my_modal_title = this.modalService.getTitle();
}
if (this.my_modal_content && this.my_modal_content.length > 0) {
this.my_modal_content = this.modalService.getMsg();
}
this.checkRefDetectChanged();
}
ngAfterViewChecked() {
this.commonService.messageDetailsObs.subscribe((messageDetails) => {
console.log('Message Details: ', messageDetails);
if (this.modalService.id.length > 0 &&
this.modalService.modalTitle.length > 0 &&
this.modalService.modalMsg.length > 0) {
this.msgObj.id = this.modalService.id;
this.my_modal_title = this.modalService.modalTitle;
this.my_modal_content = this.modalService.modalMsg;
}
this.checkRefDetectChanged();
});
}
ngAfterContentChecked() {
if (this.modalService.id.length > 0 &&
this.modalService.modalTitle.length > 0 &&
this.modalService.modalMsg.length > 0) {
this.id = this.modalService.id;
this.my_modal_title = this.modalService.modalTitle;
this.my_modal_content = this.modalService.modalMsg;
}
this.cdref.detectChanges();
}
ngOnDestroy() {
this.modalService.remove(this.id);
this.element.remove();
this.sessionStorageService.remove('isGenericModalOpen');
this.cdref.detach();
this.my_modal_content = '';
this.my_modal_title = '';
this.id = '';
}
public checkRefDetectChanged(): void {
if (!(<ViewRef>this.cdref).destroyed) {
this.cdref.detectChanges();
}
}
// open modal
public open(): void {
// this.element.style.display = 'block';
// document.body.classList.add('modal-open');
this.showModal = this.modalService.open(this.id);
}
// close modal
public close(): void {
// this.element.style.display = 'none';
// document.body.classList.remove('modal-open');
this.showModal = false;
this.commonService.showHideGenericModal('close');
}
// Set Modal Title
public setTitle(title: string): void {
this.my_modal_title = this.modalService.getTitle();
this.modalService.setTitle(this.id, title);
this.msgObj.title = this.my_modal_title;
this.checkRefDetectChanged();
}
// Set Modal Message
public setMsg(msg: string): void {
this.my_modal_content = this.modalService.getMsg();
this.modalService.setMsg(this.id, msg);
this.msgObj.msg = this.my_modal_content;
this.checkRefDetectChanged();
}
// Set the ID of the modal dynamically
public setID(id: string): void {
this.id = id;
this.msgObj.id = this.id;
this.checkRefDetectChanged();
}
// Get the ID of the modal set
public getID(): string {
return this.id;
}
public receiveMessage(event: any): void {
this.message = event;
console.log('Received Message: ', this.message);
this.my_modal_title = this.commonService.modalTitle;
this.my_modal_content = this.commonService.modalMsg;
this.msgObj.id = this.commonService.id;
this.msgObj.title = this.my_modal_title;
this.msgObj.msg = this.my_modal_content;
this.checkRefDetectChanged();
}
public get data(): any {
return this.msgObj;
}
public setMsgObj(): void {
this.message = this.msgObj;
}
}
Common Service
import { Injectable, OnInit } from '#angular/core';
import { SessionStorageService } from 'angular-web-storage';
import { Subject } from 'rxjs';
import { GenericModalObj } from '../components/form/Form.interface';
import { ModalService } from './modal.service';
import { FormelementService } from './Form.element.service';
declare global {
interface Window {
initAutocomplete: () => void;
}
}
#Injectable({
providedIn: 'root'
})
export class CommonSvcsService implements OnInit {
modals: any[] = [];
modalMsg: string = '';
modalID: string = '';
modalTitle: string = '';
private _messageDetails: Subject<any> = new Subject<any>();
public messageDetailsObs = this._messageDetails.asObservable();
startDate!: Date;
endDate!: Date;
currDate!: Date;
vaFormData: any;
showModal: boolean = true;
autocomplete!: google.maps.places.Autocomplete;
address1Field!: HTMLInputElement;
address2Field!: HTMLInputElement;
postalField!: HTMLInputElement;
genericModal!: HTMLElement;
id!: string;
isGenericModalOpen: boolean = false;
constructor(
private sessionStorageService: SessionStorageService,
private modalService: ModalService,
private formService: FormelementService
) { }
ngOnInit() {
window.initAutocomplete = this.initAutocomplete;
if (this.isGenericModalOpen) {
this.isGenericModalOpen = false;
this.sessionStorageService.set('isGenericModalOpen', this.isGenericModalOpen);
}
this.formService.regForSettingModalID(this.regForSettingModalID.bind(this));
this.formService.regForSettingModalTitle(this.regForSettingModalTitle.bind(this));
this.formService.regForSettingModalMsg(this.regForSettingModalMsg.bind(this));
}
ngOnDestroy() {
this.sessionStorageService.remove('isGenericModalOpen');
}
ngAfterViewInit() {
if (this.isGenericModalOpen) {
this.isGenericModalOpen = false;
this.sessionStorageService.set('isGenericModalOpen', this.isGenericModalOpen);
}
}
checkDate(date1: Date, date2: Date): string {
let retVal = "";
this.currDate = new Date();
this.startDate = new Date(date1);
if (this.startDate.getTime() > this.currDate.getTime()) {
retVal = "Your Entry Date cannot be greater than today's Date.";
}
return retVal;
}
initAutocomplete() {
this.address1Field = document.querySelector("#homeaddress") as HTMLInputElement;
this.address2Field = document.querySelector("#hmapt") as HTMLInputElement;
this.postalField = document.querySelector("#hmpostal") as HTMLInputElement;
// Create the autocomplete object, restricting the search predictions to
// addresses in the US and Canada.
this.autocomplete = new google.maps.places.Autocomplete(this.address1Field, {
componentRestrictions: { country: ["us", "ca"] },
fields: ["address_components", "geometry"],
types: ["address"],
});
this.address1Field.focus();
// When the user selects an address from the drop-down, populate the
// address fields in the form.
this.autocomplete.addListener("place_changed", this.fillInAddress);
}
fillInAddress() {
// Get the place details from the autocomplete object.
const place = this.autocomplete.getPlace();
let address1 = "";
let postcode = "";
// Get each component of the address from the place details,
// and then fill-in the corresponding field on the form.
// place.address_components are google.maps.GeocoderAddressComponent objects
// which are documented at http://goo.gle/3l5i5Mr
for (const component of place.address_components as google.maps.GeocoderAddressComponent[]) {
// #ts-ignore remove once typings fixed
const componentType = component.types[0];
switch (componentType) {
case "hmaddy":
case "bzaddy": {
address1 = `${component.long_name} ${address1}`;
break;
}
case "route": {
address1 += component.short_name;
break;
}
case "postal_code": {
postcode = `${component.long_name}${postcode}`;
break;
}
case "postal_code_suffix": {
postcode = `${postcode}-${component.long_name}`;
break;
}
case "hmcity":
case "bzcity":
(document.querySelector("#hmcity") as HTMLInputElement).value =
component.long_name;
break;
case "administrative_area_level_1": {
(document.querySelector("#hmstate") as HTMLInputElement).value =
component.short_name;
break;
}
case "hmcountry":
case "bzcountry":
(document.querySelector("#hmcountry") as HTMLInputElement).value =
component.long_name;
break;
}
}
}
regForCheckDate(checkDateCallback: Function): void {
console.log('Check Date Callback: ', checkDateCallback);
}
regForfillInAddress(checkfillInAddressCallback: Function): void {
console.log('Check fillInAddress Callback: ', checkfillInAddressCallback);
}
regForSettingModalID(checkSettingModalIDCallback: Function): void {
console.log('Check for Modal ID', checkSettingModalIDCallback);
this.formService.regForSettingModalID(checkSettingModalIDCallback);
}
regForSettingModalTitle(checkSettingModalTitleCallback: Function): void {
console.log('Check for Modal Title', checkSettingModalTitleCallback);
this.formService.regForSettingModalTitle(checkSettingModalTitleCallback);
}
regForSettingModalMsg(checkSettingModalMsgCallback: Function): void {
console.log('Check for Modal ID', checkSettingModalMsgCallback);
this.formService.regForSettingModalMsg(checkSettingModalMsgCallback);
}
showHideGenericModal(fromwhere: string): void {
switch (fromwhere) {
case "voice":
case "button":
this.openModalDialog();
break;
case "close":
this.closeModalDialog();
break;
}
}
/**
* Open Generic Modal
*/
public openModalDialog(): void {
this.genericModal = document.getElementsByClassName('drag-block')[0] as HTMLElement;
this.isGenericModalOpen = this.sessionStorageService.get('isGenericModalOpen');
let id = '', title = '', msg = '', msgObj = {};
if (!this.isGenericModalOpen) {
this.isGenericModalOpen = true;
this.sessionStorageService.set('isGenericModalOpen', this.isGenericModalOpen);
console.log('Inside openGenericModal: ', this.genericModal);
this.id = this.modalService.getID();
this.modalTitle = this.modalService.getTitle();
this.modalMsg = this.modalService.getMsg();
this.genericModal.classList.add('show');
this.isGenericModalOpen = false;
id = 'genericmodal';
title = this.modalTitle;
msg = this.modalMsg;
msgObj = this.modalService.getMsgObj();
// Send Message
this.messageDetails({id, title, msg, msgObj});
this.updateModal({
id, title, msg,
msgObj: {}
});
this.sessionStorageService.set('isGenericModalOpen', this.isGenericModalOpen);
} else {
// ('The modal is already opened');
}
}
public closeModalDialog() {
let isGenericModalOpen;
isGenericModalOpen = false;
this.genericModal = document.getElementsByClassName('drag-block')[0] as HTMLElement;
this.sessionStorageService.set('isDebugModalOpen', isGenericModalOpen);
// console.log('Inside closeDebugModal: ', isDebugModalOpen);
this.genericModal.classList.remove('show')
}
public updateModal(options: GenericModalObj): void {
const modal = this.modals.find(x => x.id === options.id);
if (!modal) return;
modal.title = options.title;
modal.message = options.msg;
modal.msgObj = options.msgObj;
}
messageDetails(message: Object): void {
this._messageDetails.next(message);
}
}
Form Component
Snippets from the full code
...
showHideGenericModal(event: any, key: any, showmodal: boolean): void {
this.showModal = showmodal;
if (event.target.id !== 'btnDemoModal1' && event.target.id !== 'btnDemoModal2') {
return;
} else {
this.genericModalDialog = true;
setTimeout(() => {
this.genericModalDialog = false;
}, 5000)
this.openModal(key, "Opening " + key);
}
}
openModal(id: string, title: string): void {
console.log('Opening the ' + id);
let msgObj: Object = {};
this.modalService.setID(id);
this.modalService.setTitle(id, title);
this.modalService.setMsgObj(id, msgObj)
this.modalService.setMsg(id, this.getMsgBody(id));
this.setModalAction(id, title, this.modalService.getMsg(), this.modalService.getMsgObj());
// Make sure we set isGenericModalOpen to FALSE just in case it's still TRUE
this.sessionStorageService.set('isGenericModalOpen', false);
this.commonService.showHideGenericModal('button');
}
closeModal(id: string, modalname: string): void {
console.log('Modal will close...' + id);
this.modalService.id = modalname;
this.modalService.setTitle(id, "");
this.modalService.setMsg(id, "");
this.commonService.showHideGenericModal('close');
}
setModalAction(id: string, title: string, msg: string, msgobj: object): void {
// Send Message to emit
this.messageOBJ.id = id;
this.messageOBJ.title = title;
this.messageOBJ.msg = msg;
this.messageOBJ.msgObj = msgobj;
this.sendMessage(this.messageOBJ);
}
// Send Message Object to be listened for
sendMessage(msgObj: Object): void {
console.log('Sending Message: ', msgObj);
this.messageEvent.emit(msgObj);
}
getMsgBody(id: string): string {
let msgBody = '';
switch (id) {
case "genericmodal1":
msgBody = "This is the body text for modal 1";
break;
case "genericmodal2":
msgBody = "This is the body text for modal 2";
break;
default:
msgBody = "This is the default modal body text!"
break;
}
return msgBody;
}
...
Finally, you can pull the data for the modal body from a JSON object or database and modify your code accordingly.
The ngOnDestroy clears out the MODAL BODY and MODAL TITLE
ngOnDestroy() {
this.modalService.remove(this.id);
this.element.remove();
this.sessionStorageService.remove('isGenericModalOpen');
this.cdref.detach();
this.my_modal_content = '';
this.my_modal_title = '';
this.id = '';
}
NOTE: the this.id is ONLY the ID of the modal body and title NOT the id of the generic modal component. Krenom said not to dynamically change the modal ID and I heeded his warning.
Happy Coding!
I created a custom validation pipe where I want to add some custom logic.I want to know how can I extend it so that I can call it with the below options
Edited:
I have referred the below link of the github file and have developed my own pipe but there still seems to be something missing as its not validating as per the DTO
https://github.com/nestjs/nest/blob/1f6fca5f55e9e51705fa326654760736b254f4e5/packages/common/pipes/validation.pipe.ts#L48
I was able to solve it by creating my own pipe.
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException, Logger, Optional } from '#nestjs/common';
import { ValidatorOptions, ValidationError, isObject, validate } from 'class-validator';
import { plainToClass, classToPlain, ClassTransformOptions } from 'class-transformer';
import { iterate } from 'iterare';
const isUndefined = (obj: any): obj is undefined =>
typeof obj === 'undefined';
const isNil = (val: any): val is null | undefined =>
isUndefined(val) || val === null;
interface ValidationPipeOptions extends ValidatorOptions {
transform?: boolean;
transformOptions?: ClassTransformOptions;
validateCustomDecorators?: boolean;
}
#Injectable()
export class ValidationPipe implements PipeTransform<any> {
private readonly logger = new Logger(ValidationPipe.name);
protected isTransformEnabled: boolean;
protected transformOptions: ClassTransformOptions;
protected validatorOptions: ValidatorOptions;
protected validateCustomDecorators: boolean;
constructor(#Optional() options?: ValidationPipeOptions) {
options = options || {};
const {
transform,
transformOptions,
validateCustomDecorators,
...validatorOptions
} = options;
this.isTransformEnabled = !!transform;
this.transformOptions = transformOptions;
this.validatorOptions = validatorOptions;
this.validateCustomDecorators = validateCustomDecorators || false;
}
async transform(value: any, metadata: ArgumentMetadata) {
const metatype = metadata.metatype;
if (!metatype || !this.toValidate(metadata)) {
return this.isTransformEnabled
? this.transformPrimitive(value, metadata)
: value;
}
const originalValue = value;
value = this.toEmptyIfNil(value);
const isNil = value !== originalValue;
const isPrimitive = this.isPrimitive(value);
this.stripProtoKeys(value);
let object = plainToClass(metatype, value, this.transformOptions);
const originalEntity = object;
const isCtorNotEqual = object.constructor !== metatype;
if (isCtorNotEqual && !isPrimitive) {
object.constructor = metatype;
} else if (isCtorNotEqual) {
// when "entity" is a primitive value, we have to temporarily
// replace the entity to perform the validation against the original
// metatype defined inside the handler
object = { constructor: metatype };
}
const errors = await this.validate(object, this.validatorOptions);
if (errors.length > 0) {
for (let error of errors) {
for (let key in error.constraints) {
this.logger.error(`${error.target.constructor.name}:${error.constraints[key]}`);
}
}
throw new BadRequestException('Invalid Input Parameters');
}
if (isPrimitive) {
// if the value is a primitive value and the validation process has been successfully completed
// we have to revert the original value passed through the pipe
object = originalEntity;
}
if (this.isTransformEnabled) {
return object;
}
if (isNil) {
// if the value was originally undefined or null, revert it back
return originalValue;
}
return Object.keys(this.validatorOptions).length > 0
? classToPlain(object, this.transformOptions)
: value;
}
protected stripProtoKeys(value: Record<string, any>) {
delete value.__proto__;
const keys = Object.keys(value);
iterate(keys)
.filter(key => isObject(value[key]) && value[key])
.forEach(key => this.stripProtoKeys(value[key]));
}
protected isPrimitive(value: unknown): boolean {
return ['number', 'boolean', 'string'].includes(typeof value);
}
protected transformPrimitive(value: any, metadata: ArgumentMetadata) {
if (!metadata.data) {
// leave top-level query/param objects unmodified
return value;
}
const { type, metatype } = metadata;
if (type !== 'param' && type !== 'query') {
return value;
}
if (metatype === Boolean) {
return value === true || value === 'true';
}
if (metatype === Number) {
return +value;
}
return value;
}
protected toEmptyIfNil<T = any, R = any>(value: T): R | {} {
return isNil(value) ? {} : value;
}
private toValidate(metadata: ArgumentMetadata): boolean {
const { metatype, type } = metadata;
if (type === 'custom' && !this.validateCustomDecorators) {
return false;
}
const types = [String, Boolean, Number, Array, Object, Buffer];
return !types.some(t => metatype === t) && !isNil(metatype);
}
protected validate(
object: object,
validatorOptions?: ValidatorOptions,
): Promise<ValidationError[]> | ValidationError[] {
return validate(object, validatorOptions);
}
}
In my cordova project, I need to use native code that scan via scan device on PDA, not by camera. I have working native android project and I changed it to android library project. Then I copied res, src, project.properties and AndroidManifest.xml files into myCordovaPlugin-> src-> android-> LibraryProject. When I build my cordova app in Ripple, there is no error. But when I build in device, I'm getting this error message. "Error 102 cmd: Command failed with exit code 8". I tried some solutions that I found online. But still the error is there.
Please help me. I'm very new to cordova and this is my first applicaton. I'm very appreciate any help. Thank you.
Here is Scanner.java.
package com.customplugin.barcodescanner;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.PluginResult;
import android.util.Log;
public class Scanner extends CordovaPlugin {
public static final int REQUEST_CODE = 1;
private static final String SCAN_INTENT = com.adlink.sample.SDK.SCAN;
private CallbackContext callbackContext;
public Scanner() {
}
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
this.callbackContext = callbackContext;
if (action.equals(SCAN)) {
scan(args);
} else {
return false;
}
return true;
}
}
public void scan(JSONArray args) {
Intent intentScan = new Intent(SCAN_INTENT);
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
// add config as intent extras
if(args.length() > 0) {
JSONObject obj;
JSONArray names;
String key;
Object value;
for(int i=0; i<args.length(); i++) {
try {
obj = args.getJSONObject(i);
} catch(JSONException e) {
Log.i("CordovaLog", e.getLocalizedMessage());
continue;
}
names = obj.names();
for(int j=0; j<names.length(); j++) {
try {
key = names.getString(j);
value = obj.get(key);
if(value instanceof Integer) {
intentScan.putExtra(key, (Integer)value);
} else if(value instanceof String) {
intentScan.putExtra(key, (String)value);
}
} catch(JSONException e) {
Log.i("CordovaLog", e.getLocalizedMessage());
continue;
}
}
}
}
// avoid calling other phonegap apps
intentScan.setPackage(this.cordova.getActivity().getApplicationContext().getPackageName());
this.cordova.startActivityForResult((CordovaPlugin) this, intentScan, REQUEST_CODE);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
JSONObject obj = new JSONObject();
try {
obj.put(TEXT, intent.getStringExtra("SCAN_RESULT"));
obj.put(FORMAT, intent.getStringExtra("SCAN_RESULT_FORMAT"));
obj.put(CANCELLED, false);
} catch (JSONException e) {
Log.d(LOG_TAG, "This should never happen");
}
//this.success(new PluginResult(PluginResult.Status.OK, obj), this.callback);
this.callbackContext.success(obj);
} else if (resultCode == Activity.RESULT_CANCELED) {
JSONObject obj = new JSONObject();
try {
obj.put(TEXT, "");
obj.put(FORMAT, "");
obj.put(CANCELLED, true);
} catch (JSONException e) {
Log.d(LOG_TAG, "This should never happen");
}
//this.success(new PluginResult(PluginResult.Status.OK, obj), this.callback);
this.callbackContext.success(obj);
} else {
//this.error(new PluginResult(PluginResult.Status.ERROR), this.callback);
this.callbackContext.error("Unexpected error");
}
}
}
/**
* Initiates a barcode encode.
*
* #param type Endoiding type.
* #param data The data to encode in the bar code.
*/
public void encode(String type, String data) {
Intent intentEncode = new Intent(ENCODE_INTENT);
intentEncode.putExtra(ENCODE_TYPE, type);
intentEncode.putExtra(ENCODE_DATA, data);
// avoid calling other phonegap apps
intentEncode.setPackage(this.cordova.getActivity().getApplicationContext().getPackageName());
this.cordova.getActivity().startActivity(intentEncode);
}
}
And here is Scanner.js.
cordova.define('cordova/plugin/Scanner', function(require, exports, module) {
var exec = require("cordova/exec");
/**
* Empty constructor
*/
var customScanner = function() {
};
customScanner.prototype.scan=
function (successCallback, errorCallback) {
exec(successCallback,
errorCallback,
"Scanner",
"scan",
[]);
}
module.exports = new customScanner();
});
Here is plugin call from onDeviceReady function of index.js.
var Scanner = cordova.require("cordova/plugin/Scanner");
Scanner.scan();
Here is my Native ScannerActivity.java.
package com.mypackage.sample.SDK;
import com.mypackage.sample.SDK.ENGINE;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.DialogInterface.OnDismissListener;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.widget.RadioButton;
import android.widget.TextView;
public class ScanActivity extends Activity implements OnClickListener
{
private static FakeR fakeR;
private static final String TAG = "ScanActivity";
private static final int DIALOG_WAITTING = 2;
private BroadcastReceiver m_msgReceiver = null;
private DBaseUtil m_db = null;
private SparseArray<Boolean> m_engineSupported = null;
private TextView m_btnScan = null;
private RadioButton m_rbBarcode1D = null;
private RadioButton m_rbBarcodeCCD = null;
private TextView m_scanSymbology = null;
private TextView m_scanSize = null;
private TextView m_scanValue = null;
private TextView m_scanElapse = null;
private boolean m_inProgress = false;
private boolean m_scanStarted = false;
private int m_nextEngine = ADMSG.ENGINE.None;
private ProgressDialog m_dlgProgress = null;
private boolean m_showMoreInfo = false;
private long m_scanStartTime = 0;
private int m_scanMode = ADMSG.SCANMODE.ONETIME;
private int m_scanKeycode = -1;
#Override
public void onCreate(Bundle savedInstanceState)
{
fakeR = new FakeR(this);
super.onCreate(savedInstanceState);
setContentView(fakeR.getId("layout", "scan_main"));
m_db = new DBaseUtil(this);
m_engineSupported = new SparseArray<Boolean>();
decideScanEngine();
boolean Barcode1D_enabled = engineEnabled(ADMSG.ENGINE.Barcode_1D);
boolean BarcodeCCD_enabled = engineEnabled(ADMSG.ENGINE.Barcode_CCD);
Log.d(TAG, "onCreate - Barcode_1D="+Barcode1D_enabled+", Barcode_CCD="+BarcodeCCD_enabled);
if(!Barcode1D_enabled && !BarcodeCCD_enabled)
{
showDialog_noSupportedEngine();
}
m_btnScan = (TextView)findViewById(fakeR.getId("id", "btnScan"));
m_btnScan.setOnClickListener(this);
//m_btnScan.setOnKeyListener(this);
m_rbBarcode1D = setEngineSwitch(fakeR.getId("id", "Reader_Barcode_1D"), Barcode1D_enabled);
m_rbBarcodeCCD = setEngineSwitch(fakeR.getId("id", "Reader_Barcode_CCD"), BarcodeCCD_enabled);
m_scanKeycode = getBgScanKeycode();
//Log.d(TAG, "onCreate - scanKeycode="+m_scanKeycode);
int currentEngine = checkAndCorrectEngine(m_db.getCurrentEngine());
Log.d(TAG, "onCreate - currentEngine="+currentEngine);
switch(currentEngine)
{
case ADMSG.ENGINE.Barcode_1D: m_rbBarcode1D.setChecked(true); break;
case ADMSG.ENGINE.Barcode_CCD: m_rbBarcodeCCD.setChecked(true); break;
}
m_scanSymbology = (TextView)findViewById(fakeR.getId("id", "scan_symbology"));
//m_scanSymbology.setVisibility((m_rbBarcodeCCD.isChecked())?(View.GONE):(View.VISIBLE));
if(m_showMoreInfo)
{
m_scanSize = (TextView)findViewById(fakeR.getId("id", "scan_size"));
m_scanSize.setVisibility(View.VISIBLE);
m_scanElapse = (TextView)findViewById(fakeR.getId("id", "scan_elapse"));
m_scanElapse.setVisibility(View.VISIBLE);
}
m_scanValue = (TextView)findViewById(fakeR.getId("id", "scan_value"));
m_scanValue.setMovementMethod(new ScrollingMovementMethod());
}
#Override
public void onRestart()
{
Log.d(TAG, "onRestart");
super.onRestart();
m_scanSymbology.setText(null);
m_scanValue.setText(null);
if(m_scanSize != null)
{
m_scanSize.setText(null);
}
if(m_scanElapse != null)
{
m_scanElapse.setText(null);
}
}
#Override
public void onResume()
{
Log.d(TAG, "onResume");
super.onResume();
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
if(pref != null)
{
m_scanMode = (pref.getBoolean("_System_ScanMode_Continuous_", false))?(ADMSG.SCANMODE.CONTINUOUS):(ADMSG.SCANMODE.ONETIME);
}
switch(m_db.getCurrentEngine())
{
case ADMSG.ENGINE.Barcode_1D: m_rbBarcode1D.setChecked(true); break;
case ADMSG.ENGINE.Barcode_CCD: m_rbBarcodeCCD.setChecked(true); break;
}
disableBgScanMode();
enableFgScanMode();
registerMessageReceivers();
}
#Override
public void onPause()
{
Log.d(TAG, "onPause");
unregisterMessageReceivers();
disableFgScanMode();
enableBgScanMode();
setScanButtonState(false);
super.onPause();
}
#Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
if(keyCode == m_scanKeycode)
{
onClick(m_btnScan);
return(true);
}
return(super.onKeyUp(keyCode, event));
}
#Override
public void onClick(View view)
{
if(view == m_btnScan)
{
if(m_inProgress)
{
if(m_scanMode == ADMSG.SCANMODE.CONTINUOUS)
{
stopScan();
}
}
else
{
startScan();
}
}
else if(view == m_rbBarcode1D)
{
changeEngine(ADMSG.ENGINE.Barcode_1D);
}
else if(view == m_rbBarcodeCCD)
{
changeEngine(ADMSG.ENGINE.Barcode_CCD);
}
}
private void decideScanEngine()
{
boolean enable_1D = m_db.getEngineSupported(ADMSG.ENGINE.Barcode_1D);
boolean enable_CCD = m_db.getEngineSupported(ADMSG.ENGINE.Barcode_CCD);
m_engineSupported.put(ADMSG.ENGINE.Barcode_1D, enable_1D);
m_engineSupported.put(ADMSG.ENGINE.Barcode_CCD, enable_CCD);
int currentEngine = m_db.getCurrentEngine();
Log.d(TAG, "decideScanEngine - currentEngine="+currentEngine);
if(!isValidEngine(currentEngine))
{
m_db.setCurrentEngine(checkAndCorrectEngine(currentEngine));
}
}
private boolean engineEnabled(int id)
{
return(m_engineSupported.get(id));
}
private boolean isValidEngine(int engine)
{
return((engine == ENGINE.Barcode_1D) || (engine == ENGINE.Barcode_CCD));
}
private int checkAndCorrectEngine(int engine)
{
int validEngine = engine;
/*
if(!isValidEngine(validEngine))
{
validEngine = m_db.getCurrentEngine();
}
*/
if(!isValidEngine(validEngine))
{
if(engineEnabled(ADMSG.ENGINE.Barcode_CCD))
{
validEngine = ADMSG.ENGINE.Barcode_CCD;
}
else if(engineEnabled(ADMSG.ENGINE.Barcode_1D))
{
validEngine = ADMSG.ENGINE.Barcode_1D;
}
else
{
validEngine = ADMSG.ENGINE.None;
}
}
Log.d(TAG, "checkAndCorrectEngine - validEngine="+validEngine);
return(validEngine);
}
private RadioButton setEngineSwitch(int resId, boolean enabled)
{
RadioButton rb = (RadioButton)findViewById(resId);
if(rb != null)
{
rb.setOnClickListener(this);
if(!enabled)
{
rb.setVisibility(View.GONE);
}
}
return(rb);
}
private void registerMessageReceivers()
{
if(m_msgReceiver == null)
{
m_msgReceiver = new MessageReceiver();
}
IntentFilter filter = new IntentFilter();
filter.addAction(ADMSG.ACTION.OBTAIN_SCAN_DATA);
filter.addAction(ADMSG.ACTION.ENGINE_STATE_CHANGED);
registerReceiver(m_msgReceiver, filter);
}
private void unregisterMessageReceivers()
{
if(m_msgReceiver != null)
{
unregisterReceiver(m_msgReceiver);
}
}
private class MessageReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
Log.d(TAG, "onReceive - action="+action);
if(ADMSG.ACTION.OBTAIN_SCAN_DATA.equals(action))
{
String value = intent.getStringExtra(ADMSG.KEY.SCAN.VALUE);
String symbology = intent.getStringExtra(ADMSG.KEY.SCAN.SYMBOLOGY);
boolean finished = intent.getBooleanExtra(ADMSG.KEY.SCAN.FINISHED, true);
Log.d(TAG, "\tvalue="+value);
m_scanSymbology.setText(symbology);
m_scanValue.setText(value);
m_scanValue.scrollTo(0, 0);
if(m_scanSize != null)
{
int dataCnt = (value != null)?(value.length()):(0);
m_scanSize.setText(String.format("%d", dataCnt));
}
if(m_scanElapse != null)
{
long elapse = SystemClock.elapsedRealtime() - m_scanStartTime;
m_scanElapse.setText(String.format("%dms", elapse));
}
setScanButtonState(!finished);
}
else if(ADMSG.ACTION.ENGINE_STATE_CHANGED.equals(action))
{
Bundle extras = intent.getExtras();
if(extras == null)
{
return;
}
int currentEngine = m_db.getCurrentEngine();
int state = extras.getInt(ADMSG.KEY.ENGINE.STATE);
boolean engineChanged = extras.getBoolean(ADMSG.KEY.ENGINE.CHANGED);
Log.d(TAG, "onReceive - ENGINE_STATE_CHANGED - state="+state+", engineChanged="+engineChanged);
if(state == ADMSG.ENGINE.STATE.STARTED)
{
showInitEngineProgress(false);
}
else if((state == ADMSG.ENGINE.STATE.STARTING) ||
(state == ADMSG.ENGINE.STATE.STOPPING))
{
}
else if(state == ADMSG.ENGINE.STATE.STOPPED)
{
Log.d(TAG, "\tSTOPPED - engine: "+currentEngine+" -> "+m_nextEngine);
if((m_nextEngine == currentEngine) ||
!isValidEngine(m_nextEngine))
{
return;
}
m_db.setCurrentEngine(checkAndCorrectEngine(currentEngine));
m_nextEngine = ADMSG.ENGINE.None;
}
}
}
}
private void setScanMode(boolean bgScan, boolean enable)
{
Log.d(TAG, "setScanMode - bgScan="+bgScan+", enable="+enable);
Intent in = new Intent();
in.setAction((bgScan)?(ADMSG.ACTION.BGSCAN_MODE):(ADMSG.ACTION.FGSCAN_MODE));
in.putExtra(ADMSG.KEY.SCAN.COMMAND, (enable)?(ADMSG.CMD.ENABLE):(ADMSG.CMD.DISABLE));
in.putExtra(ADMSG.KEY.REQUESTER.PACKAGE, getPackageName());
in.putExtra(ADMSG.KEY.REQUESTER.CLASS, getClass().getName());
in.putExtra(ADMSG.KEY.SCAN.MODE, m_scanMode);
if(m_scanMode == ADMSG.SCANMODE.CONTINUOUS)
{
in.putExtra(ADMSG.KEY.SCAN.OPT.CS.FIXED_PERIOD, false);
in.putExtra(ADMSG.KEY.SCAN.OPT.CS.IGNORE_SAME_ONE, true);
in.putExtra(ADMSG.KEY.SCAN.OPT.CS.STOP_IF_SUCCESS, true);
in.putExtra(ADMSG.KEY.SCAN.OPT.CS.TIME_INTERVAL, 1500);
}
//in.putExtra(ADMSG.KEY.BGSCAN_KEYCODE, KeyEvent.KEYCODE_ENTER);
sendBroadcast(in);
}
private void enableBgScanMode()
{
setScanMode(true, true);
}
private void disableBgScanMode()
{
setScanMode(true, false);
}
private void enableFgScanMode()
{
setScanMode(false, true);
}
private void disableFgScanMode()
{
setScanMode(false, false);
}
private void startScan()
{
m_scanSymbology.setText(null);
m_scanValue.setText(null);
if(m_scanSize != null)
{
m_scanSize.setText(null);
}
if(m_scanElapse != null)
{
m_scanElapse.setText(null);
}
setScanButtonState(true);
sendScanCommand(this, -1, ADMSG.CMD.START);
m_scanStartTime = SystemClock.elapsedRealtime();
}
private void stopScan()
{
sendScanCommand(this, -1, ADMSG.CMD.STOP);
}
private void changeScanEngine(Context context, int engine)
{
//Log.d(TAG, "chageEngine - engine="+engine);
Intent in = new Intent();
in.setAction(ADMSG.ACTION.ENGINE_CHANGE);
in.putExtra(ADMSG.KEY.ENGINE.ID, engine);
context.sendBroadcast(in);
}
private void sendScanCommand(Context context, int engine, String cmd)
{
//Log.d(TAG, "startScanService - engine="+engine+", cmd="+cmd);
Bundle extras = new Bundle();
extras.putString(ADMSG.KEY.SCAN.COMMAND, cmd);
extras.putInt(ADMSG.KEY.SCAN.ENGINE, engine);
sendScanCommand(context, extras);
}
private void sendScanCommand(Context context, Bundle extras)
{
Intent in = new Intent();
in.setAction(ADMSG.ACTION.SCAN);
in.putExtras(extras);
context.sendBroadcast(in);
}
private void enableScanButton(boolean enabled)
{
//Log.d(TAG, "> enableScanButton - m_scanStarted="+m_scanStarted);
m_inProgress = !enabled;
Resources rcs = getResources();
if(m_scanMode == ADMSG.SCANMODE.CONTINUOUS)
{
int txtRes = (enabled)?(fakeR.getId("string", "txt_Scan")):(fakeR.getId("string", "txt_Stop"));
m_btnScan.setText(txtRes);
}
else
{
int txtColorRes = (enabled)?(fakeR.getId("color", "text_color_tab_checked")):(fakeR.getId("color", "text_color_tab_unchecked"));
m_btnScan.setTextColor(rcs.getColor(txtColorRes));
m_btnScan.setEnabled(enabled);
}
}
private void setScanButtonState(boolean pressed)
{
Log.d(TAG, ">> setScanButtonState - pressed="+pressed);
m_scanStarted = pressed;
enableScanButton(!pressed);
}
private void changeEngine(int engine)
{
int currentEngine = m_db.getCurrentEngine();
Log.d(TAG, "changeEngine - engine: "+currentEngine+" -> "+engine);
if(engine == currentEngine)
{
return;
}
showInitEngineProgress(true);
m_scanSymbology.setText(null);
m_scanValue.setText(null);
if(m_scanSize != null)
{
m_scanSize.setText(null);
}
if(m_scanElapse != null)
{
m_scanElapse.setText(null);
}
changeScanEngine(this, engine);
m_nextEngine = engine;
}
private void showInitEngineProgress(boolean show)
{
if(show)
{
enableScanButton(false);
showDialog(DIALOG_WAITTING);
return;
}
Log.d(TAG, "showInitEngineProgress - m_dlgProgress="+m_dlgProgress);
if(m_dlgProgress != null)
{
m_dlgProgress.dismiss();
//m_dlgProgress = null;
}
if(!m_scanStarted)
{
enableScanButton(true);
}
}
private int getBgScanKeycode()
{
try
{
return(Integer.valueOf(m_db.getBgScanKeycode()));
}
catch(NumberFormatException e)
{
Log.e(TAG, "getBgScanKeycode - "+e.toString());
}
catch(NullPointerException e)
{
Log.e(TAG, "getBgScanKeycode - "+e.toString());
}
return(KeyEvent.KEYCODE_DPAD_CENTER);
}
#Override
protected Dialog onCreateDialog(int id)
{
switch(id)
{
case DIALOG_WAITTING:
{
String msg = getResources().getString(fakeR.getId("string", "txt_Engine_initializing"));
if(m_dlgProgress == null)
{
m_dlgProgress = new ProgressDialog(this);
m_dlgProgress.setIndeterminate(true);
m_dlgProgress.setCancelable(false);
}
m_dlgProgress.setMessage(msg);
return(m_dlgProgress);
}
}
return null;
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(fakeR.getId("menu", "settings_menu"), menu);
MenuItem item = null;
item = menu.getItem(0);
item.setIcon(fakeR.getId("drawable", "ic_menu_preferences"));
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener()
{
public boolean onMenuItemClick(MenuItem item)
{
Intent preferences = new Intent();
preferences.setClass(ScanActivity.this, SettingsActivity.class);
startActivity(preferences);
return(true);
}
});
return(true);
}
private void showDialog_noSupportedEngine()
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
//builder.setTitle(R.string.txt_ResetToDefault);
builder.setMessage(fakeR.getId("string", "txt_NoAnyValidScanEngine"));
builder.setPositiveButton(fakeR.getId("string", "txt_OK"), null);
builder.show().setOnDismissListener(m_dlgResetDismissListener);
}
OnDismissListener m_dlgResetDismissListener = new OnDismissListener()
{
public void onDismiss(DialogInterface dialog)
{
finish();
}
};
}