Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrapper function for functions with similar functionality #1731

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 108 additions & 134 deletions lib/api/traversing.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,62 @@ var isTag = utils.isTag;
var slice = Array.prototype.slice;
var reSiblingSelector = /^\s*[~+]/;

/**
* Matcher provides function, what finds elements based on function provided. It
* also houses filtering. What may provided later when function is called.
*
* @private
* @param {string} name - Function name.
* @param {Function} fn - Function for collecting elements.
* @returns {Function} - Wrapped function.
*/
function _matcher(name, fn) {
// customized Map, discards null elements
function matchMap(elems) {
var len = elems.length;
var value;
var i = 0;
var ret = [];
for (; i < len; i++) {
value = fn(elems[i], i);
if (value !== null) {
ret.push(value);
}
}
return Array.prototype.concat.apply([], ret);
}

return function (selector) {
if (this[0]) {
var matched = matchMap(this);

// select.filter uses uniqueSort already internally
if (selector) {
if (typeof selector === 'string') {
matched = select.filter(selector, matched, this.options);
} else {
matched = matched.filter(getFilterFn(selector));
}
}

// Sorting happend only if collection had more than one elements
if (this.length > 1) {
if (!(name === 'next' || name === 'prev' || name === 'children')) {
matched = uniqueSort(matched);
}

// Reverse order
if (name === 'parents' || name === 'prevAll') {
matched.reverse();
}
}

return this._make(matched);
}
return this;
};
}

/**
* Get the descendants of each element in the current set of matched elements,
* filtered by a selector, jQuery object, or element.
Expand Down Expand Up @@ -70,30 +126,15 @@ exports.find = function (selectorOrHaystack) {
* $('.pear').parent().attr('id');
* //=> fruits
*
* @function
* @param {string} [selector] - If specified filter for parent.
* @returns {Cheerio} The parents.
* @see {@link https://api.jquery.com/parent/}
*/
exports.parent = function (selector) {
var set = [];

domEach(this, function (_, elem) {
var parentElem = elem.parent;
if (
parentElem &&
parentElem.type !== 'root' &&
set.indexOf(parentElem) < 0
) {
set.push(parentElem);
}
});

if (selector) {
set = exports.filter.call(set, selector, this);
}

return this._make(set);
};
exports.parent = _matcher('parent', function (elem) {
var parent = elem.parent;
return parent && parent.type !== 'root' ? parent : null;
});

/**
* Get a set of parents filtered by `selector` of each element in the current
Expand All @@ -105,30 +146,18 @@ exports.parent = function (selector) {
* $('.orange').parents('#fruits').length;
* // => 1
*
* @function
* @param {string} [selector] - If specified filter for parents.
* @returns {Cheerio} The parents.
* @see {@link https://api.jquery.com/parents/}
*/
exports.parents = function (selector) {
var parentNodes = [];

// When multiple DOM elements are in the original set, the resulting set will
// be in *reverse* order of the original elements as well, with duplicates
// removed.
this.get()
.reverse()
.forEach(function (elem) {
traverseParents(this, elem.parent, selector, Infinity).forEach(function (
node
) {
if (parentNodes.indexOf(node) === -1) {
parentNodes.push(node);
}
});
}, this);

return this._make(parentNodes);
};
exports.parents = _matcher('parents', function (elem) {
var matched = [];
while ((elem = elem.parent) && elem.type !== 'root') {
matched.push(elem);
}
return matched;
});

/**
* Get the ancestors of each element in the current set of matched elements, up
Expand Down Expand Up @@ -227,29 +256,15 @@ exports.closest = function (selector) {
* $('.apple').next().hasClass('orange');
* //=> true
*
* @function
* @param {string} [selector] - If specified filter for sibling.
* @returns {Cheerio} The next nodes.
* @see {@link https://api.jquery.com/next/}
*/
exports.next = function (selector) {
if (!this[0]) {
return this;
}
var elems = [];

domEach(this, function (_, elem) {
while ((elem = elem.next)) {
if (isTag(elem)) {
elems.push(elem);
return;
}
}
});

return selector
? exports.filter.call(elems, selector, this)
: this._make(elems);
};
exports.next = _matcher('next', function (elem) {
while ((elem = elem.next) && !isTag(elem));
return elem;
});

