diff --git a/wire/core/Page.php b/wire/core/Page.php index 03296d55..349f6ad2 100644 --- a/wire/core/Page.php +++ b/wire/core/Page.php @@ -46,6 +46,7 @@ * @property int $numChildren The number of children (subpages) this page has, with no exclusions (fast). #pw-group-traversal * @property int $hasChildren The number of visible children this page has. Excludes unpublished, no-access, hidden, etc. #pw-group-traversal * @property int $numVisibleChildren Verbose alias of $hasChildren #pw-internal + * @property int $numDescendants Number of descendants (quantity of children, and their children, and so on). #pw-group-traversal * @property PageArray $children All the children of this page. Returns a PageArray. See also $page->children($selector). #pw-group-traversal * @property Page|NullPage $child The first child of this page. Returns a Page. See also $page->child($selector). #pw-group-traversal * @property PageArray $siblings All the sibling pages of this page. Returns a PageArray. See also $page->siblings($selector). #pw-group-traversal @@ -142,6 +143,11 @@ * @method PageArray references($selector = '', $field = '') Return pages that are pointing to this one by way of Page reference fields. #pw-group-traversal * @method PageArray links($selector = '', $field = '') Return pages that link to this one contextually in Textarea/HTML fields. #pw-group-traversal * + * Alias/alternate methods + * ----------------------- + * @method PageArray descendants($selector = '', array $options = array()) Find descendant pages, alias of `Page::find()`, see that method for details. #pw-group-traversal + * @method Page|NullPage descendant($selector = '', array $options = array()) Find one descendant page, alias of `Page::findOne()`, see that method for details. #pw-group-traversal + * */ class Page extends WireData implements \Countable, WireMatchable { @@ -597,6 +603,7 @@ class Page extends WireData implements \Countable, WireMatchable { 'namePrevious' => 'p', 'next' => 'm', 'numChildren' => 's', + 'numDescendants' => 'm', 'numLinks' => 't', 'numReferences' => 't', 'output' => 'm', @@ -661,6 +668,17 @@ class Page extends WireData implements \Countable, WireMatchable { 'templatesID' => 'templates_id', ); + /** + * Method alternates/aliases (alias => actual) + * + * @var array + * + */ + static $baseMethodAlternates = array( + 'descendants' => 'find', + 'descendant' => 'findOne', + ); + /** * Create a new page in memory. * @@ -1063,7 +1081,7 @@ class Page extends WireData implements \Countable, WireMatchable { $value = $this->template ? $this->template->id : 0; break; case 'fieldgroup': - $value = $this->template->fieldgroup; + $value = $this->template ? $this->template->fieldgroup : null; break; case 'modifiedUser': case 'createdUser': @@ -1175,6 +1193,7 @@ class Page extends WireData implements \Countable, WireMatchable { public function getFields() { if(!$this->template) return new FieldsArray(); $fields = new FieldsArray(); + /** @var Fieldgroup $fieldgroup */ $fieldgroup = $this->template->fieldgroup; foreach($fieldgroup as $field) { if($fieldgroup->hasFieldContext($field)) { @@ -1661,8 +1680,10 @@ class Page extends WireData implements \Countable, WireMatchable { if(count($arguments)) { return $this->getFieldValue($method, $arguments[0]); } else { - return $this->get($method); + return $this->get($method); } + } else if(isset(self::$baseMethodAlternates[$method])) { + return call_user_func_array(array($this, self::$baseMethodAlternates[$method]), $arguments); } else { return parent::___callUnknown($method, $arguments); } @@ -1925,7 +1946,7 @@ class Page extends WireData implements \Countable, WireMatchable { } /** - * Find pages matching given selector in the descendent hierarchy + * Find descendant pages matching given selector * * This is the same as `Pages::find()` except that the results are limited to descendents of this Page. * @@ -1952,6 +1973,35 @@ class Page extends WireData implements \Countable, WireMatchable { } return $this->_pages('find', $selector, $options); } + + /** + * Find one descendant page matching given selector + * + * This is the same as `Pages::findOne()` except that the match is always a descendant of page it is called on. + * + * ~~~~~ + * // Find the most recently modified descendant page + * $item = $page->findOne("sort=-modified"); + * ~~~~~ + * + * #pw-group-common + * #pw-group-traversal + * + * @param string|array $selector Selector string or array + * @param array $options Optional options to modify default bheavior, see options for `Pages::find()`. + * @return Page|NullPage Returns Page when found, or NullPage when nothing found. + * @see Pages::findOne(), Page::child() + * + */ + public function findOne($selector = '', $options = array()) { + if(!$this->numChildren) return $this->wire('pages')->newNullPage(); + if(is_string($selector)) { + $selector = trim("has_parent={$this->id}, $selector", ", "); + } else if(is_array($selector)) { + $selector["has_parent"] = $this->id; + } + return $this->_pages('findOne', $selector, $options); + } /** * Return this page’s children, optionally filtered by a selector @@ -2232,6 +2282,35 @@ class Page extends WireData implements \Countable, WireMatchable { } return $this->traversal()->siblings($this, $selector); } + + /** + * Return number of descendants (children, grandchildren, great-grandchildren, …), optionally with conditions + * + * Use this over the `$page->numDescendants` property when you want to specify a selector or apply + * some other filter to the result (see options for `$selector` argument). If you want to include only + * visible descendants specify a selector (string or array) or boolean true for the `$selector` argument, + * if you don’t need a selector. + * + * If you want to find descendant pages (rather than count), use the `Page::find()` method. + * + * ~~~~~ + * // Find how many descendants were modified in the last week + * $qty = $page->numDescendants("modified>='-1 WEEK'"); + * ~~~~~ + * + * #pw-group-traversal + * + * @param bool|string|array $selector + * - When not specified, result includes all descendants without conditions, same as $page->numDescendants property. + * - When a string or array, a selector is assumed and quantity will be counted based on selector. + * - When boolean true, number includes only visible descendants (excludes unpublished, hidden, no-access, etc.) + * @return int Number of descendants + * @see Page::numChildren(), Page::find() + * + */ + public function numDescendants($selector = null) { + return $this->traversal()->numDescendants($this, $selector); + } /** * Return the next sibling page diff --git a/wire/core/PageTraversal.php b/wire/core/PageTraversal.php index 905d059a..7199d8a8 100644 --- a/wire/core/PageTraversal.php +++ b/wire/core/PageTraversal.php @@ -22,43 +22,87 @@ class PageTraversal { * @param Page $page * @param bool|string|int|array $selector * When not specified, result includes all children without conditions, same as $page->numChildren property. - * When a string or array, a selector is assumed and quantity will be counted based on selector. + * When a string or array, a selector is assumed and quantity will be counted based on selector. * When boolean true, number includes only visible children (excludes unpublished, hidden, no-access, etc.) * When boolean false, number includes all children without conditions, including unpublished, hidden, no-access, etc. * When integer 1 number includes viewable children (as opposed to visible, viewable includes hidden pages + it also includes unpublished pages if user has page-edit permission). + * @param array $options + * - `descendants` (bool): Use descendants rather than direct children * @return int Number of children * */ - public function numChildren(Page $page, $selector = null) { + public function numChildren(Page $page, $selector = null, array $options = array()) { + + $descendants = empty($options['descendants']) ? false : true; + $parentType = $descendants ? 'has_parent' : 'parent_id'; + if(is_bool($selector)) { // onlyVisible takes the place of selector $onlyVisible = $selector; - if(!$onlyVisible) return $page->get('numChildren'); - return $page->_pages('count', "parent_id=$page->id"); + $numChildren = $page->get('numChildren'); + if(!$numChildren) { + return 0; + } else if($onlyVisible) { + return $page->_pages('count', "$parentType=$page->id"); + } else if($descendants) { + return $this->numDescendants($page); + } else { + return $numChildren; + } } else if($selector === 1) { // viewable pages only $numChildren = $page->get('numChildren'); if(!$numChildren) return 0; - if($page->wire('user')->isSuperuser()) return $numChildren; - if($page->wire('user')->hasPermission('page-edit')) { - return $page->_pages('count', "parent_id=$page->id, include=unpublished"); + if($page->wire('user')->isSuperuser()) { + if($descendants) return $this->numDescendants($page); + return $numChildren; + } else if($page->wire('user')->hasPermission('page-edit')) { + return $page->_pages('count', "$parentType=$page->id, include=unpublished"); + } else { + return $page->_pages('count', "$parentType=$page->id, include=hidden"); } - return $page->_pages('count', "parent_id=$page->id, include=hidden"); } else if(empty($selector) || (!is_string($selector) && !is_array($selector))) { - return $page->get('numChildren'); + // no selector provided + if($descendants) return $this->numDescendants($page); + return $page->get('numChildren'); } else { + // selector string or array provided if(is_string($selector)) { - $selector = "parent_id=$page->id, $selector"; + $selector = "$parentType=$page->id, $selector"; } else if(is_array($selector)) { - $selector["parent_id"] = $page->id; + $selector[$parentType] = $page->id; } return $page->_pages('count', $selector); } } + /** + * Return number of descendants, optionally with conditions + * + * Use this over $page->numDescendants property when you want to specify a selector or when you want the result to + * include only visible descendants. See the options for the $selector argument. + * + * @param Page $page + * @param bool|string|int|array $selector + * When not specified, result includes all descendants without conditions, same as $page->numDescendants property. + * When a string or array, a selector is assumed and quantity will be counted based on selector. + * When boolean true, number includes only visible descendants (excludes unpublished, hidden, no-access, etc.) + * When boolean false, number includes all descendants without conditions, including unpublished, hidden, no-access, etc. + * When integer 1 number includes viewable descendants (as opposed to visible, viewable includes hidden pages + it also includes unpublished pages if user has page-edit permission). + * @return int Number of descendants + * + */ + public function numDescendants(Page $page, $selector = null) { + if($selector === null) { + return $page->_pages('count', "has_parent=$page->id, include=all"); + } else { + return $this->numChildren($page, $selector, array('descendants' => true)); + } + } + /** * Return this page's children pages, optionally filtered by a selector * diff --git a/wire/modules/Process/ProcessPageList/ProcessPageList.js b/wire/modules/Process/ProcessPageList/ProcessPageList.js index eec0da6c..2505a6ad 100644 --- a/wire/modules/Process/ProcessPageList/ProcessPageList.js +++ b/wire/modules/Process/ProcessPageList/ProcessPageList.js @@ -131,6 +131,10 @@ $(document).ready(function() { // session field name that holds page label format, when used labelName: '', + + // what to show in the PageListNumChildren quantity: 'children', 'total', 'children/total', 'total/children', or 'id' + // default is blank, which implies 'children' + qtyType: '', }; // array of "123.0" (page_id.start) that are currently open (used in non-select mode only) @@ -511,7 +515,9 @@ $(document).ready(function() { var nextStart = data.start + data.limit; //var openPageKey = id + '-' + start; - if($target.hasClass('PageListItem')) setNumChildren($target, data.page.numChildren); + if($target.hasClass('PageListItem')) { + setNumChildren($target, data.page.numChildren, data.page.numTotal); + } if(data.page.numChildren > nextStart) { var $a = $("").attr('href', nextStart).data('pageId', id).text(options.moreLabel).click(clickMore); @@ -717,8 +723,7 @@ $(document).ready(function() { }); $li.append($a); - var $numChildren = $("" + (child.numChildren ? child.numChildren : '') + "").addClass('PageListNumChildren detail'); - $li.append($numChildren); + setNumChildren($li, child.numChildren, child.numTotal, true); if(child.note && child.note.length) $li.append($("" + child.note + "").addClass('PageListNote detail')); @@ -794,30 +799,101 @@ $(document).ready(function() { * Get number of children for given .PageListItem * */ - function getNumChildren($item) { - var $numChildren = $item.children('.PageListNumChildren'); - if(!$numChildren.length) return 0; - var numChildren = $numChildren.text(); - return numChildren.length ? parseInt(numChildren) : 0; + function getNumChildren($item, getTotal) { + if(typeof getTotal == "undefined") var getTotal = false; + if(getTotal) { + var n = $item.attr('data-numTotal'); + } else { + var n = $item.attr('data-numChild'); + } + return n && n.length > 0 ? parseInt(n) : 0; + } + + function getNumTotal($item) { + return getNumChildren($item, true); } /** * Set number of children for given PageListItem * */ - function setNumChildren($item, numChildren) { - var $numChildren = $item.children('.PageListNumChildren'); - if(!$numChildren.length) { - $numChildren = $('0').addClass('PageListNumChildren detail'); - $item.append($numChildren); + function setNumChildren($item, numChildren, numTotal, addNew) { + + if(typeof numTotal == "undefined") var numTotal = numChildren; + if(typeof addNew == "undefined") var addNew = false; + + var $numChildren = addNew ? '' : $item.children('.PageListNumChildren'); + var n = numChildren === false ? numTotal : numChildren; + + if(addNew || !$numChildren.length) { + $numChildren = $('').addClass('PageListNumChildren detail'); + addNew = true; } - if(numChildren < 1) { + + if(n < 1) { $item.removeClass('PageListHasChildren').addClass('PageListNoChildren'); - numChildren = ''; + if(numTotal !== false) numTotal = 0; } else { $item.removeClass('PageListNoChildren').addClass('PageListHasChildren'); } - $numChildren.text(numChildren); + + if(numTotal === false) { + numTotal = getNumTotal($item); + } else { + $item.attr('data-numTotal', numTotal); + } + + if(numChildren === false) { + numChildren = getNumChildren($item); + } else { + if(numChildren < 0) numChildren = 0; + $item.attr('data-numChild', numChildren); + } + + var numLabel = ''; + switch(options.qtyType) { + case 'total': + numLabel = numTotal; + break; + case 'total/children': + var slash = "/"; + numLabel = numTotal > 0 && numTotal != numChildren ? numTotal + slash + numChildren : numTotal; + break; + case 'children/total': + var slash = "/"; + numLabel = numTotal > 0 && numTotal != numChildren ? numChildren + slash + numTotal : numTotal; + break; + case 'id': + numLabel = $item.data('pageId'); + break; + default: + numLabel = numChildren; + } + if(!numLabel) numLabel = ''; + $numChildren.html(numLabel); + + if(addNew) $item.append($numChildren); + } + + /** + * Set total/descendants + * + */ + function setNumTotal($item, numTotal) { + setNumChildren($item, false, numTotal); + } + + /** + * Recursively adjust total/descendants number up the tree by given amount (n) + * + */ + function adjustNumTotal($item, n) { + var numTotal = getNumTotal($item); + numTotal += n; + if(numTotal < 0) numTotal = 0; + setNumTotal($item, numTotal); + var $parentItem = $item.closest('.PageList').prev('.PageListItem'); + if($parentItem.length) adjustNumTotal($parentItem, n); } /** @@ -935,12 +1011,15 @@ $(document).ready(function() { $msg.fadeOut('normal', function () { var $parentItem = $liNew.closest('.PageList').prev('.PageListItem'); var numChildren = getNumChildren($parentItem); + var numTotal = getNumTotal($parentItem); if(removeItem) { numChildren--; + adjustNumTotal($parentItem, -1); } else if(addNew) { numChildren++; + adjustNumTotal($parentItem, 1); } - setNumChildren($parentItem, numChildren); + setNumChildren($parentItem, numChildren, false); setForceReload($parentItem); if(removeItem) { $liNew.next('.PageList').fadeOut('fast'); @@ -1031,7 +1110,7 @@ $(document).ready(function() { } } else { $li.addClass('PageListItemOpen'); - var numChildren = parseInt($li.children('.PageListNumChildren').text()); + var numChildren = getNumChildren($li); if(numChildren > 0 || $li.hasClass('PageListForceReload')) { ignoreClicks = true; var start = getOpenPageStart(id); @@ -1169,7 +1248,7 @@ $(document).ready(function() { // make an invisible PageList placeholder that allows 'move' action to create a child below this $root.find('.PageListItemOpen').each(function() { - var numChildren = $(this).children('.PageListNumChildren').text(); + var numChildren = getNumChildren($(this)); // if there are children and the next sibling doesn't contain a visible .PageList, then don't add a placeholder if(parseInt(numChildren) > 1 && $(this).next().find(".PageList:visible").length == 0) { return; @@ -1333,20 +1412,22 @@ $(document).ready(function() { if(!$ul.is("#PageListMoveFrom")) { // update count where item came from var $fromItem = $from.prev(".PageListItem"); - var $numChildren = $fromItem.children(".PageListNumChildren"); - var n = $numChildren.text().length > 0 ? parseInt($numChildren.text()) - 1 : 0; - if(n == 0) { - n = ''; + var numChildren = getNumChildren($fromItem); + var numTotal = getNumTotal($fromItem); + if(numChildren > 0) { + numChildren--; + adjustNumTotal($fromItem, -1); + } else { $from.remove(); // empty list, no longer needed } - $numChildren.text(n); + setNumChildren($fromItem, numChildren, false); setForceReload($fromItem); // update count where item went to var $toItem = $ul.prev(".PageListItem"); - $numChildren = $toItem.children(".PageListNumChildren"); - n = $numChildren.text().length > 0 ? parseInt($numChildren.text()) + 1 : 1; - $numChildren.text(n); + numChildren = getNumChildren($toItem) + 1; + adjustNumTotal($toItem, 1); + setNumChildren($toItem, numChildren, false); setForceReload($toItem); } $from.attr('id', ''); // remove tempoary #PageListMoveFrom diff --git a/wire/modules/Process/ProcessPageList/ProcessPageList.min.js b/wire/modules/Process/ProcessPageList/ProcessPageList.min.js index 765e44b7..8312172d 100644 --- a/wire/modules/Process/ProcessPageList/ProcessPageList.min.js +++ b/wire/modules/Process/ProcessPageList/ProcessPageList.min.js @@ -1 +1 @@ -function ProcessPageListInit(){if(ProcessWire.config.ProcessPageList){$("#"+ProcessWire.config.ProcessPageList.containerID).ProcessPageList(ProcessWire.config.ProcessPageList)}}$(document).ready(function(){ProcessPageListInit()});(function(a){a.fn.ProcessPageList=function(c){var d={mode:"",limit:35,rootPageID:0,showRootPage:true,selectedPageID:0,adminPageID:2,trashPageID:7,langID:0,selectAllowUnselect:false,selectShowPageHeader:true,selectShowPath:true,selectStartLabel:"Change",selectCancelLabel:"Cancel",selectSelectLabel:"Select",selectUnselectLabel:"Unselect",moreLabel:"More",trashLabel:"Trash",moveInstructionLabel:"Click and drag to move",selectSelectHref:"#",selectUnselectHref:"#",ajaxURL:ProcessWire.config.urls.admin+"page/list/",ajaxMoveURL:ProcessWire.config.urls.admin+"page/sort/",paginationClass:"PageListPagination",paginationCurrentClass:"PageListPaginationCurrent",paginationLinkClass:"ui-state-default",paginationLinkCurrentClass:"ui-state-active",paginationHoverClass:"ui-state-hover",paginationDisabledClass:"ui-priority-secondary",openPagination:0,openPageIDs:[],openPageData:{},speed:200,useHoverActions:false,hoverActionDelay:250,hoverActionFade:150,useNarrowActions:a("body").hasClass("pw-narrow-width"),spinnerMarkup:"",labelName:""};var b=[];var f=false;var e=a("body").hasClass("modal")||a("body").hasClass("pw-iframe");if(typeof ProcessWire.config.ProcessPageList!="undefined"){a.extend(d,ProcessWire.config.ProcessPageList)}a.extend(d,c);return this.each(function(m){var y=a(this);var u;var I;var x=a(d.spinnerMarkup);var K=0;var h=0;function B(){I=a("
");if(y.is(":input")){d.selectedPageID=y.val();if(!d.selectedPageID.length){d.selectedPageID=0}d.mode="select";y.before(I);u=y.closest(".InputfieldContent");G()}else{d.mode="actions";y.append(I);u=y;o(d.rootPageID>0?d.rootPageID:1,I,0,true)}a(document).on("pageListRefresh",function(M,L){J(L)});if(d.useHoverActions){I.addClass("PageListUseHoverActions");p()}}function p(){var N=null;var O=null;var M=null;function L(Q){var R=Q.find(".PageListActions");if(!R.is(":visible")||Q.hasClass("PageListItemOpen")){Q.addClass("PageListItemHover");R.css("display","inline").css("opacity",0).animate({opacity:1},d.hoverActionFade)}}function P(Q){var R=Q.find(".PageListActions");Q.removeClass("PageListItemHover");if(R.is(":visible")){R.animate({opacity:0},d.hoverActionFade,function(){R.hide()})}}u.on("keydown",".PageListItem",function(R){R=R||window.event;if(R.keyCode==0||R.keyCode==32){var Q=a(this).find(".PageListActions");if(Q.is(":visible")){Q.css("display","none")}else{Q.css("display","inline-block")}return false}});u.on("mouseover",".PageListItem",function(S){if(I.is(".PageListSorting")||I.is(".PageListSortSaving")){return}if(!a(this).children("a:first").is(":hover")){return}M=a(this);if(M.hasClass("PageListItemHover")){return}var Q=a(this);if(N){clearTimeout(N)}var R=d.hoverActionDelay;N=setTimeout(function(){if(M.attr("class")==Q.attr("class")){if(!M.children("a:first").is(":hover")){return}var T=u.find(".PageListItemHover");L(M);T.each(function(){P(a(this))})}},R)}).on("mouseout",".PageListItem",function(S){if(I.is(".PageListSorting")||I.is(".PageListSortSaving")){return}var Q=a(this);if(Q.hasClass("PageListItemOpen")){return}if(!Q.hasClass("PageListItemHover")){return}var R=d.hoverActionDelay*0.7;O=setTimeout(function(){if(Q.is(":hover")){return}if(Q.attr("class")==M.attr("class")){return}P(Q)},R)})}function G(){var L=a("").html(ab+1).attr("href",ab).addClass(d.paginationLinkClass);var aa=a("").addClass(d.paginationClass+S).append(T);if(ab==i){aa.addClass(d.paginationCurrentClass).find("a").removeClass(d.paginationLinkClass).addClass(d.paginationLinkCurrentClass)}W.append(aa);if(!R){R=aa.clone().removeClass(d.paginationCurrentClass+" "+d.paginationLinkCurrentClass);R.find("a").removeClass(d.paginationLinkCurrentClass).addClass(d.paginationLinkClass)}if(!U){U=R.clone().removeClass(d.paginationLinkClass).addClass(d.paginationDisabledClass).html("…")}if(S>=X&&ab0){$firstItem=R.clone();$firstItem.find("a").text("1").attr("href","0").click(Y);W.prepend(U.clone()).prepend($firstItem)}if(i+1").attr("href",i+1);W.append($nextBtn)}if(i>0){$prevBtn=R.clone();$prevBtn.find("a").attr("href",i-1).html("");W.prepend($prevBtn)}W.find("a").click(Y).hover(function(){a(this).addClass(d.paginationHoverClass)},function(){a(this).removeClass(d.paginationHoverClass)});return W}function p(P,S,Q,U,X,R,W){if(X==undefined){X=true}if(R==undefined){R=false}var T=function(ac){if(ac&&ac.error){ProcessWire.alert(ac.message);z.hide();f=false;return}var Y=m(a(ac.children));var ab=ac.start+ac.limit;if(S.hasClass("PageListItem")){E(S,ac.page.numChildren,ac.page.numTotal)}if(ac.page.numChildren>ab){var aa=a("").attr("href",ab).data("pageId",P).text(d.moreLabel).click(H);Y.append(a("").addClass("PageListActions actions").append(a("").addClass("PageListActionMore").append(aa)))}if(X&&(ac.page.numChildren>ab||ac.start>0)){Y.prepend(F(P,ac.start,ac.limit,ac.page.numChildren))}Y.hide();if(U){var ad;ad=m(a(ac.page));if(d.showRootPage){ad.children(".PageListItem").addClass("PageListItemOpen")}else{ad.children(".PageListItem").hide().parent(".PageList").addClass("PageListRootHidden")}ad.append(Y);S.append(ad)}else{if(S.is(".PageList")){var Z=Y.children(".PageListItem, .PageListActions");if(R){S.children(".PageListItem, .PageListActions").replaceWith(Z)}else{S.append(Z)}}else{S.after(Y)}}if(z.parent().is(".PageListRoot")){z.hide()}else{z.fadeOut("fast")}if(R){Y.show();C();if(W!=undefined){W()}}else{Y.slideDown(d.speed,function(){C();if(W!=undefined){W()}})}Y.prev(".PageListItem").data("start",ac.start)};if(!R){S.append(z.fadeIn("fast"))}var V=P+"-"+Q;if(typeof d.openPageData[V]!="undefined"&&!S.hasClass("PageListID7")&&!S.hasClass("PageListForceReload")){T(d.openPageData[V]);return}var O=d.ajaxURL+"?id="+P+"&render=JSON&start="+Q+"&lang="+d.langID+"&open="+d.openPageIDs[0]+"&mode="+d.mode;if(d.labelName.length){O+="&labelName="+d.labelName}a.getJSON(O).done(function(Z,aa,Y){T(Z)}).fail(function(Y,aa,Z){T({error:1,message:!Y.status?d.ajaxNetworkError:d.ajaxUnknownError})})}function m(O){var Q=a("").addClass("PageList");var P=Q;O.each(function(S,R){P.append(l(R))});x(P);return Q}function v(O,U){if(O.hasClass("PageList")){O=O.prev(".PageListItem")}O.addClass("PageListForceReload");var T=O.data("pageId");var S=T+"-";if(typeof d.openPageData!="undefined"){var Q={};for(var P in d.openPageData){if(P.indexOf(S)===0){}else{Q[P]=d.openPageData[P]}}d.openPageData=Q}if(typeof U!="undefined"&&U){var R=O.children("a.PageListPage");if(O.hasClass("PageListItemOpen")){R.click();setTimeout(function(){R.click()},250)}else{R.click()}}}function x(O){a("a.PageListPage",O).click(y);O.on("click",".PageListActionMove a",j);a(".PageListActionSelect a",O).click(G);a(".PageListTriggerOpen:not(.PageListID1) > a.PageListPage",O).click();a(".PageListActionExtras > a:not(.clickExtras)",O).addClass("clickExtras").on("click",s)}function l(O){var T=a("").data("pageId",O.id).addClass("PageListItem").addClass("PageListTemplate_"+O.template);var Q=a("").attr("href","#").attr("title",O.path).html(O.label).addClass("PageListPage label");T.addClass(O.numChildren>0?"PageListHasChildren":"PageListNoChildren").addClass("PageListID"+O.id);if(O.status==0){T.addClass("PageListStatusOff disabled")}if(O.status&2048){T.addClass("PageListStatusUnpublished secondary")}if(O.status&1024){T.addClass("PageListStatusHidden secondary")}if(O.status&512){T.addClass("PageListStatusTemp secondary")}if(O.status&16){T.addClass("PageListStatusSystem")}if(O.status&8){T.addClass("PageListStatusSystem")}if(O.status&4){T.addClass("PageListStatusLocked")}if(O.addClass&&O.addClass.length){T.addClass(O.addClass)}if(O.type&&O.type.length>0){if(O.type=="System"){T.addClass("PageListStatusSystem")}}a(d.openPageIDs).each(function(Z,Y){Y=parseInt(Y);if(O.id==Y){T.addClass("PageListTriggerOpen")}});T.append(Q);E(T,O.numChildren,O.numTotal,true);if(O.note&&O.note.length){T.append(a(""+O.note+"").addClass("PageListNote detail"))}var V=a("
").addClass("PageListActions actions");var X=d.rootPageID==O.id?[]:[{name:d.selectSelectLabel,url:d.selectSelectHref}];if(d.mode=="actions"){X=O.actions}else{if(d.selectAllowUnselect){if(O.id==A.val()){X=[{name:d.selectUnselectLabel,url:d.selectUnselectHref}]}}}var S=null;var W=null;var R={};var U=false;if(d.useNarrowActions){for(var P=0;P
").html(ab.name).attr("href",ab.url);if(!e){if(ab.cn=="Edit"){aa.addClass("pw-modal pw-modal-large pw-modal-longclick");aa.attr("data-buttons","#ProcessPageEdit > .Inputfields > .InputfieldSubmit .ui-button")}else{if(ab.cn=="View"){aa.addClass("pw-modal pw-modal-large pw-modal-longclick")}}}if(typeof ab.extras!="undefined"){for(var Z in ab.extras){R[Z]=ab.extras[Z]}W=aa}var ac=a("").addClass("PageListAction"+Y).append(aa);if(Y=="Extras"){S=ac}else{V.append(ac)}});if(W){W.data("extras",R)}if(S){V.append(S);S.addClass("ui-priority-secondary")}T.append(V);return T}function k(P,O){if(typeof O=="undefined"){var O=false}if(O){var Q=P.attr("data-numTotal")}else{var Q=P.attr("data-numChild")}return Q&&Q.length>0?parseInt(Q):0}function L(O){return k(O,true)}function E(P,U,T,S){if(typeof T=="undefined"){var T=U}if(typeof S=="undefined"){var S=false}var O=S?"":P.children(".PageListNumChildren");var V=U===false?T:U;if(S||!O.length){O=a("").addClass("PageListNumChildren detail");S=true}if(V<1){P.removeClass("PageListHasChildren").addClass("PageListNoChildren");if(T!==false){T=0}}else{P.removeClass("PageListNoChildren").addClass("PageListHasChildren")}if(T===false){T=L(P)}else{P.attr("data-numTotal",T)}if(U===false){U=k(P)}else{if(U<0){U=0}P.attr("data-numChild",U)}var R="";switch(d.qtyType){case"total":R=T;break;case"total/children":var Q="/";R=T>0&&T!=U?T+Q+U:T;break;case"children/total":var Q="/";R=T>0&&T!=U?U+Q+T:T;break;case"id":R=P.data("pageId");break;default:R=U}if(!R){R=""}O.html(R);if(S){P.append(O)}}function h(O,P){E(O,false,P)}function r(O,R){var P=L(O);P+=R;if(P<0){P=0}h(O,P);var Q=O.closest(".PageList").prev(".PageListItem");if(Q.length){r(Q,R)}}function s(V){var P=a(this);var R=P.data("extras");if(typeof R=="undefined"){return false}var Y=P.closest(".PageListItem");var Z=P.closest(".PageListActions");var T=null;var W=P.children("i.fa");var X=Z.find("li.PageListActionExtra");W.toggleClass("fa-flip-horizontal");if(X.length){X.fadeOut(100,function(){X.remove()});return false}for(var Q in R){var O=R[Q];var U=a("").addClass("PageListActionExtra PageListAction"+O.cn).attr("href",O.url).html(O.name);if(typeof O.ajax!="undefined"&&O.ajax==true){U.click(function(){Y.find(".PageListActions").hide();var ag=a(d.spinnerMarkup);var ad=a(this).attr("href");var ac=ad.match(/[\?&]action=([-_a-zA-Z0-9]+)/)[1];var ab=parseInt(ad.match(/[\?&]id=([0-9]+)/)[1]);var af=a("#PageListContainer").attr("data-token-name");var ae=a("#PageListContainer").attr("data-token-value");var aa={action:ac,id:ab};aa[af]=ae;Y.append(ag);a.post(ad+"&render=json",aa,function(ah){if(ah.success){Y.fadeOut("fast",function(){var am=false;var an=ah.remove;var al=ah.refreshChildren;var ak=false;if(typeof ah.child!="undefined"){ak=l(ah.child)}else{if(typeof ah.newChild!="undefined"){ak=l(ah.newChild);am=true}}if(ak){var ai=a("").addClass("notes").html(ah.message);ai.prepend(" ");ak.append(ai);x(ak)}if(am){ag.fadeOut("normal",function(){ag.remove()});ak.hide();Y.after(ak);ak.slideDown();v(ak.closest(".PageList"))}else{if(ak){if(Y.hasClass("PageListItemOpen")){ak.addClass("PageListItemOpen")}Y.replaceWith(ak)}}Y.fadeIn("fast",function(){setTimeout(function(){ai.fadeOut("normal",function(){var aq=ak.closest(".PageList").prev(".PageListItem");var ap=k(aq);var ao=L(aq);if(an){ap--;r(aq,-1)}else{if(am){ap++;r(aq,1)}}E(aq,ap,false);v(aq);if(an){ak.next(".PageList").fadeOut("fast");ak.fadeOut("fast",function(){ak.remove()})}else{ai.remove()}})},1000)});if(al){var aj=a(".PageListID"+al);if(aj.length){v(aj,true)}}})}else{ag.remove();ProcessWire.alert(ah.message)}});return false})}else{}var S=a("").addClass("PageListActionExtra PageListAction"+O.cn).append(U);U.hide();if(O.cn=="Trash"){Y.addClass("trashable");T=S}else{Z.append(S)}}if(T){Z.append(T)}Z.find(".PageListActionExtra a").fadeIn(50,function(){a(this).css("display","inline-block")});return false}function y(R){var V=a(this);var U=V.parent(".PageListItem");var T=U.data("pageId");if(f&&!U.hasClass("PageListTriggerOpen")){return false}if(K.is(".PageListSorting")||K.is(".PageListSortSaving")){return false}if(U.hasClass("PageListItemOpen")){var O=true;if(U.hasClass("PageListID1")&&!U.hasClass("PageListForceReload")&&d.mode!="select"){var P=a(this).closest(".PageListRoot").find(".PageListItemOpen:not(.PageListID1)");if(P.length){K.find(".PageListItemOpen:not(.PageListID1)").each(function(){a(this).children("a.PageListPage").click()});O=false}}if(O){U.removeClass("PageListItemOpen").next(".PageList").slideUp(d.speed,function(){a(this).remove()})}}else{U.addClass("PageListItemOpen");var Q=k(U);if(Q>0||U.hasClass("PageListForceReload")){f=true;var S=J(T);p(T,U,S,false)}}if(d.mode!="select"){setTimeout(function(){g()},250)}return false}function J(S){var R=0;for(n=0;n 1&&a(this).next().find(".PageList:visible").length==0){return}var V=a("").addClass("PageListPlaceholder").addClass("PageList");V.append(a("").addClass("PageListItem PageListPlaceholderItem").html(" "));a(this).after(V)});var S={stop:t,helper:"PageListItemHelper",items:".PageListItem:not(.PageListItemOpen)",placeholder:"PageListSortPlaceholder",start:function(W,V){a(".PageListSortPlaceholder").css("width",V.item.children(".PageListPage").outerWidth()+"px")}};var P=K.children(".PageList").children(".PageList");var O=a(""+d.selectCancelLabel+"").click(function(){return u(T)});var R=T.children("ul.PageListActions");var Q=a(" "+d.moveInstructionLabel+"");Q.append(O);R.before(Q);T.addClass("PageListSortItem");T.parent(".PageList").attr("id","PageListMoveFrom");K.addClass("PageListSorting");P.addClass("PageListSortingList").sortable(S);return false}function u(P){var O=K.find(".PageListSortingList");O.sortable("destroy").removeClass("PageListSortingList");P.removeClass("PageListSortItem").parent(".PageList").removeAttr("id");P.find(".PageListMoveNote").remove();K.find(".PageListPlaceholder").remove();K.removeClass("PageListSorting");return false}function B(R){var O=K.find(".PageListID"+d.trashPageID);if(!O.hasClass("PageListItemOpen")){K.removeClass("PageListSorting");O.children("a").click();K.addClass("PageListSorting")}var P=O.next(".PageList");if(P.length==0){P=a("");O.after(P)}P.prepend(R);var Q={item:R};t(null,Q)}function t(U,Z){var W=Z.item;var R=W.children(".PageListPage");var P=parseInt(W.data("pageId"));var T=W.parent(".PageList");var X=a("#PageListMoveFrom");var Y=T.prev().is(".PageListItem")?T.prev():T.prev().prev();var V=parseInt(Y.data("pageId"));var Q=W.prev(".PageListItem");if(Q.is(".PageListItemOpen")){return false}if(T.is(".PageListPlaceholder")){var aa=T.next();if(aa.is(".PageList:visible")){aa.prepend(W);T=aa}else{T.removeClass("PageListPlaceholder").children(".PageListPlaceholderItem").remove()}}K.addClass("PageListSortSaving");u(W);W.append(z.fadeIn("fast"));var S="";T.children(".PageListItem").each(function(){S+=a(this).data("pageId")+","});var O={id:P,parent_id:V,sort:S};O[a("#PageListContainer").attr("data-token-name")]=a("#PageListContainer").attr("data-token-value");var ab="unknown";a.post(d.ajaxMoveURL,O,function(ad){z.fadeOut("fast");R.fadeOut("fast",function(){a(this).fadeIn("fast");W.removeClass("PageListSortItem");K.removeClass("PageListSorting")});if(ad&&ad.error){ProcessWire.alert(ad.message)}if(!T.is("#PageListMoveFrom")){var ac=X.prev(".PageListItem");var ag=k(ac);var af=L(ac);if(ag>0){ag--;r(ac,-1)}else{X.remove()}E(ac,ag,false);v(ac);var ae=T.prev(".PageListItem");ag=k(ae)+1;r(ae,1);E(ae,ag,false);v(ae)}X.attr("id","");K.removeClass("PageListSortSaving")},"json");W.trigger("pageMoved");return true}function G(){var U=a(this);var T=U.parent("li").parent("ul.PageListActions").parent(".PageListItem");var S=T.data("pageId");var Q=T.children(".PageListPage");var R=Q.text();var P=Q.attr("title");var O=K.children(".PageListSelectHeader");if(U.text()==d.selectUnselectLabel){S=0;R=""}if(S!=A.val()){A.val(S).change()}if(d.selectShowPageHeader){O.children(".PageListSelectName").text(R)}A.trigger("pageSelected",{id:S,url:P,title:R,a:Q});O.find(".PageListSelectActionToggle").click();if(d.selectSelectHref=="#"){return false}return true}D()})}})(jQuery); \ No newline at end of file diff --git a/wire/modules/Process/ProcessPageList/ProcessPageList.module b/wire/modules/Process/ProcessPageList/ProcessPageList.module index a3f750da..a4b26479 100644 --- a/wire/modules/Process/ProcessPageList/ProcessPageList.module +++ b/wire/modules/Process/ProcessPageList/ProcessPageList.module @@ -20,6 +20,7 @@ * @property int $hoverActionFade Milliseconds to spend fading in or out actions. * @property bool|int $useBookmarks Allow use of PageList bookmarks? * @property bool|int $useTrash Allow non-superusers to use Trash? + * @property string $qtyType What to show in children quantity label? 'children', 'total', 'children/total', 'total/children', or 'id' * * @method string ajaxAction($action) * @method PageArray find($selectorString, Page $page) @@ -116,6 +117,7 @@ class ProcessPageList extends Process implements ConfigurableModule { $this->set('useBookmarks', false); $this->set('useTrash', false); $this->set('bookmarks', array()); + $this->set('qtyType', ''); parent::__construct(); } @@ -302,6 +304,7 @@ class ProcessPageList extends Process implements ConfigurableModule { 'limit' => $this->limit, 'start' => $this->start, 'speed' => ($this->speed !== null ? (int) $this->speed : self::defaultSpeed), + 'qtyType' => $this->qtyType, 'useHoverActions' => $this->useHoverActions ? true : false, 'hoverActionDelay' => (int) $this->hoverActionDelay, 'hoverActionFade' => (int) $this->hoverActionFade, @@ -347,12 +350,14 @@ class ProcessPageList extends Process implements ConfigurableModule { $children = $this->wire('pages')->newPageArray(); } + /** @var ProcessPageListRender $renderer */ $renderer = $this->wire(new $className($page, $children)); $renderer->setStart($start); $renderer->setLimit($limit); $renderer->setPageLabelField($this->getPageLabelField()); $renderer->setLabel('trash', $this->trashLabel); $renderer->setUseTrash($this->useTrash || $this->wire('user')->isSuperuser()); + $renderer->setQtyType($this->qtyType); return $renderer; } @@ -700,6 +705,20 @@ class ProcessPageList extends Process implements ConfigurableModule { $field->description = $this->_('This is the speed at which each branch in the page tree animates up or down. Lower numbers are faster but less visible. For no animation specify 0.'); // Animation speed description $field->notes = sprintf($defaultNote1, self::defaultSpeed) . ' ' . $defaultNote2; $fields->append($field); + + /** @var InputfieldRadios $field */ + $field = $modules->get('InputfieldRadios'); + $field->attr('name', 'qtyType'); + $field->label = $this->_('Children quantity type'); + $field->description = $this->_('In the page list, a quantity of children is shown next to each page when applicable. What type of quantity should it show?'); + $field->notes = $this->_('When showing descendants, the quantity includes all descendants, whether listable to the user or not.'); + $field->addOption('', $this->_('Immediate children (default)')); + $field->addOption('total', $this->_('Descendants: children, grandchildren, great-grandchildren, and on…')); + $field->addOption('children/total', $this->_('Both: children/descendants')); + $field->addOption('total/children', $this->_('Both: descendants/children')); + $field->addOption('id', $this->_('Show Page ID number instead')); + $field->attr('value', empty($data['qtyType']) ? '' : $data['qtyType']); + $fields->append($field); return $fields; } diff --git a/wire/modules/Process/ProcessPageList/ProcessPageListRender.php b/wire/modules/Process/ProcessPageList/ProcessPageListRender.php index d4b192b2..7fedb96f 100644 --- a/wire/modules/Process/ProcessPageList/ProcessPageListRender.php +++ b/wire/modules/Process/ProcessPageList/ProcessPageListRender.php @@ -20,6 +20,7 @@ abstract class ProcessPageListRender extends Wire { protected $actions = null; protected $options = array(); protected $useTrash = false; + protected $qtyType = ''; public function __construct(Page $page, PageArray $children) { $this->page = $page; @@ -77,6 +78,10 @@ abstract class ProcessPageListRender extends Wire { $this->pageLabelField = $pageLabelField; } + public function setQtyType($qtyType) { + $this->qtyType = $qtyType; + } + public function actions() { return $this->actions; } diff --git a/wire/modules/Process/ProcessPageList/ProcessPageListRenderJSON.php b/wire/modules/Process/ProcessPageList/ProcessPageListRenderJSON.php index 8f3e1670..8afb9007 100644 --- a/wire/modules/Process/ProcessPageList/ProcessPageListRenderJSON.php +++ b/wire/modules/Process/ProcessPageList/ProcessPageListRenderJSON.php @@ -72,13 +72,18 @@ class ProcessPageListRenderJSON extends ProcessPageListRender { if($child->listable()) $numChildren++; } } + if(strpos($this->qtyType, 'total') !== false) { + $numTotal = $this->wire('pages')->trasher()->getTrashTotal(); + } else { + $numTotal = $numChildren; + } } else { if($page->hasStatus(Page::statusTemp)) $icons[] = 'bolt'; if($page->hasStatus(Page::statusLocked)) $icons[] = 'lock'; if($page->hasStatus(Page::statusDraft)) $icons[] = 'paperclip'; $numChildren = $page->numChildren(1); + $numTotal = strpos($this->qtyType, 'total') !== false ? $page->numDescendants : $numChildren; } - if(!$label) $label = $this->getPageLabel($page); if(count($icons)) foreach($icons as $n => $icon) { @@ -90,6 +95,7 @@ class ProcessPageListRenderJSON extends ProcessPageListRender { 'label' => $label, 'status' => $page->status, 'numChildren' => $numChildren, + 'numTotal' => $numTotal, 'path' => $page->template->slashUrls || $page->id == 1 ? $page->path() : rtrim($page->path(), '/'), 'template' => $page->template->name, //'rm' => $this->superuser && $page->trashable(),