Closed ccorcos closed 9 years ago
Here's an even better example:
Here's some html:
<tr id="frank_menu">
<th class="pomona_background"><a href="http://www.pomona.edu/administration/dining/menus/frank.aspx" target="_blank">Frank</a></th>
<td>
<span class="mobile_meal_header">Breakfast</span>
<ul>
<li>Bacon</li>
<li> Vegan Sausage</li>
<li> Hash Brown Potatoes</li>
<li> Tofu & Apple Sausage Scramble</li>
<li> Hard Boiled Eggs</li>
<li> Scrambled Eggs</li>
<li> Scrambled Egg Whites</li>
<li> Breakfast Special: Breakfast Burrito: Flour Tortilla</li>
<li> Scrambled Eggs</li>
<li> Sausage</li>
<li> Potatoes</li>
<li> Cheese</li>
<li>Omelet Bar</li>
<li>Breakfast Smoothies</li>
<li>Made To Order: Eggs</li>
<li> Buttermilk Pancakes & Cinnamon French Toast</li>
<li> Topping Bar Make Your Own Belgian Waffle</li>
<li>Oatmeal with Assorted Toppings: Brown Sugar</li>
<li> Raisins</li>
<li> Cinnamon Sugar</li>
<li> Banana Chips</li>
<li> Golden Raisins</li>
<li> Chocolate Chips</li>
<li>Bagel</li>
<li> Fruit & Yogurt Bar</li>
<li> Frank's Granola</li>
<li> Breakfast Muesli</li>
<li> Smoothies</li>
</ul>
</td>
<td>
<span class="mobile_meal_header">Lunch</span>
<ul>
<li>Kung Pao Chicken Wraps</li>
<li> Broccoli Beef</li>
<li> Mushu Seitan & Mushrooms</li>
<li> Steamed Rice</li>
<li> Noodles</li>
<li> Spring Rolls</li>
<li> Tofu-Vegetable Stir Fry</li>
<li>Cheese</li>
<li> Sour Cream</li>
<li> Apple</li>
<li> Pecan & White Chocolate Pizza</li>
<li> Pepperoni</li>
<li>Chicken & Noodle</li>
<li>Fresh Fruit</li>
</ul>
</td>
<td>
<span class="mobile_meal_header">Dinner</span>
<ul>
<li>Hamburgers</li>
<li> Cheeseburgers</li>
<li> Garden Burgers</li>
<li> Bacon</li>
<li> Chili</li>
<li> Grilled Onions & Mushrooms</li>
<li> French Fries</li>
<li> Onion Rings</li>
<li> Lettuce</li>
<li> Tomato</li>
<li> Red Onions</li>
<li> Pickles</li>
<li>Cheese</li>
<li> Vegetable</li>
<li> Pepperoni</li>
<li> Special</li>
<li>Fish-N-Chips</li>
<li>Spicy Chicken & Noodle</li>
<li>Fruit Salad</li>
</ul>
</td>
</tr>
And here's the solution:
$("tr#frank_menu>td").toArray().map(function(x) {
return $(x).find('ul').children().toArray().map(function(x) {
return $(x).text();
});
});
Its not that this library isn't useful -- it really is! But it would be a lot cleaner...
There are certainly more concise ways to achieve your goal. This is no different than how you would do this in the browser with jQuery.
-$("tr#frank_menu>td").toArray().map(function(x) {
- return $(x).find('ul').children().toArray().map(function(x) {
- return $(x).text();
- });
-});
+$("#frank_menu li").map(function() {
+ return $(this).text();
+}).toArray();
Does that help?
+$("#frank_menu li").map(function() {
+ return $(this).text();
+}).toArray();
This doesnt produce separate lists:
[ 'Bacon',
' Vegan Sausage',
' Hash Brown Potatoes',
' Tofu & Apple Sausage Scramble',
' Hard Boiled Eggs',
' Scrambled Eggs',
' Scrambled Egg Whites',
' Breakfast Special: Breakfast Burrito: Flour Tortilla',
' Scrambled Eggs',
' Sausage',
' Potatoes',
' Cheese',
'Omelet Bar',
'Breakfast Smoothies',
'Made To Order: Eggs',
' Buttermilk Pancakes & Cinnamon French Toast',
' Topping Bar Make Your Own Belgian Waffle',
'Oatmeal with Assorted Toppings: Brown Sugar',
' Raisins',
' Cinnamon Sugar',
' Banana Chips',
' Golden Raisins',
' Chocolate Chips',
'Bagel',
' Fruit & Yogurt Bar',
' Frank\'s Granola',
' Breakfast Muesli',
' Smoothies',
'Kung Pao Chicken Wraps',
' Broccoli Beef',
' Mushu Seitan & Mushrooms',
' Steamed Rice',
' Noodles',
' Spring Rolls',
' Tofu-Vegetable Stir Fry',
'Cheese',
' Sour Cream',
' Apple',
' Pecan & White Chocolate Pizza',
' Pepperoni',
'Chicken & Noodle',
'Fresh Fruit',
'Hamburgers',
' Cheeseburgers',
' Garden Burgers',
' Bacon',
' Chili',
' Grilled Onions & Mushrooms',
' French Fries',
' Onion Rings',
' Lettuce',
' Tomato',
' Red Onions',
' Pickles',
'Cheese',
' Vegetable',
' Pepperoni',
' Special',
'Fish-N-Chips',
'Spicy Chicken & Noodle',
'Fruit Salad' ]
while my method does:
-$("tr#frank_menu>td").toArray().map(function(x) {
- return $(x).find('ul').children().toArray().map(function(x) {
- return $(x).text();
- });
-});
which produces:
[ [ 'Bacon',
' Vegan Sausage',
' Hash Brown Potatoes',
' Tofu & Apple Sausage Scramble',
' Hard Boiled Eggs',
' Scrambled Eggs',
' Scrambled Egg Whites',
' Breakfast Special: Breakfast Burrito: Flour Tortilla',
' Scrambled Eggs',
' Sausage',
' Potatoes',
' Cheese',
'Omelet Bar',
'Breakfast Smoothies',
'Made To Order: Eggs',
' Buttermilk Pancakes & Cinnamon French Toast',
' Topping Bar Make Your Own Belgian Waffle',
'Oatmeal with Assorted Toppings: Brown Sugar',
' Raisins',
' Cinnamon Sugar',
' Banana Chips',
' Golden Raisins',
' Chocolate Chips',
'Bagel',
' Fruit & Yogurt Bar',
' Frank\'s Granola',
' Breakfast Muesli',
' Smoothies' ],
[ 'Kung Pao Chicken Wraps',
' Broccoli Beef',
' Mushu Seitan & Mushrooms',
' Steamed Rice',
' Noodles',
' Spring Rolls',
' Tofu-Vegetable Stir Fry',
'Cheese',
' Sour Cream',
' Apple',
' Pecan & White Chocolate Pizza',
' Pepperoni',
'Chicken & Noodle',
'Fresh Fruit' ],
[ 'Hamburgers',
' Cheeseburgers',
' Garden Burgers',
' Bacon',
' Chili',
' Grilled Onions & Mushrooms',
' French Fries',
' Onion Rings',
' Lettuce',
' Tomato',
' Red Onions',
' Pickles',
'Cheese',
' Vegetable',
' Pepperoni',
' Special',
'Fish-N-Chips',
'Spicy Chicken & Noodle',
'Fruit Salad' ] ]
First of all, the API is inherited from jQuery and, considering how long it's been around, doesn't follow many newer best practices. Especially the order of arguments in .map
(and other array methods) is outdated.
Being aware of that, it would still be possible to extend the API to simplify your use case. But even if we add a .mapArray
method, which is equivalent to .toArray().map
, it would only save a couple of keystrokes and probably isn't worth the effort.
@ccorcos Do you have any recommendations for API extensions?
Well I'm not terribly familiar with this api but here's what I would expect.
$("tr#frank_menu>td")
returns an object. in that object there is an array of td
elements and prototype functions:
{
elements: [{type:'tag', name:'td', ...}, ...],
depth: 0,
map: function() { this.elements.map.apply(this.arguments)} // something like this
find: function...
children: function...
text: function...
}
Then any function such as find
, children
or text
will go into the elements array and apply to every element. For example:
$("tr#frank_menu>today").find('ul')
will go through each element of the elements array finding the ul
elements. Thus if after $("tr#frank_menu>today")
the elements array looks like [td, td]
, then $("tr#frank_menu>today").find('ul')
produces [ [ul, ul], [ul, ul, ul] ]
.
Now, the elements array is an array of arrays. The next time a function is applied to this object, it will apply to each ul
element, not the top level array. That is, whenever we call find
, children
or text
, we recursively go to the deepest element that is not an array and apply to that element.
so, $("tr#frank_menu>today").find('ul').children()
produces [ [ [span], [span]], [[span], [span], [span]] ]
flatten()
would be a very helpful function here as well. a function that flattens the inner most arrays
$("tr#frank_menu>today").find('ul').children().flatten()
will produce [ [ span, span], [span, span, span] ]
and finally:
$("tr#frank_menu>today").find('ul').children().flatten().text()
produces [ ['apple', 'orage'], ['12', 'pear', 'whatever'] ]
Does that make sense? I can explain more if necessary.
@ccorcos That would be a change in the signature of some methods, which would break existing projects depending on cheerio. Sadly, that won't be possible.
Well, to keep things backwards compatible, you could create new methods names such as deepFind
, mapFind
, or find2
This worked for me:
let list = Array.from($('li'));
Using the example, I find it ridiculous that this is what I have to do to get the list of items: