QML AppSwitch sending onCheckedChanged signal each time page is opened? - qml

I have an AppSwitch, which is being used to 'activate/deactivate' user accounts in my app, It's checked status is read from a database, which works as intended, changing both the switch and text depending on the bool per user.
My problem is
Each time I open the page with the AppSwitch on, it sends the onCheckedChanged signal - in turn sending a notification to the user their account has been activated? I had planned to notify users that their account was active with a push notification, but don't want this sending every time the admin moves to their page!
I am using Felgo(formerly V-Play) for development, a link to the AppSwitch documentation is:
Felgo AppSwitch
My code is:
Rectangle {
width: parent.width / 2
height: parent.height
Column {
id: column
anchors.fill: parent
AppButton {
id: groupStatusText
width: parent.width
height: parent.height / 2
}
AppSwitch {
id: editStatus
knobColorOn: "#b0ced9"
backgroundColorOn: "#81b1c2"
checked: {
//userListItem is the current profile details of the user page
if(userListItem.groupStatus === 1) {
groupStatusText.text = "Deactivate this user"
}
else if(userListItem.groupStatus === 0) {
groupStatusText.text = "Activate this user"
}
}
onCheckedChanged: {
if(editStatus.checked === true) {
//the signal which sends to my database on updating the value,
//works as intended but sends signal each time page is created?
detailPage.adminEditGroupStatus(1)
} else {
detailPage.adminEditGroupStatus(0)
}
}
}
}
Is it normal behaviour for the onCheckedChanged signal to be firing on each instance the page is loaded? or how could I amend this that it only fires when the user changes it!?
Thanks for any help!

Add a condition to be sure the signal is sent after full init of the component (including after creation of the binding on the checked property).
You can add a boolean property on the switch :
AppSwitch {
property bool loaded: false
Component.onCompleted: loaded = true
onCheckedChanged: {
if (loaded) {
// notify user
}
}
}
or simply use the state:
AppSwitch {
Component.onCompleted: state = "loaded"
onCheckedChanged: {
if (state === "loaded") {
// notify user
}
}
}

Related

QML - change how transition between pages looks

I know this is probably super basic, but I am new to learning QML and have a question about transition between pages.
In this example I have a button with which I want to switch between my 3 pages.
the transition works, but the pages always move from the right-to-the-left-side of the window.
how can I change this? I need the new page to appear as a whole right away.
(e.g. when changing from firstPage to secondPage, for the user it looks like only the AppText changes, because the button is at the same position in both cases)
code example:
App {
id: app
width: px(250); height: px(250)
NavigationStack {
Page {
id: page
navigationBarHidden: true
AppText { text: "startpage" }
SimpleButton{
x: 220; y: 0
onClicked: page.navigationStack.push(firstPage)
}
}
}
Component {
id: firstPage
Page {
navigationBarHidden: true
AppText { text: qsTr("1st page") }
SimpleButton{
x: 220; y: 0
onClicked: page.navigationStack.push(secondPage)
}
}
}
Component {
id: secondPage
Page {
navigationBarHidden: true
AppText { text: qsTr("2nd page") }
SimpleButton{
x: 220; y: 0
onClicked: page.navigationStack.push(page)
}
}
}
}
Any help would be greatly appreciated!
It looks like you're using Felgo, which I think is an extra library on top of Qt. For instance, there is no built-in QML component called NavigationStack. That comes from Felgo. You should mention that in your question to get better help with it.
I've never used Felgo myself, but just looking at the documentation real quick, it looks like you need to define a new transitionDelegate for your needs. Here is the example they give where new pages fade in/fade out.
NavigationStack {
// custom transition delegate
transitionDelegate: StackViewDelegate {
pushTransition: StackViewTransition {
NumberAnimation {
target: enterItem
property: "opacity"
from: 0
to: 1
duration: 1000
}
}
popTransition: StackViewTransition {
NumberAnimation {
target: exitItem
property: "opacity"
from: 1
to: 0
duration: 1000
}
}
}
initialPage: pageComponent
}

How to run a function continuously in RN

I am trying to create a sound metering app with React Native, but it seems that if I start the metering, which I have as a infinite loop until the user stops it, it hangs the app.
Basically the user taps a button, invokes startMonitor(), then the runMonitor starts and checkSound() does a check for the current decibel level.
The relevant code section here
startMonitor() {
KeepAwake.activate();
this.setState({ monitorStatus: true
}, this.runMonitor );
}
runMonitor() {
while(this.state.monitorStatus) {
if(this.state.beginWait) {
this.wait(5000);
}
if(!this.state.isTransmitting) {
this.checkSound();
}
}
}
Any ideas on how to run a function continuously/infinite until the user cancels it?
You can use setInterval:
startMonitor() {
this.runMonitorIV = setInterval(runMonitor, 1000);
}
runMonitor() {
while(this.state.monitorStatus) {
if(this.state.beginWait) {
this.wait(5000);
}
if(!this.state.isTransmitting) {
this.checkSound();
}
}
}
//called on user event
stopMonitor() {
clearInterval(this.runMonitorIV);
}
(Not sure about your context or scope, but that's the basic idea)
There's also the TimerMixin that clears those timers triggers when a component unmounts.

How to send signals between tabs of TabView (Qt5)

I have TabView with two tabs. Each tab has Item element (which contains other stuff). I need to send signal from one tab and to catch (to handle) it in other tab.
If I try to send signal from one Tab (Item) to other - it doesn't work, without showing of any errors.
I found a way to send signal out from tab to TabView scope using Connections as written here (http://qt-project.org/doc/qt-5/qml-qtquick-loader.html).
declare in the first tab signal:
signal message()
Make a call in this tab (in onClicked handler? for example):
onClicked: {
nameOfItemInFirstTab.message()
}
In TabView scope I declared Connections:
Connections {
target: nameOfTheFirstTab.item
onMessage:
{
doSomething()
}
}
Using this way, it's possible to catch the signal out of the first tab. But how, now, to send signal to the second tab? Is there another way to do it?
You can do it like this:
import QtQuick 2.3
import QtQuick.Controls 1.2
Rectangle {
id: myRect
width: 100
height: 100
TabView {
anchors.fill: parent
Component.onCompleted: {
tab1.tab1Signal.connect(tab2.onTab1Signal)
tab2.tab2Signal.connect(tab1.onTab2Signal)
}
Tab {
id: tab1
title: "Tab 1"
signal tab1Signal
function onTab2Signal() {
print("Tab 1 received Tab 2's signal")
}
Item {
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab1Signal()
}
}
}
Tab {
id: tab2
title: "Tab 2"
signal tab2Signal
function onTab1Signal() {
print("Tab 2 received Tab 1's signal")
}
Item {
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab2Signal()
}
}
}
}
}
The functions can of course be named anything; I only prefixed them with "on" because that's the convention for signal handler names.
For more information on connecting signals in QML, see Connecting Signals to Methods and Signals.
Is it possible to make a onTab1Signal() in Item? I need to send some information (String/int) to elements of Item of tab2.
Yes, but it's a bit trickier. First of all, remember that Tab is a Loader, so your Item declared inside it may not always be valid. In fact, if you look at the implementation of Tab, you'll see that it sets its active property to false, effectively meaning that tab content is loaded only after that tab has been made current (a design called lazy loading). A naive implementation (i.e my first attempt :p) will encounter strange errors, so you must either:
Set active to true in each Tab, or
Add an intermediate item that accounts for this
The first solution is the easiest:
import QtQuick 2.3
import QtQuick.Controls 1.2
Rectangle {
id: myRect
width: 100
height: 100
TabView {
id: tabView
anchors.fill: parent
Component.onCompleted: {
tab1.tab1Signal.connect(tab2Item.onTab1Signal)
tab2.tab2Signal.connect(tab1Item.onTab2Signal)
}
// You can also use connections if you prefer:
// Connections {
// target: tab1
// onTab1Signal: tabView.tab2Item.onTab1Signal()
// }
// Connections {
// target: tab2
// onTab2Signal: tabView.tab1Item.onTab2Signal()
// }
property alias tab1Item: tab1.item
property alias tab2Item: tab2.item
Tab {
id: tab1
title: "Tab 1"
active: true
signal tab1Signal
Item {
id: tab1Item
function onTab2Signal() {
print("Tab 1 received Tab 2's signal")
}
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab1Signal()
}
}
}
Tab {
id: tab2
title: "Tab 2"
active: true
signal tab2Signal
Item {
function onTab1Signal() {
print("Tab 2 received Tab 1's signal")
}
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab2Signal()
}
}
}
}
}
The second solution is slightly more difficult, and comes with a caveat:
import QtQuick 2.3
import QtQuick.Controls 1.2
Rectangle {
id: myRect
width: 100
height: 100
TabView {
id: tabView
anchors.fill: parent
QtObject {
id: signalManager
signal tab1Signal
signal tab2Signal
}
property alias tab1Item: tab1.item
property alias tab2Item: tab2.item
Tab {
id: tab1
title: "Tab 1"
signal tab1Signal
onLoaded: {
tab1Signal.connect(signalManager.tab1Signal)
signalManager.tab2Signal.connect(tab1.item.onTab2Signal)
}
Item {
id: tab1Item
function onTab2Signal() {
print("Tab 1 received Tab 2's signal")
}
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab1Signal()
}
}
}
Tab {
id: tab2
title: "Tab 2"
signal tab2Signal
onLoaded: {
tab2Signal.connect(signalManager.tab2Signal)
signalManager.tab1Signal.connect(tab2.item.onTab1Signal)
}
Item {
function onTab1Signal() {
print("Tab 2 received Tab 1's signal")
}
Button {
text: "Send signal"
anchors.centerIn: parent
onClicked: tab2Signal()
}
}
}
}
}
Because active isn't set to true, the second tab isn't loaded at start up, so it won't receive the first tab's signals until it's made current (i.e by clicking on it). Perhaps that's acceptable for you, so I documented this solution as well.

