bernhard

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

Recommended Posts

Edit: Because of the great response to this topic I wrote a guest blogpost: https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/

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 32
  • Thanks 15

Share this post


Link to post
Share on other sites

The timing of this couldn't have been better. Big thanks :)

  • Like 1

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 3

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 5
  • Thanks 1

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
  • Thanks 1

Share this post


Link to post
Share on other sites

bernhard, I appreciate the effort and enthusiasm that has gone into this tutorial, but for me it goes too deep into the weeds too quick after the basic 'hello world' example.

If I want to add a custom admin page to post/edit basic content - title, body, image - how would I do that? Would I add the code for that form to the ProcessSimple.module file or somewhere else?

The stuff about panels, tables and charts is very confusing to me.

If I want to create a custom admin with several pages/functions, would I need several *.module files for each or would there be a *.module folder/directory where I can add different files/processes?

Can I start my custom admin area simple and build it out later, add stuff to it? Or should I build it all at once as one module?

I am trying to figure this out on a "concierge job", so my brain/concentration operates at below 50%.

Share this post


Link to post
Share on other sites

hi modifiedcontent,

4 minutes ago, modifiedcontent said:

If I want to add a custom admin page to post/edit basic content - title, body, image - how would I do that? Would I add the code for that form to the ProcessSimple.module file or somewhere else?

this sounds a little strange. the easiest way of editing content is of course the normal pw edit screen. you can do a lot with normal pw features (like hiding fields on several conditions etc). if you really need more customized forms I show how you can do that here: https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/#create-our-first-real-inputfields ; that's only one way of doing it. you can also easily modify the page edit forms via hooks.

 

6 minutes ago, modifiedcontent said:

The stuff about panels, tables and charts is very confusing to me.

sorry for that. you can also start by using InputfieldRuntimeMarkup - maybe that's easier to begin with...

8 minutes ago, modifiedcontent said:

If I want to create a custom admin with several pages/functions, would I need several *.module files for each or would there be a *.module folder/directory where I can add different files/processes?

totally up to you. you can create several pages in one module: https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/#hello-page2 ; but you could also create one module for each page. It depends on you and your usecase which level of separation makes sense.

9 minutes ago, modifiedcontent said:

Can I start my custom admin area simple and build it out later, add stuff to it? Or should I build it all at once as one module?

sure, thats what i do throughout the tutorial. start with hello world, end with a custom CRM;)

  • Like 1

Share this post


Link to post
Share on other sites
Quote

this sounds a little strange. the easiest way of editing content is of course the normal pw edit screen

Yes, of course it is. Unfortunately I actually have a client who thinks Processwire's admin area is confusing and wants an admin area that looks exactly like the horrible system he is trying to replace.

I have to restructure/reorder all the standard admin functionality/edit screens into something he recognizes to gradually lure him over to the PW dark side.

But apart from that, I am looking for the most basic proof of concept. Basic stuff that the default admin already does, stuff that everybody understands; how would you replicate that in a custom admin area?

I am looking for a guide for the basic principles.

Quote

Most modules seem to come in a folder/dir with other file types like js and css, not a single text file. How that works is basic module writing of course, but I was hoping to see some pointers in your tutorial how to structure that for a custom admin area. Your hello-page2 example still goes in the same single text file?

Edit:

Quote

... so as long as you don't come up with preciser descriptions of what EXACTLY you are trying to do ...

Let's say you want to create a wizard style interface for adding/editing content, that starts with a title + body + images form, followed by steps or dropdowns to put the content in the right page. Instead of the page tree. More content-centric instead of structure-centric. I prefer the Processwire approach, but my client is used to that content-centric approach.

But it doesn't matter what exactly I am trying to achieve in this case. I am looking for a general basic recipe how to create an alternative custom admin area or how to completely restructure the admin. Bernhard's tutorial is more about adding advanced features to the existing admin.

 

Share this post


Link to post
Share on other sites

I'm sorry @modifiedcontent but I put a LOT of effort in the blogpost, so as long as you don't come up with preciser descriptions of what EXACTLY you are trying to do I can not help. Maybe someone else understands your needs better and can therefore help you better.

maybe a google search for "dashboard" has some interesting reads for you: https://www.google.at/search?q=site:processwire.com+dashboard

Share this post


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

I actually have a client who thinks Processwire's admin area is confusing

That's a first ;)

6 hours ago, modifiedcontent said:

wants an admin area that looks exactly like the horrible system he is trying to replace

What does the system looks like? If you could post some screenshots I'm sure we could help you out. But you should start with some basic stuff like bernhard already described in his tutorial.

  • Like 2

Share this post


Link to post
Share on other sites
Quote

What does the system looks like? ...

Seriously, you don't want to know. Awful mess. I disagree with my client, but have to try to meet him halfway.

See edit in my comment above for more details. Again, I am not asking for a solution for my particular issue. This discussion is probably closer to what I am looking for. 

Share this post


Link to post
Share on other sites

@bernhard going through your epic tutorial again and again, I discover stuff I overlooked the first time.

e.g. I didn't know it was so easy to add an additional page to a module such as you describe @ https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/#hello-page2

Quick question: Do you know if there is a native PW method I can use here to define my own h1 title that looks a bit more human-readable? i.e. being able to display "My Second Page" instead of "Mysecondpage". I tried camel case and underscores for the function-name - didn't work.

 

  • Like 1

Share this post


Link to post
Share on other sites
Quote

Method naming tip

If you want to use hyhens in your URL segments, simply use camel case for your method name. Any uppercase characters (after the first) translate to hyphens in the URL. The following examples help to clarify:

  • executeMySecondPage translates to my-second-page in the URL
  • executeMysecondpage translates to mysecondpage in the URL

Credits to Ryan - I didn't know about that detail and he added it to the blogpost while proofreading it :)

Sorry I think I misunderstood you :) try

$headline = 'Happy ' . date('Y');
$this->headline($headline);
$this->wire('processBrowserTitle', $headline);

 

  • Like 2

Share this post


Link to post
Share on other sites

Thanks.

However, that sets the html title, not the h1 that users see when visiting the page. Strangely, with code like this there is no h1 output at all:

	public function ___executeMySecondPage() {
		$headline = 'Happy ' . date('Y');
		$this->headline($headline);
		$this->wire('processBrowserTitle', $headline);
		return '<p> Hello Page2 :) </p> <p> <a href="./" class="ui-button ui-state-default"> Go to Page 1 </a> </p>';
	}

Did Ryan tell you where all these "secret" methods are hidden? :-)

 

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.