Jump to content

Recommended Posts

Aligator (wip)

Processwire Module to render a nested tree starting from a single root or an array of pages.

Aligator is similar to MarkupSimpleNavigation but has a different approach of how to define the markup for your menu. It doesn't assume any markup or classes. It's up to you to define them where needed. It's less plug and play and requires some more advanced knowledge of ProcessWire, as some additional setup and coding is needed. But allows for powerful and easier customization without using hooks.

Aligator uses callback functions to achieve this. Additionally a selector can be used to filter the children for your navigation.

Note: This module is a fun project trying to find simple configurable method to render navigations. It's a work in progress and there might be major changes to how the module works.

 

See further infos and examples on the repository:

https://github.com/somatonic/Aligator

  • Like 21
Link to comment
Share on other sites

20 hours ago, Ivan Gretsky said:

Is it called Aligator because it eats hell lot of memory or something?

Jokes aside, why Aligator? Why with the single "L"?

And you know my obsession... Can it render MenuBuilder contents somehow?

No it's powerful and fast and dangerous ...

With one "L"? It's more efficient...

No I don't know your obsession. Well I don't know what MenuBuilder contents are. Why? Does MenuBuilder not have any render functions? Aligator and MSN only work with Pages.

 

  • Like 3
Link to comment
Share on other sites

  • 5 months later...
  • 9 months later...

Hi @Soma,

I finally got around to trying this module - it's really powerful, I love it!

A couple of questions:

1. Is there a way to include the Home page in a navigation generated by this module? Something like the 'show_root' option in MarkupSimpleNavigation?

2. If I want to add 'first' and 'last' classes to my nav items (like the 'firstlast' option in MarkupSimpleNavigation) is the following a good way or can you see a more efficient way?

In my callback function I add to the $class variable like this...

$class .= $item == $item->siblings()->first() ? ' first' : '';
$class .= $item == $item->siblings()->last() ? ' last' : '';

TIA.

Link to comment
Share on other sites

Had to look more at it again. :)

No there's no such built in option. But there's some ways to do it. 

The first argument not only works with a root page but also with a PageArray.

So you could do build a PageArray, add children of root, then prepend the home page to it and send that to Aligator. All you need to make sure you don't render level2 of home page which would render your menu again if home is your root. Got it? :)

$menuPages = new PageArray();
$home = $pages->get("/");
$menuPages->add($home->children());
$menuPages->prepend($home);

$content .= $modules->Aligator->render($menuPages, array(
    array( // level1
        "selector" => ""
    ),
    array( // level2 : don't render the children of "home"
        "selector" => "parent!=1"
    )
), 3);

Or a more straight forward version is to use the wrapperOpen and add it manually.

$homeUrl = $pages->get(1)->url;

$nav = $modules->Aligator->render($pages->get(1), array(
	array( // level1
		"selector" => "",
		"callback" => function($item, $level) use($homeUrl){
			return array(
				"wrapperOpen" => "<ul><li><a href='$homeUrl'>Home</a></li>",
			);
		}
	)
), 3);

The first last class thingy is not something I really ever use, but if it works for your navigation then it's ok to do it that way. It's just you would be careful if there's lots of pages as siblings. You'd also want to use the same selector you use for the level. Otherwise it would not work as the last sibling might be not rendered as by the selector.

  • Like 1
Link to comment
Share on other sites

36 minutes ago, Soma said:

No there's no such built in option. But there's some ways to do it. 

Thanks for the suggestions for including "Home" - I had been playing around with adding a "show_root" option to Aligator but your suggestions are simpler and better.

I made a few other changes in a fork of Aligator that I'll pass on for consideration.

1. I think there is a typo here - should be...

if($return["wrapperClose"]) {

2. I changed the early returns so that an empty string is returned, to be consistent with the @return tag in the function DocBlock.

3. I added a new argument that is supplied to the callback function - a $props array of some menu-related properties of the current $item.

The reason being that these properties are commonly needed to work out the menu markup, and they are already determined within the Aligator render() function - so it makes sense to pass 'is_parent', 'is_current' and 'num_children' as an argument rather than have to determine them again separately in the callback function.

I also included 'is_first' and 'is_last' in the $props array because it is much more efficient to determine this within render() from the $key in the foreach (just like is done in MarkupSimpleNavigation) than in the callback as per my post above. But now that I think about it I don't know that these properties are that useful after all - probably anything you would use them for could be just as easily achieved in CSS or JS with :first-child and :last-child.

4. Lastly, because of the $props argument I override 'is_parent' and 'num_children' in the case of the Home page being rendered. That's because despite the way PW treats Home as the root parent of all pages (there are probably good reasons for this), I think most website users think of the Home page as a sibling of the top-level pages rather than a parent of them.

The commit with my changes is here if you are interested.

Link to comment
Share on other sites

Me again. :)

