2

UPDATE 4 Hi @Paul. I think I know what's happening, I just don't know how to fix it. The Alert I had in touchstart was causing the app to stop and when I clicked OK the touchend event had already passed. I removed the touchstart Alert and the touchend alert worked, the istouch value was "true". I added a few more alerts to see where it is failing and found that math.abs(e.pageX) was not a number - the alert showed NaN. Also $snd.data('tx') showed as "Undefined". Because of this vx was also reported as NaN. The value for ds displayed a numeric value.

So, I think the problem is that $snd is defined in .on(touchstart...) and is not referencable from .on(touchend...). Could this be a scope issue for the variables? At least the original terms and the search results that are copied into the search results page are showing the same symptoms so the onclick handler is being triggered which is good progress. Below is the current version of the click handlers, could you let me know what I should change, I'm quite new at js and this has been really challenging for me. Thanks again.

$(".wrapper")  //i'm attaching all event handlers to body, so everything with .spanish class will have them attached
        .on("click", ".spanish", function(e) {
            e.preventDefault();
        })
        .on("touchstart", ".spanish", function(e) {
            if (istouch) {
                var $snd = $(this);
                $snd.data({
                    tstart: new Date().getTime(),
                    tx: e.pageX,
                    ty: e.pageY
                });
            }
        })
        .on("touchend", ".spanish", function(e) {
            if (istouch) {
                alert("touchend detected istouch val = " + istouch); // shows istouch = true
                var $snd = $(this);
                var snd = this;
                var ds = $snd.data('tstart');
                alert("ds = " + ds); // shows a numeric value
                if (!ds) {
                    return;
                }
                alert("math.abs e.pagex = " + Math.abs(e.pageX)); // shows NaN
                alert("$snd.data('tx') = " + $snd.data('tx')); // shows "undefined"
                var vx = Math.abs(e.pageX - $snd.data('tx'));
                alert("vx = " + vx); // shows NaN
                var vy = Math.abs(e.pageY - $snd.data('ty'));
                Math.abs(e.pageY - $snd.data('ty'));
                alert("vx= "+ vx +" vy= " + vy + " ds = " + ds);
                if (vx < 20 && vy < 20 && new Date().getTime() - ds < 400) {
                    alert("tap detected and about to call playstop");
                    playstop.apply(snd, [e]);
                    $snd.data({
                        tstart: null,
                        tx: null,
                        ty: null
                    });
                }
            }
        });
    });

UPDATE 3 Hi @Paul, I've made a minor change to your code, I applied the click handler to the .wrapper div instead of Body because there's a home page with links that stopped working when I applied your update, the home page links work OK now. Each English phrase can be translated into 1,2 or 3 Spanish phrases, all of the Spanish phrases are contained within a single .wrapper div for any given English div.

Below is a sample showing 3 English divs with their Spanish translations. I have 800+ English phrases altogether. When the user searches these phrases the complete 'english' div, including the wrapper div, should be copied to the search results page and it looks like it is being copied correctly.

<div class="english">
      How old are you?
   <div class="wrapper">
       <a class="formal spanish" data-ignore="true" href="ageHowOldF.mp3">F: Cuántos años tiene?</a>
       <a class="informal spanish" data-ignore="true" href="ageHowOldI.mp3">I: Cuántos años tienes?</a>
   </div> <!-- End .wrapper -->
</div> <!-- End English -->

<div class="english">
       When were you born?
    <div class="wrapper">
        <a class="formal spanish" data-ignore="true" href="ageWhenBornF.mp3">F: Cuando nació?</a>
        <a class="informal spanish" data-ignore="true" href="ageWhenBornI.mp3">I: Cuando naciste?</a>
    </div> <!-- End .wrapper -->
</div> <!-- End English -->     

<div class="english">
       How old was he?
    <div class="wrapper">
        <a class="spanish" data-ignore="true" href="ageHowOldMale.mp3">Cuántos años ten&iacute;a &eacute;l?</a>
    </div> <!-- End .wrapper -->
</div> <!-- End English -->

UPDATE 2 @Paul, as described in my comment below your reply, please see below the full document ready script with your changes and the searchApp script that matches/ copies the selected element to an empty div.

