Jump to content

Simple example for widget management


Macrura

Recommended Posts

Some sites need widgets, as they have been called in some systems; a widget can be almost anything, like:

  • tag cloud
  • mini calendar
  • menu
  • quote rotator
  • free text
  • social sharing
  • search
  • contact info
  • map

This is a simple way to create widgets that can be shown in multiple "areas" of a page, as well as on specific pages.

In this particular method you would need to setup each widget type you want and then determine how best to accept any necessary user input like content, pages select (like for a menu) or settings.

This example uses include files for each widget type, and the name of the include file would match the name of the widget type, which is also a page field.

In this example, I'm also using ListerPro to provide a widget management page.

post-136-0-36099500-1419542584_thumb.png

Fields

The main fields used on this widget example are :

title

widget_location (page select - options in this case are footer and sidebar)

widget_type (page select, you would configure your widget types as selectable options)

pages_select (would be used for multiple pages and might apply to a menu widget)

body - used for plain widgets

selector (selector inputfield, used for telling the system where to show the widget)

text_structured - for this i'm using a YAML field, but it could just as easily be a table; would depend on what you want to store; YAML would allow this single field to be used for varying requirements based on the widget type, but would be harder to validate and prone to user error;

icon - a page select for an optional icon which is being used in the template, and would be shown as part of the widget.

post-136-0-09537600-1419542583_thumb.png

Files

for each widget type you want to allow users to select from, you would need to create an include file with the markup for that widget, and then add that widget to the list of available widgets.

here is an example for a site with several widget types:

post-136-0-64137500-1419542689_thumb.png

Selector & Output

wherever you want to include the widgets (footer, sidebar etc.) you would run a $pages->find and then foreach through the widgets (in this case finding all footer widgets).

In this case the (incredibly amazing new) selector field would be specifying what pages to show the widget on.

post-136-0-28663900-1419542582_thumb.png

We assume that most widgets won't have a selector specified, and will default to show the widget.

if a selector is specified, we can check to see if this page fits the selector by using the $page->is($selector) syntax.

<?php
$widgets = $pages->find("template=widget, widget_location=footer, sort=sort");
    
foreach($widgets as $widget) {
    
    // check if the selector field is in use and if so, see if this page is supposed to display it:
    if( $widget->selector) {
        if( !$page->is("$widget->selector") ) continue;
    }

    $widgetType = $widget->widget_type->name;    
    $include = file_exists("./inc/widget-{$widgetType}-foot.inc") ? "./inc/widget-{$widgetType}-foot.inc" : './inc/widget-footer.inc';
    include($include);
}
?>   

this example also has a fallback file in case the widget type is not specified, sort of a default.

the widget's .inc file will be unique to your design and how you have it setup.

  • Like 19
Link to comment
Share on other sites

In this case the (incredibly amazing new) selector field would be specifying what pages to show the widget on.

great writeup macrura! what do you mean by the incredibly amazing new selector field? :)

edit: oh - just saw what you meant on your screenshot and didn't see it before!!!  :huh: i thought you were talking about the page select field... where can i find more info on this?

--------------------------------------

here is my old posting - just for the record. it seems it is quite useless having the new selector field  :rolleyes:

edit: polished version here: https://processwire.com/talk/topic/8635-simple-example-for-widget-management/?p=95532

i also have a widget setup on one of my sites with a little different approach of how to manage visibility of the widgets (3rd column: if this box is checked, the rule applies also to its sub-pages)

post-2137-0-80114300-1419630850_thumb.pn

for every widget you can setup "display rules" including #page #include/exclude and #include_children. it's kind of a bottom-up approach: the example above would show the widget on all subpages of /games AND on page /games (rule #3), but would NOT show an page /games (rule #2 removes access given from #3) and would not show an any other page (home + subpages, rule #1).

a simpler example would be:

rule | page      |         | sub-pages?
------------------------------------------
#1   | home      | exclude | yes
#2   | /section1 | include | yes

so this widget would show on all pages in section1 (eg /section1, /section1/post-1, /section1/post-2 ...)

you could easily exclude the widget on page /section1/post-2 by adding

#3   | /section1/post-2 | exclude | no

...and it would still display an all other pages (posts) in section1, including newly added ones (post-3, post-4...).

if anyone is interested in the code i can share it with you - altough it's quite messy because it's only a prototype for now. i also have to say that i don't like the repeaters because they are wasting lot of space for displaying only 3 small pieces of information. maybe a pagetable field would be better for the next version...

