Scraping from specific part of html based on right click - javascript

I am building a chrome extension whereby I want to be able to right click on a certain part of a page and then scrape some info from it. So using chrome.contextMenus I'd like to be able to only scrape from the element(one of it's attributes) where I've right clicked. Sort of like the behaviour in chrome when you right click somewhere on a page and select inspect it will open the element view on the page element you right clicked. The reason I want to do this is because there will be a number of similar type elements with different ids(attribute) so I want to be able get only the id of the particular element I'm interested in.
Is this even possible?
I was looking though the chrome.contextMenus documentation and I'm wondering if I know the element type(article)could I set the context menu on that and get the id that is stored in it that way?

I'd say your extension has to remember what was the last element under the right click. And content-script suits well for it.
background.js
chrome.contextMenus.create({
title: "Get ID",
id: "menu1",
onclick: function (info, tab) {
// send message about context manu action
chrome.tabs.sendMessage(tab.id, {
msg: 'get_id'
}, {
frameId: info.frameId
});
}
});
content-script.js
let lastClickedEl = null;
// remember last clicked element
document.body.addEventListener('mousedown', function (e) {
if (e.button === 2) { // right click to an element
lastClickedEl = e.target;
}
});
// receive message about context menu action
chrome.runtime.onMessage.addListener(function (request) {
if(request.msg === 'get_id') {
console.log(lastClickedEl.id); // your code here
}
});

Related

How to override a function with another function and then itself

There is an iFrame with buttons inside that I cannot change directly. The requirement is to use this specific iFrame. Inside this iFrame, there have many buttons with the same classname that the user can click to navigate to another page. Outside the iFrame, I have a form of user data and tracking to know when the user has made any changes.
Note: I'm not actually using an iFrame, but the basic idea is I have content that I cannot directly change.
I have a message when the user has unsaved changes and they click on any item inside the iFrame ~ "You have unsaved changes, are you sure you want to discard them?".
I would like to override the original function's definition to have my blocking confirmation logic in front of whatever logic is already there. When the user confirms, I need to continue the original navigation.
The best way I can think to do this is to query the button element and override the button.click() function with an my popup message and then the original function call. Maybe something like this:
useEffect(()=>{
const elements = document.getElementsByClassname("classname");
for (let element of elements){
const originalFunction = element.onclick // <--
element.onclick = () => {
if (blocked) {
confirmation({
content: "You have unsaved changes, are you sure you want to discard them?",
proceed: "Discard changes",
cancel: "Cancel",
handler: function () {
discardChanges();
originalFunction(); // <--
}
});
} else {
originalFunction(); // <--
}
}
}
return ()=>{
restoreOriginalFunctionDefinitions(); // <--
}
},[]);
What is the correct way to inside a function in-front of another function definition?
Is it possible to do this and then restore the original definition when the element unmounts?
Is there just a better way to do this?
I've tried the following useEffect
useEffect(()=>{
const elements = Array.from(document.getElementsByClassName(watchingClassName));
console.info("elements", elements);
elements.forEach((element)=>{
const htmlElement = element as HTMLElement;
AdviceFunction(htmlElement.click, blocking, "Are you sure", "Discard",onDiscard,"Cancel" );
});
}, [])
Advice Function has the following definition:
const AdviceFunction = (
originalFunction,
blocked,
message,
discardCaption,
discardAction,
cancelCaption
) =>
function (...args) {
console.info("advice", { this: this, originalFunction, blocked });
if (blocked) {
confirmation({
content: message,
proceed: discardCaption,
cancel: cancelCaption,
handler: function () {
discardChanges(discardAction);
originalFunction.apply(this, args);
}
});
} else {
originalFunction.apply(this, args);
}
};
Actual result: useEffect successfully queries the dom for the buttons. When a button is clicked, only the original function is ran and the console.info in AdviceFunction is never logged.
I came up with another approach that works.
Use a class name with document.getElementsByClassname to get a list
of content to supersede
Iterate that list to create components that have the same position and dimensions as the elements retrieved (position fixed, top, left, width, height, opacity: 0)
When the user clicks on the component, record the coordinates of the click event then show the confirmation message
If the user confirms the navigation, use document.elementsFromPoint(x,y)1 to get the first content underneath the generated components then click that content
See code here:
Main component https://github.com/bsgriggs/mendix-unsaved-changes-message/blob/master/src/UnsavedChangesMessage.tsx
Blocker component https://github.com/bsgriggs/mendix-unsaved-changes-message/blob/master/src/components/Blocker.tsx
Demo https://widgettesting105-sandbox.mxapps.io/p/unsaved-changes-message
Back to the original example, I can simply add a class to the iFrame itself and any other buttons I want to block to cover it with a transparent div.
In the screenshot, the red content is what was retrieved by the class name
When the user clicks on any red container, it shows the confirmation.

Send message from page to popup after click

Let me start with the fact that I did not find such information, so I am asking.
I would like to make an extension that will get the clicked item and send it to the popup.
I mean something like:
document.addEventListener ('click', (e) => {
console.log (e.target);
e.target;
})
So far I am not able to do it, I was based on this:
Chrome Extension how to send data from content script to popup.html
but I can't send the click information.
I will be grateful for your help.

Bootstrap 4 Sidebar menu to stay open on page reload