Here is the document ready script:

 jQuery(function($){
            var highlight = 'yellow', origcolor = 'transparent', curSnd = null,
            istouch = !!('ontouchstart' in window) || !!('ontouchstart' in document.documentElement) || !!window.ontouchstart || (!!window.Touch && !!window.Touch.length) || !!window.onmsgesturechange || (window.DocumentTouch && window.document instanceof window.DocumentTouch);
        function playstop(e){
            alert("Start playstop");
            e.preventDefault();
            var $this = $(this);
            if(curSnd && curSnd.sound){
                if(this === curSnd.tag){
                    curSnd.sound.stop();
                    return;
                }
                curSnd.sound.stop();
            }
                    $this.stop(true, true).css({backgroundColor: highlight});
            var filename = this.href.substring(this.href.lastIndexOf('/')), myMedia = new Media(
                this.href, 
                function() {
                    myMedia && myMedia.release();
                    curSnd = myMedia = null;
                    $this.stop(true, true).animate({backgroundColor: origcolor}, 500);
                },
                function(e) {
                    myMedia && myMedia.release();
                    curSnd = myMedia = null;
                    $this.stop(true, true).animate({backgroundColor: origcolor}, 500);
                    window.console && console.log ("Audio play error - " + filename + "\ncode: " + e.code + "\nmessage: " + e.message);
                }
            );
            alert("Start playing sound")
            curSnd = {tag: this, sound: myMedia}; 
            curSnd.sound.play();
        } // End playstop
    $("body")  //i'm attaching all event handlers to body, so everything with .spanish class will have them attached
            .on("click", ".spanish", function(e) {
                e.preventDefault();
            })
            .on("touchstart", ".spanish", function(e) {
                if (istouch) {
                    var $snd = $(this);
                    $snd.data({
                        tstart: new Date().getTime(),
                        tx: e.pageX,
                        ty: e.pageY
                    });
                }
            })
            .on("touchend", ".spanish", function(e) {
                if (istouch) {
                    var $snd = $(this);
                    var snd = this;
                    var ds = $snd.data('tstart');
                    if (!ds) {
                        return;
                    }
                    var vx = Math.abs(e.pageX - $snd.data('tx'));
                    var vy = 'enter code here';
                    Math.abs(e.pageY - $snd.data('ty'));
                    if (vx < 20 && vy < 20 && new Date().getTime() - ds < 400) {
                        playstop.apply(snd, [e]);
                        $snd.data({
                            tstart: null,
                            tx: null,
                            ty: null
                        });
                    }
                }
            });
        });

</script>

Here is the search/Copy script

function searchApp() {
    var $searchTerm = $(".searchField").val().toLowerCase(); // Convert search term to all lower case
    var $searchResults = "";
    document.getElementById("searchResultsDiv").innerHTML = "";
    $(".english").each(function () {
        if ($(this).text().toLowerCase().match($searchTerm)) { // If this specific '.english' class 
    $(this).clone(true, true).appendTo("#searchResultsDiv");
        }
    });
}; // /searchApp

UPDATE

I think I've localized the area that is causing my problem - which is when I copy an element to an empty div in a search results page the event listener from the original element is not being triggered when I tap on the search results page.

Below is a sample English/ Spanish element that is being copied:

<div class="english">
    How old are you?
    <div class="wrapper">
        <a class="formal spanish" data-ignore="true" 
            href="ageHowOldF.mp3">F: Cuántos años tiene?</a>
        <a class="informal spanish" data-ignore="true"
            href="ageHowOldI.mp3">I: Cuántos años tienes?</a>
    </div> <!-- End .wrapper -->
</div> <!-- End English -->

and here's the code that copies the original element when it finds a match on the English text:

if ($(this).text().toLowerCase().match($searchTerm)) {
    $(this).clone(true, true).appendTo("#searchResultsDiv");
}

The English/Spanish phrases correctly appear on the search results page but do not respond to a tap. I added an alert message to the event listener and this does not display when I tap on the search page but does display when i tap on the original page.

I would really appreciate any suggestions you may have. Thanks.

END UPDATE

I have a smartphone app that is a talking English/Spanish phrasebook, when the user taps a Spanish phrase a short mp3 file is played by jQuery and this is working correctly, the jQuery player is assigned at document ready. I have now created a Search function which copies all matching English/Spanish phrases to a search results page and this also works correctly.

The problem I’m having is when the user taps any of the Spanish phrases on the search results page the app invokes the system sound player (I think) and not the jQuery sound player and the system sound player displays an audio control bar, which I don’t want my users to see.

I have tried everything I can think of, basically following two approaches:

  1. Copy all the attributes of the element
  2. Assign an onclick event to a parent element in the search results page