i'm also managing this site on my own, so i have no experience with clients handling this display rules for now!

  • Like 2
Link to comment
Share on other sites

Hi BernhardB - awesome and thanks so much for joining in on this.. i really like your approach to the widgets also; 

edit: oh dear - just saw what you meant on your screenshot and didn't see it before!!!  :huh: i thought you were talking about the page select field... where can i find more info on this?

the InputfieldSelector field isn't that new, i can't remember when it was announced  - i think this past summer; but in terms of the CMS, it is pretty new and it was conceived by  Antti Peisa, coded by Ryan Cramer and sponsored by Avoine; it is in the core, so you just need to install it.

The field is used in the listers; as a standalone field i haven't seen many posts about day-to-day use of it and i kept thinking of when and where it might come in handy.. then while i was setting up this one site I came across this situation where i wanted a certain widget only to show up on the child pages of another page; so first i did a plain field and typed in the selector and sort of moved on... but something kept nagging at me and then it suddenly dawned on me that there is already this field that could make setting up the selector more user friendly and reliable, and it works great; It provides feedback to tell you how many pages match your selector;

for some clients who have a little technical knowledge and skill, this is a really simple and easy way to specify a selector for pages, and then be able to test against that selector in conditionals.. i think in some instances it is probably too technical to be of use;

i wonder if you could use the new selector field in your setup, as you can add additional lines to the selector, which would be like your repeaters... i haven't done extensive testing or experimenting yet with this, but i have 3 more sites to launch and i'm sure this selector field will get more use in situations like this..

  • Like 4
Link to comment
Share on other sites

  • 3 weeks later...

@adrianmak,

the widget include file should contain all of the necessary markup to render the widget;

for example, here is a simple text widget, that is being used in a bootstrap based site:

<div class="col-md-4 bottommargin<?=$wClass?>">
    <?php echo $widget->body?>
    <?php if($widget->page_select) { ?>
    <a href="<?=$widget->page_select->url?>" class="more-link">Read More</a><? } ?>
    <div class="clear"></div>
</div>

here's another widget that is being used in a "call to action"

<?php
$image = $widget->images->first();
$image = $image->width(720);
$description = $image->description;
$markdown->format($description);
?>

<div class="col-md-4 bottommargin <?php echo $widget->widget_type->name?><?=$wClass?>">
    <div class="entry clearfix">
        
        <div class="entry-image">
            <?php if($widget->page_select) { ?><a href="<?=$widget->page_select->url?>"><?php } ?>
            <img src="<?=$image->url?>" class="image_fade" alt="<?=$widget->title?>">
            <?php if($widget->page_select) { ?></a><?php } ?>
            <div class="caption"><?php echo $description?></div>
        </div>
        
        <?php if($widget->page_select) { ?>
        <a href="<?=$widget->page_select->url?>" class="more-link">Read More</a><? } ?>
        
    </div>
</div>
  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Some sites need widgets, as they have been called in some systems; a widget can be almost anything, like:

  • tag cloud
  • mini calendar
  • menu
  • quote rotator
  • free text
  • social sharing
  • search
  • contact info
  • map

This is a simple way to create widgets that can be shown in multiple "areas" of a page, as well as on specific pages.

In this particular method you would need to setup each widget type you want and then determine how best to accept any necessary user input like content, pages select (like for a menu) or settings.

This example uses include files for each widget type, and the name of the include file would match the name of the widget type, which is also a page field.

In this example, I'm also using ListerPro to provide a widget management page.

attachicon.gifwidgets.png

Fields

The main fields used on this widget example are :

title

widget_location (page select - options in this case are footer and sidebar)

widget_type (page select, you would configure your widget types as selectable options)

pages_select (would be used for multiple pages and might apply to a menu widget)

body - used for plain widgets

selector (selector inputfield, used for telling the system where to show the widget)

text_structured - for this i'm using a YAML field, but it could just as easily be a table; would depend on what you want to store; YAML would allow this single field to be used for varying requirements based on the widget type, but would be harder to validate and prone to user error;

icon - a page select for an optional icon which is being used in the template, and would be shown as part of the widget.

attachicon.gifwidget_template.png

Files

for each widget type you want to allow users to select from, you would need to create an include file with the markup for that widget, and then add that widget to the list of available widgets.

here is an example for a site with several widget types:

attachicon.gifwidgets_inc.png

Selector & Output

