Jump to content

Sorting pages by multiple values


janlonden
 Share

Recommended Posts

Hi!

I´m really liking ProcessWire, even though it´s a little intimidating to a complete PHP noob like me. The basics are pretty easy understand, but one function I still lack is the option to sort pages by custom fields. I looked at Ryans Scryscraper Site Profile to try to figure out how he did it for about 5 minutes before my brain exploded. I´ve also tried to search the forums without success. So if this is something that some of you PHP gurus could help me with that would be awesome.

Here´s my question. Lets say I have a photos page with lots of photo pages in it which include custom fields such as resolution, camera, size etc.. How would i go about to list them by these fields? For example like this:

select

-option(Camera model from A-Z)

-option(Camera model from Z-A)

select

-option(Highest resolution)

-option(Lowest resolution)

select

-option(Highest size)

-option(Lowest size)

[Find]

Thank you.

/Jan Londén

Edit:

Here´s the final code. Remember this is an ugly copy/paste version from a non PHP programmer. Use at your own risk, etc.. If anyone wants to improve, make this more elegant, please do. Huge thanks to adrian who helped a lot.

<?php

/**
 * Books template
 *
 */

include("./head.inc");

$selector = '';

// For the keyword search input. Searches both title and body.

if($input->get->keywords) {
    $value = $sanitizer->selectorValue($input->get->keywords);
    $selector .= "title|body%=$value, ";
    $summary["keywords"] = $sanitizer->entities($value);
    $input->whitelist('keywords', $value);
}

// For the book publisher select tag. Sorts alphabetically from A to Z.

if($input->get->book_publisher == 'desc') {
    $value = $sanitizer->selectorValue($input->get->book_publisher);
    $selector .= "sort=book_publisher,";
    $summary["book_publisher"] = $sanitizer->entities($value);
    $input->whitelist('book_publisher', $value);
}

// From Z to A.

elseif($input->get->book_genre == 'asc') {
    $value = $sanitizer->selectorValue($input->get->book_genre);
    $selector .= "sort=-book_genre,";
    $summary["book_genre"] = $sanitizer->entities($value);
    $input->whitelist('book_genre', $value);
}

// For the book chapters select tag. Sorts numerically from highest to lowest number of pages.

if($input->get->book_chapters == 'desc') {
    $value = $sanitizer->selectorValue($input->get->book_chapters);
    $selector .= "sort=book_chapters,";
    $summary["book_chapters"] = $sanitizer->entities($value);
    $input->whitelist('book_chapters', $value);
}

// From lowest to highest.

elseif($input->get->book_chapters == 'asc') {
    $value = $sanitizer->selectorValue($input->get->book_chapters);
    $selector .= "sort=-book_chapters,";
    $summary["book_chapters"] = $sanitizer->entities($value);
    $input->whitelist('book_chapters', $value);
}

// For the book pages select tag. Sorts numerically from highest to lowest number of pages.

if($input->get->book_pages == 'desc') {
    $value = $sanitizer->selectorValue($input->get->book_pages);
    $selector .= "sort=book_pages,";
    $summary["book_pages"] = $sanitizer->entities($value);
    $input->whitelist('book_pages', $value);
}

// From lowest to highest.

elseif($input->get->book_pages == 'asc') {
    $value = $sanitizer->selectorValue($input->get->book_pages);
    $selector .= "sort=-book_pages,";
    $summary["book_pages"] = $sanitizer->entities($value);
    $input->whitelist('book_pages', $value);
}

?>

<form id="book_search" method="get" action="<?php echo $config->urls->root?>books/">
    <h3>Browse books</h3>
    <p>
        <label for="search_keywords">Search</label>
        <input type="text" name="keywords" id="search_keywords" value="<?php if($input->whitelist->keywords) echo $sanitizer->entities($input->whitelist->keywords); ?>" />
    </p>
    <p>
        <label for="book_publisher">Publisher</label>
        <select name="book_publisher" id="book_publisher">
            <option value="">Any</option>
            <option value="desc"<?php if ($input->get->book_publisher == 'desc') { echo ' selected="selected"'; } ?>>Alphabetically A-Z</option>
            <option value="asc"<?php if ($input->get->book_publisher == 'asc') { echo ' selected="selected"'; } ?>>Alphabetically Z-A</option>
        </select>
    </p>
    <p>
        <label for="book_chapters">Number of chapters</label>
        <select name="book_chapters" id="book_chapters">
            <option value="">Any</option>
            <option value="desc"<?php if ($input->get->book_chapters == 'desc') { echo ' selected="selected"'; } ?>>Highest to lowest</option>
            <option value="asc"<?php if ($input->get->book_chapters == 'asc') { echo ' selected="selected"'; } ?>>Lowest to highest</option>
        </select>
    </p>
    <p>
        <label for="book_pages">Number of pages</label>
        <select name="book_pages" id="book_pages">
            <option value="">Any</option>
            <option value="desc"<?php if ($input->get->book_pages == 'desc') { echo ' selected="selected"'; } ?>>Highest to low</option>
            <option value="asc"<?php if ($input->get->book_pages == 'asc') { echo ' selected="selected"'; } ?>>Lowest to high</option>
        </select>
    </p>
    <p>
        <input type="submit" id="book_submit" name="submit" value="Find" />
    </p>
