// TODO: consider putting it in a webjar instead
require(['jquery', '$xwiki.getSkinFile("uicomponents/widgets/tree.min.js", true)'], function ($) {
  'use strict';
  
  /**
   * Class that represent a decoded URL.
   * TODO: make it more generic and put it in some library.
   */
  var DecodedURL = function (path, params) {
    
    /**
     * Fields
     */
    var self    = this;
    self.path   = path;
    self.params = params;
    
    /**
     * Parse an URL and returns an object containing the path and the parameters decoded from the query string.
     * Hashes (#) are not supported.
     */
    self.initFromURL = function (url) {
      self.params = {};
      var queryStringLocator = url.indexOf('?');
      if (queryStringLocator == -1) {
        self.path = url;
      } else {
        self.path = url.substring(0, queryStringLocator);
        var queryString = url.substring(queryStringLocator + 1);
        // Parse the query string
        var parts = queryString.split('&');
        for (var i = 0; i < parts.length; ++i) {
          var temp  = parts[i].split('=');
          var name  = decodeURIComponent(temp[0]);
          var value = decodeURIComponent(temp[1]);
          if (self.params[name] !== undefined) {
            if (Array.isArray(self.params[name])) {
              self.params[name].push(value);
            } else {
              var oldValue = self.params[name];
              self.params[name] = [oldValue, value];
            }
          } else {
            self.params[name] = value;
          }
        }
      } 
    };
    
    /**
     * Return a serialized version of the url
     */
    self.serialize = function () {
      var url = self.path;
      var separator = '?';
      for (var param in self.params) {
        var value = self.params[param];
        if (param != '' && value !== undefined) {
          if (Array.isArray(value)) {
            for (var i = 0; i < value.length; ++i) {
              url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value[i]);
              separator = '&';
            }
          } else if (typeof value === 'string') {
            url += separator + encodeURIComponent(param) + '=' + encodeURIComponent(value);
            separator = '&';
          }
        }
      }
      return url;
    };

    /**
     * Return a clone of the object
     */
    self.clone = function () {
      var clone = new DecodedURL();
      clone.path = self.path;
      clone.params = [];
      for (var param in self.params) {
        if (Array.isArray(self.params[param])) {
          clone.params[param] = [];
          for (var i = 0; i < self.params[param].length; ++i) {
            clone.params[param].push(self.params[param][i]);
          }
        } else if (typeof self.params[param] === 'string') {
          clone.params[param] = self.params[param];
        }
      }
      return clone;
    }
  };

  /**
   * Work with the Export Tree
   */
  require(['tree'], function () {
    $(document).ready(function () {
      
      /**
       * Save the default url
       */
      $('#exportModal a.btn').each(function() {
        var button = $(this);
        var url = new DecodedURL();
        url.initFromURL(button.attr('href'));
        button.data('url', url);
      });
      
      /**
       * Initialize the tree.
       * Note: we need to wait the 'loaded.jstree' event otherwise we have synchronization issues.
       */
      var tree = $('#exportModal .xtree');
      tree.on('loaded.jstree', function () {
        
        // Change the tree behaviour
        var treeReference = $.jstree.reference(tree);
        treeReference.settings.checkbox.cascade        = 'down';
        treeReference.settings.checkbox.three_state    = false ;
        treeReference.settings.contextmenu.select_node = false ;
        
        // Check all by default
        treeReference.check_all();
      
        /**
         * Modifying the context menu
         */
        treeReference.settings.contextmenu.items = function (node) {
          var items = {};
          
          items.select_children = {
            label: "$escapetool.javascript($services.localization.render('core.exporter.selectChildren'))",
            action: function () {
              for (var i = 0; i < node.children.length; ++i) {
                var child = node.children[i];
                treeReference.check_node(child);
              }
            },
            _disabled: !node.state.opened
          };
          items.unselect_children = {
            label: "$escapetool.javascript($services.localization.render('core.exporter.unselectChildren'))",
            action: function () {
              for (var i = 0; i < node.children.length; ++i) {
                var child = node.children[i];
                treeReference.uncheck_node(child);
              }
            },
            _disabled: !node.state.opened
          };
          
          return items;
        };

        /**
         * Handle 'select all' button
         */
        $('#exportModal .tree_select_all').click(function() {
          treeReference.check_all();
          return false;
        });
        
        /**
         * Handle 'select none' button
         */
        $('#exportModal .tree_select_none').click(function() {
          treeReference.uncheck_all();
          return false;
        });
          
        /**
         * Update the URL of the export buttons according to the tree 
         */
         $('#exportModal #exportModelOtherCollapse a.btn').click(function(event) {
          var button = $(this);
          var url = button.data('url').clone();
          var checkedNodes = treeReference.get_checked(true);
          if (url.params === undefined) {
            url.params = {'pages': []};
          }
          
          url.params.pages = [];
          for (var i = 0; i < checkedNodes.length; ++i) {
            var node = checkedNodes[i];
            var pageReference = XWiki.Model.resolve(node.data.id, XWiki.EntityType.DOCUMENT);
            url.params.pages.push(XWiki.Model.serialize(pageReference));
            // In case the page coudl have children
            if (pageReference.getName() == 'WebHome') {
              // The node could be checked but not loaded
              if (node.state.loaded == false) {
                // In that case, we need to add all children using a wildcard.
                var spaceReference = new XWiki.EntityReference('%', XWiki.EntityType.DOCUMENT, pageReference.parent);
                url.params.pages.push(XWiki.Model.serialize(spaceReference));
              }
              // Special behaviour for the XAR export
              if (url.params.format == 'xar') {
                // Also add the WebPreferences
                var webPreferencesReference = new XWiki.EntityReference('WebPreferences', XWiki.EntityType.DOCUMENT, 
                  pageReference.parent);
                url.params.pages.push(XWiki.Model.serialize(webPreferencesReference));
              }
            }
          }
          button.attr('href', url.serialize());
        });
      });
    });

  });
});

@@ -51,16 +51,19 @@
 equire(%5B
+%0A  
 'jquery'
@@ -67,71 +67,130 @@
 ry',
- '$xwiki.getSkinFile(%22uicomponents/widgets/tree
+%0A  %22$!services.webjars.url('org.xwiki.platform:xwiki-platform-tree-webjar', 'require-config
 .min.js
-%22, true)'
+', %7B'evaluate': true%7D)%22%0A
 %5D, f

Allow selection of children in the Export dialog * Don't depend on uicomponents/widgets/tree.min.js because it wasn't meant for this (and it's not an actual module). Instead, depend on the require-config.min.js that brings the configuration for the 'tree' module (which we ask for a couple of lines below)