Transition with keepScrollPosition and navigateBack

We are using Durandal for our SPA application and came to a, in my opinion, common use case. We have two pages: one page is a list of entities (with filters, sorting, virtual scroll) and another is detail preview of an entity. So, user is on list page and set a filter and a list of results comes out. After scrolling a little bit down user notice an entity which he/she would like to see details for. So clicking on a proper link user is navigated to details preview page.
After "work finished" on preview page user click back button (in app itself or browser) and he/she is back on the list page. However, default 'entrance' transition scroll the page to the top and not to the position on list where user pressed preview. So in order to 'read' list further user have to scroll down where he/she was before pressing preview.
So I started to create new transition which will for certain pages (like list-search pages) keep the scroll position and for other pages (like preview or edit pages) scroll to top on transition complete. And this was easy to do however, I was surprised when I noticed that there are strange behavior on preview pages when I hit navigateBack 'button'. My already long story short, after investigation I found out that windows.history.back is completing earlier then the transition is made and this cause that preview pages are scrolled automatically down to position of previous (list) page when back button is hit. This scrolling have a very unpleasant effect on UI not mentioning that it is 'total catastrophe' for my transition.
Any idea or suggestion what could I do in this case?
Here is the code of transition. It is just a working copy not finished yet as far as I have this problem.
define(['../system'], function (system) {
var fadeOutDuration = 100;
var scrollPositions = new Array();
var getScrollObjectFor = function (node) {
var elemObjs = scrollPositions.filter(function (ele) {
return ele.element === node;
});
if (elemObjs.length > 0)
return elemObjs[0];
else
return null;
};
var addScrollPositionFor = function (node) {
var elemObj = getScrollObjectFor(node);
if (elemObj) {
elemObj.scrollPosition = $(document).scrollTop();
}
else {
scrollPositions.push({element: node, scrollPosition: $(document).scrollTop()});
}
};
var scrollTransition = function (parent, newChild, settings) {
return system.defer(function (dfd) {
function endTransition() {
dfd.resolve();
}
function scrollIfNeeded() {
var elemObj = getScrollObjectFor(newChild);
if (elemObj)
{
$(document).scrollTop(elemObj.scrollPosition);
}
else {
$(document).scrollTop(0);
}
}
if (!newChild) {
if (settings.activeView) {
addScrollPositionFor(settings.activeView);
$(settings.activeView).fadeOut(fadeOutDuration, function () {
if (!settings.cacheViews) {
ko.virtualElements.emptyNode(parent);
}
endTransition();
});
} else {
if (!settings.cacheViews) {
ko.virtualElements.emptyNode(parent);
}
endTransition();
}
} else {
var $previousView = $(settings.activeView);
var duration = settings.duration || 500;
var fadeOnly = !!settings.fadeOnly;
function startTransition() {
if (settings.cacheViews) {
if (settings.composingNewView) {
ko.virtualElements.prepend(parent, newChild);
}
} else {
ko.virtualElements.emptyNode(parent);
ko.virtualElements.prepend(parent, newChild);
}
var startValues = {
marginLeft: fadeOnly ? '0' : '20px',
marginRight: fadeOnly ? '0' : '-20px',
opacity: 0,
display: 'block'
};
var endValues = {
marginRight: 0,
marginLeft: 0,
opacity: 1
};
$(newChild).css(startValues);
var animateOptions = {
duration: duration,
easing : 'swing',
complete: endTransition,
done: scrollIfNeeded
};
$(newChild).animate(endValues, animateOptions);
}
if ($previousView.length) {
addScrollPositionFor(settings.activeView);
$previousView.fadeOut(fadeOutDuration, startTransition);
} else {
startTransition();
}
}
}).promise();
};
return scrollTransition;
});
A simpler approach could be to store the scroll position when the module deactivates and restore the scroll on viewAttached.
You could store the positions in some global app variable:
app.scrollPositions = app.scrollPositions || {};
app.scrollPositions[system.getModuleId(this)] = theCurrentScrollPosition;

