Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 6 of 6
  1. #1
    New to the CF scene
    Join Date
    Jun 2019
    Posts
    6
    Thanks
    2
    Thanked 0 Times in 0 Posts

    Jquery hide collapsing or not?

    This simplified code from my site basically calculating travelling costs based on how many travellers are there.
    Moving the cursor over a seat toggles it on/off and the cost changes accordingly with a hide + fadein effect.
    I expected the DOM element disappear and mess up the layout but it doesn't, works just fine, only the value changes.
    However if I move the cursor quickly, setting two seats off before fadein kicks in, it does collapse.

    Can someone explain the reason behind this? I'd like to do it the right way (css visibility hidden for example if needed) but I don't really understand this double-effect.

    http://veveve.hu/utitars/js2.html

    Code:
    <!DOCTYPE html>
    <html>
    <head>
      <style type="text/css">
        body {background: #333;color:#ddd;}
        .seatOn{background:url('http://veveve.hu/utitars/images/seat_on_16.png') no-repeat;width:20px;height:30px;}
        .seatOff{background:url('http://veveve.hu/utitars/images/seat_off_16.png') no-repeat;width:20px;height:30px;}
      </style>
      <title></title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    </head>
    <body>
      <table id="detailsTable">
        <tr><td class='left'>Cost per head:</td><td id='cost'>133</td><td>EUR</td></tr>
        <tr>
          <td class='left'>&nbsp;</td>
          <td>
            <div id='s40' class='seatOn'></div>
            <div id='s41' class='seatOnOff seatOn'></div>
            <div id='s42' class='seatOnOff seatOn'></div>
          </td>
        </tr>
      </table>
    <script>
      var grandTotal=400;
      var seatNum=3;
      $(function() {
        $(".seatOnOff").mouseover(function() {changeSeat(this)});
      });
    function changeSeat(seat){
        if($(seat).hasClass('seatOn')) {
        	$(seat).removeClass('seatOn');
        	$(seat).addClass('seatOff');
          seatNum--;
          $("#cost").hide().html(Math.floor(grandTotal/seatNum)).fadeIn(450);
        }
        else {
        	$(seat).removeClass('seatOff');
        	$(seat).addClass('seatOn');
          seatNum++;
          $("#cost").hide().html(Math.floor(grandTotal/seatNum)).fadeIn(450);
        }
    }
    </script>
    </body>
    </html>

  2. #2
    Senior Coder deathshadow's Avatar
    Join Date
    Feb 2016
    Location
    Keene, NH
    Posts
    3,513
    Thanks
    4
    Thanked 506 Times in 494 Posts
    Why would you trigger it on HOVER? Something that doesn't even exist on mobile! Much less, what even makes this JavaScript's job?

    This looks like form data, why are you using JavaScript to make it as inaccessible / broken as possible? Use checkboxes, you want to style them slide the checkboxes off screen and use a sibling selector to style a label.

    Unless I'm completely misunderstanding you, whatever it is you're trying to do here? It's not a good thing. NOT something to use JavaScript for, NOT something to do on hover, etc, etc... unless you're in a very NARROW control over the end user device, settings, screen resolution, and don't give a flying purple fish about usability/accessibility.

    I also fail to see what makes this tabular data.
    “There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.” – C.A.R. Hoare, The 1980 ACM Turing Award Lecture
    http://www.cutcodedown.com

  3. #3
    New to the CF scene
    Join Date
    Jun 2019
    Posts
    6
    Thanks
    2
    Thanked 0 Times in 0 Posts
    This is just a simplified code, focusing on the problem, obviously there are a lot of removed classes having - responsive - CSS code.
    On mobile it will work as a click.
    WHat it does is calculating traveling costs including highway tolls, several types of fuel and consumption. And of course it matters how many people have to share the cost
    And I thought it was cool that the user doesn't even have to click just swipe one passenger out of the car.

  4. #4
    New to the CF scene
    Join Date
    Jun 2019
    Posts
    6
    Thanks
    2
    Thanked 0 Times in 0 Posts
    I had a look at the elements panel and what I noticed is that triggering hide() doesn't set display:none but opacity:0 (and rising during fadeIn()) that's why the DOM element stays - however triggering hide() quickly again (before opacity goes back to 1) does set the element style to display:none.
    Question is still the same: why?

  5. #5
    Senior Coder deathshadow's Avatar
    Join Date
    Feb 2016
    Location
    Keene, NH
    Posts
    3,513
    Thanks
    4
    Thanked 506 Times in 494 Posts
    Quote Originally Posted by zozoo View Post
    Question is still the same: why?
    Animations, ESPECIALLY scripted ones, REALLY don't like being "interrupted" mid draw, so if you do it quickly, it tends to go bits-up face down. This goes quadruple for the train wreck laundry list of how NOT to use JavaScript that is jQuery.

    My advice, lose the animation, and stop using .html()... you should be doing a node flush with createTextNode, or using the element's raw .textContent. Even jQ's pointlessly redundant .text() would be better.

    If I were to try and have an animated fade in/fade/out I would indeed use opacity as they did, the difference being that I would do a class toggle to trigger a CSS animation instead of their (in my opinion) .fade() rubbish.

    To get this type of fade working, off the shelf animation isn't going to cut it if you want to be 100% sure the size doesn't change until after the fade-out is complete.

    Also you REALLY should be making this work first as if JavaScript and CSS don't even EXIST, that way it's accessible to everyone. Don't know what industry you're doing this for, but if it's for a website you could end up in trouble legally under laws like the US ADA and UK EQA.

    So... first:

    Code:
    <form><!-- assumging this is a form, right? -->
    	<fieldset id="seats">
    		<input type="checkbox" id="seat1" checked disabled>
    		<label for="seat1">Seat</label>
    		<br>
    		<input type="checkbox" id="seat2">
    		<label for="seat2">Seat</label>
    		<br>
    		<input type="checkbox" id="seat3">
    		<label for="seat3">Seat</label>
    	</fieldset>
    </form>
    Cute thing about labels? Clicking on them is the same as clicking on the element who's ID they are for="". Notice I am not putting the calculation or its text in the markup. Since that is scripting only functionality, it has no business in the HTML where scripting off/blocked it's gibberish.

    Then to style them to behave as you wanted, as well as to prepare for our fade in / fade out animations:

    Code:
    #seats input {
    	/* hide them without screwing up <label for=""> association in IE */
    	position:absolute;
    	left:-999em;
    }
    
    #seats input + label {
    	position:relative;
    	display:inline-block;
    	overflow:hidden;
    	width:16px;
    	height:22px;
    	margin-bottom:2px;
    	text-indent:-999em; /* hide the non-screen media text */
    	background:url(images/seats_on_off.png) 0 0 no-repeat;
    }
    
    #seats input:checked + label {
    	background-position:0 -23px;
    }
    
    .fadeIn {
    	animation:fadeIn 0.3s 1;
    }
    
    .fadeOut {
    	animation:fadeOut 0.3s 1;
    }
    
    @keyframes fadeIn {
    	0% { opacity:0; }
    	100% { opacity:1; }
    }
    
    #keyFrames fadeOut {
    	0% { opacity:1; }
    	100% { opacity:0; }
    }
    I also switched it to use the incorrectly named "CSS sprites" technique which reduces the number of separate files -- and the required handshakes -- for faster page loading, and it pre-caches the other image state, AND even can reduce the total file sizes. One big image often compresses more than multiple smaller ones.

    Though in practice, I'd draw up a webfont of the two states and use that instead of images. We live in the age of scaleable interfaces, helps to keep that in mind.

    Now on the scripting side of things it gets a wee bit more complex, but nothing unmanageable.

    Code:
    (function(d) {
    
    	/* first a simple helper function -- helps kick jQ to the curb */
    	
    	function appendMany(target) {
    		for (var i = 1, arg; arg = arguments[i]; i++) target.appendChild(
    			'object' == typeof arg ? arg : d.createTextNode(arg)
    		);
    	}
    	
    	var
    		grandTotal = 400,
    		seatSet = d.getElementById('seats'),
    		seatLabels = seatSet.getElementsByTagName('label'),
    		/*
    			As the output only functions scripting enabled, it has ZERO business
    			in your HTML! Generate it in the scripting.
    		*/
    		outputLine = seatSet.parentNode.insertBefore(
    			d.createElement('div'),
    			seatSet
    		),
    		outputSpan = d.createElement('span'),
    		outputTimeout = false;
    		
    	outputLine.id = 'seatOutput';
    	appendMany(outputLine, 'Cost per head: ', outputSpan, ' EUR');
    	
    	function updateOutput() {
    		if (outputTimeout) clearTimeout(outputTimeout);
    		outputSpan.classList.remove('fadeIn');
    		outputSpan.classList.add('fadeOut');
    		outputTimeout = setTimeout(updateOutputStage2, 300);
    	}
    	
    	function updateOutputStage2() {
    		outputTimeout = false;
    		outputSpan.classList.remove('fadeOut');
    		outputSpan.classList.add('fadeIn');
    		for (
    			var i = 0, seats = 0, input;
    			label = seatLabels[i];
    			i++
    		) if (label.previousElementSibling.checked) seats++;
    		outputSpan.textContent = Math.floor(grandTotal / seats);
    	}
    	
    	function seatToggle(e) {
    		e.currentTarget.previousElementSibling.click();
    	}
    	
    	for (
    		var i = 0, label;
    		label = seatLabels[i];
    		i++
    	) if (
    		!label.previousElementSibling.disabled
    	) {
    		label.addEventListener('mouseover', seatToggle, false);
    		label.previousElementSibling.addEventListener('change', updateOutput, false);
    	}
    	
    	updateOutputStage2();
    	
    })(document);
    SIF/IIFE so other scripts can't see anything this one is doing (and part of why I think "let" is pointless).

    First I have a helper to just make it easier to create the content of new elements quickly.

    We then set that grand total, grab hold of that fieldset and all labels inside that fieldst. We then create the output DIV, the SPAN that will be inside said DIV to hold the value. The outputTimeout variable will hold our timeout for the animation. (we'll get to that shortly).

    Then we give the outputLine an ID, and fill it up with content using our helper function.

    The updateOutput() function is what will be called by our onchange even. Yes, ONCHANGE. When the input's state changes we call the fade out, calc, and then fade in. This way we have our keyboard / touch / non-hover already implemented!

    The magic here is:

    updateOutput -- called when a checkbox changes.

    1) if there's a timeout in progress, KILL IT. This prevents multiple calculations and fades from occuring simultaneously.

    2) remove the fadeIn class if present so we can trigger the animation again.

    3) add the fadeOut class to, well, fade out the existing value.

    4) set a timeout to fire around the same time the fadeout animation is complete. This will call:

    UpdateOutputStage2

    5) set outputTimeout to be boolean false. Makes 100% sure our check for it being set works right in updateOutput.

    6) remove the fadeOut class since that should be done.

    7) add the fadeIn class. Note that animated effects / markup changes do NOT render until scripting execution is released. (end of this function)

    8) calculate the total number checked. I do this EVERY time we get this far since who knows how many times they've been checked/unchecked and/or from WHERE. Could be the mouseover, could be a click, could be keyboard. Some browsers (IE) can also do wonky nonsense on reporting state changes. I do this off the already useful label list so we don't need to maintain a second list for all the input.

    9) Set the value using textContent.

    That's the guts of it, so now all we need do is implement the hover and hook the changes to the input.

    seatToggle is our callback for when an element gets mouseover. It too uses previousElementSibling to get at the input to click()

    We then loop through all the seatLabels, and if they aren't disabled (I noticed you didn't want the first one toggling) we hook them for the mouseover, and then hook the input preceding them for the change.

    Finally we call that updateOutputStage2() so that the inital value is calculated properly. As it checks all the input you can set "checked" on them server-side when feeding the page and the scripting will dynamically adjust as appropriate.

    Live Demo here:
    https://cutcodedown.com/for_others/zozoo/

    About what you're aiming for?
    “There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.” – C.A.R. Hoare, The 1980 ACM Turing Award Lecture
    http://www.cutcodedown.com

  6. Users who have thanked deathshadow for this post:

    zozoo (Jun 17th, 2019)

  7. #6
    New to the CF scene
    Join Date
    Jun 2019
    Posts
    6
    Thanks
    2
    Thanked 0 Times in 0 Posts
    Oh my God. Wow. I'm gonna have a look now. Thanks.


 

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •