2

The following script is aimed to run on facebook.com's conversations page (the page in which a user can see all its conversations).

The script's purpose is to automize the "delete conversation" process which naturally includes 4 clicks and can be tiresome and time wasting when you have hundreds of conversations --- deletion will be done from keyboard by hitting the "D" key.

I run the script with Greasemonkey.


The script is comprised of 4 main segments:

  1. Listen to all "D" key hitting events.
  2. Click the link that opens the modal with the "Delete" option (the small chainwheel).
  3. Clink the "Delete" link in that modal (it will open a second modal "delete confirmation").
  4. Click the new "Delete" link in that modal, to confirm conversation deletion.

My script

document.addEventListener('keydown', (k)=>{
    if ( k.keyCode === 68 ) {
        console.log('keydown: D');
        return dC();
    }
});

let dC = ()=>{
    document.querySelector('._5blh._4-0h').click();
    document.querySelector('.uiContextualLayer > [id^="js"] > div > ul > li:nth-child(4)').click();
    setTimeout(()=>{ document.querySelector('._3quh._30yy._2t_._3ay_._5ixy').click(); }, 500);
};

As a beginner, I tried put parts of the code in functions, I tried iterating with for instead forEach(), I tried using return dC() or return false under the dC() call. All of these yielded the same results so I walked in circles not understanding (or denying) a deeper logical error which I sorely miss.

Reproducing