Is there a way I can set options for all levels of a particular menu without changing the default options?

Suppose I have several menus in my site and I have defined some default options for Aligator that suit most of these menus. Now on one of these menus I want to exclude a particular template from all levels in the menu. I don't know how many levels will be in this menu (users may add pages creating additional levels of nesting).

So I can use the "selector" setting for this, but do I use this in the default options or the options for that specific menu? It seems like there are catches either way.

Default options: works for all levels (good), but affects all the menus in my site (bad).
Menu options: limited to this specific menu (good), but I have to repeat the setting over and over some arbitrary number of times to account for all the levels that might exist in the menu (bad).

Seems like there should be some way to set default options (all levels) but just for this one menu. Or am I missing something?

Link to comment
Share on other sites

Posting some more stuff while it is fresh in my mind...

I made a new commit here. Changes...

1. Fix for selector not falling back to what is defined in the default options.

2. Allows menu options to be defined for all levels by using "all" as the options array key. Example:

$menuOptions = array(
    // use these options for all levels in this menu
    "all" => array(
        "selector" => "template=basic-page|domain_root",
        "callback" => function($item, $level){
            $class = $item === wire("page") ? " current" : "";
            $class .= wire("page")->parents->has($item) ? " parent" : "";
            $class .= $item->numChildren("template=basic-page") ? " has_children" : "";
            return array(
                "item" => "<a href='$item->url'>$item->title $item->template</a>",
                "listOpen" => "<li class='level$level$class'>",
                "listClose" => "</li>",
                "wrapperOpen" => "<ul class='mainnav'>",
                "wrapperClose" => "</ul>",
            );
        },
    )
);

Setting an explicit array key also made me think of something else that it would be good to mention in the module readme. If you want to set options only for level 3 (for example) it isn't necessary to include anything for the other levels. Instead of...

$menuOptions = array(
    array(), // level 1
    array(), // level 2
    array( // level 3
        "selector" => "template=basic-page",
    ),
);

...you can do...

$menuOptions = array(
    // options for level 3 (array keys are zero-indexed)
    2 => array(
        "selector" => "template=basic-page",
    ),
);

 

And an issue to report: there is a problem when using Aligator to render more than one menu - the $level variable in the render() method can be incorrect for any menus after the first menu. This is because $level is declared as a static variable, so after the first menu is rendered $level can retain its value when it should be reset to zero for each menu that is rendered. Not sure of the best way to fix that (the whole static variable / recursive function thing makes my head spin).
Edit: In this commit I changed from the static variable to passing $level as an argument to render() and it seems like an okay solution.

  • Like 1
Link to comment
Share on other sites

Hey, thanks for the input. I was putting my head into it quite a few time yesterday, and messed up something. Took a while to figure it all out today. :) It can get complicated. You refactor stuff, then suddenly it all messed up. 

I commited a new version with some of your input and various fixes. https://github.com/somatonic/Aligator/commit/dc4d8aa19ff665e24aa07ccba65fd3aff0d4e002

The static level I also recognized that it gives problem when using more than one menu. Also the default options now carry over all levels. I don't think a "all" array is needed just for that, since it was the intention to have a default that is the fallback. 

I added a $states variable to callback (third argument) that returns a string with classes for the different states. You can enable it and configure it too.

Something like this was my testing scenario:

/**
 * load Alitgaor module
 * --------------------------------------------------------------------------------
 */

$nav = $modules->Aligator;

/**
 * first menu
 * using PageArray for first level and prepend root page
 * --------------------------------------------------------------------------------
 */

$menuPages = new PageArray();
$home = $pages->get("/");
$menuPages->add($home->children("template=basic-page"));
$menuPages->prepend($home);

$nav->enableStates = true; // enable states ("parent current has_children first last")
$nav->levels = 3; // set max levels to render