dojo non-modal dialog

Is there a way to create a non-modal dialog window using dojo? jQuery UI supports both modal and non-modal dialog boxes. I am trying to convert a SilverLight application to HTML/javascript and finding it difficult to create non-modal windows using dojo.
You can set the display of the underlay to 'none', and you will have a non-modal Dialog. To do that, set the class of the Dialog to 'nonModal' (that's just a convention I'm creating right now), and in the CSS for the page, have an entry for .nonModal_underlay.
require(['dijit/form/Button','dijit/Dialog'],
function (Button, Dialog) {
var d = new Dialog({
'title':'I am nonmodal',
'class':'nonModal'
});
});
.nonModal_underlay {
display:none;
}
you might try dojox.layout.FloatingPane
Hi Just add this in your css
.dijitDialogUnderlayWrapper{
display:none !important
}
For people that need truly modeless Dialog, Roy J provides only partial solution as the focus still is held within the Dialog. To fix this, you can copy the dijit/Dialog.js and its dijit/templates/Dialog.html to your own folder structure and rename them to ModelessDialog. Then remove the focus handler, the key handler and the underlay. Here is the full result:
define([
"require",
"dojo/_base/array", // array.forEach array.indexOf array.map
"dojo/aspect",
"dojo/_base/declare", // declare
"dojo/Deferred", // Deferred
"dojo/dom", // dom.isDescendant
"dojo/dom-class", // domClass.add domClass.contains
"dojo/dom-geometry", // domGeometry.position
"dojo/dom-style", // domStyle.set
"dojo/_base/fx", // fx.fadeIn fx.fadeOut
"dojo/i18n", // i18n.getLocalization
"dojo/keys",
"dojo/_base/lang", // lang.mixin lang.hitch
"dojo/on",
"dojo/ready",
"dojo/sniff", // has("ie") has("opera") has("dijit-legacy-requires")
"dojo/window", // winUtils.getBox, winUtils.get
"dojo/dnd/Moveable", // Moveable
"dojo/dnd/TimedMoveable", // TimedMoveable
"dijit/focus",
"dijit/_base/manager", // manager.defaultDuration
"dijit/_Widget",
"dijit/_TemplatedMixin",
"dijit/_CssStateMixin",
"dijit/form/_FormMixin",
"dijit/_DialogMixin",
"dijit/layout/ContentPane",
"dijit/layout/utils",
"dojo/text!./templates/ModelessDialog.html",
"dijit/a11yclick", // template uses ondijitclick
"dojo/i18n!dijit/nls/common"
], function(require, array, aspect, declare, Deferred,
dom, domClass, domGeometry, domStyle, fx, i18n, keys, lang, on, ready, has, winUtils,
Moveable, TimedMoveable, focus, manager, _Widget, _TemplatedMixin, _CssStateMixin, _FormMixin, _DialogMixin,
ContentPane, utils, template){
// module:
// company/common/ModelessDialog
var resolvedDeferred = new Deferred(), _DialogBase, ds = null, DialogLevelManager = null, ModelessDialog,
_currentDialog = null;
resolvedDeferred.resolve(true);
_DialogBase = declare("dijit._DialogBase" + (has("dojo-bidi") ? "_NoBidi" : ""),
[_TemplatedMixin, _FormMixin, _DialogMixin, _CssStateMixin], {
templateString: template,
baseClass: "dijitDialog",
cssStateNodes: {
closeButtonNode: "dijitDialogCloseIcon"
},
// Map widget attributes to DOMNode attributes.
_setTitleAttr: { node: "titleNode", type: "innerHTML" },
// open: [readonly] Boolean
// True if ModelessDialog is currently displayed on screen.
open: false,
// duration: Integer
// The time in milliseconds it takes the dialog to fade in and out
duration: manager.defaultDuration,
// refocus: Boolean
// A Toggle to modify the default focus behavior of a ModelessDialog, which
// is to re-focus the element which had focus before being opened.
// False will disable refocusing. Default: true
refocus: true,
// autofocus: Boolean
// A Toggle to modify the default focus behavior of a ModelessDialog, which
// is to focus on the first dialog element after opening the dialog.
// False will disable autofocusing. Default: true
autofocus: true,
// _firstFocusItem: [private readonly] DomNode
// The pointer to the first focusable node in the dialog.
// Set by `dijit/_DialogMixin._getFocusItems()`.
_firstFocusItem: null,
// _lastFocusItem: [private readonly] DomNode
// The pointer to which node has focus prior to our dialog.
// Set by `dijit/_DialogMixin._getFocusItems()`.
_lastFocusItem: null,
// draggable: Boolean
// Toggles the movable aspect of the ModelessDialog. If true, ModelessDialog
// can be dragged by it's title. If false it will remain centered
// in the viewport.
draggable: true,
_setDraggableAttr: function(/*Boolean*/ val){
// Avoid _WidgetBase behavior of copying draggable attribute to this.domNode,
// as that prevents text select on modern browsers (#14452)
this._set("draggable", val);
},
// maxRatio: Number
// Maximum size to allow the dialog to expand to, relative to viewport size
maxRatio: 0.8,
// closable: Boolean
// ModelessDialog show [x] icon to close itself, and ESC key will close the dialog.
closable: true,
_setClosableAttr: function(val){
this.closeButtonNode.style.display = val ? "" : "none";
this._set("closable", val);
},
postMixInProperties: function(){
var _nlsResources = i18n.getLocalization("dijit", "common");
lang.mixin(this, _nlsResources);
this.inherited(arguments);
},
postCreate: function(){
domStyle.set(this.domNode, {
display: "none",
position: "absolute"
});
this.ownerDocumentBody.appendChild(this.domNode);
this.inherited(arguments);
aspect.after(this, "onExecute", lang.hitch(this, "hide"), true);
aspect.after(this, "onCancel", lang.hitch(this, "hide"), true);
this._modalconnects = [];
},
onLoad: function(){
// summary:
// Called when data has been loaded from an href.
// Unlike most other callbacks, this function can be connected to (via `dojo.connect`)
// but should *not* be overridden.
// tags:
// callback
// when href is specified we need to reposition the dialog after the data is loaded
// and find the focusable elements
this.resize();
this._position();
if(this.autofocus && DialogLevelManager.isTop(this)){
this._getFocusItems();
focus.focus(this._firstFocusItem);
}
this.inherited(arguments);
},
focus: function(){
this._getFocusItems();
focus.focus(this._firstFocusItem);
},
_endDrag: function(){
// summary:
// Called after dragging the ModelessDialog. Saves the position of the dialog in the viewport,
// and also adjust position to be fully within the viewport, so user doesn't lose access to handle
var nodePosition = domGeometry.position(this.domNode),
viewport = winUtils.getBox(this.ownerDocument);
nodePosition.y = Math.min(Math.max(nodePosition.y, 0), (viewport.h - nodePosition.h));
nodePosition.x = Math.min(Math.max(nodePosition.x, 0), (viewport.w - nodePosition.w));
this._relativePosition = nodePosition;
this._position();
},
_setup: function(){
// summary:
// Stuff we need to do before showing the ModelessDialog for the first
// time (but we defer it until right beforehand, for
// performance reasons).
// tags:
// private
var node = this.domNode;
if(this.titleBar && this.draggable){
// prevent overload, see #5285
if(has("ie") == 6) {
this._moveable = new TimedMoveable(node, { handle: this.titleBar });
} else {
this._moveable = new Moveable(node, { handle: this.titleBar });
}
aspect.after(this._moveable, "onMoveStop", lang.hitch(this, "_endDrag"), true);
}else{
domClass.add(node, "dijitDialogFixed");
}
},
_size: function(){
// TODO: remove for 2.0
this.resize();
},
_position: function(){
// summary:
// Position the dialog in the viewport. If no relative offset
// in the viewport has been determined (by dragging, for instance),
// center the dialog. Otherwise, use the ModelessDialog's stored relative offset,
// adjusted by the viewport's scroll.
if(!domClass.contains(this.ownerDocumentBody, "dojoMove")){ // don't do anything if called during auto-scroll
var node = this.domNode,
viewport = winUtils.getBox(this.ownerDocument),
p = this._relativePosition,
bb = p ? null : domGeometry.position(node),
l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)),
t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2))
;
domStyle.set(node, {
left: l + "px",
top: t + "px"
});
}
},
show: function(){
// summary:
// Display the dialog
// returns: dojo/promise/Promise
// Promise object that resolves when the display animation is complete
if(this.open){
return resolvedDeferred.promise;
}
if(_currentDialog && _currentDialog != this) {
_currentDialog.destroy();
}
_currentDialog = this;
if(!this._started){
this.startup();
}
// first time we show the dialog, there's some initialization stuff to do
if(!this._alreadyInitialized){
this._setup();
this._alreadyInitialized = true;
}
if(this._fadeOutDeferred){
// There's a hide() operation in progress, so cancel it, but still call DialogLevelManager.hide()
// as though the hide() completed, in preparation for the DialogLevelManager.show() call below.
this._fadeOutDeferred.cancel();
DialogLevelManager.hide(this);
}
// Recenter ModelessDialog if user scrolls browser. Connecting to document doesn't work on IE, need to use window.
// Be sure that event object doesn't get passed to resize() method, because it's expecting an optional
// {w: ..., h:...} arg.
var win = winUtils.get(this.ownerDocument),
// fade-in Animation object, setup below
fadeIn = null, promise;
this._modalconnects.push(on(win, "scroll", lang.hitch(this, "resize", null)));
//this._modalconnects.push(on(this.domNode, "keydown", lang.hitch(this, "_onKey")));
domStyle.set(this.domNode, {
opacity: 0,
display: ""
});
this._set("open", true);
this._onShow(); // lazy load trigger
this.resize();
this._position();
this._fadeInDeferred = new Deferred(lang.hitch(this, function(){
fadeIn.stop();
delete this._fadeInDeferred;
}));
// If delay is 0, code below will delete this._fadeInDeferred instantly, so grab promise while we can.
promise = this._fadeInDeferred.promise;
fadeIn = fx.fadeIn({
node: this.domNode,
duration: this.duration,
beforeBegin: lang.hitch(this, function(){
DialogLevelManager.show(this/*, this.underlayAttrs*/);
}),
onEnd: lang.hitch(this, function(){
if(this.autofocus && DialogLevelManager.isTop(this)){
// find focusable items each time dialog is shown since if dialog contains a widget the
// first focusable items can change
this._getFocusItems();
focus.focus(this._firstFocusItem);
}
this._fadeInDeferred.resolve(true);
delete this._fadeInDeferred;
})
}).play();
return promise;
},
hide: function(){
// summary:
// Hide the dialog
// returns: dojo/promise/Promise
// Promise object that resolves when the display animation is complete
// If we haven't been initialized yet then we aren't showing and we can just return.
// Likewise if we are already hidden, or are currently fading out.
_currentDialog = null;
if(!this._alreadyInitialized || !this.open){
return resolvedDeferred.promise;
}
if(this._fadeInDeferred){
this._fadeInDeferred.cancel();
}
// fade-in Animation object, setup below
var fadeOut = null, promise, h = null;
this._fadeOutDeferred = new Deferred(lang.hitch(this, function(){
fadeOut.stop();
delete this._fadeOutDeferred;
}));
// fire onHide when the promise resolves.
this._fadeOutDeferred.then(lang.hitch(this, 'onHide'));
// If delay is 0, code below will delete this._fadeOutDeferred instantly, so grab promise while we can.
promise = this._fadeOutDeferred.promise;
fadeOut = fx.fadeOut({
node: this.domNode,
duration: this.duration,
onEnd: lang.hitch(this, function(){
this.domNode.style.display = "none";
DialogLevelManager.hide(this);
this._fadeOutDeferred.resolve(true);
delete this._fadeOutDeferred;
})
}).play();
if(this._scrollConnected){
this._scrollConnected = false;
}
while(h = this._modalconnects.pop()){
h.remove();
}
if(this._relativePosition){
delete this._relativePosition;
}
this._set("open", false);
return promise;
},
resize: function(dim){
// summary:
// Called with no argument when viewport scrolled or viewport size changed. Adjusts ModelessDialog as
// necessary to keep it visible.
//
// Can also be called with an argument (by dojox/layout/ResizeHandle etc.) to explicitly set the
// size of the dialog.
// dim: Object?
// Optional dimension object like {w: 200, h: 300}
if(this.domNode.style.display != "none"){
this._checkIfSingleChild();
if(!dim){
if(this._shrunk){
// If we earlier shrunk the dialog to fit in the viewport, reset it to its natural size
if(this._singleChild){
if(typeof this._singleChildOriginalStyle != "undefined"){
this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle;
delete this._singleChildOriginalStyle;
}
}
array.forEach([this.domNode, this.containerNode, this.titleBar], function(node){
domStyle.set(node, {
position: "static",
width: "auto",
height: "auto"
});
});
this.domNode.style.position = "absolute";
}
// If necessary, shrink ModelessDialog to fit in viewport and have some space around it
// to indicate that it's a popup. This will also compensate for possible scrollbars on viewport.
var viewport = winUtils.getBox(this.ownerDocument), bb, contentDim, centerSize, cb;
viewport.w *= this.maxRatio;
viewport.h *= this.maxRatio;
bb = domGeometry.position(this.domNode);
if(bb.w >= viewport.w || bb.h >= viewport.h){
dim = {
w: Math.min(bb.w, viewport.w),
h: Math.min(bb.h, viewport.h)
};
this._shrunk = true;
}else{
this._shrunk = false;
}
}
// Code to run if user has requested an explicit size, or the shrinking code above set an implicit size
if(dim){
// Set this.domNode to specified size
domGeometry.setMarginBox(this.domNode, dim);
// And then size this.containerNode
contentDim = utils.marginBox2contentBox(this.domNode, dim);
centerSize = {domNode: this.containerNode, region: "center"};
utils.layoutChildren(this.domNode, contentDim,
[ {domNode: this.titleBar, region: "top"}, centerSize ]);
// And then if this.containerNode has a single layout widget child, size it too.
// Otherwise, make this.containerNode show a scrollbar if it's overflowing.
if(this._singleChild){
cb = utils.marginBox2contentBox(this.containerNode, centerSize);
// note: if containerNode has padding singleChildSize will have l and t set,
// but don't pass them to resize() or it will doubly-offset the child
this._singleChild.resize({w: cb.w, h: cb.h});
// TODO: save original size for restoring it on another show()?
}else{
this.containerNode.style.overflow = "auto";
this._layoutChildren(); // send resize() event to all child widgets
}
}else{
this._layoutChildren(); // send resize() event to all child widgets
}
if(!has("touch") && !dim){
// If the user has scrolled the viewport then reposition the ModelessDialog. But don't do it for touch
// devices, because it will counteract when a keyboard pops up and then the browser auto-scrolls
// the focused node into view.
this._position();
}
}
},
_layoutChildren: function(){
// Override _ContentPaneResizeMixin._layoutChildren because even when there's just a single layout child
// widget, sometimes we don't want to size it explicitly (i.e. to pass a dim argument to resize())
array.forEach(this.getChildren(), function(widget){
if(widget.resize){
widget.resize();
}
});
},
destroy: function(){
if(this._fadeInDeferred){
this._fadeInDeferred.cancel();
}
if(this._fadeOutDeferred){
this._fadeOutDeferred.cancel();
}
if(this._moveable){
this._moveable.destroy();
}
var h = null;
while(h = this._modalconnects.pop()){
h.remove();
}
DialogLevelManager.hide(this);
_currentDialog = null;
this.inherited(arguments);
}
});
if(has("dojo-bidi")){
_DialogBase = declare("dijit._DialogBase", _DialogBase, {
_setTitleAttr: function(/*String*/ title){
this._set("title", title);
this.titleNode.innerHTML = title;
this.applyTextDir(this.titleNode);
},
_setTextDirAttr: function(textDir){
if(this._created && this.textDir != textDir){
this._set("textDir", textDir);
this.set("title", this.title);
}
}
});
}
ModelessDialog = declare("beacons.common.ModelessDialog", [ContentPane, _DialogBase], {
// summary:
// A modal dialog Widget.
// description:
// Pops up a modeless dialog window. ModelessDialog is extended from
// ContentPane so it supports all the same parameters (href, etc.).
// example:
// | <div data-dojo-type="beacons/common/ModelessDialog" data-dojo-props="href: 'test.html'"></div>
// example:
// | var foo = new ModelessDialog({ title: "test dialog", content: "test content" });
// | foo.placeAt(win.body());
// | foo.startup();
});
ModelessDialog._DialogBase = _DialogBase; // for monkey patching and dojox/widget/DialogSimple
DialogLevelManager = ModelessDialog._DialogLevelManager = {
// summary:
// Controls the various active "levels" on the page, starting with the
// stuff initially visible on the page (at z-index 0), and then having an entry for
// each ModelessDialog shown.
_beginZIndex: 750,
show: function(/*dijit/_WidgetBase*/ dialog/*, / *Object* / underlayAttrs*/){
// summary:
// Call right before fade-in animation for new dialog.
// Saves current focus, displays/adjusts underlay for new dialog,
// and sets the z-index of the dialog itself.
//
// New dialog will be displayed on top of all currently displayed dialogs.
//
// Caller is responsible for setting focus in new dialog after the fade-in
// animation completes.
// Save current focus
ds[ds.length - 1].focus = focus.curNode;
// Set z-index a bit above previous dialog
var zIndex = ds[ds.length - 1].dialog ? ds[ds.length - 1].zIndex + 2 : ModelessDialog._DialogLevelManager._beginZIndex;
domStyle.set(dialog.domNode, 'zIndex', zIndex);
ds.push({dialog: dialog, /*underlayAttrs: underlayAttrs, */zIndex: zIndex});
},
hide: function(/*dijit/_WidgetBase*/ dialog){
// summary:
// Called when the specified dialog is hidden/destroyed, after the fade-out
// animation ends, in order to reset page focus, fix the underlay, etc.
// If the specified dialog isn't open then does nothing.
//
// Caller is responsible for either setting display:none on the dialog domNode,
// or calling dijit/popup.hide(), or removing it from the page DOM.
var pd, focus, idx;
if(ds[ds.length - 1].dialog == dialog){
// Removing the top (or only) dialog in the stack, return focus
// to previous dialog
ds.pop();
pd = ds[ds.length - 1]; // the new active dialog (or the base page itself)
// Adjust focus.
// TODO: regardless of setting of dialog.refocus, if the exeucte() method set focus somewhere,
// don't shift focus back to button. Note that execute() runs at the start of the fade-out but
// this code runs later, at the end of the fade-out. Menu has code like this.
if(dialog.refocus){
// If we are returning control to a previous dialog but for some reason
// that dialog didn't have a focused field, set focus to first focusable item.
// This situation could happen if two dialogs appeared at nearly the same time,
// since a dialog doesn't set it's focus until the fade-in is finished.
focus = pd.focus;
if(pd.dialog && (!focus || !dom.isDescendant(focus, pd.dialog.domNode))){
pd.dialog._getFocusItems();
focus = pd.dialog._firstFocusItem;
}
if(focus){
// Refocus the button that spawned the ModelessDialog. This will fail in corner cases including
// page unload on IE, because the dijit/form/Button that launched the ModelessDialog may get destroyed
// before this code runs. (#15058)
try{
focus.focus();
}catch(e){
;
}
}
}
}else{
// Removing a dialog out of order (#9944, #10705).
// Don't need to mess with underlay or z-index or anything.
idx = array.indexOf(array.map(ds, function(elem){
return elem.dialog;
}), dialog);
if(idx != -1){
ds.splice(idx, 1);
}
}
},
isTop: function(/*dijit/_WidgetBase*/ dialog){
// summary:
// Returns true if specified ModelessDialog is the top in the task
return ds[ds.length - 1].dialog == dialog;
}
};
// Stack representing the various active "levels" on the page, starting with the
// stuff initially visible on the page (at z-index 0), and then having an entry for
// each ModelessDialog shown.
// Each element in stack has form {
// dialog: dialogWidget,
// focus: returnFromGetFocus(),
// underlayAttrs: attributes to set on underlay (when this widget is active)
// }
ds = ModelessDialog._dialogStack = [
{dialog: null, focus: null/*, underlayAttrs: null*/} // entry for stuff at z-index: 0
];
// Back compat w/1.6, remove for 2.0
if(has("dijit-legacy-requires")){
ready(0, function(){
var requires = ["dijit/TooltipDialog"];
require(requires); // use indirection so modules not rolled into a build
});
}
ModelessDialog.closeCurrent = function() {
if(_currentDialog) {
_currentDialog.destroy();
}
};
return ModelessDialog;
});