horst Posted November 10, 2015 Share Posted November 10, 2015 On a site we have a branch with 1200+ pages. The sort order is manual drag and drop. Sometimes we need to move new created pages 300 or 400 places in the pagetree. This is a real pain. Is there any way (besides moving them manually) how we can sort in a page at a desired place? Link to comment Share on other sites More sharing options...
Soma Posted November 10, 2015 Share Posted November 10, 2015 I don't know but manually sorting 400 pages uff..? Some kind of sort integer field to enter position in a field. Build an custom admin page for that? It's not easy anyway. And experiment with maybe grid instead of a 1 column? 1 Link to comment Share on other sites More sharing options...
LostKobrakai Posted November 10, 2015 Share Posted November 10, 2015 Maybe an ajax loaded pagelistselect (like children), where one can select a page, where the current page should be sorted after on save or when some own submit button is clicked. 1 Link to comment Share on other sites More sharing options...
horst Posted November 10, 2015 Author Share Posted November 10, 2015 I don't know but manually sorting 400 pages uff..? Some kind of sort integer field to enter position in a field. Build an custom admin page for that? It's not easy anyway. And experiment with maybe grid instead of a 1 column? It isn't about sorting 400 pages. The pages have kind of a chronological order. And mostly there will be added new projects to the tree what only need to be sorted within the top 20 pages. That's just fine. But there are also some older projects that should find the way into the online archive. And this pages needs to be pulled down 400 pages or more. (sigh') I think the first thing I will experiment with, is to display the pages sort (range) number in the pagetree. So we can see them and easily pick one we want a new page moved to. Add a "desired sort range" inputfield and maybe a button to those pages template, where we can add in a sort range number where the page gets moved to on / after saving or when an own button was pressed. Link to comment Share on other sites More sharing options...
MuchDev Posted November 10, 2015 Share Posted November 10, 2015 I know I've always wished I could drag a page on to a number to drop it on that page... Link to comment Share on other sites More sharing options...
bernhard Posted November 11, 2015 Share Posted November 11, 2015 I would also go with lostkobrakai's suggestion and create a pagefield to choose the new "top" page. You don't have to look up sort numbers and remember them. Easy and built in scalability due to ajax pagination! 1 Link to comment Share on other sites More sharing options...
Soma Posted November 11, 2015 Share Posted November 11, 2015 Nice ideas, but the doing the save new sorting like an "insertAfter" is the tricky part... 1 Link to comment Share on other sites More sharing options...
LostKobrakai Posted November 11, 2015 Share Posted November 11, 2015 Ryan could be asked to abstract ProcessPageSort::execute out a bit, so the sorting logic is also usable without faking the ajax data coming from ProcessPageList. 1 Link to comment Share on other sites More sharing options...
SteveB Posted November 11, 2015 Share Posted November 11, 2015 Had something like this once (not in PW) where I updated the integer sort field to be 100, 200, 300... Then to move item X to be just after item Y you simply give it Y's sort value plus an offset. Use offset of 1 the first time and increment it each time. A negative offset can be used for inserting before. You need a function to normalize the sort values when you are done or after a large number of moves but that's easy enough to do. 1 Link to comment Share on other sites More sharing options...
bernhard Posted November 11, 2015 Share Posted November 11, 2015 ok i thought there was an easy way like almost always in pw but wouldn't it be relatively easy to create a hook to update all sort fields in the database? quite similar to the sortPages method: https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/modules/Process/ProcessPageSort.module#L101 1 Link to comment Share on other sites More sharing options...
horst Posted November 14, 2015 Author Share Posted November 14, 2015 Had something like this once (not in PW) where I updated the integer sort field to be 100, 200, 300... Then to move item X to be just after item Y you simply give it Y's sort value plus an offset. Use offset of 1 the first time and increment it each time. A negative offset can be used for inserting before. You need a function to normalize the sort values when you are done or after a large number of moves but that's easy enough to do. I believe this isn't doable in PW because on every manually move in the pagetree all integer sort fields gets rebuild in a linear row, without those niches. Otherwise this would be the best or simplest solution, I agree. Link to comment Share on other sites More sharing options...
horst Posted November 14, 2015 Author Share Posted November 14, 2015 (edited) Many thanks for all the suggestions! My current working solution is using a Pagefield with PageTreeSelect, and as a sidenote: wow! I never have used this fieldtype combination before. I always used ASM-Select. Just fallen in love again with PW. The second element is an injected button, what I have learned recently from a nice and usefull snippet of @Lostkobrakai. The working code is located in the ready.php and uses the PW API. Our pages in the example here uses the PageTreeAddNewChildReverse module, so the sort numbers stored in the DB are very high. (If you wonder why they have such high numbers in the screenshots.) The Pagefield where we select the new neighbour page is named "albumselect". /* moving albumpages programatically in the PageTree */ $wire->addHookAfter("ProcessPageEdit::buildForm", function(HookEvent $event) { $DEBUG = true; $FIELD = 'albumselect'; // change to the name of your Pagefield ! $page = $event->object->getPage(); if (!(bool)$page->fields->get($FIELD)) return; // check if the page includes the submitbutton for our function if (!$page->sortable()) { // check if the page is sortable and the user has the right to sort it $this->warning($this->_("You have not the right to sort this page.")); return; } if ('sort' != $page->parent->template->sortfield && '' != $page->parent->template->sortfield) { $this->warning($this->_("The sortsetting of the parent page does not allow drag and drop. It is set to: {$page->parent->template->sortfield}")); return; } $form = $event->return; $input = wire('input'); $pages = wire('pages'); $i = $num = 0; if ($input->post('hook_move_page_in_pagetree') && $input->post($FIELD)) { $page->of(false); $selectedNeighbour = $pages->get($input->post($FIELD)); #$sortLow = $page->parent->child('include=all')->sort; // the sortnumber of the first page in the tree, top page $sortCur = $page->get('sort'); // the current number of the page we want to move $sortEnd = $selectedNeighbour->getUnformatted('sort'); // the number of the page where we want to sort in the current page if ($DEBUG) my_var_dump(array('sortCur' => $sortCur, 'sortEnd' => $sortEnd), 1); if (0 < $selectedNeighbour->id) { if ($sortCur != $sortEnd) { // has the user selected a real or valid change of position? if ($sortCur < $sortEnd && $sortCur != $sortEnd - 1) { // we want to move this page down in the tree foreach($page->parent->children('include=all') as $p) { $num++; $p->of(false); if ($i++ > 50) { set_time_limit(30); $i = 0; } // just to play save, reset timelimit if ($p->sort == $sortEnd) { // we reached the page on which top we want to place the current one if ($DEBUG) { my_var_dump(array('num' => $num, 'page->sort' => $page->sort, 'result' => $sortEnd - 1, 'sortCur' => $sortCur, 'sortEnd' => $sortEnd, 'break' => true), 1); } else { #$page->setAndSave('sort', $sortEnd - 1, array('quiet' => true)); databaseUpdateSortInPages($page->id, $sortEnd - 1); } break; // we are finished now } if ($sortCur < $p->sort) { // only change sort for pages that are between $sortCur and $sortEnd if ($DEBUG) { my_var_dump(array('num' => $num, 'p->sort' => $p->sort, 'result' => $p->sort - 1, 'sortCur' => $sortCur, 'sortEnd' => $sortEnd), 1); } else { #$p->setAndSave('sort', $p->sort - 1, array('quiet' => true)); databaseUpdateSortInPages($p->id, $p->sort - 1); } } $pages->uncache($p); // free memory } } elseif ($sortCur > $sortEnd || $sortCur == $sortEnd - 1) { // we want to move this page up in the tree foreach($page->parent->children('include=all') as $p) { $num++; $p->of(false); if ($i++ > 50) { set_time_limit(30); $i = 0; } if ($p->sort >= $sortEnd && $p->sort <= $sortCur) { if ($p->sort == $sortCur) { if ($DEBUG) { my_var_dump(array('num' => $num, 'p->sort' => $p->sort, 'result' => $sortEnd, 'sortCur' => $sortCur, 'sortEnd' => $sortEnd, 'break' => true), 1); } else { #$page->setAndSave('sort', $sortEnd, array('quiet' => true)); databaseUpdateSortInPages($page->id, $sortEnd); } break; } else { if ($DEBUG) { my_var_dump(array('num' => $num, 'p->sort' => $p->sort, 'result' => $p->sort + 1, 'sortCur' => $sortCur, 'sortEnd' => $sortEnd), 1); } else { #$p->setAndSave('sort', $p->sort + 1, array('quiet' => true)); databaseUpdateSortInPages($p->id, $p->sort + 1); } } } $pages->uncache($p); } } } } if ($DEBUG) die(); } $button = wire('modules')->get('InputfieldSubmit'); $button->attr('id+name', 'hook_move_page_in_pagetree'); $button->value = __('jetzt einsortieren!'); $button->addClass('ui-priority-secondary'); $form->insertAfter($button, $form->get($FIELD)); }); function databaseUpdateSortInPages($id, $sort) { $sql = "UPDATE pages SET sort = $sort WHERE id = $id"; $query = wire('database')->prepare($sql); $query->execute(); } . . EDIT: Updated the above code. It supports up and down moving now. Also it has a debug function now. (The used function for debug output is in the spoiler beneath) What could be done better: [x] first check if the pages are set to use manually drag/drop sortorder or not. [x] test with direct manipulating DB-tables to improve speed. Currently, on my server, it took up to 35 seconds for iterating over 1250 pages and saving a new sortvalue for each of them. But this only happens if we move it from one end to the other (first place to last place). If we move a page 150 places, there will be only changed 150 pages in DB. This tooks around 4 seconds here. EDIT 2: Updated the code and included @Lostkobrakais idea to check for the PagetreeField on every page, and not on templatenames etc. This way we can add/remove the functionality to every templates we like, just by adding/removing the Pagefield to them, without altering the hook-function. (In our case the pagefield is called 'albumselect'). EDIT 3: Updated the code. Now it doesn't alter the modified time and modified user-id anymore. Before the change, it altered it of each page that changes the sort value! Also the execution time is much faster now. It only takes 3 seconds on my server to move it 1200 pages down or up. (Before using direct DB-access, it took 35 seconds) if (!function_exists('my_var_dump')) { function my_var_dump($v, $outputMode=2, $filename='') { ob_start(); var_dump($v); $content = ob_get_contents(); ob_end_clean(); $m = 0; preg_match_all('#^(.*)=>#mU', $content, $stack); $lines = $stack[1]; $indents = array_map('strlen', $lines); if ($indents) $m = max($indents) + 1; $content = preg_replace('#^(.*)=>\\n\s+(\S)#eUm', '"\\1" . str_repeat(" ", $m - strlen("\\1") > 1 ? $m - strlen("\\1") : 1) . "\\2"', $content); $content = preg_replace('#^((\s*).*){$#m', "\\1\n\\2{", $content); $content = str_replace(array('<pre>','</pre>'), '', $content); switch($outputMode) { case 1: // Output to Browser-Window echo "<pre style=\"margin:10px 10px 10px; padding:10px 10px 10px 10px; background-color:#F2F2F2; color:#000; border:1px solid #333; font-family:'Hack', 'Source Code Pro', 'Lucida Console', 'Courier', monospace; font-size:12px; line-height:15px; overflow:visible;\">{$content}</pre>"; break; case 2: // Output to Commandline-Window or to Browser as hidden comment echo isset($_SERVER['HTTP_HOST']) ? "\n<!--\n".$content."\n-->\n" : $content."\n"; break; case 3: // Output into a StringVar return "<pre style=\"margin: 10px 10px 10px; padding: 10px 10px 10px 10px; background-color:#F2F2F2; color:#000; border: 1px solid #333; font-family:'Hack', 'Source Code Pro', 'Lucida Console', 'Courier', monospace; font-size:12px; line-height:15px; overflow: visible;\">{$content}</pre>"; break; case 4: // Output into a file, if a valid filename is given and we have write access to it @touch($filename); if (is_writable($filename)) { $content = str_replace(array('>','"',' '), array('>','"',''), strip_tags($content)); $res = file_put_contents($filename, $content, FILE_APPEND); wireChmod($filename); return $res === strlen($content); } return false; break; } } } Edited November 16, 2015 by horst EDIT 3: Now it doesn't alter the modified time .. 4 Link to comment Share on other sites More sharing options...
LostKobrakai Posted November 14, 2015 Share Posted November 14, 2015 I was about to say, that you could add the page field via the hook as well, but I think it's actually quite nice to just look if the page field is part of a page and enhance it automatically. So one doesn't have to touch the hooks code after setting the fields name once. 1 Link to comment Share on other sites More sharing options...
horst Posted November 14, 2015 Author Share Posted November 14, 2015 just completed and updated the function in the post above. 4 Link to comment Share on other sites More sharing options...
horst Posted November 16, 2015 Author Share Posted November 16, 2015 EDIT 3:Updated the code. Now it doesn't alter the modified time and modified user-id anymore. Before the change, it altered it of each page that changes the sort value! Also now it is lightning fast! instead of 30 seconds it now only uses 2 seconds. 4 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now