// the default options for all levels
$nav->setDefaultOptions(array(
        "selector" => "template=basic-page",
        "callback" => function($item, $level, $states){
            // $states is a string of item state classes to insert somewhere
            $classes = $states ? " class='$states'" : "";
            return array(
                "item" => "<a href='$item->url'>$item->title</a>",
                "listOpen" => "<li$classes>",
                "listClose" => "</li>",
                "wrapperOpen" => "<ul class='dropdown'>",
                "wrapperClose" => "</ul>",
            );
        }
    )
);

// render the menu
$content .= $nav->render($menuPages, array(
    array( // level1
        "selector" => "template=basic-page",
    ),
    array( // level2
        "selector" => "template=basic-page|article"
    )
));


/**
 * render different menu with default options and using a root page
 * --------------------------------------------------------------------------------
 */

$nav->levels = 4;
$nav->collapsed = true;
$nav->setDefaultStates(array("is_current" => "active"));
$nav->setDefaultOptions(array(
        "selector" => "template=basic-page",
        "callback" => function($item, $level, $states){
            return array(
                "item" => "<a href='$item->url'>$item->title</a>",
                "listOpen" => "<li class='$states'>",
                "listClose" => "</li>",
                "wrapperOpen" => "<ol>",
                "wrapperClose" => "</ol>"
            );
        }
    )
);

$content .= $nav->render($pages->get("/"));

I'm not sure, there's possibly still something that isn't covered or I missed but seems working fine here.

Thanks

 

  • Like 2
Link to comment
Share on other sites

13 hours ago, Soma said:

I commited a new version with some of your input and various fixes.

Thanks for the update!

A few little things...

1. $debug variable has been left behind in the module and may be undefined.

2. The default selector should be an empty string.

3. Wouldn't it be more efficient to set is_first and is_last according to array key like you did in MarkupSimpleNavigation?

 

In terms of understanding the module, I find the way that $defaultOptions, $defaultStates, $enableStates, $levels and $collapsed are set to be a bit counter-intuitive. To my way of thinking, you only want to set properties to the module itself for settings that are intended to be global (applying to all menus in your site). Because when these options are set as properties of the module they persist for all subsequent menus unless you explicitly change them.

But things like $levels and $collapsed are things that you want to set individually for a single menu - you don't want these settings affecting other menus. So I'd rather be passing these settings in as arguments to the render() method - that way I don't have to keep track and reset them for menus that are rendered subsequently.

Similar for setting menu options for all levels of a menu - that's why I thought something like a specific array key 'all' could be used when passing in the menu options. So I'm just setting default options for that one menu and not affecting other menus.

To be clear, I still like the idea of setting some global $defaultOptions, $defaultStates, etc - but for things like setting a levels limit or setting options for all levels of a specific menu it's less error-prone if these don't affect other menus.

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

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