wherever you want to include the widgets (footer, sidebar etc.) you would run a $pages->find and then foreach through the widgets (in this case finding all footer widgets).

In this case the (incredibly amazing new) selector field would be specifying what pages to show the widget on.

attachicon.gifpopup2.png

We assume that most widgets won't have a selector specified, and will default to show the widget.

if a selector is specified, we can check to see if this page fits the selector by using the $page->is($selector) syntax.

<?php
$widgets = $pages->find("template=widget, widget_location=footer, sort=sort");
    
foreach($widgets as $widget) {
    
    // check if the selector field is in use and if so, see if this page is supposed to display it:
    if( $widget->selector) {
        if( !$page->is("$widget->selector") ) continue;
    }

    $widgetType = $widget->widget_type->name;    
    $include = file_exists("./inc/widget-{$widgetType}-foot.inc") ? "./inc/widget-{$widgetType}-foot.inc" : './inc/widget-footer.inc';
    include($include);
}
?>   

this example also has a fallback file in case the widget type is not specified, sort of a default.

the widget's .inc file will be unique to your design and how you have it setup.

without using the ListerPro paid module, how could i build such a management interface ?

Link to comment
Share on other sites

could be done super easy/fast - you would probably use admin custom pages and then build your table that returns the list of widgets and the various columns;

i think there may be a core inputfield for the table, or you roll your own data tables; admin custom files lets you load any js/css you need;

Link to comment
Share on other sites

  • 3 months later...
  • 2 weeks later...

It took me some time to find how to use Selector Inputfield, so I think this post just might help someone else.

As it should be obvious from the word Inputfield (was not for me :)) it is not a fieldtype. That means you should create a field with fieldtype "Page" and then select "Selector" inputfield on the "Input" tab. But it can't be done without some customization. You need to go to the Inputfield Page module config in Modules and add it to the allowed inputfield list. After that all works as expected.

Thank you, Macrura! Without you topic I wouldn't even know this awesome inputfield existed in the first place.

  • Like 2
Link to comment
Share on other sites

AFAIK this is not true; you can just create a field and use type Selector from the dropdown; there shouldn't be any extra steps needed.

@Ivan Gretsky - maybe you didn't install the module?

  • Like 1
Link to comment
Share on other sites

  • 4 months later...

I am converting a Joomla/Seblod install and in that process developed a widget system quite similar to Marcrura's. In Joomla widgets are called modules and in that particular install those modules were used quite a lot.

To render modules (widgets) in Joomla you do something like

<?php if ($this->countModules('content_bottom')): ?>
<div class="content_bottom">
	<jdoc:include type="modules" name="content_bottom" style="inner" />
</div>
<?php endif; ?>

So I tried to find a way in PW to conditionally output widgets.

In my widget template, I have widget positions and layouts as page fields, just like Marcrura shows. Plus a page field of type PageListSelectMultiple to determine on which pages to show the widget. And, like Berhard, I have a checkbox to determine whether to show the widget also on child pages.

Instead of using includes for each widget position, I use switch case statements in my widget template

// get layout
$layout = $page->layout->name;

// layouts
switch ($layout) {
    case 'carousel':

      // code to render carousel markup
      echo $carousel;

      break;

    case 'teaser':

      // code to render teaser markup

      echo $teaser;

      break;
    // ... etc.

I use the delegate template approach like in the default site template with an _init.php that gets prepended and a _main.php that gets appended and outputs variables that are being populated in the page templates.

Here is the logic to render widgets in the main content area of my _main.php

<main class="col-sm-<?php echo $contentwidth; ?> content equalHeight">
  <?php if($above) { ?> <!-- widget position above -->
  <div class="above">
    <?php echo $above; ?>
  </div>
  <?php } ?>

  <?php echo $content; ?>

  <?php if($below) { ?> <!-- widget position below -->
  <div class="row below">
    <?php echo $below; ?>
  </div>
  <?php } ?>
</main>

$above and $below hold the markup for all widgets in that position on that page and are false if there are none.

And this is the code I use to determine whether there are widgets in that particular position on that particular page.

In my _init.php I first set all position variables ($above, $below etc.) to false. (module translates to widget here)

// Set all module variables to false

foreach ($pages->find('template=modulepositions_items') as $position) {
  ${$position->name} = false;
}

The widget position pages all have the template modulepositions_items. So I iterate through all positions and then use Variable variables to create the position variables ($above, $below etc.) and set them to false.

This step is needed to avoid PHP "Undefined Variable" notices.

In my _main.php I store the widget markup in the position variables before all the other template markup (again module translates to widget)

// get modules
$modules = $pages->find('template=module, sort=sort');

// render modules in their positions
foreach ($modules as $module) {
    // if module is assigned to this page or it's parent, render it on it's position
  if (count($module->onpages) == 0) { // if no specific pages are assigned, render module on all pages
    ${$module->moduleposition->name} .= $module->render();
  } elseif ($module->include_children  == 1 ) {
   if ($module->onpages->has($page) || $module->onpages->has($page->parent('id!=1'))) ${$module->moduleposition->name} .= $module->render();
  } elseif ($module->include_children == 0 && $module->onpages->has($page)) {
   ${$module->moduleposition->name} .= $module->render();
  }
}

Lets brake this down:

"onpages" is the page field in my widget template that determines on which pages to show the widget.

${$module->moduleposition->name} .= $module->render(); renders the markup $module->render() of the widget and adds it to the position variable ${$module->moduleposition->name} which again is a variable variable (just like that expression too much ;) )