Install as Greasemonkey script, (match as https:www.facebook.com/* just for the test), go to conversations page and hit "D".

My question

Why the event is listened only once? That is, why clicking D once will case the script to work but any further clicks will do nothing?

I will have to refresh the page for that to reuse the script and that's not the intended behavior.

Note: I would prefer a vanilla solution.

user8551674
  • 173
  • 1
  • 2
  • 13
  • 1
    if `document.querySelector(' ._5blh._4-0h ')` doesn't find anything, then the rest of the script wont run, because of the error thrown by `document.querySelector(' ._5blh._4-0h ').click();` – Jaromanda X Sep 04 '17 at 02:50
  • I swear that for me it finds and opens the modal... – user8551674 Sep 04 '17 at 02:51
  • 1
    oh, I'm not saying that is your issue, I'm saying that if that fails, then the following code wont even run - have you checked the **developer** tools console for any errors? – Jaromanda X Sep 04 '17 at 02:52
  • Yes, several times. There are no errors there - in fact, if I click "D" and move until being stuck in segment 2, but than run segment 3 in console --- It works (a conversation is deleted)... yet I still can't reuse the script when clicking "D". – user8551674 Sep 04 '17 at 04:19
  • You most certainly have a bug in this code or somewhere else... or the clicks are firing but perhaps the conversations aren't really deleted (i.e. removed) but rather hidden in the DOM, so after the first keydown it looks like nothing is happening but the events are still firing on the hidden elements. See: https://jsfiddle.net/wmvLv7jL/ You can just hold the D key down or hit it multiple times - it obviously works. – skyline3000 Sep 04 '17 at 18:16
  • FYI: `string.contains` is probably not something that exists, it would be `string.includes` or `string.indexOf` instead – adeneo Sep 04 '17 at 18:23
  • @skyline3000, I tried to hold D until I printed above two hundred logs of it in the console. Yet nothing happened. There is no error... The hits after the 1 just being ignored. – user8551674 Sep 04 '17 at 23:45
  • I'm pretty sure you want to use `setTimeout`, not `setInterval` - otherwise every keypress will start an interval, which will quickly exhaust resources. – Bergi Sep 05 '17 at 01:09
  • Please do some logging and tell us what part doesn't work as expected on the second run. Does it find the elements you are looking for? Log them right before you call `.click()`. – Bergi Sep 05 '17 at 01:12
  • Still, no errors and exact same console behavior happens (each D adds 1 more count). I putted `console.log()` right before `.click()`. Anyway, I tried change setInterval to setTimeout, it stopped working even if ms raised to 1000. I don't know why. – user8551674 Sep 05 '17 at 02:29
  • Make sure you are logging all of the elements and not just some generic message. Store `document.querySelector('._5blh._4-0h')` as a variable and log it before the click. Log every button and span `e` right before the click. Then check in the inspector that those are the elements you think they are - something tells me they are not (I don't see how they can be since on every keydown you are clicking on every span and button... those elements don't change between keydown events). – skyline3000 Sep 06 '17 at 02:38

2 Answers2

1

As written, function dC(), it does 4 actions:

The problem was in action #3:

Seemingly, every click on 'settings' menu (action #2), a new 'settings-menu' instance is created (new object added to the DOM), thus this line won't work:

document.querySelector('.uiContextualLayer > [id^="js"] > div > ul > li:nth-child(4)').click();

On the one hand, querySelector returns the first element that answers the given pattern.

On the other hand, Facebook creates a new '.uiContextualLayer' each time the settings button is clicked (target the menu chainwheel link and stretch up your devtool window to note the new element added).

Hence, what we do is to check all chainwheel elements after and then work with the newest (last) element each time:

let menu = document.querySelectorAll('.uiContextualLayer._5v-0._53il');
menu = menu[menu.length-1];


Here is the final code.
(I added few more timeouts to make sure drawing the UI is finished)

let dC = ()=>
{
  // clicking the 'settings'
  document.querySelector('._5blh._4-0h').click();
  setTimeout(() => {
    // taking the last instance of 'menu popup':
    let menu = document.querySelectorAll('.uiContextualLayer._5v-0._53il');
    menu = menu[menu.length-1];

    // finding 'delete' button inside the menu popup
    let lis = menu.querySelectorAll('ul > li');
    let target = null;
    for (let i=0;!target && i<lis.length;++i)
    {
        let span = lis[i].querySelector('a span span');
        if (!span) continue;
        if (span.innerHTML.contains('Delete'))
            target = lis[i];
    }
    if (!target) {console.log('cannot find delete btn'); return;}


    // clicking 'delete' button in menu
    setTimeout(() => {
        target.click();

        setTimeout(()=>{ 
            // clicking delete in modal
            document.querySelector('._3quh._30yy._2t_._3ay_._5ixy').click(); 
        }, 500);

    }, 10);
  },10);
};
OsiOs
  • 39
  • 10
Omeriko
  • 1,173
  • 11
  • 22
0

Push the key values into arrays and unset these arrays after the event was over.

// Create two empty arrays
    var map = [];
    var down = [];

    $(document).on('keydown', 'body', function(e) {
// Check whether the map having any key values
      if (!map[e.which]) {
// Set the keydown value to down array
        down.push(e.which);

        if (down[0] === 68) {
          // Events to be done
        }
      }

    map[e.which] = true;
// Once the key-down pressed and event done ,the keyup unset the array while key up 
    }).keyup(function(e) {
      map[e.which] = false;

      // unset(down,e.which);  
      down = [];
      e.which = [];
    });
user8551674
  • 173
  • 1
  • 2
  • 13
Harish Karthick
  • 710
  • 2
  • 12
  • 26
  • @user8551674 i'll try if its useful upvote and make it as accepted so it will also help others – Harish Karthick Sep 04 '17 at 06:09
  • Sure no problem. I would edit myself, but I prefer let you the honor of making it only vanilla, my edit my be rejected as deviated from the OP's intention... – user8551674 Sep 04 '17 at 06:10
  • Harish, can you please add comments on what is being done? For me personally it's not very clear and it is important for me to learn. Also, I'm sure this could ensure more thumbs up. – user8551674 Sep 04 '17 at 12:53
  • @user8551674 i updated the code with comments , iam just setting the key press value to array and destroy the event on keyup .the purpose is to avoid the event happening in individual press eg: shift+g-> some event: the event happen only when i press both at same time else it wont ouccer – Harish Karthick Sep 05 '17 at 01:18
  • Thank you again!!! What I miss is what is the `map` var and what is the `[e.which]` syntax. – user8551674 Sep 05 '17 at 02:32
  • Oh, and please Harish, I don't know jQuery, but please, why do we need the `body` in the middle of `$(document).on('keydown', 'body', function(e) {...}`? – user8551674 Sep 05 '17 at 02:54
  • 1
    @user8551674 This is exact meaning for $(document).on('keydown', 'body', function(e) {...} "when the document is clicked, if it's something with the body then trigger this code" " To know more detail follow the link :" https://stackoverflow.com/questions/14879168/document-onclick-id-function-vs-id-onclick-function – Harish Karthick Sep 05 '17 at 03:04
  • @user8551674 The event.which property normalizes event.keyCode and event.charCode. It is recommended to watch event.which for keyboard key input. For more detail, read about event.charCode on the MDN.(https://developer.mozilla.org/en/DOM/event.charCode#Notes) – Harish Karthick Sep 05 '17 at 03:07