Rev 22 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download
/*** ProcessWire Page List Process, JQuery Plugin** Provides the Javascript/jQuery implementation of the PageList process when used with the JSON renderer** ProcessWire 3.x (development), Copyright 2016 by Ryan Cramer* https://processwire.com**/function ProcessPageListInit() {if(ProcessWire.config.ProcessPageList) {$('#' + ProcessWire.config.ProcessPageList.containerID).ProcessPageList(ProcessWire.config.ProcessPageList);}}$(document).ready(function() {ProcessPageListInit();});(function($) {$.fn.ProcessPageList = function(customOptions) {/*** List of options that may be passed to the plugin**/var options = {// mode: 'select' or 'actions', currently this is automatically determined based on the element the PageList is attached tomode: '',// default number of pages to show before paginationlimit: 35,// the page ID that starts the listrootPageID: 0,// show the page identified by 'rootPageID' ?showRootPage: true,// the page ID currently selectedselectedPageID: 0,// id of the admin pageadminPageID: 2,// id of the trash pagetrashPageID: 7,// language ID, if applicablelangID: 0,// in 'select' mode, allow no value to be selected (to abort a selected value)selectAllowUnselect: false,// show the 'currently selected' page header? (should be false on multi-selection)selectShowPageHeader: true,// show the parent path in the selected page label?selectShowPath: true,// the label to click on to change the currently selected pageselectStartLabel: 'Change',// the label to click on to cancel selecting a pageselectCancelLabel: 'Cancel',// the label to click on to select a given pageselectSelectLabel: 'Select',// the label to click on to unselect a selected pageselectUnselectLabel: 'Unselect',// label used for 'more' in paginated listsmoreLabel: 'More',// label used for 'trash' button in Move modetrashLabel: 'Trash',// label used for the move instructionmoveInstructionLabel: "Click and drag to move",// href attribute of 'select' linkselectSelectHref: '#',// href attribute of 'unselect' linkselectUnselectHref: '#',// URL where page lists are loaded fromajaxURL: ProcessWire.config.urls.admin + 'page/list/',// URL where page move's should be postedajaxMoveURL: ProcessWire.config.urls.admin + 'page/sort/',// classes for paginationpaginationClass: 'PageListPagination',paginationCurrentClass: 'PageListPaginationCurrent',paginationLinkClass: 'ui-state-default',paginationLinkCurrentClass: 'ui-state-active',paginationHoverClass: 'ui-state-hover',paginationDisabledClass: 'ui-priority-secondary',// pagination number that you want to open (to correspond with openPageIDs)openPagination: 0,// IDs of the pages that we want to automatically open (default none)openPageIDs: [],// pre-rendered data corresponding to openPageIDs, indexed by 'id-start', i.e. 123-0openPageData: {},// speed at which the slideUp/slideDown run (in ms)speed: 200,// whether or not hovering an item reveals its actionsuseHoverActions: false,// milliseconds delay between hovering an item and it revealing actionshoverActionDelay: 250,// milliseconds in fade time to reveal or hide hover actionshoverActionFade: 150,// show only edit and view actions, and make everything else extra actionsuseNarrowActions: $('body').hasClass('pw-narrow-width'),// markup for the spinner used when ajax calls are madespinnerMarkup: "<span class='PageListLoading'><i class='ui-priority-secondary fa fa-fw fa-spin fa-spinner'></i></span>",// session field name that holds page label format, when usedlabelName: '',// 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)var currentOpenPageIDs = [];// true when operations are occurring where we want to ignore clicksvar ignoreClicks = false;var isModal = $("body").hasClass("modal") || $("body").hasClass("pw-iframe");if(typeof ProcessWire.config.ProcessPageList != "undefined") {$.extend(options, ProcessWire.config.ProcessPageList);}$.extend(options, customOptions);return this.each(function(index) {var $container = $(this);var $outer;var $root;var $loading = $(options.spinnerMarkup);var firstPagination = 0; // used internally by the getPaginationList() functionvar curPagination = 0; // current page number used by getPaginationList() function/*** Initialize the Page List**/function init() {$root = $("<div class='PageListRoot'></div>");if($container.is(":input")) {options.selectedPageID = $container.val();if(!options.selectedPageID.length) options.selectedPageID = 0;options.mode = 'select';$container.before($root);$outer = $container.closest('.InputfieldContent');setupSelectMode();} else {options.mode = 'actions';$container.append($root);$outer = $container;loadChildren(options.rootPageID > 0 ? options.rootPageID : 1, $root, 0, true);/*// longclick to initiate sort, still marinating on whether to support this$(document).on('longclick', 'a.PageListPage', function() {$(this).parent().find('.PageListActionMove > a').click();});*/}$(document).on('pageListRefresh', function(e, pageID) {// i.e. $(document).trigger('pageListRefresh', pageID);refreshList(pageID);});if(options.useHoverActions) {$root.addClass('PageListUseHoverActions');setupHoverActions();}}/*** If hover actions enabled, setup events to hide/show hover actions**/function setupHoverActions() {var hoverTimeout = null;var hoverOutTimeout = null;var $hoveredItem = null;function showItem($item) {var $actions = $item.find('.PageListActions');if(!$actions.is(":visible") || $item.hasClass('PageListItemOpen')) {// we confirm :visible so that we don't iterfere with admin themes that already// make the PageList items visible with css hover states$item.addClass('PageListItemHover');$actions.css('display', 'inline').css('opacity', 0).animate({opacity: 1.0}, options.hoverActionFade);}}function hideItem($item) {var $actions = $item.find('.PageListActions');$item.removeClass('PageListItemHover');if($actions.is(":visible")) { // || $hoveredItem.hasClass('PageListItemOpen')) {$actions.animate({opacity: 0}, options.hoverActionFade, function () {$actions.hide();});}}$outer.on('keydown', '.PageListItem', function(e) {// PR#1 makes page-list keyboard accessiblee = e || window.event;if(e.keyCode == 0 || e.keyCode == 32) {// spacebarvar $actions = $(this).find('.PageListActions');if($actions.is(":visible")) {$actions.css('display', 'none');} else {$actions.css('display', 'inline-block');}return false;}});$outer.on('mouseover', '.PageListItem', function(e) {if($root.is(".PageListSorting") || $root.is(".PageListSortSaving")) return;if(!$(this).children('a:first').is(":hover")) return;$hoveredItem = $(this);//console.log('pageX=' + e.pageX);//console.log('offsetX=' + $hoveredItem.offset().left);//var maxPageX = $(this).children('.PageListNumChildren').offset().left + 100;//if(e.pageX > maxPageX) return;if($hoveredItem.hasClass('PageListItemHover')) return;var $item = $(this);if(hoverTimeout) clearTimeout(hoverTimeout);var delay = options.hoverActionDelay;hoverTimeout = setTimeout(function() {if($hoveredItem.attr('class') == $item.attr('class')) {if(!$hoveredItem.children('a:first').is(":hover")) return;var $hideItems = $outer.find(".PageListItemHover");showItem($hoveredItem);$hideItems.each(function() { hideItem($(this)); });}}, delay);}).on('mouseout', '.PageListItem', function(e) {if($root.is(".PageListSorting") || $root.is(".PageListSortSaving")) return;var $item = $(this);if($item.hasClass('PageListItemOpen')) return;if(!$item.hasClass('PageListItemHover')) return;var delay = options.hoverActionDelay * 0.7;hoverOutTimeout = setTimeout(function() {if($item.is(":hover")) return;if($item.attr('class') == $hoveredItem.attr('class')) return;hideItem($item);}, delay);});}/*** Sets up a mode where the user is given a "select" link for each page, rather than a list of actions** When they hit "select" the list collapses and the selected page ID is populated into an input**/function setupSelectMode() {var $actions = $("<ul></ul>").addClass('PageListActions PageListSelectActions actions');var $pageLabel = $("<p></p>").addClass("PageListSelectName");if(options.selectShowPageHeader) $pageLabel.append($loading);var $action = $("<a></a>").addClass("PageListSelectActionToggle").addClass("PageListSelectActionToggleStart").attr('href', '#').text(options.selectStartLabel).click(function() {if($(this).text() == options.selectStartLabel) {loadChildren(options.rootPageID > 0 ? options.rootPageID : 1, $root, 0, true);$(this).text(options.selectCancelLabel).removeClass('PageListSelectActionToggleStart').addClass('PageListSelectActionToggleCancel');} else {$(this).addClass('PageListSelectActionToggleStart').removeClass('PageListSelectActionToggleCancel');$root.children(".PageList").slideUp(options.speed, function() {$(this).remove();});$(this).text(options.selectStartLabel);}return false;});$actions.append($("<li></li>").append($action));$root.append($("<div></div>").addClass('PageListSelectHeader').append($pageLabel).append($actions));if(options.selectShowPageHeader) {var ajaxURL = options.ajaxURL +"?id=" + options.selectedPageID +"&render=JSON&start=0&limit=0&lang=" + options.langID +"&mode=" + options.mode;if(options.labelName.length) ajaxURL += '&labelName=' + options.labelName;$.getJSON(ajaxURL, function(data) {var parentPath = '';if(options.selectShowPath) {parentPath = data.page.path;if(parentPath.substring(-1) == '/') parentPath = parentPath.substring(0, parentPath.length-1);parentPath = parentPath.substring(0, parentPath.lastIndexOf('/')+1);parentPath = '<span class="detail">' + parentPath + '</span> ';}var label = options.selectedPageID > 0 ? parentPath + data.page.label : '';$root.children(".PageListSelectHeader").find(".PageListSelectName").html(label);});}}/*** Method that is triggered when the processChildren() method completes**/function loaded() {ignoreClicks = false;}/*** Handles pagination of PageList items** @param int id ID of the page having children to show* @param start Index that we are starting with in the current list* @param int limit The limit being applied to the list (items per page)* @param int total The total number of items in the list (excluding any limits)* @return jQuery $list The pagination list ready for insertion**/function getPaginationList(id, start, limit, total) {// console.log('getPaginationList(id=' + id + ', start=' + start + ", limit=" + limit + ", total=" + total + ')');var maxPaginationLinks = 9;var numPaginations = Math.ceil(total / limit);curPagination = start >= limit ? Math.floor(start / limit) : 0;if(curPagination == 0) {firstPagination = 0;} else if((curPagination-maxPaginationLinks+1) > firstPagination) {firstPagination = curPagination - Math.floor(maxPaginationLinks / 2);} else if(firstPagination > 0 && curPagination == firstPagination) {firstPagination = curPagination - Math.ceil(maxPaginationLinks / 2);}// if we're on the last page of pagination links, then make the firstPagination static at the endif(firstPagination > numPaginations - maxPaginationLinks) firstPagination = numPaginations - maxPaginationLinks;if(firstPagination < 0) firstPagination = 0;var $list = $("<ul></ul>").addClass(options.paginationClass).data('paginationInfo', {start: start,limit: limit,total: total});/*** paginationClick is the event function called when an item in the pagination nav is clicked** It loads the new pages (via loadChildren) and then replaces the old pageList with the new one**/var paginationClick = function(e) {var $curList = $(this).parents("ul." + options.paginationClass);var info = $curList.data('paginationInfo');if(!info) return false;var start = parseInt($(this).attr('href')) * info.limit;if(start === NaN) start = 0;var $newList = getPaginationList(id, start, info.limit, info.total);var $spinner = $(options.spinnerMarkup);var $loading = $("<li> </li>").addClass(options.paginationDisabledClass).append($spinner.hide());$curList.siblings(".PageList").remove(); // remove any open lists below current$curList.replaceWith($newList);$newList.append($loading);$spinner.fadeIn('fast');var $siblings = $newList.siblings().css('opacity', 0.5);loadChildren(id, $newList.parent(), $(this).attr('href') * info.limit, false, false, true, function() {$spinner.fadeOut('fast', function() {$loading.remove();});$newList.parent('.PageList').prev('.PageListItem').data('start', start);updateOpenPageIDs();});return false;}var $separator = null;var $blankItem = null;for(var pagination = firstPagination, cnt = 0; pagination < numPaginations; pagination++, cnt++) {var $a = $("<a></a>").html(pagination+1).attr('href', pagination).addClass(options.paginationLinkClass);var $item = $("<li></li>").addClass(options.paginationClass + cnt).append($a); // .addClass('ui-state-default');if(pagination == curPagination) {//$item.addClass("PageListPaginationCurrent ui-state-focus");$item.addClass(options.paginationCurrentClass).find("a").removeClass(options.paginationLinkClass).addClass(options.paginationLinkCurrentClass);}$list.append($item);if(!$blankItem) {$blankItem = $item.clone().removeClass(options.paginationCurrentClass + ' ' + options.paginationLinkCurrentClass);$blankItem.find('a').removeClass(options.paginationLinkCurrentClass).addClass(options.paginationLinkClass);}// if(!$blankItem) $blankItem = $item.clone().removeClass('PageListPaginationCurrent').find('a').removeClass('ui-state-focus').addClass('ui-state-default');if(!$separator) $separator = $blankItem.clone().removeClass(options.paginationLinkClass).addClass(options.paginationDisabledClass).html("…");//if(!$separator) $separator = $blankItem.clone().html("…");if(cnt >= maxPaginationLinks && pagination < numPaginations) {$lastItem = $blankItem.clone();$lastItem.find("a").text(numPaginations).attr('href', numPaginations-1);$list.append($separator.clone()).append($lastItem);break;}}if(firstPagination > 0) {$firstItem = $blankItem.clone();$firstItem.find("a").text("1").attr('href', '0').click(paginationClick);$list.prepend($separator.clone()).prepend($firstItem);}//if(curPagination+1 < maxPaginationLinks && curPagination+1 < numPaginations) {if(curPagination+1 < numPaginations) {$nextBtn = $blankItem.clone();$nextBtn.find("a").html("<i class='fa fa-angle-right'></i>").attr('href', curPagination+1);$list.append($nextBtn);}if(curPagination > 0) {$prevBtn = $blankItem.clone();$prevBtn.find("a").attr('href', curPagination-1).html("<i class='fa fa-angle-left'></i>");$list.prepend($prevBtn);}$list.find("a").click(paginationClick).hover(function() {$(this).addClass(options.paginationHoverClass);}, function() {$(this).removeClass(options.paginationHoverClass);});return $list;}/*** Load children via ajax call, attach them to $target and show.** @param int id ID of the page having children to show* @param jQuery $target Item to attach children to* @param int start If not starting from first item, num of item to start with* @param bool beginList Set to true if this is the first call to create the list* @param bool pagination Set to false if you don't want pagination, otherwise leave it out* @param bool replace Should any existing list be replaced (true) or appended (false)**/function loadChildren(id, $target, start, beginList, pagination, replace, callback) {if(pagination == undefined) pagination = true;if(replace == undefined) replace = false;var processChildren = function(data) {if(data && data.error) {ProcessWire.alert(data.message);$loading.hide();ignoreClicks = false;return;}var $children = listChildren($(data.children));var nextStart = data.start + data.limit;//var openPageKey = id + '-' + start;if($target.hasClass('PageListItem')) {setNumChildren($target, data.page.numChildren, data.page.numTotal);}if(data.page.numChildren > nextStart) {var $a = $("<a></a>").attr('href', nextStart).data('pageId', id).text(options.moreLabel).click(clickMore);$children.append($("<ul></ul>").addClass('PageListActions actions').append($("<li></li>").addClass('PageListActionMore').append($a)));}if(pagination && (data.page.numChildren > nextStart || data.start > 0)) {$children.prepend(getPaginationList(id, data.start, data.limit, data.page.numChildren));}$children.hide();if(beginList) {var $listRoot;$listRoot = listChildren($(data.page));if(options.showRootPage) $listRoot.children(".PageListItem").addClass("PageListItemOpen");else $listRoot.children('.PageListItem').hide().parent('.PageList').addClass('PageListRootHidden');$listRoot.append($children);$target.append($listRoot);} else if($target.is(".PageList")) {var $newChildren = $children.children(".PageListItem, .PageListActions");if(replace) $target.children(".PageListItem, .PageListActions").replaceWith($newChildren);else $target.append($newChildren);} else {$target.after($children);}if($loading.parent().is('.PageListRoot')) {$loading.hide();} else {$loading.fadeOut('fast');}if(replace) {$children.show();loaded();if(callback != undefined) callback();} else {$children.slideDown(options.speed, function() {loaded();if(callback != undefined) callback();});}$children.prev('.PageListItem').data('start', data.start);// if a pagination is requested to be opened, and it exists, then open it/*if(options.openPagination > 1) {//var $a = $(".PageListPagination" + (options.openPagination-1) + ">a");var $a = $(".PageListPagination a[href=" + (options.openPagination-1) + "]");if($a.size() > 0) {$a.click();options.openPagination = 0;} else {// last pagination link$(".PageListPagination9 a").click();}}*/};if(!replace) $target.append($loading.fadeIn('fast'));var key = id + '-' + start;if(typeof options.openPageData[key] != "undefined"&& !$target.hasClass('PageListID7') // trash&& !$target.hasClass('PageListForceReload')) {processChildren(options.openPageData[key]);return;}// @teppokoivula PR #1052var ajaxURL = options.ajaxURL +"?id=" + id +"&render=JSON&start=" + start +"&lang=" + options.langID +"&open=" + options.openPageIDs[0] +"&mode=" + options.mode;if(options.labelName.length) ajaxURL += '&labelName=' + options.labelName;$.getJSON(ajaxURL).done(function(data, textStatus, jqXHR) {processChildren(data);}).fail(function(jqXHR, textStatus, errorThrown) {processChildren({error: 1,message: !jqXHR.status ? options.ajaxNetworkError : options.ajaxUnknownError});});// end #1052}/*** Given a list of pages, generates a list of them** @param jQuery $children**/function listChildren($children) {var $list = $("<div></div>").addClass("PageList");var $ul = $list;$children.each(function(n, child) {$ul.append(listChild(child));});addClickEvents($ul);return $list;}/*** Set a parent to reload its children on next access rather than using cached data** @param $pageListItem .PageList or .PageListItem element* @param reloadNow Reload the list now? (default=false)**/function setForceReload($pageListItem, reloadNow) {if($pageListItem.hasClass('PageList')) $pageListItem = $pageListItem.prev('.PageListItem');$pageListItem.addClass('PageListForceReload');var id = $pageListItem.data('pageId');var prefix = id + '-';if(typeof options.openPageData != "undefined") {// remove cached items from openPageDatavar openPageData = {};for(var key in options.openPageData) {if(key.indexOf(prefix) === 0) {// skip it} else {openPageData[key] = options.openPageData[key];}}options.openPageData = openPageData;}if(typeof reloadNow != "undefined" && reloadNow) {var $a = $pageListItem.children('a.PageListPage');if($pageListItem.hasClass('PageListItemOpen')) {$a.click();setTimeout(function() { $a.click(); }, 250);} else {$a.click();}}}/**** @param $ul Any element that contains items needing click events attached**/function addClickEvents($ul) {$("a.PageListPage", $ul).click(clickChild);$ul.on('click', '.PageListActionMove a', clickMove);// $(".PageListActionMove a", $ul).click(clickMove);$(".PageListActionSelect a", $ul).click(clickSelect);$(".PageListTriggerOpen:not(.PageListID1) > a.PageListPage", $ul).click();$(".PageListActionExtras > a:not(.clickExtras)", $ul).addClass('clickExtras').on('click', clickExtras);// if(options.useHoverActions) $(".PageListActionExtras > a", $ul).on('mouseover', clickExtras);}/*** Given a single page, generates the list item for it** @param map child**/function listChild(child) {var $li = $("<div></div>").data('pageId', child.id).addClass('PageListItem').addClass('PageListTemplate_' + child.template);var $a = $("<a></a>").attr('href', '#').attr('title', child.path).html(child.label).addClass('PageListPage label');$li.addClass(child.numChildren > 0 ? 'PageListHasChildren' : 'PageListNoChildren').addClass('PageListID' + child.id);if(child.status == 0) $li.addClass('PageListStatusOff disabled');if(child.status & 2048) $li.addClass('PageListStatusUnpublished secondary');if(child.status & 1024) $li.addClass('PageListStatusHidden secondary');if(child.status & 512) $li.addClass('PageListStatusTemp secondary'); // typically combined with PageListStatusUnpublishedif(child.status & 16) $li.addClass('PageListStatusSystem');if(child.status & 8) $li.addClass('PageListStatusSystem');if(child.status & 4) $li.addClass('PageListStatusLocked');if(child.addClass && child.addClass.length) $li.addClass(child.addClass);if(child.type && child.type.length > 0) if(child.type == 'System') $li.addClass('PageListStatusSystem');$(options.openPageIDs).each(function(n, id) {id = parseInt(id);if(child.id == id) $li.addClass('PageListTriggerOpen');});$li.append($a);setNumChildren($li, child.numChildren, child.numTotal, true);if(child.note && child.note.length) $li.append($("<span>" + child.note + "</span>").addClass('PageListNote detail'));var $actions = $("<ul></ul>").addClass('PageListActions actions');var links = options.rootPageID == child.id ? [] : [{ name: options.selectSelectLabel, url: options.selectSelectHref }];if(options.mode == 'actions') {links = child.actions;} else if(options.selectAllowUnselect) {if(child.id == $container.val()) links = [{ name: options.selectUnselectLabel, url: options.selectUnselectHref }];}var $lastAction = null;var $extrasLink = null; // link that toggles extra actionsvar extras = {}; // extra actionsvar hasExtrasLink = false; // only referenced if options.useNarrowActionsif(options.useNarrowActions) {for(var n = 0; n < links.length; n++) {if(typeof links[n].extras != "undefined") hasExtrasLink = true;}}$(links).each(function(n, action) {var actionName;if(action.name == options.selectSelectLabel) {actionName = 'Select';} else if(action.name == options.selectUnselectLabel) {actionName = 'Select';} else {actionName = action.cn; // cn = classNameif(options.useNarrowActions && hasExtrasLink&& (actionName != 'Edit' && actionName != 'View' && actionName != 'Extras')) {// move non-edit/view actions to extras when in narrow modeextras[actionName] = action;return;}}var $a = $("<a></a>").html(action.name).attr('href', action.url);if(!isModal) {if(action.cn == 'Edit') {$a.addClass('pw-modal pw-modal-large pw-modal-longclick');$a.attr('data-buttons', '#ProcessPageEdit > .Inputfields > .InputfieldSubmit .ui-button');} else if(action.cn == 'View') {$a.addClass('pw-modal pw-modal-large pw-modal-longclick');}}if(typeof action.extras != "undefined") {for(var key in action.extras) {extras[key] = action.extras[key];}$extrasLink = $a;}var $action = $("<li></li>").addClass('PageListAction' + actionName).append($a);if(actionName == 'Extras') $lastAction = $action;else $actions.append($action);});if($extrasLink) $extrasLink.data('extras', extras);if($lastAction) {$actions.append($lastAction);$lastAction.addClass('ui-priority-secondary');}$li.append($actions);return $li;}/*** Get number of children for given .PageListItem**/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, 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 = $('<span></span>').addClass('PageListNumChildren detail');addNew = true;}if(n < 1) {$item.removeClass('PageListHasChildren').addClass('PageListNoChildren');if(numTotal !== false) numTotal = 0;} else {$item.removeClass('PageListNoChildren').addClass('PageListHasChildren');}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 = "<span class='ui-priority-secondary'>/</span>";numLabel = numTotal > 0 && numTotal != numChildren ? numTotal + slash + numChildren : numTotal;break;case 'children/total':var slash = "<span class='ui-priority-secondary'>/</span>";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);}/*** Extra actions button click handler**/function clickExtras(e) {var $a = $(this);var extras = $a.data('extras');if(typeof extras == "undefined") return false;var $li = $a.closest('.PageListItem');var $actions = $a.closest('.PageListActions');var $lastItem = null;var $icon = $a.children('i.fa');var $extraActions = $actions.find("li.PageListActionExtra");/*if($extraActions.length && e.type != 'click') {// mouseover only opens, but a click is required to closereturn;}*/$icon.toggleClass('fa-flip-horizontal');if($extraActions.length) {$extraActions.fadeOut(100, function() {$extraActions.remove();});return false;}for(var extraKey in extras) {var extra = extras[extraKey];var $extraLink = $("<a />").addClass('PageListActionExtra PageListAction' + extra.cn).attr('href', extra.url).html(extra.name);/*if(extra.cn == 'Trash') {// handler for trash action$extraLink.click(function() {trashPage($li);return false;});} else*/if(typeof extra.ajax != "undefined" && extra.ajax == true) {// ajax action$extraLink.click(function () {$li.find('.PageListActions').hide();var $spinner = $(options.spinnerMarkup);var href = $(this).attr('href');var actionName = href.match(/[\?&]action=([-_a-zA-Z0-9]+)/)[1];var pageID = parseInt(href.match(/[\?&]id=([0-9]+)/)[1]);var tokenName = $("#PageListContainer").attr('data-token-name');var tokenValue = $("#PageListContainer").attr('data-token-value');var postData = {action: actionName,id: pageID,};postData[tokenName] = tokenValue;$li.append($spinner);$.post(href + '&render=json', postData, function (data) {if (data.success) {$li.fadeOut('fast', function() {var addNew = false;var removeItem = data.remove;var refreshChildren = data.refreshChildren;var $liNew = false;if(typeof data.child != "undefined") {// prepare update existing item$liNew = listChild(data.child);} else if(typeof data.newChild != "undefined") {// prepare append new item$liNew = listChild(data.newChild);addNew = true;}// display a message for a second to let them know what was doneif($liNew) {var $msg = $("<span />").addClass('notes').html(data.message);$msg.prepend(" <i class='fa fa-check-square ui-priority-secondary'></i> ");$liNew.append($msg);addClickEvents($liNew);}if(addNew) {// append new item$spinner.fadeOut('normal', function() { $spinner.remove() });$liNew.hide();$li.after($liNew);$liNew.slideDown();setForceReload($liNew.closest('.PageList'));} else if($liNew) {// update existing itemif($li.hasClass('PageListItemOpen')) $liNew.addClass('PageListItemOpen');$li.replaceWith($liNew);}$li.fadeIn('fast', function () {// display message for 1 second, then removesetTimeout(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, false);setForceReload($parentItem);if(removeItem) {$liNew.next('.PageList').fadeOut('fast');$liNew.fadeOut('fast', function() {$liNew.remove();});} else {$msg.remove();}});}, 1000);});// refresh the children of the page represented by refreshChildrenif(refreshChildren) {var $refreshParent = $(".PageListID" + refreshChildren);if($refreshParent.length) setForceReload($refreshParent, true);}});} else {// data.success === false, so display error$spinner.remove();ProcessWire.alert(data.message);}});return false;});} else {// some other action where the direct URL can be used, so we don't need to do anything}var $extraLinkItem = $("<li />").addClass('PageListActionExtra PageListAction' + extra.cn).append($extraLink);$extraLink.hide();if(extra.cn == 'Trash') {$li.addClass('trashable');// ensure the Trash item is always the last one$lastItem = $extraLinkItem;} else {$actions.append($extraLinkItem);}}if($lastItem) $actions.append($lastItem);$actions.find(".PageListActionExtra a").fadeIn(50, function() {$(this).css('display', 'inline-block');});return false;}/*** Event called when a page label is clicked on** @param event e**/function clickChild(e) {var $t = $(this);var $li = $t.parent('.PageListItem');var id = $li.data('pageId');if(ignoreClicks && !$li.hasClass("PageListTriggerOpen")) return false;if($root.is(".PageListSorting") || $root.is(".PageListSortSaving")) {return false;}if($li.hasClass("PageListItemOpen")) {var collapseThis = true;if($li.hasClass('PageListID1') && !$li.hasClass('PageListForceReload') && options.mode != 'select') {var $collapseItems = $(this).closest('.PageListRoot').find('.PageListItemOpen:not(.PageListID1)');if($collapseItems.length) {// collapse all open items, except homepage, when homepage link is collapsed$root.find('.PageListItemOpen:not(.PageListID1)').each(function() {$(this).children('a.PageListPage').click();});collapseThis = false;}}if(collapseThis) {$li.removeClass('PageListItemOpen').next(".PageList").slideUp(options.speed, function() {$(this).remove();});}} else {$li.addClass('PageListItemOpen');var numChildren = getNumChildren($li);if(numChildren > 0 || $li.hasClass('PageListForceReload')) {ignoreClicks = true;var start = getOpenPageStart(id);loadChildren(id, $li, start, false);}}if(options.mode != 'select') {setTimeout(function() { updateOpenPageIDs() }, 250);}return false;}/*** Get the pagination "start" index for the given open page ID** @param id* @returns {number}**/function getOpenPageStart(id) {var start = 0;for(n = 0; n < options.openPageIDs.length; n++) {var key = options.openPageIDs[n];if(key.indexOf('-') == -1) continue;var parts = options.openPageIDs[n].split('-');var _id = parseInt(parts[0]);if(_id == id) {start = parseInt(parts[1]);break;}}return start;}/*** Update the currentOpenPageIDs list and cookie to reflect the current open pages**/function updateOpenPageIDs() {currentOpenPageIDs = [];$('.PageListItemOpen').each(function() {var id = $(this).data('pageId');var start = $(this).data('start');if(typeof start == "undefined" || start === null) {start = 0;} else {var start = parseInt(start);}if(jQuery.inArray(id, currentOpenPageIDs) == -1) {currentOpenPageIDs.push(id + '-' + start); // id.start}});// console.log(currentOpenPageIDs);$.cookie('pagelist_open', currentOpenPageIDs);}/*** Force refresh the list that pageID appears in** @param pageID* @param animate**/function refreshList(pageID, animate) {var $parentList = $('.PageListID' + pageID).parent('.PageList');var $parentItem = $parentList.prev('.PageListItem');if(!$parentItem.length) return;var parentID = $parentItem.attr('class').match(/PageListID(\d+)/)[1];var start = 0;var $pagination = $parentList.children("ul." + options.paginationClass);var paginationInfo = $pagination.data('paginationInfo');var $removeItems = null;if(paginationInfo) {var $a = $pagination.find('.pw-link-active');if($a.length) start = parseInt($a.attr('href')) * paginationInfo.limit;if(start === NaN) start = 0;}if(parentID == 1) {$removeItems = $parentList.children();$parentList = $parentItem;}setForceReload($parentList);loadChildren(parentID, $parentList, start, false, true, true, function() {if($removeItems && $removeItems.length) $removeItems.remove();});}/*** Event called when the 'more' action/link is clicked on** @param event e**/function clickMore(e) {var $t = $(this);var $actions = $t.parent('li').parent('ul.PageListActions');var $pageList = $actions.parent('.PageList');var id = $t.data('pageId');var nextStart = parseInt($t.attr('href'));loadChildren(id, $pageList, nextStart, false);$actions.remove();return false;}/*** Event called when the 'move' action/link is clicked on** @param event e**/function clickMove() {if(ignoreClicks) return false;var $t = $(this);if($(".PageListItem:visible").length == 1) {// no other items to sort/move to$t.css('text-decoration', 'line-through').addClass('ui-state-disabled');return false;}var $li = $t.parent('li').parent('ul.PageListActions').parent('.PageListItem');// $li.children(".PageListPage").click();if($li.hasClass("PageListItemOpen")) $li.children(".PageListPage").click(); // @somatonic PR163// make an invisible PageList placeholder that allows 'move' action to create a child below this$root.find('.PageListItemOpen').each(function() {var numChildren = getNumChildren($(this));// if there are children and the next sibling doesn't contain a visible .PageList, then don't add a placeholderif(parseInt(numChildren) > 1 && $(this).next().find(".PageList:visible").length == 0) {return;}var $ul = $("<div></div>").addClass('PageListPlaceholder').addClass('PageList');$ul.append($("<div></div>").addClass('PageListItem PageListPlaceholderItem').html(' '));$(this).after($ul);//$(this).prepend($ul.clone());//$(this).addClass('PageListItemNoSort');});var sortOptions = {stop: stopMove,helper: 'PageListItemHelper',items: '.PageListItem:not(.PageListItemOpen)',placeholder: 'PageListSortPlaceholder',start: function(e, ui) {$(".PageListSortPlaceholder").css('width', ui.item.children(".PageListPage").outerWidth() + 'px');}};var $sortRoot = $root.children('.PageList').children('.PageList');var $cancelLink = $("<a href='#'>" + options.selectCancelLabel + "</a>").click(function() {return cancelMove($li);});var $actions = $li.children("ul.PageListActions");var $moveAction = $("<span class='PageListMoveNote detail'><i class='fa fa-fw fa-sort'></i> " + options.moveInstructionLabel + "<i class='fa fa-fw fa-angle-left'></i></span>");$moveAction.append($cancelLink);$actions.before($moveAction);$li.addClass('PageListSortItem');$li.parent('.PageList').attr('id', 'PageListMoveFrom');$root.addClass('PageListSorting');$sortRoot.addClass('PageListSortingList').sortable(sortOptions);return false;}/*** Remove everything setup from an active 'move'** @param jQuery $li List item that initiated the 'move'* @return bool**/function cancelMove($li) {var $sortRoot = $root.find('.PageListSortingList');$sortRoot.sortable('destroy').removeClass('PageListSortingList');$li.removeClass('PageListSortItem').parent('.PageList').removeAttr('id');$li.find('.PageListMoveNote').remove();$root.find(".PageListPlaceholder").remove();$root.removeClass('PageListSorting');return false;}/*** Remove everything setup from an active 'move'** @param jQuery $li List item that initiated the 'move'* @return bool**/function trashPage($li) {var $trash = $root.find('.PageListID' + options.trashPageID);if(!$trash.hasClass('PageListItemOpen')) {$root.removeClass('PageListSorting');$trash.children('a').click();$root.addClass('PageListSorting');}var $trashList = $trash.next('.PageList');if($trashList.length == 0) {$trashList = $("<div class='PageList'></div>");$trash.after($trashList);}$trashList.prepend($li);var ui = { item: $li };stopMove(null, ui);}/*** Event called when the mouse stops after performing a 'move'** @param event e* @param jQueryUI ui* @return bool**/function stopMove(e, ui) {var $li = ui.item;var $a = $li.children('.PageListPage');var id = parseInt($li.data('pageId'));var $ul = $li.parent('.PageList');var $from = $("#PageListMoveFrom")// get the previous sibling .PageListItem, and skip over the pagination list if it's therevar $ulPrev = $ul.prev().is('.PageListItem') ? $ul.prev() : $ul.prev().prev();var parent_id = parseInt($ulPrev.data('pageId'));// check if item was moved to an invalid spot// in this case, a spot between another open PageListItem and its PageListvar $liPrev = $li.prev(".PageListItem");if($liPrev.is(".PageListItemOpen")) return false;// check if item was moved into an invisible parent placeholder PageListif($ul.is('.PageListPlaceholder')) {var $ulNext = $ul.next();if($ulNext.is('.PageList:visible')) {// first item at top of list ended up in placeholder, but belongs in next sibling PageList$ulNext.prepend($li);$ul = $ulNext;} else {// if so, it's no longer a placeholder, but a real PageList$ul.removeClass('PageListPlaceholder').children('.PageListPlaceholderItem').remove();}}$root.addClass('PageListSortSaving');cancelMove($li);// setup to save the change$li.append($loading.fadeIn('fast'));var sortCSV = '';// create a CSV string containing the order of Page IDs$ul.children(".PageListItem").each(function() {sortCSV += $(this).data('pageId') + ',';});var postData = {id: id,parent_id: parent_id,sort: sortCSV};postData[$('#PageListContainer').attr('data-token-name')] = $('#PageListContainer').attr('data-token-value'); // CSRF Tokenvar success = 'unknown';// save the change$.post(options.ajaxMoveURL, postData, function(data) {$loading.fadeOut('fast');$a.fadeOut('fast', function() {$(this).fadeIn("fast")$li.removeClass('PageListSortItem');$root.removeClass('PageListSorting');});if(data && data.error) {ProcessWire.alert(data.message);}// if item moved from one list to another, then update the numChildren countsif(!$ul.is("#PageListMoveFrom")) {// update count where item came fromvar $fromItem = $from.prev(".PageListItem");var numChildren = getNumChildren($fromItem);var numTotal = getNumTotal($fromItem);if(numChildren > 0) {numChildren--;adjustNumTotal($fromItem, -1);} else {$from.remove(); // empty list, no longer needed}setNumChildren($fromItem, numChildren, false);setForceReload($fromItem);// update count where item went tovar $toItem = $ul.prev(".PageListItem");numChildren = getNumChildren($toItem) + 1;adjustNumTotal($toItem, 1);setNumChildren($toItem, numChildren, false);setForceReload($toItem);}$from.attr('id', ''); // remove tempoary #PageListMoveFrom$root.removeClass('PageListSortSaving');}, 'json');// trigger pageMoved event: @teppokoivula$li.trigger('pageMoved');return true; // whether or not to allow the sort}/*** Event called when the "select" link is clicked on in select mode** This also triggers a 'pageSelected' event on the attached text input.** @see setupSelectMode()**/function clickSelect() {var $t = $(this);var $li = $t.parent('li').parent('ul.PageListActions').parent('.PageListItem');var id = $li.data('pageId');var $a = $li.children(".PageListPage");var title = $a.text();var url = $a.attr('title');var $header = $root.children(".PageListSelectHeader");if($t.text() == options.selectUnselectLabel) {// if unselect link clicked, then blank out the valuesid = 0;title = '';}if(id != $container.val()) $container.val(id).change();if(options.selectShowPageHeader) {$header.children(".PageListSelectName").text(title);}// trigger pageSelected event$container.trigger('pageSelected', {id: id,url: url,title: title,a: $a});$header.find(".PageListSelectActionToggle").click(); // close the list// jump to specified anchor, if providedif(options.selectSelectHref == '#') return false;return true;}// initialize the plugininit();});};})(jQuery);