NativeScript JavaScript modal dialog fails with ViewHierarchy error on iOS - nativescript

I have a relatively simple modal dialog within a much larger NativeScript JavaScript app which when launched fails with the error message,
CONSOLE ERROR file:///app/tns_modules/tns-core-modules/trace/trace.js:166:30: ViewHierarchy: Parent is already presenting view controller. Close the current modal page before showing another one!
This works fine on Android but fails consistently on iOS. The code follows the sample at https://docs.nativescript.org/ui/modal-view. tns doctor shows no errors and all code up to date. What's stumping me here is figuring out what I can change, as this is the only modal dialog in the entire app.
I'd welcome any ideas.
Edit:
I tried a similar but older app and the same modal dialog works as expected there. But, it's running 4.2.0 versions of core modules and iOS.
✔ Component nativescript has 5.3.2 version and is up to date.
⚠ Update available for component tns-core-modules. Your current version is 4.2.0 and the latest available version is 5.3.1.
⚠ Update available for component tns-android. Your current version is 4.2.0 and the latest available version is 5.3.1.
⚠ Update available for component tns-ios. Your current version is 4.2.0 and the latest available version is 5.3.1.
Here's the modal dialog code in question:
/**
* Open Modal dialog to select phone number when multiple exist
* #param {object} page
* #param {object} contact - object returned from getContact
* #return {object} - Promise, result from selected contact, or null
*/
selectPhone = ((page, contact) => {
return new Promise((resolve, reject) => {
var modalPageModule = "views/phone-select-page";
var fullscreen = false;
var slept; // Promise for wait on iOS
// Wait briefly on iOS for contacts app to close (admittedly a hack)
if (page.ios) {
slept = myutils.sleep(50);
} else {
slept = Promise.resolve();
}
slept.then(() => {
page.showModal( // Ref: https://docs.nativescript.org/ui/modal-view
modalPageModule,
contact,
((response) => { // anonymous closeCallback function
if (response) {
resolve(response); // return promise with modified contact
} else { // (handle back button press on Android)
reject(response);
}
}), // end closeCallback function
fullscreen
); // end page.showModal
}); // end slept.then
}); // end return New Promise
}); // end selectPhone
Here's the modal dialog itself
<Page xmlns="http://www.nativescript.org/tns.xsd" shownModally="onShownModally"
xmlns:dd="nativescript-drop-down">
<StackLayout class="modal-page">
<Label class="instructions" textWrap="true" text="Select the phone number to use" />
<dd:DropDown class="phones" items="{{ phones }}" selectedIndex="{{ phoneIndex }}" hint="Tap here to choose number" />
<StackLayout class="hr"/>
<GridLayout columns="*,*" rows="auto">
<Button class="btn navbtn" col="1" tap="onOkTap" text="OK"/>
</GridLayout>
</StackLayout>
</Page>
And here's the modal's .js file:
var observableModule = require("tns-core-modules/data/observable");
var closeCallback; // make modal closeCallback function global
exports.onShownModally = function (args) {
const page = args.object;
var model = new observableModule.fromObject(args.context);
page.bindingContext = model;
closeCallback = args.closeCallback;
}
exports.onOkTap = function (args) {
var page = args.object.page;
var response = page.bindingContext; // use input context object as response object
response.phone = response.phones[response.phoneIndex];
response.phone = response.phone.substr(response.phone.indexOf(":")).match(/\d/g).join("");
page.closeModal();
closeCallback(response);
}