Neither of these approaches fixes this – at least nothing I know of fixes this.

I’m pretty new to js/jQuery so please forgive me if I’ve missed something blindingly obvious.

Please find below:

  1. A sample English/Spanish element, there are several hundred of these altogether. These are copied to the search results page if the search matches the English phrase/word
  2. The document ready script that assigns the click handler to the Spanish phrases
  3. The search function that finds and copies matching elements to the search results page

SAMPLE English/ Spanish element

<div class="english">
    How old are you?
    <div class="wrapper">
        <a class="formal spanish" data-ignore="true" 
            href="ageHowOldF.mp3">F: Cuántos años tiene?</a>
        <a class="informal spanish" data-ignore="true"
            href="ageHowOldI.mp3">I: Cuántos años tienes?</a>
    </div> <!-- End .wrapper -->
</div> <!-- End English -->

Document ready script

This also does things like changing the background color, checking for a tap vs. a swipe etc.

jQuery(function($) {
    var highlight = 'yellow',
        origcolor = 'transparent',
        curSnd = null,
        istouch = !!('ontouchstart' in window) || !!('ontouchstart' in document.documentElement) || !!window.ontouchstart || (!!window.Touch && !!window.Touch.length) || !!window.onmsgesturechange || (window.DocumentTouch && window.document instanceof window.DocumentTouch);

    function playstop(e) {
            e.preventDefault();
            var $this = $(this);
            if (curSnd && curSnd.sound) {
                if (this === curSnd.tag) {
                    curSnd.sound.stop();
                    return;
                }
                curSnd.sound.stop();
            }
            $this.stop(true, true).css({
                backgroundColor: highlight
            });
            var filename = this.href.substring(this.href.lastIndexOf('/')),
                myMedia = new Media(
                    this.href,
                    function() {
                        myMedia && myMedia.release();
                        curSnd = myMedia = null;
                        $this.stop(true, true).animate({
                            backgroundColor: origcolor
                        }, 500);
                    },
                    function(e) {
                        myMedia && myMedia.release();
                        curSnd = myMedia = null;
                        $this.stop(true, true).animate({
                            backgroundColor: origcolor
                        }, 500);
                        window.console && console.log("Audio play error - " + filename + "\ncode: " + e.code + "\nmessage: " + e.message);
                    }
                );
            curSnd = {
                tag: this,
                sound: myMedia
            };
            curSnd.sound.play();
        } // End playstop
    $('.spanish').click(function(e) {
        e.preventDefault();
    }).each(function(i, snd) {
        if (istouch) {
            var $snd = $(snd);
            snd.addEventListener('touchstart', function(e) {
                $snd.data({
                    tstart: new Date().getTime(),
                    tx: e.pageX,
                    ty: e.pageY
                });
            }, false);
            snd.addEventListener('touchend', function(e) {
                var ds = $snd.data('tstart');
                if (!ds) {
                    return;
                }
                var vx = Math.abs(e.pageX - $snd.data('tx')),
                    vy = `enter code here`
                Math.abs(e.pageY - $snd.data('ty'));
                if (vx < 20 && vy < 20 && new Date().getTime() - ds < 400) {
                    playstop.apply(snd, [e]);
                    $snd.data({
                        tstart: null,
                        tx: null,
                        ty: null
                    });
                }
            }, false);
        }
    });
});

Search function

function searchApp() {
    // Convert search term to all lower case
    var $searchTerm = $(".searchField").val().toLowerCase();
    var $searchResults = "";
    document.getElementById("searchResultsDiv").innerHTML = "";
    $(".english").each(function() {
        // If this specific '.english' class 
        if ($(this).text().toLowerCase().match($searchTerm)) {
            // Append all HTML contents of '.english' div into $searchResults variable 
            $searchResults += $(this)[0].outerHTML;
        }
    });
    // Within searchResultsDiv tag, write $searchResults variable to HTML
    $("#searchResultsDiv").html($searchResults);
}; // /searchApp

UPDATE 5 I changed the alerts to console.log. First test was to tap a Spanish phrase that already existed on the search results page. Below is the console log

touchstart $snd = [object Object] index.html:67
touchend detected istouch val = true index.html:72
touchend ds = 1427885920974 index.html:76
pageX in touchend = undefined index.html:77
touchend math.abs e.pagex = NaN index.html:82
touchend $snd.data('tx') = undefined index.html:83
touchend vx = NaN index.html:85
touchend vx= NaN vy= NaN ds = 1427885920974 