I integrated a menu based on https://www.codeply.com/go/T2mpwMOt60 into a website I'm building. Having used it during the build process there is one feature which I feel it misses.
Ideally I would like to have a menu item stay open when navigating to another page. So, looking at the sample menu, that would mean that if Item 3 was open, it should stay open when the page is reloaded, but close if another menu heading was clicked.
Given that every menu section starts with
<a href="#menu1"
<a href="#menu2"
etc, and when opened, the class changes from
class="list-group-item collapsed"
to
class="list-group-item"
I figured that the current menu state could be written to local storage and then read back in on page load to restore the previous state.
Does anyone know of examples that would point me in the right direction on coding this type of functionality?
I've just tried using the following script to save to localStorage
$(document).ready(function () {
$('a').click(function() {
//store the id of the collapsible element
localStorage.setItem('collapseItem', $(this).attr('href'));
});
var collapseItem = localStorage.getItem('collapseItem');
if (collapseItem) {
$(collapseItem).collapse('show')
}
})
It doesn't reopen the menu, but I suspect that is due to what is getting put into local.storage
As an example, When I first click to open the 'customers' sub menu, it stores #menu5, which is the sub menu I would want to be reopened on reload, but when clicking any of the children inside that menu, the stored data will change to the url of the last clicked link.
Additional note, if I reload the page whilst #menu1, #menu2 etc is stored, then it loads with the menu displaying correctly. So it is purely a case of figuring out how to NOT store anything other than the initial #menu open.
The problem is with the objects you are referencing in the event handler.
$(document).ready(function () {
$('a').click(function() {
//store the id of the collapsible element
localStorage.setItem('collapseItem', $(this).attr('href'));
});
var collapseItem = localStorage.getItem('collapseItem');
if (collapseItem) {
$(collapseItem).collapse('show')
}
})
On line 2, it shows that the function will trigger if an a tag is clicked. Try using something else such as button and use that as the link that opens the menus, rather than using the same tag as the one for the hyperlinks. If you can't get that to work then maybe try adding something like an OnMouseDown attribute to each menu-opening button.
Or try my current solution, which checks for a different attribute before saving. This is one that should work, as long as you give each menu-opening link the name here:
<script>
$(document).ready(function () {
$('a').click(function() {
if($(this).attr('name') == "menubtn"){
//store the id of the collapsible element
localStorage.setItem('collapseItem', $(this).attr('href'));
};
});
var collapseItem = localStorage.getItem('collapseItem');
if (collapseItem) {
$(collapseItem).collapse('show')
}
})
</script>
Test 1
Test 2
I wrote those modifications assuming that $ was already defined in your full project, if not then I'll leave it to you to work on that. Tell me if this works.
The solution I used for this is below.
$(document).ready(function () {
$('a').click(function() {
var menuNumber = $(this).attr('href').slice(0, -1);
//console.log(menuNumber);
if (menuNumber == '#menu') {
localStorage.setItem('collapseItem', $(this).attr('href'));
}
var menuHome = $(this).attr('href').slice(-9, -4);
//console.log(menuHome);
if (menuHome == 'index') {
localStorage.setItem('collapseItem', '');
}
});
var collapseItem = localStorage.getItem('collapseItem');
if (collapseItem) {
$(collapseItem).collapse('show')
}
// Clear local storage on menu close action
$('#sidebar .list-group > div').on('hide.bs.collapse', function () {
localStorage.setItem('collapseItem', '');
})
})
I needed to be able to also clear localStorage if the Home link was clicked to prevent the menu from reopening on the last used submenu.
Also added is a check to clear the localStorage data if the arrow icon on the menu was used to close it.
Although it's unlikely that someone would close the accordion in this way and then refresh the page, I thought it better to be thorough.

Fire an event when menu is clicked

I'm working on a Chrome extension. I've used chrome.contextMenus.create to create a menu item, and also passed in the function name to fire on click. Now what I want to do is, get the active link's textContent when I click on the context menu item. What type and/or event should I listen to? I don't know if textContent is even the correct thing. Let me try to describe: When you make a query on Google (let's say "putty" for the sake of example), the first hit is "PuTTY Download Page - Chiark" which points to some URL. What I want to get is this "PuTTy Download Page - Chiark" rather than its URL.
EDIT: This is what I have so far:
chrome.contextMenus.create({'title': 'Add to mySU bookmarks', 'contexts': ['link', 'page'], 'onclick': mySUBookmark});
function mySUBookmark() {
var a = document.addEventListener('click', function() {
...
});
}
I wonder if I'm in the right path.
Regards,
mto
Your click handler, is passed a contextMenusInternal.OnClickData info object, which has some properties of what was clicked (not the actual object itself). For link elements this includes a linkUrl property, which you could use to create an element selector for the element, and pass this to your content script (which can access the page DOM).
This may be something like the below (you may need to change to work for your extension)
function mySUBookmark(info, tab) {
var elSelector = 'a[href="'+info.linkUrl+'"]';
console.log(elSelector);
// now send the selector to the content script on the page so it can use it
// to select the element from the page DOM and do whatever you want with the
// text / html of the element
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, { elSel: elSelector }, function(resp) {});
});
}
These links could help in understanding how all this stuff works,
Sample context menu example
Chrome extension content scripts
Sending message from background to
context script

Javascript: Delaying display of the context menu (Chrome)

I'm trying to dynamically create context menu items in a Chrome extension; this involves sending a request to the main extension process to modify the menu, and blocking the menu from being displayed until a response is sent. I've been experimenting with different solutions all day, and I have yet to come up with one that works. For example,
node.oncontextmenu = initContext({...});
initContext = function(menuParams) {
return function(event) {
if (event.showContext) // Menu has been created and is ready for display
return true;
event.preventDefault(); // Block original menu event
chrome.extension.sendRequest(menuParams, function() { // Send request to modify menu,
event.showContext = true; // generate new event when menu is ready
event.target.dispatchEvent(event);
});
};
};
The menu is blocked with preventDefault(), and the callback function to chrome.extension.sendRequest() is executed when the menu is ready to be displayed. With this solution (and all the others I've tried), the menu is never displayed. I'd appreciate any help.

Categories

Resources