The if and elseif statements  check whether the widget should be rendered on that particular page. They feel a bit clumsy and I think I will change my setup to using the selector field instead.

To summarize: with this method you get a quite flexible widget system with the benefit of minimizing the code used to render them in _main.php and the possibility to have conditional markup depending on whether there are widgets for that position or not.

Thank you guys for sharing your approaches which gives me some good ideas for improving on my own.

  • Like 3
Link to comment
Share on other sites

@gebeer - many thanks for your time in sharing your technique - as a former Joomla(er?) also, i appreciate your approach to the modular widgets.. i'll surely study your example when i have another scenario like this...

Link to comment
Share on other sites

  • 3 months later...

Many thanks for sharing this example.

I've implemented this on my website, but i'm kinda stuck now. I've created an if statement to check if there are 0, 1 or 2 sidebars. With this if statement i'm changing the class on my body element so I can correctly style the page to display these sidebars.

This is working till I set the 'selector' for a specific widget. If I set a widget to be displayed e.g. only on the homepage, the widget is displayed correctly. However on all the other pages, the class still prints out as if there is 1 sidebar.

I've added my if statement below:

$sidebar_left = $pages->find("template=widget, widget_location.value=left");
$sidebar_right = $pages->find("template=widget, widget_location.value=right");

if(count($sidebar_right) > 0 && count($sidebar_left) > 0) {
    $body_class = "two-columns";
} elseif(count($sidebar_right) > 0) {
    $body_class = "sidebar-right";
} elseif(count($sidebar_left) > 0) {
    $body_class = "sidebar-left";
} else {
    $body_class = "one-column";
}

I've tried different versions, but those don't seem to work:

find("template=widget, widget_location.value=left, selector!=")
find("template=widget, widget_location.value=left, selector!=0")
find("template=widget, widget_location.value=left, selector<0")

Is there a way to check inside the find() method if the selector field is set or is there perhaps a better way to set the body class?

Link to comment
Share on other sites

you may need to make a pagearray, it's hard to tell because i can't see where/how you're excluding widgets by selector.

my example runs a 'negative' $page->is($selector) over the found items - i would assume you actually want to find all items that don't have the selector set:

Link to comment
Share on other sites

@Macrura, thanks for your reply.

I've tried multiple setups today, but unfortunately I can't seem to get it working. Ideally the body_class and the if statement for sidebars are adjusted based on the selector field.

I currently have the following code inside my _main.php file.

If statement at the top of the page to check if there is widget for sidebar left or sidebar right:

$sidebar_left = $pages->find("template=widget, widget_location.value=left");
$sidebar_right = $pages->find("template=widget, widget_location.value=right");

if(count($sidebar_right) > 0 && count($sidebar_left) > 0) {
    $body_class = "two-columns";
} elseif(count($sidebar_right) > 0) {
    $body_class = "sidebar-right";
} elseif(count($sidebar_left) > 0) {
    $body_class = "sidebar-left";
} else {
    $body_class = "one-column";
}

Setting the body class

<body class="<?php echo $body_class; ?>">

Sidebar Left

<?php if(count($sidebar_left)): ?>
    <aside class="sidebar-left">
        <?php

            foreach($sidebar_left as $widget) {

                // check if the selector field is in use and if so, see if this page is supposed to display it:
                if( $widget->selector) {
                    if( !$page->is("$widget->selector") ) continue;
                }

                $widgetType = $widget->widget_type->value;
                $widgetTemplate = file_exists("./partials/widget_{$widgetType}.php") ? "./partials/widget_{$widgetType}.php" : "./partials/widget_next_race.php";
                include($widgetTemplate);
                echo $page->is("$widget->selector");
                echo $widget->selector;
            }

          ?>
    </aside>