</form>

<?php $books = $page->children($selector); ?>
<?php if (count($books)) : ?>
<ul>
<?php foreach($books as $book) : ?>
    <li>
        <p>
            <a href="<?=$book->url?>"><?=$book->title?></a><br>
            Publisher: <?=$book->book_publisher?><br>
            Number of chapters: <?=$book->book_chapters?><br>
            Number of pages: <?=$book->book_pages?>

        </p>
    </li>
<?php endforeach ?>
</ul>
<?php else : ?>
<p>No books were found.</p>
<?php endif ?>
<?php include("./foot.inc");

You might need to change desc/asc values to get them working the way you want.

Edited by janlonden
Link to comment
Share on other sites

The trick is to build up a string variable eg. $selector, based on the options chosen by the user and using that variable as part of something like:

$images = $page->children($selector); 
 

This assumes of course that all your images are child pages of the main gallery page.

You'll probably want $selector to end up being something like:

sort=-camera_model, sort=resolution, sort=-size
 

The way to build $selector can be seen in the Skyscraper's search.php file. For example, this section:

if($input->get->keywords) {
	$value = $sanitizer->selectorValue($input->get->keywords);
	$selector .= "title|body%=$value, "; 
	$summary["keywords"] = $sanitizer->entities($value); 
	$input->whitelist('keywords', $value); 
}

Each section like this appends more component parts to the $selector string using the '.=' notation. The $input->get->keywords refers to a variable passed from your form using the GET method. You could of course also use POST. GET will put the variables in the URL like ?keywords=word& etc. This is useful for allowing a user to bookmark or share a query link.

I am not exactly sure your plans for the resolution and size selects - sounds like you want to offer sorting highest to lowest and lowest to highest? If so, you can build your selector using sort options. Take a look at the sort options on this page: http://processwire.com/api/selectors/

Once you have defined your $selector and searched and sorted the children with it, you can simply foreach through the images:

foreach($images as $image){
       echo $image->url ...... //etc
}
 

Hope that helps. 

  • Like 3
Link to comment
Share on other sites

Thank you for your help. This is what I´ve managed to come up with:

<?php $images = $page->children($selector);

if($input->get->keywords) {
	$value = $sanitizer->selectorValue($input->get->keywords);
	$selector .= "title|body%=$value, "; 
	$summary["keywords"] = $sanitizer->entities($value); 
	$input->whitelist('keywords', $value); 
} ?>

<h1>Browse photos</h1>

<form id="photo_search" method="get" action="<?php echo $config->urls->root?>browse/?>">
	<p>
		<label for='search_keywords'>Keywords</label>
		<input type='text' name='keywords' id='search_keywords' value='<?php if($input->whitelist->keywords) echo $sanitizer->entities($input->whitelist->keywords); ?>' />
	</p>
	<p>
		<input type="submit" id="search_submit" name="submit" value="Find" />
	</p>
</form>

<?php foreach($images as $image) {
	echo $image->url;
} ?>

... As i said, I´m a total PHP noob. This won't even display the search results :D

I looked at the selector page and found that the code below works to display the photos by model/resolution/size.

$photos = $pages->find("template=photo, sort=camera_model");
foreach($photos as $photo) { ... }

But how to implement that into the search form i have no idea.

Maybe I´ll have to settle for a single select input which links to pages with custom querys?

Link to comment
Share on other sites