/**
* Gets all the following siblings of the first selected element, optionally
Expand All @@ -261,28 +276,18 @@ exports.next = function (selector) {
* $('.apple').nextAll('.orange');
* //=> [<li class="orange">Orange</li>]
*
* @function
* @param {string} [selector] - If specified filter for siblings.
* @returns {Cheerio} The next nodes.
* @see {@link https://api.jquery.com/nextAll/}
*/
exports.nextAll = function (selector) {
if (!this[0]) {
return this;
exports.nextAll = _matcher('nextAll', function (elem) {
var matched = [];
while ((elem = elem.next)) {
if (isTag(elem)) matched.push(elem);
}
var elems = [];

domEach(this, function (_, elem) {
while ((elem = elem.next)) {
if (isTag(elem) && elems.indexOf(elem) === -1) {
elems.push(elem);
}
}
});

return selector
? exports.filter.call(elems, selector, this)
: this._make(elems);
};
return matched;
});

/**
* Gets all the following siblings up to but not including the element matched
Expand Down Expand Up @@ -342,29 +347,16 @@ exports.nextUntil = function (selector, filterSelector) {
* $('.orange').prev().hasClass('apple');
* //=> true
*
* @function
* @param {string} [selector] - If specified filter for siblings.
* @returns {Cheerio} The previous nodes.
* @see {@link https://api.jquery.com/prev/}
*/
exports.prev = function (selector) {
if (!this[0]) {
return this;
}
var elems = [];

domEach(this, function (_, elem) {
while ((elem = elem.prev)) {
if (isTag(elem)) {
elems.push(elem);
return;
}
}
});

return selector
? exports.filter.call(elems, selector, this)
: this._make(elems);
};
exports.prev = _matcher('prev', function (elem) {
// eslint-disable-next-line no-empty
while ((elem = elem.prev) && !isTag(elem)) {}
return elem;
});

/**
* Gets all the preceding siblings of the first selected element, optionally
Expand All @@ -376,28 +368,18 @@ exports.prev = function (selector) {
* $('.pear').prevAll('.orange');
* //=> [<li class="orange">Orange</li>]
*
* @function
* @param {string} [selector] - If specified filter for siblings.
* @returns {Cheerio} The previous nodes.
* @see {@link https://api.jquery.com/prevAll/}
*/
exports.prevAll = function (selector) {
if (!this[0]) {
return this;
exports.prevAll = _matcher('prevAll', function (elem) {
var matched = [];
while ((elem = elem.prev)) {
if (isTag(elem)) matched.push(elem);
}
var elems = [];

domEach(this, function (_, elem) {
while ((elem = elem.prev)) {
if (isTag(elem) && elems.indexOf(elem) === -1) {
elems.push(elem);
}
}
});

return selector
? exports.filter.call(elems, selector, this)
: this._make(elems);
};
return matched;
});

/**
* Gets all the preceding siblings up to but not including the element matched
Expand Down Expand Up @@ -459,24 +441,21 @@ exports.prevUntil = function (selector, filterSelector) {
* $('.pear').siblings('.orange').length;
* //=> 1
*
* @function
* @param {string} [selector] - If specified filter for siblings.
* @returns {Cheerio} The siblings.
* @see {@link https://api.jquery.com/siblings/}
*/
exports.siblings = function (selector) {
var parent = this.parent();

var elems = (parent ? parent.children() : this.siblingsAndMe())
.toArray()
.filter(function (elem) {
return isTag(elem) && !this.is(elem);
}, this);

if (selector !== undefined) {
return exports.filter.call(elems, selector, this);
exports.siblings = _matcher('siblings', function (elem) {
var node = elem.parent && elem.parent.firstChild;
var matched = [];
for (; node; node = node.next) {
if (isTag(node) && node !== elem) {
matched.push(node);
}
}
return this._make(elems);
};
return matched;
});

/**
* Gets the children of the first selected element.
Expand All @@ -488,19 +467,14 @@ exports.siblings = function (selector) {
* $('#fruits').children('.pear').text();
* //=> Pear
*
* @function
* @param {string} [selector] - If specified filter for children.
* @returns {Cheerio} The children.
* @see {@link https://api.jquery.com/children/}
*/
exports.children = function (selector) {
var elems = this.toArray().reduce(function (newElems, elem) {
return newElems.concat(elem.children.filter(isTag));
}, []);

if (selector === undefined) return this._make(elems);

return exports.filter.call(elems, selector, this);
};
exports.children = _matcher('children', function (elem) {
return elem.children.filter(isTag);
});

/**
* Gets the children of each element in the set of matched elements, including
Expand Down
18 changes: 18 additions & 0 deletions test/__fixtures__/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ exports.drinks = [
'</ul>',
].join('');

exports.eleven = [
'<html>\n<body>\n<ul>',
'<li>One</li>',
'<li>Two</li>',
'<li class="blue sel">Three</li>',
'<li class="red">Four</li>',
'</ul>\n\n<ul>',
'<li class="red">Five</li>',
'<li>Six</li>',
'<li class="blue">Seven</li>',
'</ul>\n\n<ul>',
'<li>Eight</li>',
'<li class="red sel">Nine</li>',
'<li>Ten</li>',
'<li class="sel">Eleven</li>',
'</ul>\n</body>\n</html>',
].join('\n');

exports.food = [
'<ul id="food">',
exports.fruits,
Expand Down
Loading