The problem seems to be a timing issue returning from the Contacts app on iOS. That was the reason for this code:
// Wait briefly on iOS for contacts app to close (admittedly a hack)
if (page.ios) {
slept = myutils.sleep(50);
} else {
slept = Promise.resolve();
}
(Alas, I had dealt with this same problem last summer when I originally developed this code.) If I change the delay from 50ms to 750ms, the code works fine. I'm guessing this is due to changes in {N} 5. So, it's still a hack, but I can live with it for now.
edit 4/24/2020: I developed a fix for this and created a pull request (#84) for nativescript-contacts. See issue #75 for more info.

Related

Nativescript Camera Plus photoCapturedEvent not called and image not saving to library nativescript+vue

I am trying to implement the nativescript-camera-plus plugin to take an image. I am developing an IOS (for iPad) application with NativeScript + vue and am using an emulator to test.
For some reason the photoCapturedEvent event is not been triggered when the takePicture() is called. Furthermore, when { saveToGallery: true } is been passed through this method, the image is not saved to the photo gallery.
The loaded event works and runs as the component is loaded. I am unsure if the errorEvent event gets triggered.
Here is a snippet of my code (in TakePhoto.vue):
<CameraPlus ref="CameraPlus" height="600" id="camPlus"
saveToGallery="true"
showCaptureIcon="true"
showGalleryIcon="false"
showToggleIcon="false"
showFlashIcon="false"
debug="true"
enableVideo="false"
confirmVideo="false"
defaultCamera="front"
#loaded="onCameraLoaded"
#photoCapturedEvent="photoCaptured($event)"
#errorEvent="onCameraError">
</CameraPlus>
And here is the associated methods
onCameraLoaded(result) {
this.cam = result.object;
console.log("Camera loaded...");
},
onButtonCapture() {
console.log('Take Picture');
this.image = this.cam.takePicture({ saveToGallery: true });
},
photoCaptured(args){
console.log("ARGS - ", args)
},
I would also like to mention that I have registered the component in the app.js file like so: Vue.registerElement('CameraPlus', () => require('#nstudio/nativescript-camera-plus').CameraPlus);
Any help would be appreciated.

Firefox webextension - confirm function causes extension popup to close immediately

I would like to port an existing fully functional Chrome extension to Firefox, everything seems to work except the confirm() function behavior.
When the user clicks a specific button in the popup.html page, he is asked to confirm the action.
Chrome successfully prompts the dialog, I then get a Boolean back as soon as "ok" or "cancel" button is clicked, code related to the boolean returned is executed.
Firefox behavior feels buggy on the other hand. The confirm dialog prompts too but the extension popup is instantly dismissed, preventing further code in the click event handler to execute.
manifest.json : …, "default_popup": "popup.html", …
popup.html :
…
<script src="js/popup.js"></script>
</body>
popup.js :
removeButton.addEventListener('click', function () {
// Firefox: calling confirm() closes the popup.html page ...
// ... terminating event handler code
if (confirm("Please confirm you wish to remove this item.")) {
// …
}
});
Is there something to do about it or should I stop using confirm() and find a workaround ?
EDIT - Workaround solution
As a workaround, I set a 3 seconds countdown when the button is clicked and change its caption every second. Before time is up, if the user click again, the final action gets cancelled, otherwise final action is performed.
let log = document.querySelector('p')
,resetInterval = null
;
document.getElementById('resetbtn').addEventListener('click', function(e) {
if (!resetInterval) {
// Create a countdown and delete data when time is up.
e.target.content = e.target.innerHTML;
resetInterval = setInterval( function() {
var counter = +(e.target.innerHTML.trim().match(/\d+/)||[4])[0];
if (counter == 1) {
// Sending command to bacground page
// chrome.runtime.sendMessage({command:'remove'}, function (){
e.target.innerHTML = e.target.content;
resetInterval && clearInterval(resetInterval);
resetInterval = null;
log.innerHTML = 'Perform action…';
// });
} else e.target.innerHTML = 'Reset in '+(counter-1)+'s';
}, 1000);
log.innerHTML = '';
} else {
resetInterval && clearInterval(resetInterval);
e.target.innerHTML = e.target.content;
resetInterval = null;
log.innerHTML = 'Action aborted';
}
});
<button type="button" id="resetbtn">Reset</button>
<p></p>
Popout windows are designed to be dismissed when you move focus to another window. You can’t use dialogs (new windows) from the popout as they’re moving focus and thus dismissing the popout.

Nativescript Switch prevent change event firing on initial binding

Hi my template is something like the below
<ListView [items]="modules">
<template let-item="item" >
<StackLayout orientation="vertical">
<Switch (checkedChange)="onSwitchModule(item.id,$event)" [checked]="item.active"></Switch>
</StackLayout>
</template>
</ListView>
My controller is
ngOnInit() {
this._moduleService.getUserModules()
.subscribe(
response=>{
this.modules = response.data;
}
)
}
onSwitchModule(itemId) {
console.log(itemID); //Gets called on initial true binding on switch checked
}
The onSwitchModule get called everytime the page loads with item.active is true on any item, how to handle this ?
NOTE: Beginner in Nativescript
What I did to overcome this is I watch for tap events instead of checkedChange:
<Switch (tap)="switchClicked" [checked]="item.active"></Switch>
and in the callback, you can get the current item from bindingContext:
function switchClicked(args) {
const item = args.object.bindingContext.item;
}
I ran into a similar issue: loading up settings data from an API, and having the checked event fire for the value I'd set from the api -- not desirable in my case. I didn't see a great way to prevent events from firing on the initial binding, so I decided to simply ignore events until I knew they were legit events from the user actually using the switch.
I did that by using a property switchReady to keep track of when you want to start recognizing change events. This pattern also keeps the toggle disabled until you're ready to start accepting changes. This makes use of Switch's isEnabled property, see docs here.
Markup
<Switch [checked]="currentSettings.pushTurnedOn" [isEnabled]="switchReady" (checkedChange)="onPushSettingChange($event)" row="0" col="1"></Switch>
Component
export class SettingsComponent implements OnInit {
currentSettings: Settings = new Settings(false)
switchReady: boolean = false
ngOnInit() {
this.getCurrentSettings()
}
public onPushSettingChange(args) {
let settingSwitch = <Switch>args.object
if (settingSwitch.isEnabled) {
// do something with the event/change
} else {
// we aren't ready to accept changes, do nothing with this change
return
}
}
getCurrentSettings() {
this.settingsService.loadCurrentSettings().subscribe(
() => {
this.currentSettings = this.settingsService.currentSettings
// we've applied our api change via data binding, it's okay to accept switch events now
this.switchReady = true
},
err => alert('There was a problem retrieving your settings.')
)
}
}

Nativescript Observable.propertyChangeEvent

I am trying to create some customer formatting on a field (to reproduce a masked text box functionality).
I have an observable and I am capturing the propertyChange Event. My question is: Can I modify the value of the observed property inside the event handler without entering in an infinite loop?
Here is my code:
model.customer.addEventListener(Observable.propertyChangeEvent, function(data) {
if (data.propertyName.toString() === 'homePhone') {
//Here is where I would like to change the value without triggering the event again
//The below code does not seem to be working
data.value = formatPhone(data.value);
}
});
I looked at https://github.com/bthurlow/nativescript-maskedinput, but unfortunately this module does not support databinding.
Thank you. Appreciate your help.
I have tested nativescript-maskedinput in a sample application and I was able to bind text property of this custom view. In regard to that if you add addEventListener and want to update for example TextField text property manually I think that property won't be updated properly. In addition I am attaching some sample code.
main-page.xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:mi="nativescript-maskedinput" navigatingTo="navigatingTo">
<StackLayout>
<Label text="Tap the button" class="title"/>
<mi:MaskedInput mask="1-999-999-9999? x999" hint="1-555-555-5555" text="{{ masktext }}" placeholder="#" />
<Button text="tapToView" tap="onTap" />
</StackLayout>
</Page>
main-page.js
var observable_1 = require("data/observable"); // Event handler for Page "navigatingTo" event attached in main-page.xml
var newObservable = new observable_1.Observable();
function navigatingTo(args) {
// Get the event sender
var page = args.object;
newObservable.set('masktext', '');
page.bindingContext = newObservable;
}
exports.navigatingTo = navigatingTo;
function onTap(args) {
var newvalue = newObservable.get('masktext');
console.log('newValueget + ' + newvalue);
}
exports.onTap = onTap;
I am not sure if it is possible to override the getter, but if it is not possible you can do this:
function isPhoneFormatted(phone) {
//your algorithm wich return true or false
}
model.customer.addEventListener(Observable.propertyChangeEvent, function(data) {
if (data.propertyName.toString() === 'homePhone') {
//Here is where I would like to change the value without triggering the event again
//The below code does not seem to be working
if (!isPhoneFormatted(data.value)) {
data.value = formatPhone(data.value);
}
}
});
Notice that it is not well tested!

jqm tap event not firing in Safari browser running on iPhone 4

I have the following script code designed to capture double tap events so I can toggle an image between a size that fits on the page and full size. The problem is that the tap event is not firing at all in Safari browser running on iPhone 4. In the below code, the alert never displays regardless of what I do on the touch screen.
$(function () {
$('#showImage').on('tap', function (event) {
alert("gets in tap event");
var d = new Date();
var tapTime = d.getTime();
if (tapTime - lastTapTime > 500) {
lastTapTime = tapTime;
}
else {
toggleResize();
}
});
});
Why isn't this working?
tap event is now working. I had some script code on an earlier page that was breaking the script code on this page. When I removed the earlier script code, this script code started working again.

Resources