I think you have the right idea with the example you showed above, but a few things:

  1. At the end of your <form tag you are using the php close tag (?>) twice. You want something like: 
    action="<?php echo $config->urls->root; ?>browse/"
    

    When you are using PHP and things aren't working, be sure to view the source of the rendered HTML page to confirm it is generating what you think it is. Also make sure you are getting your PHP errors generated somewhere, either on the page or in a log file. Not sure how this is set up as it can depend on the settings on your host. If you don't see them, ask them and/or google turn on php error reporting.

  2. Also, turn on debug on PW config.

  3. You should define $selector once as $selector = ''; before you start appending parts to it. Not a fatal error, but good practice.

  4. You are trying to use the $selector before you have generated it

Try this:

<?php 

if($input->get->keywords) {
    $value = $sanitizer->text($input->get->keywords);
    $selector .= "title|body%=$value, "; 
    $summary["keywords"] = $sanitizer->entities($value); 
    $input->whitelist('keywords', $value); 
} 

$images = $page->children($selector);

foreach($images as $image) {
    echo '<img src="'.$image->url.'" alt="'.$image->title.'" title="'.$image->title.'" />';
}

?>

<h1>Browse photos</h1>

<form id="photo_search" method="get" action="action="./"">
    <p>
        <label for='search_keywords'>Keywords</label>
        <input type='text' name='keywords' id='search_keywords' value='<?php if($input->whitelist->keywords) echo $sanitizer->entities($input->whitelist->keywords); ?>' />
    </p>
    <p>
        <input type="submit" id="search_submit" name="submit" value="Find" />
    </p>
</form> 

Because I am still not sure of how your pages are structured, this may not work. But if as you say, 

$photos = $pages->find("template=photo, sort=camera_model");
 

worked, then maybe you could replace the selector line I have above with:

$photos = $pages->find($selector);
 

If you decide to follow the second link that kongondo mentions above, be sure to read through to the end of the thread. I made a few changes to that code to improve it, thanks to some suggestions by Soma. Honestly, I think in your case, building a custom form like we are doing here will be a better approach.

Let us know how you go.

Link to comment
Share on other sites

Thank you both kongondo and adrian for taking the time to help. I read both of those posts and tried the codes in them but couldn't get them to work. I also tested adrians new code, which just displays a unordered list of the photos and where the search doesn´t work.

I think i need to add something like this:

<?php

$selector = '';

if($input->get->keywords) {
    $value = $sanitizer->selectorValue($input->get->keywords);
    $selector .= "title|body%=$value, "; 
    $summary["keywords"] = $sanitizer->entities($value); 
    $input->whitelist('keywords', $value); 
}

// add something here, like:

if($input->get->camera_model) {
    $selector .= "template=photo sort=camera_model";
    $selector .= "template=photo sort=-camera_model";
}

?>

And then in the form add $selector or some other php to <select> camera_model and camera_resolution.

<form id="photo_search" method="get" action="<?php echo $config->urls->root?>photos/">

<h3>Browse photos</h3>

<p>
    <label for="search_keywords">Keywords</label>
    <input type="text" name="keywords" id="search_keywords" value="<?php if($input->whitelist->keywords) echo $sanitizer->entities($input->whitelist->keywords); ?>" />
</p>

<p>
    <label for="camera_model">Camera model</label>
    <select name="camera_model" id="camera_model">
        <option>Sort alphabetically(Ascending)</option>
        <option>Sort alphabetically(Desdending)</option>
    </select>
</p>

<p>
    <label for="camera_resolution">Camera resolution</label>
    <select name="camera_resolution" id="camera_resolution">
        <option>Highest to low</option>
        <option>Lowest to high</option>
    </select>
</p>

<p>
    <input type="submit" id="search_submit" name="submit" value="Search" />
</p>

</form>

Any help would be greatly appreciated. ProcessWire is such a great system otherwise which is why I´m willing to go through this trouble to get this working. I just tested the views module in drupal 7. With just a few clicks I had a nice custom query thingy. I wish ProcessWire had something similar :)

Link to comment
Share on other sites

Sorry, my example wasn't supposed to include the ordering - it was just an example for the keyword search. If that is working, then it shouldn't be hard to add the sort by those field options. It looks like your additions are on the right track, but you might want to take out the template=photo bit. If you are only searching the children of the photos main page, you won't need this. If you do want to keep it in, then define it first $selector = "template=photo" and then when you append other parts:

if($input->get->camera_resolution == 'asc') {
    $selector .= "sort=camera_model,";
}
elseif($input->get->camera_resolution == 'desc'){
    $selector .= "sort=-camera_model,";
}

Then in your form you need the select options to have values like:

<select name="camera_resolution" id="camera_resolution">
<option value="desc">Highest to low</option>
<option value="asc">Lowest to high</option>
</select>
 