Second test is tapping one of the created Spanish elements. Here's the log

touchstart $snd = [object Object] index.html:67
touchend detected istouch val = true index.html:72
touchend ds = 1427885920974 index.html:76
pageX in touchend = undefined index.html:77
touchend math.abs e.pagex = NaN index.html:82
touchend $snd.data('tx') = undefined index.html:83
touchend vx = NaN index.html:85
touchend vx= NaN vy= NaN ds = 1427885920974 

Neither of these played a sound. Below is the touchstart and touchend handler scripts showing where I added the console.log statements.

.on("touchstart", ".spanish", function(e) {
    if (istouch) {
    $snd = $(this);
    $snd.data({
         tstart: new Date().getTime(),
         tx: e.pageX,
         ty: e.pageY
                });
    console.log("touchstart $snd = " + $snd);
        }
    })

 .on("touchend", ".spanish", function(e) {
     if (istouch) {
    console.log("touchend detected istouch val = " + istouch);
         var $snd = $(this);
         var snd = this;
         var ds = $snd.data('tstart');
    console.log("touchend ds = " + ds); 
    console.log("pageX in touchend = " + $snd.data('tx'));
    if (!ds) {
                    return;
                }
console.log("touchend math.abs e.pagex = " + Math.abs(e.pageX)); 
    console.log("touchend $snd.data('tx') = " + $snd.data('tx'));
         var vx = Math.abs(e.pageX - $snd.data('tx'));
    console.log("touchend vx = " + vx); 
         var vy = Math.abs(e.pageY - $snd.data('ty'));
         Math.abs(e.pageY - $snd.data('ty'));
    console.log("touchend vx= "+ vx +" vy= " + vy + " ds = " + ds);
         if (vx < 20 && vy < 20 && new Date().getTime() - ds < 400) {
    console.log("touchend tap detected and about to call playstop");
         playstop.apply(snd, [e]);
         $snd.data({
               tstart: null,
               tx: null,
               ty: null
                    });
                }
            }
        });
    });
