Source: sputils.termstore.js

(function () {
  // a function that creates common properties on a TreeNode-like
  // object. A TreeNode-like object has children, and sortedChildren
  var initTreeNode = function (n) {
    // indexed by name, no guaranteed order
    n.children = {};
    // indexed by order (by custom sort order if defined)
    n.sortedChildren = [];
  };

  var TermsTree = function SputilsTermsTree(termSet) {
    this.termSet = termSet;

    initTreeNode(this);
  };

  TermsTree.prototype = {
    getSortOrder: function () {
      return this.termSet.get_customSortOrder();
    }
  };

  // A data structure wrapping SP.Term objects
  // with convenience functions and a children object
  var TreeNode = function SputilsTreeNode(term) {
    this.term = term;

    initTreeNode(this);
  };

  TreeNode.prototype = {
    getName: function () {
      return this.term.get_name();
    },

    getUrl: function () {
      return this.getLocalCustomProperty('_Sys_Nav_SimpleLinkUrl');
    },

    getSortOrder: function () {
      return this.term.get_customSortOrder();
    },

    getIsRoot: function () {
      return this.term.get_isRoot();
    },

    getGuid: function () {
      return this.term.get_id();
    },

    getLocalCustomProperty: function (propertyName) {
      return this.term.get_localCustomProperties()[propertyName];
    },

    toString: function () {
      return this.getName();
    },
    toLocaleString: function () {
      return this.getName();
    }
  };

  // A ListNode do not have children/sortedChildren.
  var ListNode = function SputilsListNode(term) {
    this.term = term;
  };

  // A ListNode do expose the same operations as a TreeNode,
  // since it does the same sort of wrapping of the underlying
  // implementation of a SP.Term.
  ListNode.prototype = TreeNode.prototype;

  var generateList = function (terms) {
    var termsEnumerator = terms.getEnumerator(),
        result = [];

    while (termsEnumerator.moveNext()) {
      result.push(termsEnumerator.get_current());
    }

    return result;
  };

  // Generates the link tree by going through them one
  // by one and putting them in the correct hierarchy
  var generateTree = function (terms) {
    // Get the hidden _termSet property
    // we put on the original terms object.
    var termSet = terms._termSet;
    var mainTree = new TermsTree(termSet);

    var populateTree = function (tree, term, path) {
      var name = path[0];

      if (path.length === 1) {
        if (tree[name]) {
          tree[name].term = term;
        } else {
          tree[name] = new TreeNode(term);
        }
      } else {
        if (!tree[name]) {
          tree[name] = new TreeNode();
        }

        populateTree(tree[name].children, term, path.slice(1));
      }
    };

    // Reuse generateList to turn the terms
    // object into a list of terms.
    generateList(terms).forEach(function (currentTerm) {
      populateTree(mainTree.children, currentTerm, currentTerm.get_pathOfTerm().split(';'));
    });

    return mainTree;
  };

  var getDefaultTermStore = function (context) {
    var session = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);

    return session.getDefaultSiteCollectionTermStore();
  };

  // Returns a promise which resolves when SP Taxonomy is loaded
  var withTaxonomyDeps = function () {
    return sputils.helpers.withSharePointDependencies([{
      file: 'sp.taxonomy.js',
      namespace: 'SP.Taxonomy'
    }]);
  };

  // populates the sortedChildren property of the node.
  var sortTerms = function (parent) {
    var sortOrder = parent.getSortOrder();
    function accordingToSortOrder(childA, childB) {
      var getGuid = function (x) {
        return x.getGuid().toString();
      };
      var a = sortOrder.indexOf(getGuid(childA)),
          b = sortOrder.indexOf(getGuid(childB));

      // numerically, ascending
      return a - b;
      // numerically, descending
      // return b - a;
    }

    var secondEl = fjs.pluck(1);
    var cArr = secondEl(fjs.toArray(parent.children));
    parent.sortedChildren = cArr;

    if (sortOrder) {
      // Sort order is a string of guids
      // separated by ":".
      sortOrder = sortOrder.split(':');

      // Replace children with an array sorted
      // according to the sortOrder.
      cArr.sort(accordingToSortOrder);
    } else {
      // sortBy with no second parameter
      // sorts on identity (just compares the values)
      // lexicographically, i.e. "alphabetically".
      cArr.sort();
    }
  };

  // Takes a tree and recursively
  // sorts all levels
  var sortTree = function (tree) {
    sortTerms(tree);

    if (tree.sortedChildren.length) {
      fjs.each(sortTree, tree.sortedChildren);
    }

    return tree;
  };

  /**
   * Returns a promise which resolves with
   * an object containing all the terms
   * corresponding to the given termset id.
   * @function sputils.termstore.getTerms
   * @param {string} id a termset guid
   * @returns {Promise<SP.TermCollection>}
   */
  var getTerms = function (id) {
    return withTaxonomyDeps().then(function () {
      var context = SP.ClientContext.get_current(),
          termStore = getDefaultTermStore(context),
          termSet = termStore.getTermSet(id),
          terms = termSet.getAllTerms();

      context.load(terms);
      context.load(termSet);
      return new Cctx(context).executeQuery()
        .then(function () {
          // Add the termSet on the terms
          // object so we can access it later.
          terms._termSet = termSet;
          return terms;
        });
    });
  };

  /**
   * Returns a promise which resolves
   * to an array. Each element
   * is a taxonomy term object.
   * @function sputils.termstore.getTermsList
   * @param {string} id a termset guid
   * @returns {Promise<Array>}
   */
  var getTermsList = function (id) {
    var wrapObjects = function (list) {
      return fjs.map(function (spTerm) {
        return new ListNode(spTerm);
      }, list);
    };

    return getTerms(id)
      .then(generateList)
      .then(wrapObjects);
  };

  /**
   * Returns a promise which resolves
   * to a tree object. Each node has
   * a children property which is sorted
   * according to customSortOrder.
   * @function sputils.termstore.getTermsTree
   * @param {string} id a termset guid
   * @returns {Promise<TermsTree>}
   */
  var getTermsTree = function (id) {
    return getTerms(id)
    // The terms are unordered and without
    // a useful way of interpreting the hierarchy,
    // so we turn them into a tree.
      .then(generateTree)
    // And then sorted according to customSortOrder
      .then(sortTree);
  };

  /** @namespace */
  sputils.termstore = {
    getTerms: getTerms,
    getTermsList: getTermsList,
    getTermsTree: getTermsTree,
    withTaxonomyDeps: withTaxonomyDeps
  };
})();