Link to comment
Share on other sites

Thanks adrian. It´s almost working now! The search works just fine. The others work like, once after I´ve used the search, and then they stop working.

The code:

<?php

$selector = '';

if($input->get->keywords) {
    $value = $sanitizer->selectorValue($input->get->keywords);
    $selector .= "title|body%=$value, "; 
    $summary["keywords"] = $sanitizer->entities($value); 
    $input->whitelist('keywords', $value); 
}

if($input->get->camera_model == 'asc') {
    $selector .= "sort=camera_model,";
}

elseif($input->get->camera_model == 'desc') {
    $selector .= "sort=-camera_model,";
}

if($input->get->photo_resolution == 'asc') {
    $selector .= "sort=photo_resolution,";
}

elseif($input->get->photo_resolution == 'desc') {
    $selector .= "sort=-photo_resolution,";
}

if($input->get->photo_size == 'asc') {
    $selector .= "sort=photo_size,";
}

elseif($input->get->photo_size == 'desc') {
    $selector .= "sort=-photo_size,";
}

?>

<form id="photo_search" method="get" action="<?php echo $config->urls->root?>photos/">

<h3>Browse photos</h3>

<p>
    <label for="search_keywords">Keywords</label>
    <input type="text" name="keywords" id="search_keywords" value="<?php if($input->whitelist->keywords) echo $sanitizer->entities($input->whitelist->keywords); ?>" />
</p>

<p>
    <label for="camera_model">Camera model</label>
    <select name="camera_model" id="camera_model">
        <option value="desc">Alphabetically A-Z</option>
        <option value="asc">Alphabetically Z-A</option>
    </select>
</p>

<p>
    <label for="photo_resolution">Photo resolution</label>
    <select name="photo_resolution" id="photo_resolution">
        <option value="desc">Highest to low</option>
        <option value="asc">Lowest to high</option>
    </select>
</p>

<p>
    <label for="photo_size">Photo size</label>
    <select name="photo_size" id="photo_size">
        <option value="desc">Highest to low</option>
        <option value="asc">Lowest to high</option>
    </select>
</p>

<p>
    <input type="submit" id="photo_submit" name="submit" value="Find" />
</p>

</form>

<?php 

$photos = $page->children($selector);

foreach($photos as $photo) {
    echo '<ul>';
    echo '<li><a href="'.$photo->url.'">'.$photo->title.'</a></li>';
    echo '</ul>';
}

?>

I suspect there´s something wrong with the upper PHP part, the ifs and elses.

Link to comment
Share on other sites

Thanks, I´ll check that one out. I think the options worked but they would work even better if I´d put them on one single select tag of course. I mean I cannot sort them by more than one custom field at a time. Not in this case atleast, right? Stupid me :)

I need them to be like this:

<p>
    <label for="sort_photos">Sort by</label>
    <select name="sort_photos" id="sort_photos">
        <optgroup label="Alphabetically">
            <option value="desc">A-Z</option>
            <option value="asc">Z-A</option>
        </optgroup>
        <optgroup label="Resolution">
            <option value="desc">Highest to low</option>
            <option value="asc">Lowest to high</option>
        </optgroup>
        <optgroup label="Size">
            <option value="desc">Highest to low</option>
            <option value="asc">Lowest to high</option>
        </optgroup>
    </select>
</p>

Any ideas how to code the PHP part?

Link to comment
Share on other sites

That option seems limiting to me - now the user can only choose one of the ways to sort, rather than a grid of options. If they are in separate selects then the user can search all the combos. Do you not want that flexibility for some reason? 

But if you do want to do that, I guess you could make the option values things like: resolution-desc and resolution-asc and then in the if statements, you could split these. Something like:

$search_order = strstr($input->get->sort_photos, '-'); // gets order, eg asc

$search_type = str_replace($search_order, '', $input->get->sort_photos); // gets search type, eg resolution

Does that make sense?

Link to comment
Share on other sites

Yeah I guess you´re right. It is possible and useable to combine the options. But there´s still problems with changing the options. If I play around with the options I might succeed changing one of them. And if i then want to change that option I have to click around with the others to change the previous option. Hard to explain :)

But yeah it´s not working properly. Could it be because there´s just one value for "asc" and one for "desc"?

Link to comment
Share on other sites

That option seems limiting to me - now the user can only choose one of the ways to sort, rather than a grid of options. If they are in separate selects then the user can search all the combos. Do you not want that flexibility for some reason? 