<?php endif; ?>

And Sidebar Right

<?php if(count($sidebar_right)): ?>
    <aside class="sidebar-right">
        <?php

            foreach($sidebar_right as $widget) {

                // check if the selector field is in use and if so, see if this page is supposed to display it:
                if( $widget->selector) {
                    if( !$page->is("$widget->selector") ) continue;
                }

                $widgetType = $widget->widget_type->value;
                $widgetTemplate = file_exists("./partials/widget_{$widgetType}.php") ? "./partials/widget_{$widgetType}.php" : "./partials/widget_next_race.php";
                include($widgetTemplate);
                echo $page->is("$widget->selector");
                echo $widget->selector;
            }

          ?>
    </aside>
<?php endif; ?>
Link to comment
Share on other sites

you'd probably need to redo this logic;

1.) maybe consider an array for your body classes which you an unset keys for based on some conditions

2.) you should not be rendering the sidebar at all if there are no widgets, which means that you need to check your widget counts for each area before _main;

that's why you probably need to do a pagearray or something; it's too late to check the selector once you are already looping, unless you can at that point you can unset/set the necessary array key for the body class;

this can be resolved but the logic needs to be adjusted because as of now you are assuming there is always something in the sidebars;

when i did my example, it was on a template that always had a sidebar no matter what, so i was able to safely foreach those addon widgets; but again, in your case you need to collate your widgets for each respective part of the page before you output any markup so that your body classes can be correct

Link to comment
Share on other sites

Thanks for the additional information, that helped me a lot in finding a solution. I will post the code below that i'm using for anyone that might want to achieve the same behavior:

If statement at top of _main.php

$sidebar_left = $pages->find("template=widget, widget_location.value=left");
$sidebar_left_count = 0;
foreach($sidebar_left as $widgets) {
  if($page->is("$widgets->selector")) {
    $sidebar_left_count++;
  }
}

$sidebar_right = $pages->find("template=widget, widget_location.value=right");
$sidebar_right_count = 0;
foreach($sidebar_right as $widgets) {
  if($page->is("$widgets->selector")) {
    $sidebar_right_count++;
  }
}

if($sidebar_right_count > 0 && $sidebar_left_count > 0) {
    $body_class = "two-columns";
} elseif($sidebar_right_count > 0) {
    $body_class = "sidebar-right";
} elseif($sidebar_left_count > 0) {
    $body_class = "sidebar-left";
} else {
    $body_class = "one-column";
}

Set the class on the body element

<body class="<?php echo $body_class; ?>">

Sidebar Left

<?php if($sidebar_left_count > 0): ?>
    <aside class="sidebar-left">
        <?php

            foreach($sidebar_left as $widget) {

                // check if the selector field is in use and if so, see if this page is supposed to display it:
                if( $widget->selector) {
                    if( !$page->is("$widget->selector") ) continue;
                }

                $widgetType = $widget->widget_type->value;
                $widgetTemplate = file_exists("./partials/widget_{$widgetType}.php") ? "./partials/widget_{$widgetType}.php" : "./partials/widget_next_race.php";
                include($widgetTemplate);
            }

          ?>
    </aside>
<?php endif; ?>

Sidebar Right

<?php if($sidebar_right_count > 0): ?>
    <aside class="sidebar-right">
        <?php

            foreach($sidebar_right as $widget) {

                // check if the selector field is in use and if so, see if this page is supposed to display it:
                if( $widget->selector) {
                    if( !$page->is("$widget->selector") ) continue;
                }

                $widgetType = $widget->widget_type->value;
                $widgetTemplate = file_exists("./partials/widget_{$widgetType}.php") ? "./partials/widget_{$widgetType}.php" : "./partials/widget_next_race.php";
                include($widgetTemplate);
            }

          ?>
    </aside>
<?php endif; ?>
  • Like 1
Link to comment
Share on other sites

  • 1 year later...

just wanted to share a screencast of one old website with a kind of widget-visibility-ui that could serve as inspiration.

it's not good enough to share as a module, maybe someone wants to take it further -> pm :)

widget.thumb.gif.ecc2d942a361fe9884823d75bf888346.gif

  • Like 3
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
×
×
  • Create New...