SitePoint Sponsor

User Tag List

Results 1 to 13 of 13
  1. #1
    SitePoint Addict
    Join Date
    Apr 2007
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Taxonomy Tree Problems

    I have been using jquery-option-tree (http://code.google.com/p/jquery-option-tree/) for a form on a clients website in order for them to choose a google category for their product search feed.
    You can see an example of what it looks like here: http://www.creativenaturemedia.co.uk...-tree-demo.php.
    The only problem is it displays some categories in the wrong section, if the parent section ends with the same wording. For example, in my demo, try clicking on Office Supplies, then General Office Supplies - you will notice that Adhesives comes under both - it should only appear under Office Supplies > General Office Supplies > Adhesives.
    I've been through the code several times and can't locate the problem - though I don't understand a lot of it to be fair as I'm a bit of a nube when it comes to javascript.

  2. #2
    SitePoint Addict
    Join Date
    Apr 2007
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Demo page:
    PHP Code:
    <script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
    <script type="text/javascript" src="jquery.optionTree.js"></script>
    <label for="gcategory" id="goocattitle">Edit Google Product Category:</label>
    <p>You can search the taxonomy here: <a class="external" href="http://support.google.com/merchants/bin/answer.py?hl=en-GB&answer=1705911">Taxonomy Search</a></p>
        <div>
        <input type="text" name="demo7" />
        </div>
        <div class="results" id="demo7-result"></div>
        <script type="text/javascript">
        $(function() {
        
            var options = {
                empty_value: 'null',
                indexed: true,  // the data in tree is indexed by values (ids), not by labels
                on_each_change: 'get-subtree.php', // this file will be called with 'id' parameter, JSON data must be returned
                choose: function(level) {
                return 'Choose level ' + level;
                },
                loading_image: 'ajax-load.gif',
                show_multiple: 21, // if true - will set the size to show all options
                choose: '', // no choose item
                set_value_on: 'each'
            };
        
            var displayParents = function() {
                var labels = []; // initialize array
                $(this).siblings('select') // find all select
                       .find(':selected') // and their current options
                         .each(function() { labels.push($(this).text()); }); // and add option text to array
                         $('#demo7-result').empty();
                $('<textarea id="goocategory" name="goocategory" cols="100">').text(labels.join(' > ')).appendTo('#demo7-result'); // and display the labels
                }
        
            $.getJSON('get-subtree.php', function(tree) { // initialize the tree by loading the file first
            $('input[name=demo7]').optionTree(tree, options).change(displayParents);
            });
        });
        </script>
    </div> 
    get-sub-tree.php:
    PHP Code:
    <?php
    require_once 'LazyTaxonomyReader.php';

    $reader = new LazyTaxonomyReader('taxonomy.txt');

    $line_no = (isset($_GET['id']) && is_numeric($_GET['id']) ? (int) $_GET['id'] : null);
    echo 
    json_encode($reader->getDirectDescendants($line_no));
    ?>
    LazyTaxonomyReader.php:
    PHP Code:
    class LazyTaxonomyReader {

        private 
    $base null;
        private 
    $separator ' > ';
        protected 
    $lines;

        public function 
    __construct($file 'taxonomy.txt') {
            
    $this->lines file($fileFILE_IGNORE_NEW_LINES);
        }

        public function 
    setBaseNode($line_no) {
            if (
    is_null($line_no)) {
                
    $this->base null;
                return;
            }

            if (!
    array_key_exists($line_no$this->lines)) {
                throw new 
    IllegalArgumentException("Invalid line number.");
            }
            
    $this->base $this->lines[$line_no];
        }

        public function 
    getDirectDescendants($line_no null) {
            
    $this->setBaseNode($line_no);
            
    // select only lines that are directly below current base node
            
    $direct array_filter($this->lines, array($this'isDirectlyBelowBase'));
            
    // return only last part of their names
            
    return array_map(array($this'getLastNode'), $direct);
        }

        protected function 
    getLastNode($line) {
            if (
    strpos($line$this->separator) === false) {
                
    // no separator present
                
    return $line;
            }
            
    // strip up to and including last separator
            
    return substr($linestrrpos($line$this->separator) + strlen($this->separator));
        }

        protected function 
    isDirectlyBelowBase($line) {

            
    // starting text that must be present
            
    if (is_null($this->base)) {
                
    $start '';
            } else {
                
    $start $this->base $this->separator;
            }

            if (
    $start !== '') {
                
    $starts_at_base = (strpos($line$start) === 0);

                if (!
    $starts_at_base) { // starts with something different
                    
    return false;
                }

                
    // remove start text AND the following separator
                
    $line str_replace($start''$line);
            }

            
    // we're direct descendants if we have no separators left on the line
            
    if (strpos($line$this->separator) !== false)
                return 
    false;

            return 
    true;
        }

    jquery.optionTree.js:
    Code:
    (function($){
    $.fn.optionTree = function(tree, options) {
    
        options = $.extend({
            choose: 'Choose...', // string with text or function that will be passed current level and returns a string
            show_multiple: false, // show multiple values (if true takes number of items as size, or number (eg. 12) to show fixed size)
            preselect: {},
            loading_image: '', // show an ajax loading graphics (animated gif) while loading ajax (eg. /ajax-loader.gif)
            select_class: '',
            leaf_class: 'final',
            empty_value: '', // what value to set the input to if no valid option was selected
            on_each_change: false, // URL to lazy load (JSON, 'id' parameter will be added) or function. See default_lazy_load
            set_value_on: 'leaf', // leaf - sets input value only when choosing leaf node. 'each' - sets value on each level change.
                                  // makes sense only then indexed=true
            indexed: true,
            preselect_only_once: false // if true, once preselected items will be chosen, the preselect list is cleared. This is to allow
                                        // changing the higher level options without automatically changing lower levels when a whole subtree is in preselect list
        }, options || {});
    
        var cleanName = function (name) {
            return name.replace(/_*$/, '');
        };
    
        var removeNested = function (name) {
            $("select[name^='"+ name + "']").remove();
        };
    
        var setValue = function(name, value) {
            $("input[name='" + cleanName(name) + "']").val(value).change();
        };
    
        // default lazy loading function
        var default_lazy_load = function(value) {
            var input = this;
            if ( options.loading_image !== '' ) {
              // show loading animation
              $("<img>")
                .attr('src', options.loading_image)
                .attr('class', 'optionTree-loader')
                .insertAfter(input);
            }
    
            $.getJSON(options.lazy_load, {id: value}, function(tree) {
                $('.optionTree-loader').remove();
                var prop;
                for (prop in tree) {
                    if (tree.hasOwnProperty(prop)) { // tree not empty
                        $(input).optionTree(tree, options);
                        return;
                    }
                }
                // tree empty, call value switch
                $(input).optionTree(value, options);
            });
        };
    
        if (typeof options.on_each_change === 'string') { // URL given as an onchange
            options.lazy_load = options.on_each_change;
            options.on_each_change = default_lazy_load;
        }
    
        var isPreselectedFor = function(clean, v) {
          if (!options.preselect || !options.preselect[clean]) {
            return false;
          }
    
          if ($.isArray(options.preselect[clean])) {
            return $.inArray(v, options.preselect[clean]) !== -1;
          }
    
          return (options.preselect[clean] === v);
        };
    
        return this.each(function() {
            var name = $(this).attr('name') + "_";
    
            // remove all dynamic options of lower levels
            removeNested(name);
    
            if (typeof tree === "object") { // many options exists for current nesting level
    
                // create select element with all the options
                // and bind onchange event to recursively call this function
    
                var $select = $("<select>").attr('name',name)
                .change(function() {
                    if (this.options[this.selectedIndex].value !== '') {
                        if ($.isFunction(options.on_each_change)) {
                          removeNested(name + '_');
                            options.on_each_change.apply(this, [this.options[this.selectedIndex].value, tree]);
                        } else {
                          // call with value as a first parameter
                            $(this).optionTree(tree[this.options[this.selectedIndex].value], options);
                        }
                        if (options.set_value_on === 'each') {
                          setValue(name, this.options[this.selectedIndex].value);
                        }
                    } else {
                      removeNested(name + '_');
                        setValue(name, options.empty_value);
                    }
                });
    
                var text_to_choose = '';
    
                if (jQuery.isFunction(options.choose)) {
                    var level = $(this).siblings().andSelf().filter('select').length;
                    text_to_choose = options.choose.apply(this, [level]);
                } else if ( options.choose !== '' ) {
                    text_to_choose = options.choose;
                }
    
                // if show multiple -> show open select
                var count_tree_objects = 0;
                if ( text_to_choose !== '' ) {
                  // we have a default value
                  count_tree_objects++;
                }
                if (options.show_multiple > 1) {
                    count_tree_objects = options.show_multiple;
                } else if (options.show_multiple === true) {
                  $.each(tree, function() {
                     count_tree_objects++;
                  });
                }
                if ( count_tree_objects > 1 ){
                  $select.attr('size', count_tree_objects);
                }
    
                if ($(this).is('input')) {
                    $select.insertBefore(this);
                } else {
                    $select.insertAfter(this);
                }
    
                if (options.select_class) {
                    $select.addClass(options.select_class);
                }
    
                if ( text_to_choose !== '' ) {
                  $("<option>").html(text_to_choose).val('').appendTo($select);
                }
    
                var foundPreselect = false;
                $.each(tree, function(k, v) {
                    var label, value;
                    if (options.indexed) {
                        label = v;
                        value = k;
                    } else {
                        label = value = k;
                    }
                    var o = $("<option>").html(label)
                        .attr('value', value);
                    var clean = cleanName(name);
                        if (options.leaf_class && typeof value !== 'object') { // this option is a leaf node
                            o.addClass(options.leaf_class);
                        }
    
                        o.appendTo($select);
                        if (isPreselectedFor(clean, value)) {
                          o.get(0).selected = true;
                          foundPreselect = true;
                        }
                });
    
                if (foundPreselect) {
                  $select.change();
                }
    
                if (!foundPreselect && options.preselect_only_once) { // clear preselect on first not-found level
                    options.preselect[cleanName(name)] = null;
                }
    
            } else if (options.set_value_on === 'leaf') { // single option is selected by the user (function called via onchange event())
                if (options.indexed) {
                    setValue(name, this.options[this.selectedIndex].value);
                } else {
                    setValue(name, tree);
                }
            }
        });
    
    };
    }(jQuery));

  3. #3
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,527
    Mentioned
    84 Post(s)
    Tagged
    4 Thread(s)
    There are a couple of issues that when fixed up, result in working code.

    • The code in LazyTaxonomyReader.php needs PHP tags to surround the code
    • The demo page says 'get-subtree.php' whereas you list the file as being called get-sub-tree.php
    • The taxonomy needs to exist in a file called taxonomy.txt
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  4. #4
    SitePoint Addict
    Join Date
    Apr 2007
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by paul_wilkins View Post
    There are a couple of issues that when fixed up, result in working code.

    • The code in LazyTaxonomyReader.php needs PHP tags to surround the code
    • The demo page says 'get-subtree.php' whereas you list the file as being called get-sub-tree.php
    • The taxonomy needs to exist in a file called taxonomy.txt
    Hi,
    The first 2 issues were typos in the forum post not the actual code. I assumed that the fact I had listed taxonomy.txt it was a given that it actually exists - it can be viewed here: http://www.creativenaturemedia.co.uk/demo/taxonomy.txt.
    Also I think you may have misunderstood the question - the code works in sense, just not the way it should. Please see working demo at: http://www.creativenaturemedia.co.uk...-tree-demo.php

  5. #5
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,527
    Mentioned
    84 Post(s)
    Tagged
    4 Thread(s)
    Quote Originally Posted by topmonkey View Post
    Hi,
    The first 2 issues were typos in the forum post not the actual code. I assumed that the fact I had listed taxonomy.txt it was a given that it actually exists - it can be viewed here: http://www.creativenaturemedia.co.uk/demo/taxonomy.txt.
    Also I think you may have misunderstood the question - the code works in sense, just not the way it should. Please see working demo at: http://www.creativenaturemedia.co.uk...-tree-demo.php
    This this problem is purely due to how LaxyTaxonomyReader.php fetches the data from taxonomy.txt, I'm moving this over to the PHP thread where you can receive the correct assistance with this.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  6. #6
    SitePoint Addict
    Join Date
    Apr 2007
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks Paul

  7. #7
    SitePoint Addict
    Join Date
    Apr 2007
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Anyone any ideas on this?

  8. #8
    SitePoint Wizard silver trophybronze trophy Cups's Avatar
    Join Date
    Oct 2006
    Location
    France, deep rural.
    Posts
    6,869
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)
    I looked at your txt file and I don't get your point.

    If you have established beyond doubt that it is a PHP problem, then you should focus down on that and set up a small test data rig and prove what works and what does not.

    If you are still stumped you should post that code here and someone will be happy to look at it.

  9. #9
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,527
    Mentioned
    84 Post(s)
    Tagged
    4 Thread(s)
    Quote Originally Posted by Cups View Post
    If you have established beyond doubt that it is a PHP problem, then you should focus down on that and set up a small test data rig and prove what works and what does not.
    What happens, is that with the following data in the taxonomy:

    Code:
    Office Supplies
    Office Supplies > General Office Supplies
    Office Supplies > General Office Supplies > Adhesives
    Office Supplies > General Office Supplies > Adhesives > Office Tape
    That ends up being incorrectly interpreted as two different sets of data.

    Code:
    Office Supplies > Adhesives > Office Tape
    Office Supplies > General Office Supplies > Adhesives > Office Tape
    The cause of that seems to be due to how "Office Supplies" is repeated in both the main category, and in the secondary category.

    The OP is looking for a way to fix that, which from what I see, seems to be caused somewhere in LazyTaxonomyReader.php
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  10. #10
    SitePoint Addict
    Join Date
    Apr 2007
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks for clarifying the issue Paul - spot on.

  11. #11
    SitePoint Wizard silver trophybronze trophy Cups's Avatar
    Join Date
    Oct 2006
    Location
    France, deep rural.
    Posts
    6,869
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)
    Right, I see it now, "Software" suffers from the same problem...

    Something to do with this line I think:

    PHP Code:
                // remove start text AND the following separator 
                
    $line str_replace($start''$line); 

  12. #12
    SitePoint Addict
    Join Date
    Apr 2007
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Or possibly this line?
    PHP Code:
    // starting text that must be present
    if (is_null($this->base)) {
      
    $start '';
    } else {
      
    $start $this->base $this->separator;


  13. #13
    SitePoint Addict
    Join Date
    Apr 2007
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I contacted the developer and he gave me the solution - for those who are interested, please see this page for the alterations needed to LazyTaxonomyReader.php:
    https://code.google.com/p/jquery-opt...e/detail?r=15#


Tags for this Thread

Bookmarks

Posting Permissions

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