bernhard

How to create custom admin pages (aka ProcessModules) - yes, it's that simple!

Recommended Posts

One of the hidden treasures of processwire seems to be the creation of custom admin pages. Technically speaking those pages are ProcessModules - but i guess that's the reason why so many people out there seem to be afraid of building them... it sounds so hard!

You've never created a module for ProcessWire? You have never created a plugin for any other CMS? You have no clue about OOP with all its classes, methods and properties?

No problem! I'll show you how simple you can start:

<?php
class CustomAdminPage extends Process {
  
  public static function getModuleinfo() {
    return [
      'title' => 'Custom Admin Page Example',
      'summary' => 'Minimalistic ProcessModule to show that nobody has to be afraid of building custom admin pages.',
      'href' => 'https://processwire.com/talk/topic/17709-how-to-create-custom-admin-pages-aka-processmodules-yes-its-that-simple/',
      'author' => 'Bernhard Baumrock, baumrock.com',
      'version' => 1,
      
      // page that you want created to execute this module
      'page' => [
        'name' => 'customadmin', // your page will be online at /youradmin/setup/customadmin/
        'parent' => 'setup', 
        'title' => 'Custom Admin Page Example'
      ],
    ];
  }
  
  public function ___execute() {
    return 'This is the most simple Admin-Page you have ever seen :)';
  }
}

Now save this file as CustomAdminPage.module and place it in your /site/modules folder.

After a refresh it will show your module in the modules manager of your site where you can install it:

5a04768ba36bf_2017-11-0916_31_18-ModulesProcessWiresandbox_dev.thumb.png.5bf45b22588b4a4c9184ecffe1472e1c.png

After installation you already have your first very own admin page! Congratulations! Was not too hard, was it? ;)

5a04774472695_2017-11-0916_40_33-CustomAdminPageExampleProcessWiresandbox_dev.png.b0919eabbad051b6dd251b10b606260f.png

 

It's as simple as that! Now lets add some more custom HTML. And to show you another nice feature we will add this code to a separate method called executeDemo(). And because everything is so simple we will also add some javascript to this page ;)

  public function ___executeDemo() {
    $out = '';
    $out .= '<h1>H1 has some special css styling in the admin, thats why it seems to have no effect</h1>';
    $out .= '<h2>H2 looks different ;)</h2>';
    $out .= '<h3>...and so does H3</h3>';

    $out .= '<button onclick="myFunction()">Click me</button>';
    $out .= '<script>function myFunction() { alert("this is a demo javascript"); }</script>';

    return $out;
    return '';
  }

Now thanks to ProcessWire-magic your page will already have its own URL: Just append /demo to your url and see what you get:

5a0477e5c144b_2017-11-0916_43_52-CustomAdminPageExampleProcessWiresandbox_dev.png.471a7f49e4aa22c42247f4eb60729aa3.png

And of course don't forget to click the button ;)

Ok, now that code looks a bit hacky, right? Inputfields and especially InputfieldMarkup for the win!

We add another method with some advanced code. To use inputfields we need a form that holds all those inputfields and that makes it possible to handle user input lateron. See somas great tutorial about forms here for a quickstart and more details: 

  public function ___executeAdvanced() {
    $out = '<h2>A more complex Example</h2>';

    $form = wire()->modules->get('InputfieldForm');

    $field = wire()->modules->get('InputfieldMarkup');
    $field->label = 'Markup Test 1';
    $field->value = '<h1>h1</h1><h2>h2</h2><h3>h3</h3><h4>h4</h4>';
    $form->add($field);

    $out .= $form->render();

    return $out;
  }

5a047d6c2c725_2017-11-0917_07_31-CustomAdminPageExampleProcessWiresandbox_dev.png.e8995e057e695c341c59abcb55f69548.png