Create an account

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

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Similar Content

    • By monollonom
      (once again I was surprised to see a work of mine pop up in the newsletter, this time without even listing the module on PW modules website 😅. Thx @teppo !)
      FieldtypeQRCode
      Github: https://github.com/romaincazier/FieldtypeQRCode
      Modules directory: https://processwire.com/modules/fieldtype-qrcode/
      A simple fieldtype generating a QR Code from the public URL of the page, and more.
      Using the PHP library QR Code Generator by Kazuhiko Arase.

      Options
      In the field’s Details tab you can change between .gif or .svg formats. If you select .svg you will have the option to directly output the markup instead of a base64 image. SVG is the default.
      You can also change what is used to generate the QR code and even have several sources. The accepted sources (separated by a comma) are: httpUrl, editUrl, or the name of any text/URL/file/image field.
      If LanguageSupport is installed the compatible sources (httpUrl, text field, ...) will return as many QR codes as there are languages. Note however that when outputting on the front-end, only the languages visible to the user will be generated.
      Formatting
      Unformatted value
      When using $page->getUnformatted("qrcode_field") it returns an array with the following structure:
      [ [ "label" => string, // label used in the admin "qr" => string, // the qrcode image "source" => string, // the source, as defined in the configuration "text" => string // and the text used to generate the qrcode ], ... ] Formatted value
      The formatted value is an <img>/<svg> (or several right next to each other). There is no other markup.
      Should you need the same markup as in the admin you could use:
      $field = $fields->get("qrcode_field"); $field->type->markupValue($page, $field, $page->getUnformatted("qrcode_field")); But it’s a bit cumbersome, plus you need to import the FieldtypeQRCode's css/js. Best is to make your own markup using the unformatted value.
      Static QR code generator
      You can call FieldtypeQRCode::generateQRCode to generate any QR code you want. Its arguments are:
      string $text bool $svg Generate the QR code as svg instead of gif ? (default=true) bool $markup If svg, output its markup instead of a base64 ? (default=false) Hooks
      Please have a look at the source code for more details about the hookable functions.
      Examples
      $wire->addHookAfter("FieldtypeQRCode::getQRText", function($event) { $page = $event->arguments("page"); $event->return = $page->title; // or could be: $event->return = "Your custom text"; }) $wire->addHookAfter("FieldtypeQRCode::generateQRCodes", function($event) { $qrcodes = $event->return; // keep everything except the QR codes generated from editUrl foreach($qrcodes as $key => &$qrcode) { if($qrcode["source"] === "editUrl") { unset($qrcodes[$key]); } } unset($qrcode); $event->return = $qrcodes; })
    • By Sebi
      AppApiFile adds the /file endpoint to the AppApi routes definition. Makes it possible to query files via the api. 
      This module relies on the base module AppApi, which must be installed before AppApiFile can do its work.
      Features
      You can access all files that are uploaded at any ProcessWire page. Call api/file/route/in/pagetree?file=test.jpg to access a page via its route in the page tree. Alternatively you can call api/file/4242?file=test.jpg (e.g.,) to access a page by its id. The module will make sure that the page is accessible by the active user.
      The GET-param "file" defines the basename of the file which you want to get.
      The following GET-params (optional) can be used to manipulate an image:
      width height maxwidth maxheight cropX cropY Use GET-Param format=base64 to receive the file in base64 format.
    • By MarkE
      This fieldtype and inputfield bundle was built for storing measurement values within a field, rendering them in a variety of formats and converting them to other units or otherwise modifying them via the API.
      The API consists of a number of predefined functions, some of which include...
      render() for rendering the measurement object, valueAs() for converting the value to another unit value, convertTo() for converting the whole measurement object to different units, and add() and subtract() for for modifying the stored value by the value (converted as required) in another measurement. In the admin the inputfield includes a checkbox (which can be optionally disabled) for converting values on page save. For an example if a value was typed in as centimeters, the unit was changed to metres, and the page saved with this checkbox selected, said value would be automatically converted so that e.g. 170 cm becomes 1.7 m.

      A simple length field using Fieldtype Measurement and Inputfield Measurement.
      Combination units (e.g. feet and inches) are also supported.
      Please note that this module is 'proof of concept' at the moment - there are limited units available and quite a lot of code tidying to do. More units will be added shortly.
      See the GitHub at https://github.com/MetaTunes/FieldtypeMeasurement for full details and updates.
    • By tcnet
      File Manager for ProcessWire is a module to manager files and folders from the CMS backend. It supports creating, deleting, renaming, packing, unpacking, uploading, downloading and editing of files and folders. The integrated code editor ACE supports highlighting of all common programming languages.
      https://github.com/techcnet/ProcessFileManager

      Warning
      This module is probably the most powerful module. You might destroy your processwire installation if you don't exactly know what you doing. Be careful and use it at your own risk!
      ACE code editor
      This module uses ACE code editor available from: https://github.com/ajaxorg/ace

      Dragscroll
      This module uses the JavaScript dragscroll available from: http://github.com/asvd/dragscroll. Dragscroll adds the ability to drag the table horizontally with the mouse pointer.
      PHP File Manager
      This module uses a modified version of PHP File Manager available from: https://github.com/alexantr/filemanager
       
    • By tcnet
      This module implements the website live chat service from tawk.to. Actually the module doesn't have to do much. It just need to inserted a few lines of JavaScript just before the closing body tag </body> on each side. However, the module offers additional options to display the widget only on certain pages.
      Create an account
      Visit https://www.tawk.to and create an account. It's free! At some point you will reach a page where you can copy the required JavaScript-code.

      Open the module settings and paste the JavaScript-code into the field as shown below. Click "Submit" and that's all.

      Open the module settings
      The settings for this module are located int the menu Modules=>Configure=>LiveChatTawkTo.

       
×
×
  • Create New...