Tony Babb
  • 85
  • 1
  • 1
  • 9
  • Suggestion: trim your question down to only what is pertinent. That is currently a LOT to read in order to help you. – Mitya Mar 18 '15 at 11:57
  • 3
    You're right there's a lot to read but I'm not really sure exactly where the problem lies. I've trimmed it down - sorry – Tony Babb Mar 18 '15 at 12:02
  • 2
    plz don't say Good Morning/ evening in question! it's read by users spread worldwide and anytime after you posted it. – Vikrant Mar 18 '15 at 12:35
  • i have tweaked http://www.script-tutorials.com/html5-audio-player-with-playlist/ for my homepage, and it plays mp3 without the mobile device sound control bars... well at least on my nexus. dunno if it's the same to ios devices... maybe this will help you – errand Mar 18 '15 at 13:03
  • @errand thanks, I took a look but I'm not sure this will solve my problem, which I think is the click handler, defined at doc ready on 1,000+ Spanish phrases, gets "disconnected" when I copy the Spanish phrases to the search results page. – Tony Babb Mar 18 '15 at 21:09
  • I haven't read it all so I might be mistaken, but I'm guessing this problem will be fixed if you use event delegation for the click handler (https://learn.jquery.com/events/event-delegation/). – myfunkyside Mar 21 '15 at 15:26
  • I noticed that you're binding to your `.wrapper` element it must be one parent object which is not copied itself. As in copying process you're loosing your binds to touchend touchstart – Paweł Tomkiel Mar 25 '15 at 10:44
  • It's so messy question now - try to edit it to be more clearer, also if you need to add UPDATE's - add them at the bottom. But for now - try to change your alerts with `console.log` it'll log your values without interrupting code. As you can read here: https://api.jquery.com/on/ - everything should work out-of-the-box - try to `console.log($snd)` to check if it's correct element. – Paweł Tomkiel Mar 26 '15 at 13:08

1 Answers1

1

Main problem in your code is that you're adding event handlers to one set of objects, and not re-adding them to cloned ones. Delegeation of events would be more suitable for you. Change your ${'.spanish') eventHandlers into:

$("body")  //i'm attaching all event handlers to body, so everything with .spanish class will have them attached
        .on("click", ".spanish", function(e) {
            e.preventDefault();
        })
        .on("touchstart", ".spanish", function(e) {
            if (istouch) {
                var $snd = $(this);
                $snd.data({
                    tstart: new Date().getTime(),
                    tx: e.pageX,
                    ty: e.pageY
                });
            }
        })
        .on("touchend", ".spanish", function(e) {
            if (istouch) {
                var $snd = $(this);
                var snd = this;
                var ds = $snd.data('tstart');
                if (!ds) {
                    return;
                }
                var vx = Math.abs(e.pageX - $snd.data('tx'));
                var vy = 'enter code here';
                Math.abs(e.pageY - $snd.data('ty'));
                if (vx < 20 && vy < 20 && new Date().getTime() - ds < 400) {
                    playstop.apply(snd, [e]);
                    $snd.data({
                        tstart: null,
                        tx: null,
                        ty: null
                    });
                }
            }
        });

Basically, i just splitted your function into two separate jquery events delegation function. All those handlers will be attached to everything matching selector .spanish in body container. You can modify it for some optimization (if you have some div as a wrapper for all those elements).

jQuery $.on function can bind some events (touchstart, touchend and click int his case) to current existing elements, and those which will be created in future, for example in process of cloning elements - it will probably be the best solution for you.

Read more about it here: http://api.jquery.com/on/

Paweł Tomkiel
  • 1,974
  • 2
  • 21
  • 39
  • Hello @Paul, thank you for the code and explanation along with the link to .on() information. I tried the code you offered but unfortunately it didn't work in the search results page, also the existing Spanish entries did not work either, they worked previously. I went through your code and did not see any errors. If you are able to have another look I have added another update to my original question showing the complete doc ready script and the script that copies the selected English/ Spanish phrases. Thank you so much for your time. – Tony Babb Mar 23 '15 at 11:30
  • When I said "didn't work" I should have said they didn't play the short sound files and didn't display the alert messages I added. – Tony Babb Mar 23 '15 at 11:42
  • @TonyBabb - show us more of your HTML - maybe that's where problem lies. – Paweł Tomkiel Mar 23 '15 at 18:51
  • it looks like touchend is not being detected, I added alert messages and the touchstart alert displayed but not the touchend. Here's the touchstart alert.on("touchstart", ".spanish", function(e) { if (istouch) { alert("touchstart detected"); – Tony Babb Mar 25 '15 at 10:23
  • and here's the touchend alert – Tony Babb Mar 25 '15 at 10:24
  • .on("touchend", ".spanish", function(e) { if (istouch) { alert("touchend detected"); – Tony Babb Mar 25 '15 at 10:25
  • @TonyBabb - Can you also check `istouch` value in your `touchend` function? If it's false, alert won't show. Also - maybe this question will help you more: http://stackoverflow.com/a/27070391/2775748 – Paweł Tomkiel Mar 25 '15 at 10:42
  • please see my comments in **UPDATE 4** above – Tony Babb Mar 26 '15 at 10:13
  • @TonyBabb - have you managed to properly run your code as you wanted? – Paweł Tomkiel Mar 30 '15 at 06:59
  • Hi @Paul, no I haven't yet. if you could take a look at **update 4** above I have shown the current code including a number of Alert statements. I added a comment with each alert statement showing what the alert reported. We are really close I think, the problem seems to be that .on(touchend... is not able to access the data stored in .on(touchstart... At least that's what I think the problem is. Thanks for following up. Tony – Tony Babb Mar 30 '15 at 08:12
  • @Tony, as i suggested in previous comment - try to replace alerts with `console.log` - see my comment under your question. – Paweł Tomkiel Mar 30 '15 at 09:39
  • H @Paul, sorry I missed your previous comment. Please see UPDATE 5 above. I changed the alerts to console.log. First test was on an existing Spanish phrase - no sound played, second test was on an element that was copied into the page and again no sound played. Thanks for any advice you may have, my inexperience really shows here... – Tony Babb Apr 01 '15 at 12:09
  • Hi @Paul, I've made progress and determined that the touchstart and touchend handlers are being launched correctly. The problem I see now is that the test for a touch or swipe is not working correctly, it does correctly identify the start and end time and calculates duration but it does not identify the start and end position correctly, that is e.pageX does not work. As this question has become very long and involved I'm going to start a new question showing my test results. Thanks so much for your assistance so far, it was invaluable. – Tony Babb Apr 06 '15 at 08:45
  • @Tony - i'm glad it helped. I'll try to follow up your new question :) – Paweł Tomkiel Apr 06 '15 at 14:27