Ok, it get's boring :) Let's do something more fun and add a chart in a second field and change the fields to 50% screen width (I'm sure you know that already from the GUI template editor)!

  public function ___executeAdvanced() {
    $out = '<h2>A more complex Example</h2>';

    $form = wire()->modules->get('InputfieldForm');

    $field = wire()->modules->get('InputfieldMarkup');
    $field->label = 'Markup Test 1';
    $field->value = '<h1>h1</h1><h2>h2</h2><h3>h3</h3><h4>h4</h4>';
    $field->columnWidth = 50;
    $form->add($field);
    
    $field = wire()->modules->get('InputfieldMarkup');
    $field->label = 'Chart Sample';
    $field->value = '$chart';
    //$field->notes = 'Example code taken from here: http://www.chartjs.org/docs/latest/getting-started/usage.html';
    $field->columnWidth = 50;
    $form->add($field);

    $out .= $form->render();

    return $out;
  }

5a047dd14468e_2017-11-0917_04_25-CustomAdminPageExampleProcessWiresandbox_dev.png.fc326c15230583af4c142cb5232a35a4.png

OK, we are almost there... we only need to add the chart library! To keep everything clean we will put the code for the chart in another method. We will make that method PRIVATE to add some security.

Our new Method:


  private function renderChart() {
    // prepare chart code
    wire()->config->scripts->add('https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.3/Chart.min.js');
    ob_start(); ?>
    <canvas id="myChart"></canvas>
    <script>
    var ctx = document.getElementById("myChart");
    var myChart = new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
        datasets: [{
          label: '# of Votes',
          data: [12, 19, 3, 5, 2, 3],
          backgroundColor: [
            'rgba(255, 99, 132, 0.2)',
            'rgba(54, 162, 235, 0.2)',
            'rgba(255, 206, 86, 0.2)',
            'rgba(75, 192, 192, 0.2)',
            'rgba(153, 102, 255, 0.2)',
            'rgba(255, 159, 64, 0.2)'
          ],
          borderColor: [
            'rgba(255,99,132,1)',
            'rgba(54, 162, 235, 1)',
            'rgba(255, 206, 86, 1)',
            'rgba(75, 192, 192, 1)',
            'rgba(153, 102, 255, 1)',
            'rgba(255, 159, 64, 1)'
          ],
          borderWidth: 1
        }]
      },
      options: {
        scales: {
          yAxes: [{
            ticks: {
              beginAtZero:true
            }
          }]
        }
      }
    });
    </script>
    <?php
    return ob_get_clean();
  }

Now we just need to call $this->renderChart() in the right place! Here is the complete Module:

<?php
class CustomAdminPage extends Process {
  
  public static function getModuleinfo() {
    return [
      'title' => 'Custom Admin Page Example',
      'summary' => 'Minimalistic ProcessModule to show that nobody has to be afraid of building custom admin pages.',
      'href' => 'https://processwire.com/talk/topic/17709-how-to-create-custom-admin-pages-aka-processmodules-yes-its-that-simple/',
      'author' => 'Bernhard Baumrock, baumrock.com',
      'version' => 1,
      
      // page that you want created to execute this module
      'page' => [
        'name' => 'customadmin', // your page will be online at /youradmin/setup/customadmin/
        'parent' => 'setup', 
        'title' => 'Custom Admin Page Example'
      ],
    ];
  }
  
  public function ___execute() {
    return 'This is the most simple Admin-Page you have ever seen :)';
  }

  public function ___executeDemo() {
    $out = '';
    $out .= '<h1>H1 has some special css styling in the admin, thats why it seems to have no effect</h1>';
    $out .= '<h2>H2 looks different ;)</h2>';
    $out .= '<h3>...and so does H3</h3>';

    $out .= '<button onclick="myFunction()">Click me</button>';
    $out .= '<script>function myFunction() { alert("this is a demo javascript"); }</script>';

    return $out;
    return '';
  }

  public function ___executeAdvanced() {
    $out = '<h2>A more complex Example</h2>';

    $form = wire()->modules->get('InputfieldForm');

    $field = wire()->modules->get('InputfieldMarkup');
    $field->label = 'Markup Test 1';
    $field->value = '<h1>h1</h1><h2>h2</h2><h3>h3</h3><h4>h4</h4>';
    $field->columnWidth = 50;
    $form->add($field);
    
    $field = wire()->modules->get('InputfieldMarkup');
    $field->label = 'Chart Sample';
    $field->value = $this->renderChart();
    $field->notes = 'Example code taken from here: http://www.chartjs.org/docs/latest/getting-started/usage.html';
    $field->columnWidth = 50;
    $form->add($field);

    $out .= $form->render();

    return $out;
  }

