I am stuck on where to start here. I want to open popovers from the angular controller when something happens.
For example, let's say the user has selected 30 checkboxes, I want to open a popover with two buttons that allow the user to "Action all Items" or "Cancel" and also have a textarea where the user can put in a comment.
Currently, I am calling external javascript function from controller. The issue here is that the popover is defined outside of the current $scope.
Like I said, I am stuck on where to start. Any help is appreciated.
Current angular code:
$scope.showActionCommentPopover = function () {
var elementId = "#CommentPopoverAnchor";
var popoverTitle = "Action Comment";
var popoverContent = "Enter comment: <br/><textarea cols='50' rows='10' id='processComment' ng-model='actionComment'></textarea>";
var width = "600px";
var placement = "right";
ShowPopover(elementId, popoverTitle, popoverContent, width, placement);
};
External js function:
function ShowPopover(elementId, popoverTitle, popoverContent, width, placement) {
$(elementId).popover({
placement: placement,
title: popoverTitle + '<a class="close" data-toggle="popover" onClick="$(\'' + elementId + '\').popover(\'destroy\');">×</a>',
content: popoverContent,
html: true
});
$(elementId).popover('show');
$(".popover").css("width", width);
$(elementId).popover('show');
}
Related
I have following menu in angular 4
<button mat-button [matMenuTriggerFor]="menu" style="display:none;">Menu</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let item of items" (click)="select(item)">
{{ item }}
</button>
</mat-menu>
I am opening the menu when user selects text on screen using
matMenuTrigger.openMenu();
but I want to open menu wherever user selects text.
I have X and Y coordinates of user selection but how can I change position of menu?
I have tried giving Id to mat-menu and changing it's position using
element.style.position = 'absolute'
element.style.left = screenX + 'px'
element.style.top = screenY + 'px'
but it's not changing position of menu.
EDIT:
I have changed position of menu by
this.matMenuTrigger.openMenu();
var element = document.getElementsByClassName('cdk-overlay-pane');
menu.style.position = "absolute";
menu.style.left = evt.pageX + 5 + 'px';
menu.style.top = evt.pageY + 5 + 'px';
where evt is mouseup event which gives co-ordinates(X,Y) of user text selection.
But, when I scroll the page , opened menu again goes back to it's original position.
How can I keep menu to it's changed position on scroll??
I have implemented opening mat-menu where ever user selectes text.
I have added hidden button which on click opens menu.
On user text selection, I have changed style="display:none;" of that button to style="display:'';" and after showing that button, I have changed position of that button to where user has selected the text by x and y co-ordinates and then opened menu programatically by this.menuTrigger.openMenu();
DEMO
menu-example.ts
export class MenuIconsExample {
#ViewChild(MatMenuTrigger)
private menuTrigger: MatMenuTrigger;
addTextToOpns: Array<String> = ["option 1", "option 2", "option 3"];
selectedOption: string = "no Option selected";
onTextSelection(event: any):void{
if(window.getSelection && window.getSelection().toString()){
var menu = document.getElementById('menuBtn');
menu.style.display = '';
menu.style.position = 'absolute';
menu.style.left = event.pageX + 5 + 'px';
menu.style.top = event.pageY + 5 + 'px';
this.menuTrigger.openMenu();
}
}
onMenuClosed():void {
var menu = document.getElementById('menuBtn');
if (menu) {
menu.style.display = 'none';
}
}
addTextTo(selectedOpn): void {
this.selectedOption = selectedOpn + ' selected';
}
}
menu-example.html
<div (mouseup)="onTextSelection($event)">
<button mat-button [matMenuTriggerFor]="menu" id="menuBtn" style="display:none;">Menu</button>
<mat-menu #menu="matMenu" (close)="onMenuClosed()">
<button class="menuOpnBtn" mat-menu-item *ngFor="let opn of addTextToOpns" (click)="addTextTo(opn)">
{{ opn }}
</button>
</mat-menu>
<p>
text for selection
</p>
</div>
<br/>
<br/>
<div><span>selected option : </span> <span>{{selectedOption}}</span></div>
In order to get this example to work in a more general case, I found it necessary to use "menu.style.position = 'fixed' (rather than "menu.style.position = 'absolute').
This is because "event.pageX" returns the coordinates relative to the viewport. Since "position = absolute" positions the button relative to its containing parent, the position of the invisible button will be correct only if its containing parent is not already nested in some other HTML element.
So, to summarize, I changed the code for menu-example.ts as follows and it now seems to work in all cases:
export class MenuIconsExample {
#ViewChild(MatMenuTrigger)
private menuTrigger: MatMenuTrigger;
addTextToOpns: Array<String> = ["option 1", "option 2", "option 3"];
selectedOption: string = "no Option selected";
onTextSelection(event: any):void{
if(window.getSelection && window.getSelection().toString()){
var menu = document.getElementById('menuBtn');
menu.style.display = '';
menu.style.position = 'fixed';
menu.style.left = event.pageX + 5 + 'px';
menu.style.top = event.pageY + 5 + 'px';
this.menuTrigger.openMenu();
}
}
onMenuClosed():void {
var menu = document.getElementById('menuBtn');
if (menu) {
menu.style.display = 'none';
}
}
addTextTo(selectedOpn): void {
this.selectedOption = selectedOpn + ' selected';
}
}
Are you also foolishly wandering here like I wasted 3 hours of mine manually setting the position of mat-menu just because the triggering element of mat-menu disappears from the DOM on some condition and you don't want mat-menu to be closed at that time, then instead of using ngIf use hidden property on that triggering element, it will save your ton of time of creating some hidden element, displaying it first, getting its coordinates bla bla bla, may be that will save someones life
The OP has edited the question to include the best answer for moving the menu position.
However, in the newer versions of angular, doing a document.getElementsByClassName returns a HTMLCollection and when accessing the element, it does not have the style property. I think this is because of the update to typescript.
I found this article which helped with it (Property 'style' does not exist on type 'Element' in TS)
The final result is this (note the setTimeout. This is done so that the mat menu can be rendered before it is moved)
this.matMenuTrigger.openMenu();
setTimeout(() => {
const menuCollection = document.getElementsByClassName('cdk-overlay-pane') as HTMLCollectionOf<HTMLElement>;
const menu = menuCollection[0];
menu.style.position = 'absolute';
menu.style.left = e.x + 5 + 'px';
menu.style.top = e.y + 5 + 'px';
}, 0);
I've got a function worked out that creates a new query ui tab panel when a user clicks a button. It also creates a new tab with a close button in it, like so:
$(function newTab() {
var $tabs = $('#nav-tabs').tabs();
$('.add-tab').click(function (e) {
e.preventDefault()
var tabName = $(this).text(),
tabLink = $(this).attr('href'),
tabNumber = -1;
$tabs.find('.nav-tab-menu li a').each(function (i) {
if ($(this).text() == tabName) {
tabNumber = i;
}
});
if (tabNumber >= 0) {
$tabs.tabs('option', 'active', tabNumber)
} else {
$("<li><a href=" + tabLink + " class='ui-icon-tab-add'>" + tabName + "</a><span class='ui-icon-close' role='presentation'><span class='sr'>Remove Tab</span></span></li>")
.appendTo(".nav-tab-menu");
$("#nav-tabs").tabs("refresh");
$('#nav-tabs').tabs('option', 'active', -1);
}
return false
})
});
It works great, but this client is a total pain in the ass, and one close button isn't good enough for them - they also want one in the newly created panel as well. I've tried cloning the button and appending it to the panel, but it doesn't seem to work. Any ideas? I should mention that the content of the current tab is replaced when the user clicks a link, so the button probably needs to be inserted before the active tab panel, rather than inside it so it doesn't get removed when the content is updated.
HTML
<div id="activities"></div>
<div id="activity-edit"></div>
JavaScript
require([
'dojo/ready', 'dojo/dom', 'dijit/registry', 'dojox/mobile/parser', 'dojox/mobile/deviceTheme', 'dojox/mobile/compat', 'dojox/mobile/Icon', 'dojox/mobile/ScrollableView', 'dojox/mobile/Heading', 'dojox/mobile/ToolBarButton', 'dojox/mobile'
],
function(ready, dom, registry, parser, deviceTheme, compat, Icon, ScrollableView, Heading, ToolBarButton, mobile) {
ready(function() {
var view_activities = new ScrollableView(null, 'activities');
view_activities.selected = true;
var heading = new Heading({
label: 'Activities',
fixed: 'top'
});
view_activities.addFixedBar(heading);
var button = new ToolBarButton({
icon: 'mblDomButtonWhitePlus',
style: 'float:right;',
moveTo: 'activity-edit',
onClick: function(e) {
click_activity_edit(e, 0);
}
});
heading.addChild(button);
var view_activity_edit = new ScrollableView(null, 'activity-edit');
view_activities.startup();
});
this.click_activity_edit = function(e, activityid) {
var view_activity_edit = registry.byId('activity-edit');
view_activity_edit.destroyDescendants(false);
heading = new Heading({
id: 'heading-activity-edit',
label: 'Activity',
fixed: 'top'
});
view_activity_edit.addChild(heading);
var button = new ToolBarButton({
label: 'Cancel',
moveTo: 'activities',
transitionDir: -1,
arrow: 'left'
});
heading.addChild(button);
button = new ToolBarButton({
label: 'Save',
style: 'float:right;',
moveTo: 'activities',
transitionDir: -1,
onClick: function(e) {
click_activity_save(e, activityid, function() {
data.get_activities(request, handle_getActivities);
});
}
});
heading.addChild(button);
view_activity_edit.startup();
};
parser.parse();
});
Steps to recreate the behavior:
Click the "+" button, click "Cancel", click the "+" button again, click "Cancel" again and the button no longer works.
If you replace addFixedBar with addChild, the button works as expected every time. I would do this, but I need the Heading to be fixed given that it is on a ScrollableView.
I understand that addFixedBar adds the widget to the domNode and not the containerNode, but I don't understand why that affects the behavior of the button and only on the second pass. My guess is that it has something to do with the destroyDescendants call not actually removing the Heading when using addFixedBar. I tried destroying the Heading manually after calling destroyDescendants, but that didn't work. The heading is undefined/null on the second pass whether I get the Heading by "dom" or "registry".
Any help or explanation is appreciated.
EDIT
Here is the JSFiddle: http://jsfiddle.net/MPUvk/
The key is the startup() calls.
The view_activity_edit.startup() call will work only once (startup() sets an internal _started flag and does nothing when it is already set). The second time the view is created, startup() does nothing.
The different behaviors between addFixedBar and addChild are because addChild calls startup() internally, whereas addFixedBar does not.
So to fix, just add heading.startup() after the addFixedBar call, that should work.
Another possibility would be to reset view_activity_edit._started = false when you destroy the view.
Chrome has something called "Page Actions", and I'm roughly trying to replicate that functionality with the Firefox Addon SDK/Jetpack. There's probably a better approach than what I've tried so far, and I'm open to suggestions.
Using tabs, I'm able to listen for tab ready and activate events, and if the tab's URL matches, the addon widget should be enabled; if not, disabled. I've got to the point where I can change the icon when appropriate, but I'd like to disable the panel as well.
Strategy 1: Steal the click event and only show the panel if we're on the right page; otherwise, ignore. Problem is, according to the docs, manually showing the panel causes it not to be anchored, a bug that's not had much progress on it.
Strategy 2: Set the contentURL to null when disabling. Get an error whining about it not being an URL.
Strategy 3: Use a different HTML document for the disabled state. Setting panel.contentURL to another URL doesn't work after going to a different page?
Here's the code:
const widgets = require("widget");
const Panel = require("panel").Panel;
const tabs = require("tabs");
const data = require("self").data;
const prefs = require("simple-prefs").prefs;
var panel = Panel({
width: 480,
height: 640,
contentURL: data.url("panel.html"),
contentScriptFile: [data.url('jquery.min.js'), data.url('panel.js')],
onMessage: function (msg) { console.log(msg) }
});
var widget = widgets.Widget({
id: "icon",
label: "Export",
contentURL: data.url("icon.png"),
panel: panel
});
function enable() {
widget.contentURL = data.url('icon.png');
panel.contentURL = data.url("panel.html");
}
function disable() {
widget.contentURL = data.url('icon_disabled.png');
panel.contentURL = data.url("panel_disabled.html");
}
function on_change_tab(tab) {
console.log(tab.url);
if (/http:\/\/example.com\/.*/.exec(tab.url)) {
console.log('ENABLE');
enable();
} else {
console.log('DISABLE');
disable();
}
console.log(panel.contentURL);
}
tabs.on('ready', on_change_tab);
tabs.on('activate', on_change_tab);
Related, but should have the anchoring problem? How to reload a widget popup panel with firefox addon sdk?
In case you still haven't solved your issue (and for anyone else having a similar problem):
I had a similar issue and resolved it by using erikvold's ToolbarButton package. Once you've installed that and its dependencies, something like this in your main.js file should work.
var pan = require("panel").Panel({
width: 400,
height: 600,
contentURL: "http://stackoverflow.com"
//maybe some more options here
});
var button = require("toolbarbutton").ToolbarButton({
id: "myButton",
label: "My Button",
image: data.url("someicon.png"),
panel: pan //This binds the panel to this toolbarbutton
});
I hope you can find some way to adapt this to your project.
toolbarbutton.ToolbarButton({
id: "MyAddon",
label: "My Addon",
tooltiptext: "My Addon Tooltip",
image: data.url("logo.png"),
onCommand: function() {
var dictionary_panel = require("panel").Panel({
width:630,
height:600,
contentURL: data.url("HtmlPage.html"),
contentScriptWhen: 'ready',
contentScriptFile: [data.url("style.css"),data.url("jquery-1.7.1.js"),
data.url("javascriptquezz.js"),data.url("create.js")]
});
dictionary_panel.show();
}
});
I would like to display a modal dialog over the entire page, but over a certain div in the DOM. Is this possible? Examples in the docs only show how to display a dialog over the entire page.
Check out the "position" option. Use it like this:
$(".selector").dialog({ position: [350,100] }); // places dialog at x:350, y:100
Then you can line up the x,y to sit above your target div.
You can use jQuery's .offset() to find the div's position and then do what John said
Fit inside Div:
var bg_div = $("#background_div");
var pos = bg_div.offset();
var margin = 10;
$(".selector").dialog({
position: [pos.left+margin,pos.top+margin],
width: bg_div.outerWidth() - margin*2,
height: bg_div.outerHeight() - margin*2
});
Center over Div:
var bg_div = $("#background_div");
var pos = bg_div.offset();
var x = pos.left + (bg_div.outerWidth()/2);
var y = pos.top + (bg_div.outerHeight()/2);
$(".selector").dialog({position: [x,y]});