I've followed MDN's document to create a toggle button addon.
Everything works fine except one problem:
Open a second browser window (cmd+n or ctrl+n) and click on the toggle button there
Click on the toggle button on the original browser window without closing the toggle button on the second window
the toggle button's panel becomes blank with the following error message:
JavaScript error: resource:///modules/WindowsPreviewPerTab.jsm, line 406: NS_ERR
OR_FAILURE: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIT
askbarTabPreview.invalidate]
// ./lib/main.js
var { ToggleButton } = require("sdk/ui/button/toggle");
var panels = require("sdk/panel");
var self = require("sdk/self");
var buttonIndex = 0;
var lastKnownButtonIndex = 0;
var button = ToggleButton({
id: "button",
label: "my button",
icon: {
"16": "./icon-16.png"
},
onClick: handleChange,
});
var panel = panels.Panel({
contentURL: self.data.url("menu.html"),
onHide: handleHide
});
function handleChange(state) {
if (state.checked) {
panel.show({
position: button
});
}
}
function handleHide() {
button.state('window', {checked: false});
}
function assignButtonIndex() {
return (buttonIndex++).toString();
}
The complete addon is here: https://goo.gl/9N3jle
To reproduce: Extract the zip file and $ cd testButton; cfx run and follow the above steps.
I really hope someone can help me with this. Thank you in advance!
It's a bug; you're not doing anything wrong. It's a racing condition between the windows' focus events, and the panel's event, that prevent somehow the panel's hidden event to be emitted properly.
You can try to mitigate with a workaround the issue until is properly fixed. I added some explanation in the bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1174425#c2 but in short, you can try to add a setTimeout to delay a bit when the panel is shown, in order to avoid the racing condition with the window's focus. Something like:
const { setTimeout } = require("sdk/timers");
/* ... your code here ... */
function handleChange(state) {
if (state.checked) {
setTimeout(() => panel.show({ position: button }), 100);
}
}
I am currently using a workaround where I dynamically create a new Panel every time the user presses the toolbar button.
It is faster than the 100ms workaround and also handles a scenario where the user outright closes one of the browser windows while the panel is open. (The 100ms workaround fails in this case and a blank panel is still displayed.)
It works like this:
let myPanel = null;
const toolbarButton = ToggleButton({
...,
onChange: function (state) {
if (state.checked) {
createPanel();
}
}
});
function createPanel(){
// Prevent memory leaks
if(myPanel){
myPanel.destroy();
}
// Create a new instance of the panel
myPanel = Panel({
...,
onHide: function(){
// Destroy the panel instead of just hiding it.
myPanel.destroy();
}
});
// Display the panel immediately
myPanel.show();
}
Related
I would like to test that a context menu has appeared after firing off a right click event with React Testing Library.
I don't know if there are timing difficulties or not, but the following test fails because it can't find the autoscale button (in the context menu) that should exist after a right click:
it('a context menu appears after a right click', async () => {
model = await makeModel(
['Channel 1'], {
laneHeaders: 'none'
}
);
const { getByTestId, getByText } = render(
<DataPreview
model={model}
/>
);
// node dealing with the right click event
const eventNode = getByTestId('data-preview-container');
// see https://testing-library.com/docs/dom-testing-library/api-events
const rightClickEvent = createEvent.click(eventNode, { button: 2 });
fireEvent.click(eventNode, rightClickEvent);
//Error: Unable to find an element with the text: Autoscale.
const autoScaleContextMenuButton = getByText('Autoscale');
expect(
autoScaleContextMenuButton
).not.toBeNull();
});
fireEvent.contextMenu(eventNode)
works for me
What you can do is:
// get the test component
const testComp = screen.getByTestId(some_test_id_here);
// create a contextMenu event
const contextMenuEvent = createEvent.contextMenu(testComp);
// fire the event with the contextMenu event
fireEvent(testComp, contextMenuEvent);
// do more checking
If you don't need to check the context menu event, i.e., propagation, prevent default, bubble, etc, you can omit the event creation and call fireEvent directly.
Reference: React - Jest - Test preventDefault Action
I'm rending map using this angular library for mapbox ngx-mapbox-gl
I'm showing a popup on mouseenter event.
mapInstance.on("mouseenter", "scoots_layers", function (e) {
var _lat = e.lngLat.lat;
var _lng = e.lngLat.lng;
var coordinates = [_lng, _lat];
this.popup = new Popup({
closeButton: true,
closeOnClick: true,
});
this.popup.setLngLat(coordinates)
.setHTML('<button (click)="goToPage()">Hello </button>')
.addTo(mapInstance);
});
Popup is working fine. But the click event of button is not triggered.
Maybe if you do it this way it works
this.popup.setLngLat(coordinates)
.setHTML('<button id="myBtn">Hello </button>')
.addTo(mapInstance);
});
and in your ts file
document.getElementById("myBtn").addEventListener("click", function(){
alert('say something...');
});
I have a jQuery mobile panel which slides in from the side, it works great.
But lets say you have a login page, that redirects to a main page with a panel. Now if the user opens the panel, and then clicks the back button, he expects the panel to close. But instead the browser navigates back to the login page.
I´ve tried adding something to the url:
window.location.hash = "panelOpen";
But that just messes up the jQuery mobile history state pattern. I´ve also tried to listen to the navigate event, and prevent it if a panel is open:
$(window).on('navigate', function (e, hans) {
var panels = $('[data-role="panel"].ui-panel-open');
if (panels&&panels.length>0) {
e.preventDefault();
e.stopPropagation();
$('#' + panels[0].id).panel('close');
return false;
}
});
This kind of works, except that the url is changed, and I cannot grab the event that changes the url. Furthermore, it also messes up the jQuery mobile history pattern.
So how does people achieve this expected 'app-like' behaviour with a jQuery mobile panel; open panel > history back > close panel. And thats it.
Thanks alot!
Updated
Instead of retrieving current URL from jQuery Mobile's history, It is safer to retrieve it from hashchange event event.originalEvent.newURL and then pass it to popstate event to be replaceState() with that URL.
Instead of listening to navigate, listen to popstate which fires before. The trick here is manipulate both browser's history and jQuery Mobile's history by replaceState() and reload same page without transition.
var newUrl;
$(window).on("hashchange", function (e) {
/* retrieve URL */
newUrl = e.originalEvent.newURL;
}).on("popstate", function (e) {
var direction = e.historyState.direction == "back" ? true : false,
activePanel = $(".ui-panel-open").length > 0 ? true : false,
url = newUrl,
title = document.title;
if (direction && activePanel) {
$(".ui-panel-open").panel("close");
$(".ui-header .ui-btn-active").removeClass("ui-btn-active");
/* reload same page to maintain jQM's history */
$.mobile.pageContainer.pagecontainer("change", url, {
allowSamePageTransition: true
});
/* replace state to maintain browsers history */
window.history.replaceState({}, title, url);
/* prevent navigating into history */
return false;
}
});
This part is meant to maintain same transition used previously as transition is set to none when reloading same page.
$(document).on("pagebeforechange", function (e, data) {
if (data.options && data.options.allowSamePageTransition) {
data.options.transition = "none";
} else {
data.options.transition = $.mobile.defaultPageTransition;
}
});
Demo - Code
I am a little bit late on the party, but i had recently the same requirements and i would like to share how i did it. So, i extended the requirement in the original question to Panels, Popups and Pages:
...an expected 'app-like' behaviour, history back > close
whaterver is open. And thats it.
In .on("panelopen"), .on("popupafteropen") and .on("pagecontainershow") i simply add another entry to the window history, by using the HTML5 API (https://developer.mozilla.org/en-US/docs/Web/API/History_API) (I believe there is no need to use the JQM navigate browser quirks for that):
window.history.pushState({}, window.document.title, window.location.href);
After that, i'm using more or less Omar's function to intercept the popstate event:
$(window).on("popstate", function (e) {
var pageId = $(":mobile-pagecontainer").pagecontainer("getActivePage").prop("id");
var pageOpen = (pageId != "page-home");
var panelOpen = $(".ui-panel-open").length > 0;
var popupOpen = $(".ui-popup-active").length > 0;
if(pageOpen) {
$.mobile.pageContainer.pagecontainer("change", "#page-home", {reverse: true});
return false;
}
if(panelOpen) {
$(".ui-panel-open").panel("close");
return false;
}
if(popupOpen) {
$(".ui-popup-active .ui-popup").popup("close")
return false;
}
});
As you see, the is just only one level to the home-page, but this can be easily extended by using JQM history implementation to get the previous page:
var activeId = $.mobile.navigate.history.activeIndex;
var jqmHistory = $.mobile.navigate.history.stack; // array of pages
and use pagecontainer to change to the active entry - 1.
As last note, this works well also by completely disabling the built-in JQM Ajax navigation system:
/* Completely disable navigation for mobile app */
$.mobile.ajaxEnabled = false;
$.mobile.loadingMessage = false;
$.mobile.pushStateEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.changePage.defaults.changeHash = false;
$.mobile.popup.prototype.options.history = false;
(Tested in Browser, on real Android and iOS devices)
When I click the back button on the browser, my popup closes but the listener events brought me back to the previous page, I want in my function an element that closes the popup without me go back. While keeping the history listener to other pages (don't stop in the whole of the application)
$(document).on("pageshow", "#member", function (){
$(window).on("navigate", function (event, data) {
var direction = data.state.direction;
if (direction == 'back') {
$("#mypopup").popup("close");
}
});
});
I try with
$(document).bind("mobileinit", function () {
$.mobile.hashListeningEnabled = false;
});
But don't working
I've created a simple interaction in Titanium where the user opens an Option Dialog and then selects "Take photo", which opens up the camera.
The problem here is that the app crashes if you do the following:
Open Option Dialog and select "Take photo" which opens up the camera
Hit Cancel
Open the Option Dialog again and select "Take photo" again
Hit Cancel again - the app crashes.
I get the following messages in the console:
Dismissing a view controller when it is not the top presented view
controller. Will probably crash now.
attempt to dismiss modal view controller whose view does not
currently appear. self = modalViewController =
CoreAnimation: warning, deleted thread with uncommitted CATransaction;
set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.
MPUSystemMediaControls] Updating supported commands for now playing
application.
It looks like this issue has been fixed in native iOS development in Objective C in this Stackoverflow Question, but how can I fixed this with Titanium?
Here is my code
$.imageView.addEventListener("click", function (e) {
$.imageDialog.cancel = 1;
$.imageDialog.show();
$.imageDialog.addEventListener("click", function (event) {
if (event.index === 0) {
$.imageDialog.hide();
Titanium.Media.showCamera({
success: function (e) {
if (e.mediaType === Ti.Media.MEDIA_TYPE_PHOTO) {
$.beerImage.image = e.media;
theImage = e.media;
$.cameraImage.opacity = 0.5;
}
},
cancel: function (e) { console.log('Action was cancelled'); },
error: function (e) { console.log('An error happened'); },
allowEditing: true,
mediaTypes: [Titanium.Media.MEDIA_TYPE_PHOTO],
videoQuality: Titanium.Media.QUALITY_HIGH
});
}
});
});
I have no problems at all if I do not use the Option Dialog and go straight to the Camera. Is is not possible to use both the Option Dialog and the Camera without having this issue? Or have a made a mistake in my code?
I found a work around for this.
The trick is to create a new OptionDialog each time the Camera needs to be opened. In the code below, the Option Dialog gets created when the button is clicked to open the camera. Therefore a new OptionDialog is created, the event listers are added to it and then it is opened.
$.imageView.addEventListener("click", function (e) {
var opts = {
cancel: 2,
options: ['Take Photo', 'Choose from gallery', 'Cancel'],
destructive: 0,
title: 'Choose'
};
var dialog = Ti.UI.createOptionDialog(opts);
dialog.addEventListener("click", function (event) {
if (event.index === 0) {
Titanium.Media.showCamera({
success: function () { ... },
cancel: function () { ... },
error: function () { ... },
allowEditing: true,
mediaTypes: [Titanium.Media.MEDIA_TYPE_PHOTO],
videoQuality: Titanium.Media.QUALITY_HIGH
});
}
$.addBeerWin.remove(dialog);
});
dialog.show();
});