How can I add a pdftron Annotation to the current mouse position - javascript

I am writing a pdf reading software using pdftron. I know that docViewer.on('mouseLeftDown', e => {} can get an event, but onClick can’t seem to get a mouse event. Is there any good solution? Thank you.
WebViewer(
{
path,
initialDoc: "",
},
viewer.value
).then(function (instance) {
const { Annotations, annotManager, docViewer } = instance;
instance.contextMenuPopup.add({
type: "actionButton",
label: "MD",
onClick: () => {
const freeText = new Annotations.FreeTextAnnotation();
freeText.PageNumber = docViewer.getCurrentPage();
freeText.X=?;// I don't know how to set the freeText.X at the location of the mouse
freeText.Y=?;
freeText.Width = 150;
freeText.Height = 50;
create-annotation-free-text
converting-between-mouse-locations-and-window-coordinates

I think one way you could do it is to listen to the contextmenu event on the iframe document and then store the last mouse event from there that you can then use in the onClick of your button.
For example
let lastContextMenuEvent;
instance.iframeWindow.document.addEventListener('contextmenu', (e) => {
lastContextMenuEvent = e;
});
instance.contextMenuPopup.add({
onClick: () => {
// use lastContextMenuEvent here
}
});

Related

Javascript navigator.mediasession image not showing in navigator / media controls disappearing

Again, working on my audio player...
I've rediscovered a mediasession updater in my code that adds a little notification from the browser with controls for my music, and it used to work ok, a little finicky, but now...
Nothing. I tinkered around a bit and got my play/pause buttons working, along with the next/prev songs and seek buttons, and the music title and artist. But I have now found that if you press the play button, the next/prev button, or sometimes the pause button, that the options and the song details disappear, leaving me with only the play/pause button, which works perfectly...
Now, to the code. I have a function that loads the songs in my player (loadSong(songIndex)), and near the end, I have a few lines of code that reset the navigator's metadata:
navigator.mediaSession.metadata.title = song.songname;
navigator.mediaSession.metadata.author = song.artistname;
navigator.mediaSession.metadata.artwork = ("./Music/" + song.thumbnail);
The artist and name work perfectly, but the thumbnail doesn't... More on that in a separate question.
I set up the actual controls like this:
/*media controls*/
let index = 0;
let skipTime = 10;
const actionHandlers = [
['play', () => { main.playPauseControl.click(); navigator.mediaSession.playbackState = "playing"; updatePositionState();}],
['pause', () => { main.playPauseControl.click(); navigator.mediaSession.playbackState = "paused"; updatePositionState();}],
['previoustrack', () => { main.prevControl.click(); updatePositionState();}],
['nexttrack', () => { main.nextControl.click(); updatePositionState();}],
['seekbackward', (details) => { main.audio.currentTime = Math.max(main.audio.currentTime - skipTime, 0); updatePositionState();}],
['seekforward', (details) => { main.audio.currentTime = Math.min(main.audio.currentTime + skipTime, main.audio.duration); updatePositionState();}],
];
for (const [action, handler] of actionHandlers) {
try {
navigator.mediaSession.setActionHandler(action, handler);
} catch (error) {
console.log(`The media session action "${action}" is not supported yet.`);
}
}
I have some other things linked to the mediaSession in order to register interactions outside the navigator
main.audio.addEventListener('play', function() {
navigator.mediaSession.playbackState = 'playing';
main.playPauseControl.classList.remove("paused");
});
main.audio.addEventListener('pause', function() {
navigator.mediaSession.playbackState = 'paused';
main.playPauseControl.classList.add("paused");
});
Now this all works, initially, but as soon as I interact with the navigator, BOOM, everything disappears. Can someone PLEASE tell me what's happening... this has been bugging me for a while...
P.S. sorry for the long question.
This answer is going to sound weird but have you tried not touching the media session from inside the callbacks? I know the Google Chrome example calls updatePositionState, but it actually has the same play/pause issue as your code.
So concretely, try this:
const actionHandlers = [
['play', () => { main.playPauseControl.click(); }],
['pause', () => { main.playPauseControl.click(); }],
['previoustrack', () => { main.prevControl.click(); }],
['nexttrack', () => { main.nextControl.click(); }],
['seekbackward', (details) => { main.audio.currentTime = Math.max(main.audio.currentTime - skipTime, 0); }],
['seekforward', (details) => { main.audio.currentTime = Math.min(main.audio.currentTime + skipTime, main.audio.duration); }],
];
I've done this for readtomyshoe and it no longer has this issue.

Javascript How to remove event that was added, and be able to add it again when need to?

I don't know to explain this, but let me try. I'm currently doing a snooker scorekeeper project, and I'll explain a problem that I have.
//Add point when player pots balls
buttons.yellowButton.addEventListener('click', () => {
coloredBall(2); // add 2 points to player
})
buttons.greenButton.addEventListener('click', () => {
coloredBall(3); // add 3 points to player
})
.
.
.
.
.
buttons.blackButton.addEventListener('click', () => {
coloredBall(7); // add 7 points to player
})
The code above works fine, It just updates the player score. Now, when all the reds are potted, I want to disable all of the buttons except the button that the players suppose to play. So I create a function that will add a new event to the buttons. The thing is that when I restart a game, I want to be able to remove those events, and to adds the same event when all the reds are potted again. How can I do this?
allRedArePotted = function() => {
buttons.yellowButton.disabled = false;
buttons.yellowButton.addEventListener('click', () => {
disableAllButtons();
buttons.greenButton.disabled = false;
yellow = 0;
})
buttons.greenButton.addEventListener('click', () => {
disableAllButtons();
buttons.brownButton.disabled = false;
green = 0;
})
buttons.brownButton.addEventListener('click', () => {
disableAllButtons();
buttons.blueButton.disabled = false;
brown = 0;
})
buttons.blueButton.addEventListener('click', () => {
disableAllButtons();
buttons.pinkButton.disabled = false;
blue = 0;
})
buttons.pinkButton.addEventListener('click', () => {
disableAllButtons();
buttons.blackButton.disabled = false;
pink = 0;
})
buttons.blackButton.addEventListener('click', () => {
black = 0;
checkWinner();
})
}
Use named functions (not anonymous) for event handlers and remove them anytime you want. For example:
// adding
document.addEventListener('click', clickHandler);
// removing
document.removeEventListener('click', clickHandler);
As mentioned in the comments, It is better to keep the state of your program somewhere and act according to that. Adding and Removing handlers is not a good approach.
someHandler(e) {
if(condition) {
// act1
}
else {
//act2
}
}

Can ResizeObserver be used to prevent Onclcik event on element

I am trying to figure out a way to detect that a TextArea is resized so i can prevent it's onClick event from being fired. Reason being that, when resizing the TextArea it also fires the onClick event. However, the onClick event handler has some logic i need to skip if it's a resize.
I tried using the ResizeObserver API to detect the resize event. However, can't seem to locate the 'event' so i can do a preventDefault on it to skip the onClick event.
Not sure if ResizeObserver can be used this way.
Anyone has had this type of requirement met using REsizeObserver or any other approach?
Unfortunately there is no resize event on elements except for on the window, but I would say that this is a solid usecase to use the ResizeObserver API.
Down here I've made an attempt to build something that uses the API and creates a custom event whenever a resize has been made. Because the observer will continuously trigger on every resized pixel, I've added a debounce function in the callback. This will fire the event after 200ms of inactivity.
Then it will fire an event with the data from the resize observer which your field can listen to.
I'm not saying that you should do it like this, but it demonstrates how you could make your own resize event using this API.
const field = document.querySelector('.field');
field.addEventListener('click', event => {
event.preventDefault();
});
field.addEventListener('textarearesize', event => {
console.log('Resize triggered');
const { contentRect } = event.detail;
const { width, height } = contentRect;
field.value = `width: ${Math.floor(width)}, height: ${Math.floor(height)}`;
});
const onResizeCallback = (() => {
let initial = true;
let timeout;
return entries => {
if (initial) {
initial = false;
return;
}
clearTimeout(timeout)
timeout = setTimeout(() => {
for (const entry of entries) {
const event = new CustomEvent('textarearesize', {
detail: entry
});
entry.target.dispatchEvent(event);
}
}, 200);
}
})()
const observer = new ResizeObserver(onResizeCallback);
observer.observe(field);
<textarea class="field" name="description">Hello, I'm a textarea</textarea>
Alternative you could check if the field has a changed in either height or width whenever you release the mouse. This does not require a ResizeObserver and is less complex to create and has more predictable behavior than the example above.
const field = document.querySelector('.field');
const watchField = field => {
let width = field.offsetWidth;
let height = field.offsetHeight;
const areDimensionsChanged = (newWidth, newHeight) =>
width !== newWidth || height !== newHeight;
field.addEventListener('mouseup', event => {
const { target } = event;
const { offsetWidth, offsetHeight } = target;
if (areDimensionsChanged(offsetWidth, offsetHeight)) {
width = offsetWidth;
height = offsetHeight;
const event = new CustomEvent('textarearesize');
target.dispatchEvent(event);
}
});
}
field.addEventListener('textarearesize', event => {
console.log('Resize triggered');
});
watchField(field);
<textarea class="field" name="description">Hello, I'm a textarea</textarea>

Capturing events in ajax modal

Okay, so on this project I have kind of a complicated set up. This is a Phaser 3 game. I load all of my scripts in the header of the html file. window.onload calls App.start(), which configures and loads Phaser and the scenes. In my title scene class, I make an ajax call and retrieve an html template which is then displayed in a modal. I cannot seem to handle events within the generated modal to work.
I've tried:
$('#loginForm').on('submit', (event) => {
event.preventDefault();
let values = $('#loginForm').serialize();
console.log(values);
}
and
$(document).on('submit', '#loginForm', (event) => {
event.preventDefault();
let values = $('#loginForm').serialize();
console.log(values);
}
as well as trying to bind to the actual submit button. With everything I've tried, the page reloads, and processes the form as a get submission (values are appended to the URL). I should note that neither the action not method of the form have been set; only the id.
What can I do to capture the submit event?
EDIT: Adding code
App.js
let App = function() {};
App.prototype.start = () => {
let scenes = [];
scenes.push(Loading);
scenes.push(Title);
scenes.push(Start);
let config = {
type: Phaser.AUTO,
width: 900,
height: 600,
parent: 'gameDiv',
scene: scenes
};
let game = new Phaser.Game(config);
...
};
Title.js (current testing)
class Title extends Phaser.Scene {
constructor(){
super({key: 'Title'});
}
preload(){}
create(){
let _t = this;
let btnLogin = this.add.sprite(300, 350, 'login');
let btnRegister = this.add.sprite(570, 350, 'register');
let logoText = this.add.bitmapText(80, 100, 'Lombardic', 'Dragon Court', 108);
btnLogin.setInteractive();
btnRegister.setInteractive();
btnLogin.on("pointerdown", (pointer) => {
DC.templateCall('user', 'mode=login_tpl', () => {
DC.templateFormHandler('loginForm', (event) => {
event.preventDefault();
let loginData = $("#loginForm").serialize();
console.log(loginData);
/*
modal.close();
Ajax.call('user', params, (result) => {
if(result.status){
_t.scene.start('Start', { fr: result.data.first_run, hc: result.data.has_char });
}else{
DC.modal('LoginError', '<div>'+result.error+'</div>', () => {});
}
});
*/
});
});
});
}
}
templateFormHandler function:
templateFormHandler: (id, callback) => {
$("#"+id).on("submit", callback);
}
I still can't find a legitimate reason for the code above not to work, but as a workaround, I've simply moved the handlers to the template file itself, and all works fine. A matter of scope somehow, I suppose.

How to tell if column resize is done manually vs. automatically with onColumnResized()?

Before ag-grid v11.0, sizeColumnsToFit() fired with an event that did not pass the parameter 'finished=true'. When a user manually resized a column, the event would pass 'finished=true' once the resize drag was complete. This allowed me to distinguish between a manual and automatic column resize.
As of ag-grid v11.0, sizeColumnsToFit() now fires an event with parameter 'finished=true'. Is there any way to distinguish between this automatic resize and a manual user resize?
The ColumnEvent, from which the ColumnResizedEvent is derived has a "source" property that reads "sizeColumnsToFit" or "uiColumnDragged" and even "autosizeColumns" when a you double click the partition.
https://www.ag-grid.com/javascript-grid-events/#properties-and-hierarchy
You should be able to use the source to determine how the event was fired.
myEventHandler(ev: ColumnResizedEvent) {
if (ev.source === 'sizeColumnsToFit') {
do.this;
} else {
do.that;
}
}
when you drag the column manually, source is always "uiColumnDragged"
if (event.source === 'uiColumnDragged') {
// your logic here
}
Code added since 10
colsToFireEventFor.forEach( (column: Column) => {
let event: ColumnResizedEvent = {
type: Events.EVENT_COLUMN_RESIZED,
column: column,
columns: [column],
finished: true,
api: this.gridApi,
columnApi: this.columnApi
};
this.eventService.dispatchEvent(event);
});
You can try to modify comment out finished: true property or just use version 10.0, where this func looks like this:
colsToFireEventFor.forEach( (column: Column) => {
let event = new ColumnChangeEvent(Events.EVENT_COLUMN_RESIZED).withColumn(column);
this.eventService.dispatchEvent(Events.EVENT_COLUMN_RESIZED, event);
});
We can set manually and automatically columns size in the following way
const onColumnResized = useCallback(params => {
if (params.source === 'uiColumnDragged' && params.finished) { // automatically
gridParams.api.sizeColumnsToFit()
} else { // manually
const colId = params.column.colId
const value = columns.map(v => {
if (v.colId === colId) {
v.width = params.column.actualWidth
}
return v
})
if (value) {
setColumns(value)
}
}
}, [ gridParams, setColumns ])

Categories