  private function renderChart() {
    // prepare chart code
    wire()->config->scripts->add('https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.3/Chart.min.js');
    ob_start(); ?>
    <canvas id="myChart"></canvas>
    <script>
    var ctx = document.getElementById("myChart");
    var myChart = new Chart(ctx, {
      type: 'bar',
      data: {
        labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
        datasets: [{
          label: '# of Votes',
          data: [12, 19, 3, 5, 2, 3],
          backgroundColor: [
            'rgba(255, 99, 132, 0.2)',
            'rgba(54, 162, 235, 0.2)',
            'rgba(255, 206, 86, 0.2)',
            'rgba(75, 192, 192, 0.2)',
            'rgba(153, 102, 255, 0.2)',
            'rgba(255, 159, 64, 0.2)'
          ],
          borderColor: [
            'rgba(255,99,132,1)',
            'rgba(54, 162, 235, 1)',
            'rgba(255, 206, 86, 1)',
            'rgba(75, 192, 192, 1)',
            'rgba(153, 102, 255, 1)',
            'rgba(255, 159, 64, 1)'
          ],
          borderWidth: 1
        }]
      },
      options: {
        scales: {
          yAxes: [{
            ticks: {
              beginAtZero:true
            }
          }]
        }
      }
    });
    </script>
    <?php
    return ob_get_clean();
  }
}

5a047f3fa68b3_2017-11-0917_15_37-CustomAdminPageExampleProcessWiresandbox_dev.png.822cb4c00ff415905a2e5b052bb1f070.png

 

I hope you enjoyed reading this and it will open up many new possibilities for you!

  • Like 27
  • Thanks 13

Share this post


Link to post
Share on other sites

Thank you very much...very easy entry on this topic....the last years i used AdminCustomPages but this is a little bit outdated since 3.x so this will be the only way to go.

Best regards from the neighborhood ;)

  • Like 1

Share this post


Link to post
Share on other sites

Thanks for this great tutorial!

I was playing around lately with modules myself. It's really remarkable how easy it can be.

Quick question: Is this the only (or just the "recommended") way to add content in a module page?

    $form = wire()->modules->get('InputfieldForm');
    $field = wire()->modules->get('InputfieldMarkup');

What I'd like, is include() a PHP file that resides either in my modules or my site/templates folder. Or - well - any kind of PHP code right in the .module.

InputFieldMarkup will not allow PHP. Is is possible to include just about anything I'd like? Or is this some kind of security limitation?

Background: I created a simple custom help page that resides under /admin/setup, and also installs a custom role. If I wanted to extend that to not only use HTML/CSS/JS, but also PHP, how would I do that? (using all of PW's methods... $pages / wire() etc.). I'd like to create some kind of dashboard (just as a proof of concept).

  • Like 1

Share this post


Link to post
Share on other sites

hi dragan,

you don't have any limitation when working with InputfieldMarkup. It just RETURNS markup, but of course you can create this markup via php or whatever you want.

For example:

$html = '<p>This is a test</p>';
for($i=0; $i<10; $i++) $html .= "<div>Line $i</div>";

$field = modules('InputfieldMarkup'); // using the functionsAPI it gets that short
$field->value = $html;
$form->add($field);

but also in my example you could place any PHP code inside the renderChart method.

  • Like 2

Share this post


Link to post
Share on other sites

Thanks for this great tutorial! I am impressed by your PW admin UI manipulation skill all time. This tutorial could definitely get someone's hands dirty.

 

On 2017/11/9 at 11:30 PM, bernhard said:

i guess that's the reason why so many people out there seem to be afraid of building them... it sounds so hard!

Yes it is really quite hard imo because the related information is not easy to find. They do exist but always sit inside various post replies. It just looks like a missing piece to beginners.

I long for a more in-depth version of this tutorial, or a complete guide precisely, that shows how to accurately use wiretab, pw-modal, panel, buttons etc. and layout all of them properly. I discovered a bit from reading through the core files and modules by trial and error but I believe I still have a long long way to go. I believe many people, like me a while ago, stuck in the layout phase and then give up their modules development.

  • Like 2

Share this post


Link to post
Share on other sites
6 hours ago, Karl_T said:

Thanks for this great tutorial! I am impressed by your PW admin UI manipulation skill all time. This tutorial could definitely get someone's hands dirty.

glad you like it and thanks for the compliment :)

6 hours ago, Karl_T said:

Yes it is really quite hard imo because the related information is not easy to find. They do exist but always sit inside various post replies. It just looks like a missing piece to beginners.