But if you do want to do that, I guess you could make the option values things like: resolution-desc and resolution-asc and then in the if statements, you could split these. Something like:

$search_order = strstr($input->get->sort_photos, '-'); // gets order, eg asc

$search_type = str_replace($search_order, '', $input->get->sort_photos); // gets search type, eg resolution

Does that make sense?

As I said, I´m with you on using seperate selects :)

That "splitting" technique might be the answer to the problem with the select options? How would i do that splitting with the multiple select tags version?

Link to comment
Share on other sites

You don't need the splitting technique with multiple selects. That was only if you wanted to use just the one select.

I am not really sure where you are still having troubles at this point. Any chance you can show us the site? I am heading out for the day now. Hopefully someone else will pick up and help you.

One more thing you will want to do is get the select options to be selected once the form has submitted so the user can see what they selected and change from there, rather than starting from scratch. For this you'll want to echo selected="selected" for the appropriate option in your form - similar to how it is done for the keywords text input field.

Good luck! You really are close :)

Link to comment
Share on other sites

I think I got them working! Just had to change som asc/desc values. The resolution and size sortings may clash because of the similarity of their values, plus having only 3 pages to sort doesn´t help.

I also manages to code and get the selected="selected" working! All by myself! :D

With this code:

    <label for="camera_model">Camera model</label>
    <select name="camera_model" id="camera_model">
        <option value="desc"<?php if ($input->get->camera_model == 'desc') { echo ' selected="selected"'; } ?>>Alphabetically A-Z</option>
        <option value="asc"<?php if ($input->get->camera_model == 'asc') { echo ' selected="selected"'; } ?>>Alphabetically Z-A</option>
    </select>
  • Like 1
Link to comment
Share on other sites

One thing I don't understand is why it sorts the "photo_resolution" from lowest to high as: 1024, 2048 and 480. It seems that it only counts the first number. I had the same problem with Hero Framework CMS. Any ideas how to fix this?

Link to comment
Share on other sites

Congrats on getting things working - feels good doesn't it :)

I am guessing the sorting issue you are having is to do with the field type you are using to store the photo_resolution. If you are using text, try switching it to Integer and I think that will solve your problem. 

One thing to note - you should make sure you sanitize all the get variables. Read this to learn more about your options:

http://processwire.com/api/variables/sanitizer/

Link to comment
Share on other sites

It does feel really good :) Thank you again for your help!

I switched to integer and now it works. The fields are now sanitized also, using this:

if($input->get->photo_resolution == 'asc') {
    $value = $sanitizer->selectorValue($input->get->photo_resolution);
    $selector .= "sort=photo_resolution,";
    $summary["photo_resolution"] = $sanitizer->entities($value);
    $input->whitelist('photo_resolution', $value);
}

elseif($input->get->photo_resolution == 'desc') {
    $value = $sanitizer->selectorValue($input->get->photo_resolution);
    $selector .= "sort=-photo_resolution,";
    $summary["photo_resolution"] = $sanitizer->entities($value);
    $input->whitelist('photo_resolution', $value);
}

Is this correct? I get no errors at least.

Link to comment
Share on other sites

I wrote my own code which works. Haha I´m feeling like a PHP pro right now :D

$photos = $page->children($selector);

if (count($photos)) {
	foreach($photos as $photo) {
		echo '<ul>';
		echo '<li><a href="'.$photo->url.'">'.$photo->title.'</a></li>';
		echo '<li>Model: '.$photo->camera_model.'</li>';
		echo '<li>Resolution: '.$photo->photo_resolution.'</li>';
		echo '<li>Size:'.$photo->photo_size.'</li>';
		echo '</ul>';
	}
}
else {
	echo '<p>No photos found.</p>';
}

?>

I have no idea if this is good PHP coding practice or not, but it works :)

  • Like 2
Link to comment
Share on other sites

A much better output with this:

<?php 

$books = $page->children($selector);

if (count($books)) {

	echo "<ul>\n";

	foreach($books as $book) {
		echo "\t<li>\n" . 
			 "\t\t<p>\n" . 
			 "\t\t\t<a href='{$book->url}'>{$book->title}</a><br>\n" . 
			 "\t\t\tModel: {$book->book_publisher}<br>\n" . 
			 "\t\t\tResolution: {$book->book_chapters}<br>\n" . 
			 "\t\t\tSize: {$book->book_pages}\n" . 
			 "\t\t</p>\n" . 
			 "\t</li>\n";
	}

	echo "</ul>\n";
}

else {
	echo "<p>No books found.</p>";
}

?>
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...