How to know which is clicked on: arrayDiv[i].mousePressed(function)?

I am coding like the below. There are a lot of clickable squares on the screen as the image at the bottom shows. I would like to know which one was clicked. But I can not get any value which was clicked from "myBlock[i].mousePressed(whichBlock);". I wonder how to give a value to the "whichBlock" function that I defined.

    var myBlock=[];
    var blockNumber=50;

    function setup(){
      for(var i=0;i<blockNumber;i++){
        myBlock[i]=createDiv(i);
        myBlock[i].size(30,30);
        myBlock[i].position(random(windowWidth*0.8),random(windowHeight*0.8));
        myBlock[i].style("background-color","#FAA");
        myBlock[i].style("border","1px solid #444");

        myBlock[i].mousePressed(whichBlock);
        //this does not send the value which is mousePressed to the function below.
      }
    }

    function whichBlock(n){
      alert(n);
      //myBlock[n].hide();  //I want to do this if possible.
    }

sample

Answers

  • edited June 2016 Answer ✓

    function whichBlock(evt){
      const num = +evt.target.textContent;
      alert(num);
      myBlock[num].hide();
    }
    
  • Dear GoToLoop, Thats great!!! I could not manage it by myself, but I understand how it works now. Event.target.textContent would be very useful to distinguish it. Thank you so much.

  • Answer ✓

    @GoToLoop 's suggestion is a reasonable one as it keeps things fairly simple; though you may not want to store the array index as text in the element - in which case you could simply add it to a data attribute and retrieve it from there...

    There is however a more elegant solution which involves using a closure - which allows you to pass a reference to your block directly to your callback function:

    var blocks=[];
    var blockNumber=50;
    
    function setup(){
      for(var i=0;i<blockNumber;i++){
        // this self-invoked function creates a closure
        (function() {
            // ...that means this variable is protected
            // in each iteration of the loop
            var block = createDiv(i);
    
            block.size(30,30);
            block.position(random(windowWidth*0.8),random(windowHeight*0.8));
            block.style("background-color", "#FAA");
            block.style("border", "1px solid #444");
    
            // here we pass an anonymous function to mousePressed
            block.mousePressed(function() {
                // where we can reference block directly
                block.hide();
                // alternatively you could pass the reference to 
                // an external callback function from here:
                //whichBlock(block);
            });
    
            // add the block to the array
            blocks.push(block);
    
        })(); //end: self-invoked function
    
      }//end: for loop
    }//end: setup
    

    It has to be said that this is a JavaScript concept that often catches people out and can initially be difficult to get your head round; so you may prefer the previous solution...

    Note also that the self-invoked function is something of a historical hack to get around JS only having function level scope. In ECMAScript2015 you can set block level scope using let.

  • edited June 2016 Answer ✓

    Nice closured callback approach @blindfish! =D>
    Only critique is that it stores 50 anonymous callbacks rather than re-using 1 only like the whichBlock().

    ... though you may not want to store the array index as text in the element - in which case you could simply add it to a data attribute and retrieve it from there...

    Excellent idea! By chance blocks' indices happen to be exactly the corresponding Element's innerHTML.

    But for most cases that may not be true. Store them in _data-*_ via attribute() is more adequate:
    block.attribute('data-idx', i);

    Then use HTMLElement's dataset property to access them and we're done: :-bd
    blocks[evt.target.dataset.idx].hide();

    Online link: http://p5js.ProcessingTogether.com/sp/pad/iframe/ro.CkNQ4FRp3hEzHn/latest

    <script src=http://p5js.org/js/p5.min.js></script>
    <script src=http://p5js.org/js/p5.dom.js></script>
    
    <script>
    
    /**
     * createDiv() + dataset (v1.01)
     * Mirror + GoToLoop (2016-Jun-11)
     *
     * https://forum.Processing.org/two/discussion/17088/
     * how-to-know-which-is-clicked-on-arraydiv-i-mousepressed-function#Item_4
     *
     * http://p5js.SketchPad.cc/sp/pad/view/ro.CkNQ4FRp3hEzHn/latest
    */
    
    "use strict";
    
    const DIAM = 0x20, BLOCKS = 50, blocks = Array(BLOCKS);
    let total = document.title = BLOCKS;
    
    function setup() {
      noCanvas();
    
      for (let len = str(BLOCKS).length, i = 0; i < BLOCKS; ++i) {
        const block = blocks[i] = createDiv('#' + nf(i, len));
    
        block.size(DIAM, DIAM)
             .style('background-color', '#FAA')
             .style('border', '1px solid #444')
             .attribute('data-idx', i)
             .mousePressed(hideDivBlockIdx);
      }
    
      randomlyPlaceBlocks();
    }
    
    function hideDivBlockIdx(evt) {
      blocks[evt.target.dataset.idx].hide();
      --total || randomlyPlaceBlocks(total = BLOCKS);
      document.title = total;
    }
    
    function randomlyPlaceBlocks() {
      for (let b of blocks) {
        const x = ~~random(windowWidth  - DIAM),
              y = ~~random(windowHeight - DIAM*2);
    
        b.position(x, y).show();
      }
    }
    
    </script>
    
  • edited June 2016

    Hello again, Both of you @blindfish & @GoToLoop are wonderful!
    The ideas are almost out of my scope.
    For myself, I did a little improvement referring to @blindfish's suggestion about using data attribute.
    For this time, I do not want to show the index numbers on the squares, which means a case of empty innerHTML/innerTEXT.

    var myBlock=[];
    var blockNumber=50;
    
    var myBlock=[];
    var blockNumber=50;
    
    function setup(){
        for(var i=0;i<blockNumber;i++){
            myBlock[i]=createDiv(''); //no index numbers shown
            myBlock[i].size(30,30);
            myBlock[i].position(random(windowWidth*0.8),random(windowHeight*0.8));
            myBlock[i].style("background-color","#FAA");
            myBlock[i].style("border","solid 1px #444");
    
             //instead, I added attribute values which are invisible on the screen
             myBlock[i].attribute("data-index-number",i);
    
             myBlock[i].mousePressed(whichBlock);
        }
    }
    
    function whichBlock(evt){
        const num = +evt.target.dataset.indexNumber;
            //alert(num);
        myBlock[num].hide();
    }
    

    This is good enough for me. But I did another code which is like this. It might be a bit strange way...

    var myBlock=[];
    var blockNumber=50;
    
    function setup(){
        for(var i=0;i<blockNumber;i++){
            myBlock[i]=createDiv('');// no numbers shown
            myBlock[i].size(30,30);
            myBlock[i].position(random(windowWidth*0.8),random(windowHeight*0.8));
            myBlock[i].style("background-color","#FAA");
            myBlock[i].style("border","solid 1px #444");
    
             //I added value using value() of p5js
             myBlock[i].value(i);
    
             //send the value to the "whichBlock" function
             myBlock[i].mousePressed(function(){
                whichBlock(this.value());
             });
        }
    }
    
    function whichBlock(num){
        myBlock[num].hide();
    }
    

    It works fine but I do not know why. Does it make sense?
    The point was I wanted to use invisible values to identify them.
    Thank you so much. It helped me a lot anyway.

  • edited June 2016

    It works fine but I do not know why. Does it make sense?

    Nice finding! Keyword this typically points to the object which had invoked the function: <:-P
    https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

    For the mousePressed()'s callback, its this is the p5.Element object which createDiv() returns:
    http://p5js.org/reference/#/p5.Element

    For a callback like this: function whichBlock(evt) {}, if we have the following statement there: alert(this.elt == evt.target);, it's gonna popup true. :-B

    p5.Element's property elt points to the actual HTMLElement wrapped by it:
    http://p5js.org/reference/#/p5.Element/elt

    In turn, Event's property target coincidentally points to that same HTMLElement too. $-)
    Therefore we just need this rather than blocks[evt.target.dataset.idx] or even blocks[this.value()].

    Actually we don't even need the blocks[] array. Neither value() nor attribute(). Only this! *-:)

    Here's latest tweaked version 2.0, which replaced everything by mere this.hide();! \m/

    And b/c there's no blocks[] anymore, randomlyPlaceBlocks()'s for..of loop is accessing the "secret" p5's property _elements as its replacement: for (let b of _elements) { :ar!

    Again it's the same online link: http://p5js.ProcessingTogether.com/sp/pad/iframe/ro.CkNQ4FRp3hEzHn/latest

    <script src=http://p5js.org/js/p5.min.js></script>
    <script src=http://p5js.org/js/p5.dom.js></script>
    
    <script>
    
    /**
     * createDiv() + mousePressed() + hide() (v2.1)
     * Mirror + GoToLoop (2016-Jun-11)
     *
     * https://forum.Processing.org/two/discussion/17088/
     * how-to-know-which-is-clicked-on-arraydiv-i-mousepressed-function#Item_6
     *
     * http://p5js.SketchPad.cc/sp/pad/view/ro.CkNQ4FRp3hEzHn/latest
    */
    
    "use strict";
    
    const DIAM = 0x20, BLOCKS = 50, LABEL = 'clickable-block';
    let total = document.title = BLOCKS;
    
    function setup() {
      noCanvas();
    
      for (let i = 0; i < BLOCKS; ++i)
        createDiv('')
          .size(DIAM, DIAM)
          .style('background-color', '#FAA')
          .style('border', '1px solid #444')
          .class(LABEL)
          .mousePressed(hideP5Element);
    
      randomlyPlaceBlocks();
    }
    
    function hideP5Element() {
      this.hide();
      --total || randomlyPlaceBlocks(total = BLOCKS);
      document.title = total;
    }
    
    function randomlyPlaceBlocks() {
      for (let b of _elements) {
        if (b.class() != LABEL)  continue;
    
        const x = ~~random(windowWidth  - DIAM),
              y = ~~random(windowHeight - DIAM*2);
    
        b.position(x, y).show();
      }
    }
    
    </script>
    
  • @GoToLoop IMHO it's dangerous to rely on 'happy accidents' like this: I don't see anything in the documentation to suggest you can rely on 'this' to be a reference to the elt. As such there's the risk that a change to the library breaks your code. Likewise referencing _elements is a bad idea: that leading underscore is a convention to suggest this is a private property: what happens if the p5 devs decide to make their privacy more robust?

    Re: the closure code I used above: it demonstrated a well established approach (i.e. it's not my original idea!) to solving scope issues and passing parameters to functions from callbacks.

    The point about storing 50 anonymous callbacks is perfectly valid. In a general web context that was never going to be an issue and if the callback does anything remotely heavy you'd simply use it to invoke a separate function that is defined once. Obviously in a canvas context where you might be invoking 1000s of objects it might become a real concern...

    Amended code using let - instead of a self-invoking function - to invoke block level scope:

    "use strict";
    
    var blocks=[];
    var blockNumber=50;
    
    function setup(){
        for(var i=0;i<blockNumber; i++){
    
            // use 'let' to create block level scope
            // works in modern browsers
            let block = createDiv(i);
    
            block.size(30,30);
            block.position(random(windowWidth*0.8),random(windowHeight*0.8));
            block.style("background-color", "#FAA");
            block.style("border", "1px solid #444");
    
            // here we pass an anonymous function to mousePressed
            block.mousePressed(function() {
                // where we can reference block directly
                // and pass the reference to a
                // separate function if need be:
                hideBlock(block);
            });
    
            // add the block to the array
            blocks.push(block);
    
        }// end for loop
    }
    
    
    function hideBlock(block) {
        block.hide();
        console.info(block.elt.textContent);
    }
    
  • edited June 2016

    I don't see anything in the documentation to suggest you can rely on this to be a reference to the elt.

    In order to avoid any confusion, lemme repeat that elt is a property of wrapper type p5.Element which points to the actual underlying HTMLElement object: http://p5js.org/reference/#/p5.Element/elt

    Callback hideP5Element() is assuming that its this is of datatype p5.Element rather than the actual HTMLElement. HTMLElement would be evt.target, where evt is callback's parameter.

    Actually by default, this would be evt.target instead of p5.Element. But an internal function within "p5.Element.js" source called attachListener() applies bind() to all of our passed EventListener callback:

    function attachListener(ev, fxn, ctx) {
      var f = fxn.bind(ctx);
      ctx.elt.addEventListener(ev, f, false);
      ctx._events[ev] = f;
    }
    

    Here's mousePressed() method which calls attachListener():

    p5.Element.prototype.mousePressed = function (fxn) {
      attachListener('mousedown', fxn, this);
      attachListener('touchstart', fxn, this);
      return this;
    };
    

    Therefore, this is guaranteed to be of type p5.Element as long attachListener() applies bind() upon its fxn parameter.

    IMO, bind() isn't necessary. And it's instantiating wrapper bound functions over our own callbacks.
    As mentioned, w/o bind(), this would be of type HTMLElement instead.

    Read about "The value of this within the handler" below for more indepth info about it:
    https://developer.Mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#The_value_of_this_within_the_handler

  • edited June 2016

    Likewise referencing _elements is a bad idea: ...

    Indeed I've made a potential buggy mistake, which noCanvas() is by chance overshadowing it. b-(
    "Private" _elements[] property stores all p5.Element objects, including even the canvas! @-)

    Since my sketch only got createDiv() + noCanvas(), _elements[] got exactly 50 p5.Element objects.

    But we shouldn't assume as such, b/c if we add more things to the program, we may end up introducing more p5.Element objects. :-SS

    Therefore, if we wanna rely on _elements[], we gotta "label" those p5.Element object groups we need to iterate over. For such, we can use class() or addClass() methods: *-:)

    1. http://p5js.org/reference/#/p5.Element/class
    2. http://p5js.org/reference/#/p5.Element/addClass
    const LABEL = 'clickable-block';
    
    createDiv('').class(LABEL).mousePressed(hideP5Element);
    
    function randomlyPlaceBlocks() {
      for (let b of _elements) {
        if (b.class() != LABEL)  continue;
    

    Already applied those fixes to my sketch v2.1 above. :bz

  • Therefore, this is guaranteed to be of type p5.Element as long attachListener() applies bind() upon its fxn parameter.

    Unless it is a documented feature of the API there is no guarantee that it won't change in future. I understand the desire to understand how the library works internally; but if you want to write sustainable code then you should limit yourself to the documented API. You should also avoid exploiting 'happy accidents' like these; particularly if doing so is dependent on adding hacks like adding class(LABEL) 8-X

  • edited June 2016

    Unless it is a documented feature of the API there is no guarantee that it won't change in future.

    Due to the experimental nature of p5.js project nothing is guaranteed there; be it documented or internal implementation! :ar!
    After all, it's still version 0.51 currently. That means it hasn't reached the "gold" 1.0 stable state. :-\"
    As long as var f = fxn.bind(ctx); statement stays that way, this means p5.Element. :P

    ... on adding hacks like adding class(LABEL).

    Specifying a className for some group of Element objects is the default procedure for CSS styling.
    That is, some specific styling can be applied to a whole "labeled" group everywhere.
    The opposite is the Element's id, which is unique by web standards:

    1. https://developer.Mozilla.org/en-US/docs/Web/API/Element/className
    2. https://developer.Mozilla.org/en-US/docs/Web/API/Element/classList
    3. https://developer.Mozilla.org/en-US/docs/Web/API/Element/id
  • function randomlyPlaceBlocks() {
      for (let b of _elements) {
        if (b.class() != LABEL)  continue;
    

    BtW, _elements[] + class() check above can be replaced by selectAll(): *-:)
    http://p5js.org/reference/#/p5/selectAll

    function randomlyPlaceBlocks() {
      for (let b of selectAll('.' + LABEL)) {
    
  • I take your point on the beta nature of p5, but that's even more reason to stick to the documented path, rather than straying into the jungle ;)

    Specifying a className for some group of Element objects is the default procedure for CSS styling.

    ...but you're using a style attribute to store state: nasty. Of course in code under your control you can get away with it, but it's still bad practice :P

    If you really must store state on elements use data attributes ;)

  • edited June 2016

    If you really must store state on elements use data-* attributes.

    I've already done that in my version 1.01@ line #30: .attribute('data-idx', i).
    But those were for individual label values, not "groupie". :P

    ...but you're using a style attribute to store state: nasty.

    It's neither style() nor attribute() but class()! [-X
    They go into both Element's properties className & classList. :-B

    Statement createDiv('').class(LABEL); is analogous to <div class=clickable-block></div> btW. ;;)

  • You misunderstood: 'class' is an attribute (of an html tag) for storing style information, so is (technically) inappropriate for state data. I didn't mean the style attribute ;)

  • edited June 2016

    Both class & id HTML tag attributes (not style 1s) are mainly used as CSS selectors for styling purposes.
    But in no way those attributes are exclusive for CSS code. [-(
    JS also got access to them via document global variable. Some examples:

    Labeling and querying elements w/ id or class is as old as HTML. Be it for CSS or JS or both. >-)

  • Labeling and querying elements w/ id or class is as old as HTML. Be it for CSS or JS or both.

    I don't dispute that; but you're talking about explicitly storing state in a presentational attribute. Experienced front end devs will tell you that this is bad practice that will lead to brittle code; even though most of us will have been guilty of doing it ;)

  • edited June 2016

    ... but you're talking about explicitly storing state...

    That "state" is stored right after the creation time: createDiv('').class(LABEL);
    It's a fixed label and it's not supposed to be mutable at all later on.

    On the other hand, position(), hide() & show() are the mutable state actions within the sketch's logic.

    ... in a presentational attribute.

    It's questionable whether class & id attributes are presentational or not.
    If we replace those 2 style() methods w/ some ".css" file, we'd surely need to label them w/ class().
    So some unified style would be applied to all of those <div> from the CSS code.

    Within the JS sketch, the class attribute is used to distinguish which elements to mutate their position and visibility rather their other styles.

    But somehow you insist my usage of class inside JS is some kinda hack.
    While I'm sure you'd laud class for exclusive CSS code usage. 8-|

  • edited June 2016

    ... this is bad practice that will lead to brittle code; ...

    This is just 99% self-contained programmatic JS code + p5.js library.
    Rather than the traditional trio: HTML + CSS + JS. :P

  • You still fail to understand my point. As I've implied, whilst using class to store state is considered bad practice (especially by MVC advocates) it is something that does happen. But your use of it in this context is a hack to get around the fact that your inappropriate reference to _elements has already led you into trouble; because it contains elements you don't wish to manipulate.

    Even accepting the use of the class hack to identify the elements you're interested in, the approach is inefficient: if _elements stores references to all elements created in the sketch you may land up iterating over any number of elements that aren't relevant. All that to save you having to explicitly store references to them in an array under your control... :(|)

  • edited June 2016

    ... to get around the fact that your inappropriate reference to _elements has already led you into trouble;

    I've stated very clearly that in my particular sketch _elements cause no problems at all, since I was using noCanvas() from the very beginning, while using more conservative approaches. See my version 1.01.

    A short after, I've realized _elements would get buggy for other regular sketches, since the canvas itself is tracked by _elements too.

    Nevertheless, I've already posted another approach which replaces _elements by selectAll(): for (let b of selectAll('.' + LABEL)) {
    It still relies on canvas HTML tag attribute for query selection, just like any CSS would.

    And that's my point: 100% JS programmatic approach solutions are as valid as CSS 1s.
    And they're not inappropriate, and neither labeling is some kinda state! ~O)

  • edited June 2016

    ... the approach is inefficient: if _elements stores references to all elements created in the sketch you may land up iterating over any number of elements that aren't relevant.

    That's true! Having some container w/ the actual objects we're interested in is clearly more efficient than iterating over a general pool.

    And that's exactly what I did in my version 1 series w/ const blocks = Array(BLOCKS).
    Version 2 series is more like a challenge at programming a sketch which can reference objects via more unusual means like this, _elements, selectAll(), etc.

    In short, series 1 are for my conservative approaches. Series 2 are my bold advanced 1s. \m/

    On the other hand, all CSS selectors demand a search throughout the whole page in order to find out the id and className attributes where it needs to apply the appropriate styles.

    In my series 2 I do the same, but for position and visibility. Which are also some sorta style too! #:-S

  • edited June 2016

    As I've implied, whilst using class to store state is considered bad practice...

    I still find that accusation bullocks! I'm tagging/labeling a whole group of <div> elements w/ 'clickable-block', in order to query them later for position + visibility styling.

    If I had done some batch of <div class=clickable-block></div> inside the HTML and used CSS for positioning + visibility as well I'm sure you wouldn't imply that class=clickable-block is putting some kinda "state" over them all. ~:>

    In both JS & HTML cases, the labeling is permanent, they don't mutate later at all.
    You'd be right if I was using method class() to mutate 'on' & 'off' when the element is clicked at.
    Or even to store the # of times it's been clicked at! X_X
    In those mutable cases, the data-* properties should be used instead.

    But clearly my usage for class() or selectAll() are only for querying purposes inside randomlyPlaceBlocks()'s loop. We can't use querying functions for data-* properties after all. :-@

  • I still find that accusation bullocks!

    young male cows? :))

    bad practice doesn't mean don't use it. It just means you should use with caution and avoid if possible; particularly in large scale projects.

    This article does a better job of justifying my recommendation ;)

  • edited June 2016

    Seems like you're not paying attention to my replies that well. :-&
    I've clearly mentioned that 'clickable-block' is a permanent label/tag/tattoo which is set at creation time and doesn't go away. And they're used for element querying selection only.

    I've also mentioned bad stateful examples like mutating 'on' and 'off' labels, increasing the label as a counter for how many times the element was clicked and such.

    Now you post a link to an article which mentions toggling the 'clickable' label as bad.
    Which is clearly right. But doesn't represent my immutable & correct usage of the label at all! (:|

  • Perhaps I haven't been especially clear that the problem here is the context in which you're trying to justify use of classes as 'labels'.

    IF the class was embedded on elements in the original HTML it would make sense to retrieve them with a DOM query referencing the common class; but you're doing this on elements that are being dynamically added by your own client-side script. In that context it makes no sense to dynamically add a class so you can later use this to query the DOM and retrieve the elements you're creating. You might want to dynamically add classes to apply styling; but, if you later need to reference the elements from within the same script, anything other than storing a direct reference to them at creation time strikes me as baffling and incorrect.

  • edited June 2016

    ... the problem here is the context in which you're trying to justify use of classes as 'labels'.
    If the class was embedded on elements in the original HTML...

    That's exactly the prejudice point I've been exposing about a couple of times:

    • If that's done in HTML + CSS it is justified according to ya.
    • If the very same thing is done in JS, it's hackish and/or inappropriate. 8-X
    • Querying elements which were added via JS is wrong. Only those created by HTML are valid! 8-}

    I understand your point that I coulda simply tracked those elements via containers in JS. That's what my version series 1 does after all.

    However I can't accept either that opting to query any DOM element, no matter the language which were used to create them, is somehow wrong! [-X

    That's very prejudice doncha think? ;;)

Sign In or Register to comment.