yeah... it's not as easy to find in the beginning (and often i'm still searching a lot around the code). but most of the necessary informations are not too hard to find if you look at the code. Inputfields for example have a baseclass here: https://github.com/processwire/processwire/blob/master/wire/core/Inputfield.php Also see somas tutorial about forms (i updated my initial post with the link: https://processwire.com/talk/topic/2089-create-simple-forms-using-api/ )

6 hours ago, Karl_T said:

I long for a more in-depth version of this tutorial, or a complete guide precisely, that shows how to accurately use wiretab, pw-modal, panel, buttons etc. and layout all of them properly. I discovered a bit from reading through the core files and modules by trial and error but I believe I still have a long long way to go. I believe many people, like me a while ago, stuck in the layout phase and then give up their modules development.

I'll see what i can do...

  • Like 3

Share this post


Link to post
Share on other sites

I'm writing on a processwire.com guest blogpost arriving on friday. any ideas what i should cover?

Building a basic custom admin page
 Hello World
 Explanations
 The manual way
Building a module with multiple pages and buttons
 Hello Page2!
 Add some HTML (eg buttons)
 Using internal components (modules)
 Add external styles and scripts (eg charts)
Handling user-input (forms & inputfields)
 adding your first field: inputfieldmarkup
 creating a custom form to add pages
 benefits: structure, render only one field in panel, ajax load
Organizing your code and your files
 module info
 views

any ideas?

  • Like 9
  • Thanks 1

Share this post


Link to post
Share on other sites

Hi @bernhard,

I like the first two options; Building a custom admin page (your post here, and @abdus tutorial), and Building a module with multiple pages and buttons. They sort of go together from an application point of view. You could incorporate real world examples, such as a help desk ticket system, or polling multiple email providers.

I'm thinking more along the lines of how a user would go about creating the components, rather than the actual application itself. For example, let's use a help desk project.

Some components would be:

  • a toolbar with options to filter the content listed in an admindatatable by department, assigned person, severity, etc. Currently ProcessWire likes a vertical orientation rather than using the horizontal screen real estate.
  • selecting from the resulting list an item to view greater detail - multiple pages. Abdus touched on this in his tutorial.
  • selecting one or more checkboxes to perform some group action, such as changing the status, or even deleting those entries.
  • periodically polling an endpoint to update the entries in the admindatatable (ajax retrieving new tickets, change of status, etc.)

I don't want to make this overly complicated for your article, nor take too much of your time. I just notice that more people are posting about individual parts of similar scenarios and think it would be a great reference for everyone.

I certainly appreciate the time you, and others here, are giving to the community. I look forward to whatever article you write on Friday!

  • Like 2

Share this post


Link to post
Share on other sites
4 minutes ago, rick said:

periodically polling an endpoint to update the entries in the admindatatable (ajax retrieving new tickets, change of status, etc.)

This is an interesting point that could fit very well, thank you. I'll try to include that in my post :)

btw, the first two points are already finished. 

  • Like 3

Share this post


Link to post
Share on other sites

For what is worth... This may be to advanced, but I would like to see a tutorial on how to create an administrative page(Import Employees) to import a public Google Spreadsheet with the following inputs:

Google Spreadsheet Url (text input)

Import Strategy: choose what happens when the import is ran.

  • Create new pages (checkbox)
  • Create new pages and Update existing pages (checkbox)
  • Disable/Unpublish missing pages (checkbox)
  • Delete missing pages (checkbox)

This page would be used to create pages of the type (Employee).

The Google Spreadsheet would have the following fields:

  • Employee Id Number
  • Employee First Name
  • Employee Last Name
  • Employee Email
  • Employee Phone

When the form is submitted, it will display the # of pages created, disabled/unpublished, or trashed/deleted.

Bonus: Would even be cool to see it ran in batches with a progress bar to prevent PHP timeouts.  Maybe an integration with the new tasker module?

No worries if this isn't a good candidate for a tutorial.  I also like your suggestions.

 

  • Like 1

Share this post


Link to post
Share on other sites

Under the Code/File organization, it would be helpful to explain ProcessWire best practices for using:

  • A single stand-alone process module.
  • More than one process module (required = array(other modules)).
  • Including a stand-alone class or interface in process modules.
  • ...

Just thinking out loud. :)

  • Like 2

Share this post


Link to post
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


  • Recently Browsing   0 members

    No registered users viewing this page.