Thread: Help with some website design...

  1. #31
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by Alpo View Post
    Edit: Holy crap, Whiteflags you ninja'ed me by 30 minutes. It's a good thing I was never in an old western shootout or I would still be sitting around looking at my toes by the time they buried me .
    Reminds me of this. He says, "I won!". I feel a bit like a similar winner here.

    Anyway, I think the tabIndex approach, with highlighting the entries that match the current search string, is viable. I'd certainly add sentinel tabIndex entries (anchor elements with 1x1-pixel transparent contents) that'd refocus to the search bar. And it all needs a lot of testing on different browsers to see if it works.

    It might even be nice to add an autoexpand (can we?) to the select elements, so that tabbing through them opens the drop-down list automagically. That'd save one awkward Alt+cursor keypress in Linux Firefox on the final target select element.

    Most importantly, I'd first generate a much, MUCH larger data set example to work on. Probably one much larger than even the typical ones, as those are the cases where this functionality is most needed in practice. And ask for opinions and ideas from other data entry folks. (Their grievances and input is to be taken with a grain of salt, because they are not necessarily using their most efficient work flow tactics.)

  2. #32
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Nominal, for some reason your code doesn't seem to run for me. What could be the reason? I'm using Firefox on Windows 7.

  3. #33
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by MutantJohn View Post
    Nominal, for some reason your code doesn't seem to run for me. What could be the reason?
    The code seems to have a transcription error. This board replaces non-breaking spaces with two dots (".." was originally U+00A0).

    If you replace the first two occurrences of .. with a space, does it work for you? You can even replace all of them, as the rest of them are in the select input text, and modifying that won't harm anything.

    You know, this makes me quite sad. You're probably the only one that even tried that page. I was hoping somebody could take it and make something actually workable out of it...

  4. #34
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Yes!!! It works!!!!

    Okay, I'm going to study the heck out of this. Already, I'm really liking your OO-based approach. Y'know, for someone who (I think) has claimed they don't like C++...

  5. #35
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by MutantJohn View Post
    Y'know, for someone who (I think) has claimed they don't like C++...
    Yeah, I don't like C++ much. For user interface stuff, I do believe an event-oriented/object-oriented approach makes perfect sense. I do believe I've mentioned that before (that I do use C++ with Qt, for example). Although I mostly do UI stuff in Python, nowadays; easier maintenance, long term.

    Do you think you could generate a large dataset we could play with? A HTML <table>...</table> snippet would be optimal, I think. Put that at pastebin or elsewhere, and we could maybe throw some better ideas around. We need lots of rows, though; thousands, and not repeated ones, either.

    We have no idea what kind of data is being manipulated here, how many items are in the drop-down lists, whether those lists are the same for all entries (or a subset of a larger set, or something other), and so on. If the original data is about people, make this dataset about cars or toys or software, no reason to have anything that links to the actual dataset. All that matters is that it has the same organization and internal structure as the target data. Having real words (instead of Lorem Ipsum or repeated garbage text) makes testing the search facility much more sensible.

    For example, I believe that highlighting all entries that contain matches makes more sense than just highlighting the match alone. I think it'd be better if the entire row background, perhaps row border too, got highlighted. If this is in a table element, then having each table row have a thin but transparent border, and the table having a thin rowspacing, so that the highlighting could add a visible border and change the background color, would bring more structure, and make it easier to see multiple matches. Maybe alternate the background colors, so that it's easy to keep track even if consecutive entries got highlighted?

    If you do that -- it's extremely boring work to generate such dummy data; you don't obviously type it all by hand, but collating the lists of data you use as the source means browsing dull web sites (Wikipedia list pages for example), and so on --, I can promise I'll look at the tabIndex sentinel thingies I mentioned, at least. They would make the tabbing cycle (Tab forwards, Shift+Tab backwards) only between the matching entries and the search box, not pass to other elements.
    Last edited by Nominal Animal; 04-23-2015 at 07:52 PM.

  6. #36
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Okay, I could work on that, if you'd like. I kinda owe you one. Let me take a look at it this weekend though. I'm kind of pressed for time during the week.

    Edit :

    I also really wanted to say, thank you, you guys. You've given me a lot of help with this and I really appreciate it.

    Alpo, thank you for introducing me to jQuery and helping me understand its syntax. I googled which I should learn first, jQuery or JavaScript. One response I saw said to some effect, learn both but jQuery is a framework written in JavaScript. Frameworks do come and go but JavaScript will likely be constant. So I'm going to learn JavaScript first and then once I'm fed up with that, I'll just jQuery to simplify everything. This is why I'm happy I learned C before C++ ('cause I really appreciated C++ when I started learning it).

    Nominal, thank you for posting a complex working example. I wanted to thank whiteflags and Alpo again for linking me to the Mozilla site. I first avoided because I was like, "Lol that site's just gonna get me to use Firefox" but I was so wrong! It's not just shameful FireFox plugs, it's also the motherload of all the technical information you could ever want! Nominal, it makes your code much better for studying now.
    Last edited by MutantJohn; 04-24-2015 at 09:50 AM.

  7. #37
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Here's my best shot yet.

    This one has proper tabbing support: tab cycles only through the select entries and the search field. You can use any number of tabbing sentinels; I used five, just in case the UI skips some when it gets behind. When you tab to the search field, the entire contents of it will be automatically selected.

    Using the ≣ handle on the search bar, you can move it around, and change its opacity using the scroll wheel. The three states -- active, moving, deactive -- all have their own opacity.

    The search is better modularized now; matching entries are highlighted in one function, dataset_highlight().

    The search is just a plain case insensitive search. Search for nothing to clear an existing search.

    Expanding the drop-down list for a select element does not seem to be possible for current Firefox versions. For Chrome, one could create and send a mousedown event, but I omitted that code since I don't have Chrome/chromium installed on this machine I'm using right now.

    The Javascript code is now about 9800 bytes long, but about 2900 of that is just multiple consecutive spaces. Anyway, it's not going to matter much wrt. page load time, and you definitely do want it as part of the page (as opposed to saved in a separate file), to avoid all sorts of glitches.

    Please test!

    I'd be very interested to hear any comments on this, especially how intuitive or unnatural the interface feels to you. I've only tested it in Firefox 37 in x86-64 Linux.
    Code:
    <!DOCTYPE html>
    <html>
     <head>
      <title> Tabbing test </title>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <style type="text/css">
    
       #table {
        border-collapse: collapse;
        border: 1px solid #cccccc;
       }
    
       #table tr {
        border: 0px none;
        border-top: 1px solid #cccccc;
       }
    
       #table tr td {
        padding: 0.1em 0.5em 0.1em 0.5em;
        border: 0 none;
        border-left: 1px solid #cccccc;
        margin: 0.1em 0.5em 0.1em 0.5em;
       }
    
       #searchbar {
        display: block;
        position: fixed;
        left: 0;
        top: -1024;
        padding: 0.2em 0.5em 0.2em 0.5em;
        border: 1px solid #000000;
        margin: 0.2em 0.5em 0.2em 0.5em;
        background: #ffffff;
        color: #000000;
       }
    
       #searchmove {
        display: inline;
        padding: 0.2em 0.5em 0.2em 0.5em;
        border: 0px none;
        margin: 0 0 0 0;
        cursor: hand;
        cursor: move;
       }
    
      </style>
      <script type="text/javascript">
    
        var tabbing = new(function() {
            var self = this;
            var bar = null;
            var handle = null;
            var form = null;
            var input = null;
            var found = null;
            var dataset = null;
            var sentinels_before_data   = null;
            var sentinels_after_data    = null;
            var sentinels_before_search = null;
            var sentinels_after_search  = null;
            var opacity_shown  = "1.0";
            var opacity_hidden = "0.5";
            var opacity_moving = "0.9";
            var moving = null;
            var shown = false;
    
            self.show = function() {
                bar.style.opacity = opacity_shown;
                shown = true;
            };
    
            self.hide = function() {
                bar.style.opacity = opacity_hidden;
                shown = false;
            };
    
            self.focus_search = function(evt) {
                var ev = evt || window.event;
                self.show();
                input.focus();
                input.setSelectionRange(0, (input.value).length + 1);
                if (ev)
                    ev.preventDefault();
                return false;
            };
    
            self.focus_first = function(evt) {
                var ev = evt || window.event;
                if (found === null || found.length < 1) {
                    self.show();
                    input.focus();
                } else {
                    self.hide();
                    found[0].target.focus();
                }
                if (ev)
                    ev.preventDefault();
                return false;
            };
    
            self.focus_last = function(evt) {
                var ev = evt || window.event;
                if (found === null || found.length < 1) {
                    self.show();
                    input.focus();
                } else {
                    self.hide();
                    found[found.length-1].target.focus();
                }
                if (ev)
                    ev.preventDefault();
                return false;
            };
    
            self.select_focused = function(evt) {
                var ev = evt || window.event;
                /* Expand (ev.target) HTMLSelectElement somehow. */
            };
    
            self.dataset_highlight = function(i, is_a_match) {
                if (is_a_match) {
                    if (i & 1) {
                        /* Matching entry, even row */
                        dataset[i].row.style.background = "#cce5ff";
                        dataset[i].row.style.border = "1px solid #99ccff";
                    } else {
                        /* Matching entry, odd row */
                        dataset[i].row.style.background = "#bfcfff";
                        dataset[i].row.style.border = "1px solid #99ccff";
                    }
                } else {
                    if (i & 1) {
                        /* Normal entry, even row */
                        dataset[i].row.style.background = "#ffffff";
                        dataset[i].row.style.border = "";
                    } else {
                        /* Normal entry, odd row */
                        dataset[i].row.style.background = "#f7f7f7";
                        dataset[i].row.style.border = "";
                    }
                }
            };
    
            self.do_find = function(evt) {
                var ev = evt || window.event;
                var phrase = String(input.value).toLowerCase();
                var tabIndex = 1;
    
                for (i = 0; i < sentinels_before_data.length; i++)
                    sentinels_before_data[i].tabIndex = tabIndex++;
    
                found = [];
                if (phrase.length > 0)
                    for (i = 0; i < dataset.length; i++)
                        if (dataset[i].text.contains(phrase)) {
                            dataset[i].target.tabIndex = tabIndex++;
                            self.dataset_highlight(i, true);
                            found[found.length] = dataset[i];
                        } else {
                            dataset[i].target.tabIndex = 0;
                            self.dataset_highlight(i, false);
                        }
                else
                    for (i = 0; i < dataset.length; i++) {
                        dataset[i].target.tabIndex = tabIndex++;
                        self.dataset_highlight(i, false);
                        found[found.length] = dataset[i];
                    }
    
                for (i = 0; i < sentinels_after_data.length; i++)
                    sentinels_after_data[i].tabIndex = tabIndex++;
    
                for (i = 0; i < sentinels_before_search.length; i++)
                    sentinels_before_search[i].tabIndex = tabIndex++;
    
                input.tabIndex = tabIndex++;
    
                for (i = 0; i < sentinels_after_search.length; i++)
                    sentinels_after_search[i].tabIndex = tabIndex++;
    
                self.focus_first(ev);
                return false;
            };
    
            self.move_begin = function(evt) {
                var ev = evt || window.event;
                if (ev && moving === null) {
                    moving = { left: bar.offsetLeft,
                               top: bar.offsetTop,
                               clientX: ev.clientX,
                               clientY: ev.clientY };
                    bar.style.opacity = opacity_moving;
                }
            };
    
            self.move_end = function() {
                if (moving) {
                    bar.style.opacity = opacity_shown;
                    moving = null;
                    self.focus_search();
                }
            };
    
            self.move = function(evt) {
                if (moving) {
                    var ev = evt || window.event;
                    if (ev && ev.buttons && (ev.buttons & 1)) {
                        var left = moving.left + ev.clientX - moving.clientX;
                        var top = moving.top + ev.clientY - moving.clientY;
                        var maxLeft = window.innerWidth - bar.offsetWidth - (document.documentElement.offsetWidth - document.body.clientWidth);
                        var maxTop = window.innerHeight - bar.offsetHeight - (document.documentElement.offsetHeight - document.body.clientHeight) / 2;
                        if (left > maxLeft) left = maxLeft;
                        if (top > maxTop) top = maxTop;
                        if (left < 0) left = 0;
                        if (top < 0) top = 0;
                        bar.style.left = String(left) + "px";
                        bar.style.top = String(top) + "px";
                        (window.getSelection()).removeAllRanges();
                        ev.preventDefault();
                        ev.stopPropagation();
                    } else
                        self.move_end();
                }
            };
    
            self.change_opacity = function(evt) {
                var ev = evt || window.event;
                var delta, opacity;
                if (ev) {
                    if (ev.deltaY > 0)
                        delta = -0.1;
                    else
                    if (ev.deltaY < 0)
                        delta = +0.1;
                    opacity = parseFloat(bar.style.opacity) + delta;
                    if (opacity > 1.0) opacity = 1.0;
                    if (opacity < 0.1) opacity = 0.1;
                    bar.style.opacity = opacity;
                    if (moving)
                        opacity_moving = opacity;
                    else
                    if (shown)
                        opacity_shown = opacity;
                    else
                        opacity_hidden = opacity;
                    ev.preventDefault();
                }
                return false;
            };
    
            self.init = function() {
                bar = document.getElementById("searchbar");
                form = document.getElementById("searchform");
                input = document.getElementById("search");
                handle = document.getElementById("searchmove");
    
                sentinels_before_data = document.getElementsByClassName("sentinel_before_data");
                sentinels_after_data = document.getElementsByClassName("sentinel_after_data");
                sentinels_before_search = document.getElementsByClassName("sentinel_before_search");
                sentinels_after_search = document.getElementsByClassName("sentinel_after_search");
    
                dataset = [];
                var element = document.getElementsByClassName("name");
                for (i = 0; i < element.length; i++) {
                    dataset[i] = { row: element[i].parentNode,
                                   text: String(element[i].textContent).toLowerCase(),
                                   target: element[i].parentNode.querySelector("select") };
                    if (dataset[i].target !== null)
                        dataset[i].target.addEventListener("focus", self.select_focused);
                }
    
                for (i = 0; i < sentinels_before_data.length; i++)
                    sentinels_before_data[i].addEventListener("focus", self.focus_search);
    
                for (i = 0; i < sentinels_after_data.length; i++)
                    sentinels_after_data[i].addEventListener("focus", self.focus_search);
    
                for (i = 0; i < sentinels_before_search.length; i++)
                    sentinels_before_search[i].addEventListener("focus", self.focus_last);
    
                for (i = 0; i < sentinels_after_search.length; i++)
                    sentinels_after_search[i].addEventListener("focus", self.focus_first);
    
                input.addEventListener("focus", self.show);
                input.addEventListener("blur", self.hide);
    
                form.addEventListener("submit", self.do_find);
                self.do_find(null);
    
                handle.addEventListener("wheel", self.change_opacity);
                handle.addEventListener("mousedown", self.move_begin);
                window.addEventListener("mousemove", self.move);
                window.addEventListener("mouseup", self.move_end);
    
                bar.style.display = "block";
                bar.style.opacity = "1";
                bar.style.top = String(window.innerHeight - bar.offsetHeight - (document.documentElement.offsetHeight - document.body.clientHeight) / 2) + "px";
    
                self.focus_search();
            };
        
        });
    
        document.addEventListener("DOMContentLoaded", tabbing.init);
    
      </script>
     </head>
     <body>
    
      <a class="sentinel_before_data" href="#"></a>
      <a class="sentinel_before_data" href="#"></a>
      <a class="sentinel_before_data" href="#"></a>
      <a class="sentinel_before_data" href="#"></a>
      <a class="sentinel_before_data" href="#"></a>
    
      <form method="POST" action="#" accept-charset="UTF-8">
       <table id="table">
        <tr><td class="name"> 1 A B C D       </td><td><select name="1"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
        <tr><td class="name"> 2 A B C D     G </td><td><select name="2"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
        <tr><td class="name"> 3 A B C     F G </td><td><select name="3"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
        <tr><td class="name"> 4 A B     E F G </td><td><select name="4"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
        <tr><td class="name"> 5 A       E F G </td><td><select name="5"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
       </table>
      </form>
    
      <a class="sentinel_after_data" href="#"></a>
      <a class="sentinel_after_data" href="#"></a>
      <a class="sentinel_after_data" href="#"></a>
      <a class="sentinel_after_data" href="#"></a>
      <a class="sentinel_after_data" href="#"></a>
    
      <a class="sentinel_before_search" href="#"></a>
      <a class="sentinel_before_search" href="#"></a>
      <a class="sentinel_before_search" href="#"></a>
      <a class="sentinel_before_search" href="#"></a>
      <a class="sentinel_before_search" href="#"></a>
    
      <div id="searchbar">
       <form id="searchform" method="POST" action="#" accept-charset="UTF-8">
        <div id="searchmove"> ≣ </div>
        <input id="search" type="text" name="q" value="">
       </form>
      </div>
    
      <a class="sentinel_after_search" href="#"></a>
      <a class="sentinel_after_search" href="#"></a>
      <a class="sentinel_after_search" href="#"></a>
      <a class="sentinel_after_search" href="#"></a>
      <a class="sentinel_after_search" href="#"></a>
    
     </body>
    </html>
    Last edited by Nominal Animal; 04-25-2015 at 12:40 AM.

  8. #38
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    O_o

    I'm too lazy/busy to alter your markup/code, but I would generate the decoration for the navigation shifting elements, and I would invert the `tabindex' if one exists with defaulting to a large negative allowing more sophisticated navigation without losing the intended functionality.

    Code:
    <body>
        <form method="POST" action="#" id="table" accept-charset="UTF-8">
            <table>
                <tr><td class="name"> 1 A B C D       </td><td><select name="1"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
                <tr><td class="name"> 2 A B C D     G </td><td><select name="2"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
                <tr><td class="name"> 3 A B C     F G </td><td><select name="3"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
                <tr><td class="name"> 4 A B     E F G </td><td><select name="4"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
                <tr><td class="name"> 5 A       E F G </td><td><select name="5"><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
            </table>
        </form>
        <div id="searchbar">
            <form id="searchform" method="POST" action="#" accept-charset="UTF-8">
                <div id="searchmove"> ≣ </div>
                <input id="search" type="text" name="q" value="">
            </form>
        </div>
    </body>
    The markup looks much nicer, and you can still use the DOMAPI (You might use `firstchild', `insertBefore', and `appendChild' for example.) to create the `a' elements for shorting normal navigation when using a filter.

    Code:
    <body>
        <form method="POST" action="#" id="table" accept-charset="UTF-8">
            <table>
                <tr><td class="name"> 1 A B C D       </td><td><select name="1" tabindex=3><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
                <tr><td class="name"> 2 A B C D     G </td><td><select name="2" tabindex=5><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
                <tr><td class="name"> 3 A B C     F G </td><td><select name="3" tabindex=1><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
                <tr><td class="name"> 4 A B     E F G </td><td><select name="4" tabindex=6><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
                <tr><td class="name"> 5 A       E F G </td><td><select name="5" tabindex=4><option value="" selected="selected">...</option><option>A</option><option>B</option><option>C</option></select></td></tr>
            </table>
        </form>
        <div id="searchbar">
            <form id="searchform" method="POST" action="#" accept-charset="UTF-8">
                <div id="searchmove"> ≣ </div>
                <input id="search" type="text" name="q" value="">
            </form>
        </div>
    </body>
    The markup looks nicer and allows to specify navigation hints. You don't need incremental `tabindex' generation because the normal behavior is already extremely similar in the absence of specific `tabindex' values when a label isn't being filtered. We could instead use hints with normal navigation plus inversion to filter. The example markup listed first doesn't include a hint so we need to invert the filtering logic; instead of increment a match, we make use of default behavior by changing the `tabindex' for elements which don't match a largish negative value which we use as a sentinel to decide if we clear on invert when the filter is changed. The second example markup includes a hint, an element with an existing `tabindex' value, which can simply have the `tabindex' value inverted (I'm using "invert" to imply multiplication by negative one, but you could arguably use any reversible measure which generates a negative.) allowing any values a creator might light except for the one which exactly matches the sentinel which could be a parameter in any event. We desire default behavior otherwise so must set the `tabindex' for the navigation anchors to a largish negative when the filter is cleared.

    The results of these few changes:

    $): The markup is simpler which isn't just a maintenance benefit. Users which need accessibility aids may have a better experience if the software the use isn't smart enough to ignore the navigation anchors.
    $): We get default behavior for a given browser when the data isn't being filtered. Considering how different the behavior is for some browsers, we reduce the risk of upsetting potential users.
    $): The filtering mechanism doesn't subvert normal navigation; the filtering mechanism works in conjunction with normal navigation allowing more purposefully shifts in focus when a more elaborate view is necessary.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  9. #39
    Registered User Alpo's Avatar
    Join Date
    Apr 2014
    Posts
    877
    @Nominal Animal - I just started looking at it, but some changes that (I think) have to be made are:

    Code:
    // EDIT:
    // FROM
        dataset[i].row.style.background = /*color*/
    // TO
        dataset[i].row.style.backgroundColor = /*color*/
    Code:
    // ADD TO BEGINING
    if (!(String.prototype.contains)) {
        String.prototype.contains = function (str) {
            return (this.indexOf(str) !== -1 ? true : false);
        }
    }

    Other things I might do differently (once I completely understand the code):
    1. wrap the 'movement' functionality into a module to be dynamically loaded. Attaching the event listeners only if the module successfully loads and evaluates.
    My reasoning is that it doesn't squash the overall functionality of the search not to have it.

    2. I might change to 'do_find' method's trigger to be something other than 'submit'.
    My reasoning here is that certain things like highlighting could be done more dynamically, maybe splitting the highlighting and focusing into separate events. (Like highlighting could be done on keyup, and focusing could be done on submit). I'm not totally certain of this, do to the potential size of the dataset, but like you said a timeout could be used if it's too slow.

    Anyway, those where just my thoughts, I like it overall, very neat .
    WndProc = (2[b] || !(2[b])) ? SufferNobly : TakeArms;

  10. #40
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by phantomotap View Post
    I would generate the decoration for the navigation shifting elements
    No objection there. This being an example, I thought having them explicitly there would make it clearer.

    What I am not sure about, is whether the elements should be generated at the DOM level (by inserting new HTMLAnchorElements), or via CSS. It does not matter much, I just have a hunch that there will be fewer browser bugs with the former ("DHTML" approach).

    Besides, I'm lazy myself: I haven't even bothered to install another browser like Chromium. It'd take all of a few clicks, or one command-line command. Mostly, I was just trying to get the code to work the way I envisioned earlier in this thread, but keep it as straightforward/readable as I could.
    Quote Originally Posted by phantomotap View Post
    and I would invert the `tabindex' if one exists with defaulting to a large negative allowing more sophisticated navigation without losing the intended functionality.
    There have been a buttload of bugs in tabIndex handling in different browsers. In particular, on my Firefox 37 on Linux x86-64, I saw some very funky behaviour when the tabIndex values were not consecutive (or were assigned to invisible elements). Reassigning them all on search was simply the way I got it to work.

    Your approach sounds better, but I fear browser stupidity. I would definitely test across multiple browsers (or versions at minimum), before going that route.
    Quote Originally Posted by phantomotap View Post
    We desire default behavior otherwise so must set the `tabindex' for the navigation anchors to a largish negative when the filter is cleared.
    In Firefox in Linux, tabbing is not limited to elements with positive tabIndex values; they only manipulate the tabbing order. The sentinel elements are needed (with appropriate tabIndex values) to redirect the focus, so that tabbing only cycles through the desired elements, instead of all elements.

    Your suggested approach sounds good, if testing indicates that (1) having nonconsecutive tabIndexes is not a problem (and the glitches I saw were unrelated), and (2) browsers treat negative tabIndexes sanely (as either "normal tab order" or "do not tab through") and do not e.g. silently change them to zeros. I'm not at all convinced all major browsers do, that's all.

    (I don't know if they do, I just suspect they don't. I could be wrong.)

    Quote Originally Posted by phantomotap View Post
    Users which need accessibility aids may have a better experience if the software the use isn't smart enough to ignore the navigation anchors.
    A very good point. (I do normally try to add descriptive/logical title attributes to my links, for example; making navigation easier for nonvisual people. And have empty alt and title attributes for decorative elements.)

    Quote Originally Posted by phantomotap View Post
    We get default behavior for a given browser when the data isn't being filtered.
    This being a specialized data entry page (I call these "tool pages"), I'm on the fence on this. It depends on exactly what the default browser behaviour is. For example, in Firefox 37 in Linux x86-64, I really do prefer limiting tabbing to the select entries and the search bar. Normally, it runs out to tabs and whatnot. Annoying.

    On the other hand, adding a close button (and, for example, a keypress/keydown handler for the window, to reopen it) for the search bar, and not automatically opening it at page load at all, would make a lot of sense.

    In that case, I'd absolutely agree with you: keep default browser behaviour, whatever that may be, and only do the tabIndex stuff et cetera if the user uses the search functionality.

    Besides, not all Data Entry people are comfortable with keyboard-based stuff. They like clicking.

    For the initialization phase, I fear a large dataset might hog the browser at the init phase on slow machines. Instead, it might be better to split it into small chunks, and do each one from e.g. a timeout handler -- if the dataset is large enough to begin with. That way the initialization phase even on huge pages would not freeze a stupid browser; you could even show the user a "Still indexing.." message instead of the search bar (if the user tries to open it) during the init phase.

    I do have an old Acer Aspire One A110L laptop at hand I could test with, if we had a representative dataset; it's about as low-powered as GUI machines typically can be today.

    Quote Originally Posted by Alpo View Post
    Code:
    // FROM
        dataset[i].row.style.background = /*color*/
    // TO
        dataset[i].row.style.backgroundColor = /*color*/
    Why? It is not incorrect to specify just a color to the background attribute.

    Ah, I think I see your point.

    I'm not actually suggesting that one should use the table row background color (and definitely not those specific colors) to highlight table rows with data that matches the search pattern; it was just an example, and something I had a hunch about and mentioned in an earlier post.

    If I understand correctly, your point is that if you want to modify only the color of the background of an element using Javascript, one should use .style.backgroundColor instead, as using .style.background modifies the entire background. For example, if a background image was previously defined, .style.background overrides that and sets just a flat color background. Using .style.backgroundColor, you only override the color of the background; if an image is shown instead, it does not override it. (Or shouldn't, anyway; I haven't actually checked if the current breed of browsers handle this correctly.)

    Quote Originally Posted by Alpo View Post
    Code:
    if (!(String.prototype.contains)) {
        String.prototype.contains = function (str) {
            return (this.indexOf(str) !== -1 ? true : false);
        }
    }
    Gah, I'm rusty. I should just have used if (dataset[i].text.indexOf(phrase) >= 0) instead.

    However, that did give me an idea. Remember when Google still supported +, -, and quotes? It is actually not that difficult to grab a search string formatted that way, and mangle it into a regular expression. After compiling that regular expression -- they're compiled into fast state machines --, we could apply the search pattern to the strings we have. Not only would it give a much better search interface, but it also might be a bit faster.

    Quote Originally Posted by Alpo View Post
    wrap the 'movement' functionality into a module to be dynamically loaded
    I agree. We shouldn't just init and enable everything unconditionally. We could just install a handler to bring up the search bar, and do the initialization at that point. Those who don't use it, don't pay for it (in CPU time used), and should get unadulterated browser behaviour. (In particular, the page should work just fine even without Javascript. There might be a good reason the user has disabled it; maybe the machine is really, really slow or something.)

    Whether the initial data gathering etc. needs special handling, would require testing, in my opinion. I think it would be fast enough to not matter even on an old machine, even on a page with ten thousand entries. The dataset is just an array of dictionaries, each dictionary containing one string and references to DOM objects, so shouldn't take much memory at all either.

    Quote Originally Posted by Alpo View Post
    I might change to 'do_find' method's trigger to be something other than 'submit'.
    Yup, could be. Actually, I wouldn't be surprised at all if at testing some users would ask "where is the search button"?
    This kind of stuff I'd normally only worry about after gathering some UX data.

    Like I said, it's been years since I last did any major work on websites, and browsers and user expectations change pretty darn fast nowadays. User experience data gathering is the best way I know to keep up with that, but to even think of doing that, you need some fake but realistic data to test on.




    This is a perfect example why it's so nice to have more than one brain look at your code. For me, it's even better when they have a slightly different approach than I do. It's nice to see stuff become better than what you could do alone.

    I do feel I'm hogging MutantJohn's project, which was not my intention.

    MutantJohn, if you (or anybody else) have any questions on the example, do not hesitate to ask; I didn't intend it to be the core of your solution, just an example to show the approach I'd take. So, if you have any questions, say "why did you do that this way", I'd be happy to try to answer: it might help you make your solution even better.

  11. #41
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I don't know if they do, I just suspect they don't. I could be wrong.
    O_o

    The behavior I described with respect to negative, positive, and zero `tabindex' values is required by the standards.

    As I'm always complaining, there is what the standard says and what the environment actually provides.

    Doing anything with `tabindex' is just guaranteed to fail with some browsers. I would point you to one version of the standard browser for an early shipping "iOS" which didn't allow you to set a `tabindex' dynamically unless the actual markup had a `tabindex'; the obvious fix of adding a `tabindex=0' to relevant elements wasn't really workable because an unrelated browser `tabindex=0' from changing unless the element was one of the elements related to forms which would have had a natural `tabindex' value thus implying a need for the server to do some lifting yet could actually be solved with a hack involving invalid markup and a "!important" CSS rule.

    *shrug*

    Bizarre bugs is one more reason why the focus should be on getting the markup correct. We are just talking about adding an entirely optional whizzy feature to an existing functional layout so I'm not much bothered by bizarre behavior in a few wonky browsers causing trouble with the code.

    For example, in Firefox 37 in Linux x86-64, I really do prefer limiting tabbing to the select entries and the search bar. Normally, it runs out to tabs and whatnot. Annoying.
    I also hate that... feature. Believe me, I wasn't saying that default behavior was anything reasonable.

    My point was only that individuals who do expect such behavior, for whatever reason, would still get the expected behavior when the data isn't being filtered.

    I think it would be fast enough to not matter even on an old machine, even on a page with ten thousand entries.
    You are doing a linear change in the view causing multiple redraws.

    You'll not find the task easy, but you could add an unused class to the relevant elements allowing you to change the styling for every matching element all at once.

    I didn't intend it to be the core of your solution, just an example to show the approach I'd take. So, if you have any questions, say "why did you do that this way", I'd be happy to try to answer: it might help you make your solution even better.
    In that spirit, I'll share how I approach the filtering mechanism for my views which may have been discussed. I honestly haven't thoroughly read every post.

    Rather than changing the `tabindex', I sort the items in the table. I store the list of matching items and other items in two separate lists which are then used to unhook and then reattach each element in a specific order to a copy of the containing table which is then replaced with the table actually being drawn. You might think of it as a double buffering mechanism where all the heavy lifting takes place in an area where drawing isn't required.

    Believe it or not, the approach of unhooking and reattaching the elements can be faster than the multiple redraws when the data set gets large enough. That said, I find the sorted view more pleasant to use even at the cost of more expense filtering if only because the elements relevant to my query are visually grouped.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  12. #42
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    I do feel I'm hogging MutantJohn's project, which was not my intention.

    MutantJohn, if you (or anybody else) have any questions on the example, do not hesitate to ask; I didn't intend it to be the core of your solution, just an example to show the approach I'd take. So, if you have any questions, say "why did you do that this way", I'd be happy to try to answer: it might help you make your solution even better.
    Lol I was gonna say, you're just moving too fast for me XD

    Here's the problem, I'm sooooo freaking close to re-writing some of this meshing code I've been working on for awhile. I hit this huge performance bottleneck with one of my GPU routines and I think I finally came up with a good implementation that will actually be performant so my focus right now is largely on just making sure what I have is actually decent. It certainly can't worse than what I had before. It's such a tug-of-war and I hate it lol.

    But I got some decent code written yesterday so today is about videogames and reading up the last little bits of your first example before I start on the new one. The first example was relatively simple. A lot of it made good sense. The new one, however, looks pretty beastly in terms of length and complexity so I'm going to have to approach it with a fresh mind. But now is the time for DotA!!!

  13. #43
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by phantomotap View Post
    I store the list of matching items and other items in two separate lists which are then used to unhook and then reattach each element in a specific order to a copy of the containing table which is then replaced with the table actually being drawn.
    Yes, that is a better approach.

    I suspect that that approach -- that is, building the table contents as DOM objects from scratch in Javascript, then replacing the entire table node and its contents -- would end up being much faster for large datasets, even if you only modify the tabIndex values. That way the browser re-layout and other triggers are only triggered once, not once for each row.

    One very interesting thing to check, is whether taking references to existing DOM objects while building the new table off-document, causes any extra events to trigger, or causes any slowdown. If not, then you could build the new replacement table off-document by using the existing DOM elements. (In particular, the two table cell nodes.) That way you'd only need to keep the searchable text string for each entry, and a reference to each entry element(s) in Javascript.

    This stuff is something that I only tend to notice when testing on real-size datasets (extra-large-size, actually) on a slower machine. (I'm like a terrier in some ways. When I find such an issue, I tend to not be able to let go until I've found either the reason, or a robust way to avoid it. I don't think I've never, ever used a "this fixes bug #X, but I don't know why" in any of my code.)

    Quote Originally Posted by phantomotap View Post
    That said, I find the sorted view more pleasant to use even at the cost of more expense filtering if only because the elements relevant to my query are visually grouped.
    It really depends on the dataset and how the search is used, I think.

    For example, if the name fields were actual names, I'd probably prefer to have a search mode that searches for full words (using glob patterns), and the list stay in the sorted-by-last-name order. I think I'd find the correct person faster in a long list that way.

    Then again, I'm so poor that whenever I visit a web shop or ebay, I always sort the view by ascending price. So, in other cases, I too prefer sorting to just highlighting.

    Quote Originally Posted by MutantJohn View Post
    Lol I was gonna say, you're just moving too fast for me XD
    No worries!

    I think I have mentioned here about a very old project of mine. It was the one I almost tried to get a software patent on (Gasp!). It's basically a server side machinery to edit web pages in a what-you-see-is-what-you-get manner, without storing the contents in a database or anything; it works on plain ol' HTML files directly. There are simple ways to limit which parts of a page which users can modify, and a way to restrict the HTML used; there is also a way to use snippet libraries the user can apply. The idea is that although it is WYSIWYG, the end result is restricted to original design, visual-wise. The original use case was maintaining the [I]content[I] of existing web pages without being able to break it visually; with the original pages created in any environment (as the necessary metadata (not much needed at all) is stored as comments within the HTML files itself).

    The server side details I first wrote in, eh, 1998 or so, but it became WYSIWYG only after DHTML support (contentEditable) arrived in browsers.

    The one part I haven't been able to finish, is the topmost UX layer (user interface elements) and parts of the DOM filter (to fix the browser-generated DOM/HTML code in the editable areas -- it's enforced on the server side at save time too, but the rule of least surprises means it should be enforced/fixed on the client side too). This is because this part would have to be constantly maintained for a number of browsers, as browsers do evolve quickly.
    While I do have shown examples of how this works in practice, nobody has been interested enough to fund the month or two of development this would take to finish it. I'm definitely not a good salesman.

    That history should explain my interest here; this thread touched Javascript issues very close to parts of one of my forever projects..

    Besides, using web pages with a robust backend for tools is just good common sense in my opinion.

  14. #44
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Can you explain this syntax?
    Code:
                dataset = [];
                var element = document.getElementsByClassName("name");
                for (i = 0; i < element.length; i++) {
                    dataset[i] = { row: element[i].parentNode,
                                   text: String(element[i].textContent).toLowerCase(),
                                   target: element[i].parentNode.querySelector("select") };
                    if (dataset[i].target !== null)
                        dataset[i].target.addEventListener("focus", self.select_focused);
                }
    Namely, I'm really surprised that the dataset assignment is valid syntax. I tried looking for the page on Mozilla Developer Network but it's tough to find.

    Also, should you be accessing element with the item() method?

    Edit : It just hit me, are you defining a tuple with your assignment of each dataset element?
    Last edited by MutantJohn; 04-26-2015 at 04:54 PM.

  15. #45
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Also, should you be accessing element with the item() method?
    O_o

    No. The `item' method wouldn't buy you anything worth having.

    It just hit me, are you defining a tuple with your assignment of each dataset element?
    The syntax shows an object literal.

    You could write `new Object()' and assign properties one at a time, but the literal version is faster and simpler.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 2
    Last Post: 12-11-2012, 12:25 AM
  2. Website Design....again
    By Sentral in forum A Brief History of Cprogramming.com
    Replies: 14
    Last Post: 10-29-2006, 05:47 AM
  3. Website design!
    By Sentral in forum A Brief History of Cprogramming.com
    Replies: 28
    Last Post: 10-07-2006, 12:38 AM
  4. website
    By the Wookie in forum Windows Programming
    Replies: 1
    Last Post: 11-07-2002, 02:25 PM