bernhard

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

Recommended Posts

Posted (edited)

OK, I think I'm really confused right now.

For some reason I was working with the default  classic or Reno admin theme. h1 isn't used (or shown) there. With the latest UIKit admin theme, everything's OK.

Edited by dragan
clarify "classic" vs. "default" theme...
  • Like 1

Share this post


Link to post
Share on other sites

Bernhard i wrote it and i repeat it you are really my hero - the admin/backend was always a ? to me since i was flashed about all the options on the frontendside with the API, creating some little modules or changing some or using small hooks to get some results are the level i was before i read your tutorials and examples...:)

Wanna share a really cool example im playing with this responsive calendar plugin:

https://tympanus.net/Development/Calendario/

in an easy process module i get some results like this (with some CSS tweaks):

Spoiler

calendario.thumb.gif.d14d555ae3735417835bafb045664d56.gif

This is with the example data from but it works like a charm and is seemless responsive within the PW admin.

Code i used on the PW side is really easy:

Spoiler

 


  public function ___execute() {
    $out = '';
    //load css and js files
    $this->config->styles->add($this->config->urls->siteModules . $this->className() . '/calendario/calendar.css');   
    $this->config->styles->add($this->config->urls->siteModules . $this->className() . '/calendario/custom_1.css');      
    $this->config->scripts->add($this->config->urls->siteModules . $this->className() . '/calendario/jquery.calendario.js');
    $this->config->scripts->add($this->config->urls->siteModules . $this->className() . '/calendario/data.js');   
    $this->config->scripts->add($this->config->urls->siteModules . $this->className() . '/calendario/script.js'); 
    //container for the calendar
    $out  = '<div class="container uk-card-default"><div class="custom-calendar-wrap custom-calendar-full">';
    //render navigation buttons
    $out .= '<div class="custom-header clearfix">
                    <h2>
                        <span id="custom-month" class="custom-month"></span>
						<span id="custom-year" class="custom-year"></span>
                    </h2>
                    <nav>
                        <span id="custom-prev" class="custom-prev"></span>
                        <span id="custom-next" class="custom-next"></span>
                        <span id="custom-current" class="custom-current" title="Got to current date"></span>
                    </nav>
				</div>';
    //render calender
    $out .= '<div id="calendar" class="fc-calendar-container"></div>';
    //end container
    $out .= '</div></div>';
    $out .= '<hr class="uk-divider-icon">';
    return $out;
  }

now i'm getting hands dirty on trigger some booking data and some actionbuttons...

Thank you for sharing your knowlegde!

I wish you a healthy and successfull year - best regards mr-fan

  • Like 6
  • Thanks 1

Share this post


Link to post
Share on other sites
On 3.1.2018 at 11:59 PM, mr-fan said:

in an easy process module i get some results like this (with some CSS tweaks):

Hi @mr-fan, thanks for sharing this calendario demo. Would you mind to post the css tweaks you show in the example?

  • Like 2

Share this post


Link to post
Share on other sites

nothing fancy i just drop the extrem use of shadows and converted colors....so it fits a little bit normal in the PW:

calendar.css

Spoiler

.fc-calendar-container {
	position: relative;
	height: 400px;
	width: 400px;
}

.fc-calendar {
	width: 100%;
	height: 100%;
}

.fc-calendar .fc-head {
	height: 30px;
	line-height: 30px;
	background: #ccc;
	color: #fff;
}

.fc-calendar .fc-body {
	position: relative;
	width: 100%;
	height: 100%;
	height: -moz-calc(100% - 30px);
	height: -webkit-calc(100% - 30px);
	height: calc(100% - 30px);
	border: 1px solid #ddd;
}

.fc-calendar .fc-row {
	width: 100%;
	border-bottom: 1px solid #ddd;
}

.fc-four-rows .fc-row  {
	height: 25%;
}

.fc-five-rows .fc-row  {
	height: 20%;
}

.fc-six-rows .fc-row {
	height: 16.66%;
	height: -moz-calc(100%/6);
	height: -webkit-calc(100%/6);
	height: calc(100%/6);
}

.fc-calendar .fc-row > div,
.fc-calendar .fc-head > div {
	float: left;
	height: 100%;
	width:  14.28%; /* 100% / 7 */
	width: -moz-calc(100%/7);
	width: -webkit-calc(100%/7);
	width: calc(100%/7);
	position: relative;
}

/* IE 9 is rounding up the calc it seems */
.ie9 .fc-calendar .fc-row > div,
.ie9 .fc-calendar .fc-head > div {
	width:  14.2%;
}

.fc-calendar .fc-row > div {
	border-right: 1px solid #ddd;
	padding: 4px;
	overflow: hidden;
	position: relative;
}

.fc-calendar .fc-head > div {
	text-align: center;
}

.fc-calendar .fc-row > div > span.fc-date {
	position: absolute;
	width: 30px;
	height: 20px;
	font-size: 20px;
	line-height: 20px;
	font-weight: 700;
	color: #ddd;
	text-shadow: 0 -1px 0 rgba(255,255,255,0.8);
	bottom: 5px;
	right: 5px;
	text-align: right;
}

.fc-calendar .fc-row > div > span.fc-weekday {
	padding-left: 5px;
	display: none;
}

.fc-calendar .fc-row > div.fc-today {
	background: #fff4c3;
}

.fc-calendar .fc-row > div.fc-out {
	opacity: 0.6;
}

.fc-calendar .fc-row > div:last-child,
.fc-calendar .fc-head > div:last-child {
	border-right: none;
}

.fc-calendar .fc-row:last-child {
	border-bottom: none;
}

 

custom_1.css

Spoiler

/*dirty hack since i don't figure out why this responsive calendar overwrap the footer...#todo
#pw-footer {display:none!important;}

.container {
	width: 100%;
	height: 600px;
	position: relative;
}

.container > header,
.main {
	padding: 0 30px 50px 30px;
	width: 100%;
	max-width: 600px;
	margin: 0 auto;
}

.container > header {
	padding: 30px;
}

.container > header h1 {
	font-size: 34px;
	line-height: 38px;
	margin: 0;
	font-weight: 700;
	color: #fff;
	float: left;
/*	text-shadow: 0 1px 1px rgba(0,0,0,0.3);*/
}

.container > header h1 span {
	font-size: 18px;
	font-weight: 300;
	display: block;
}


/* Demo Buttons Style */
.codrops-demos {
	float: right;
}

.codrops-demos a {
    display: inline-block;
    margin: 10px;
    color: #fff;
    font-weight: 700;
    line-height: 30px;
    border-bottom: 4px solid transparent;
}

.codrops-demos a:hover {
	color: #000;
	border-color: #000;
}

.codrops-demos a.current-demo,
.codrops-demos a.current-demo:hover {
	color: rgba(0,0,0,0.5);
	border-color: rgba(0,0,0,0.5);
}

.custom-calendar-full {
	position: absolute;
	top: 24px;
	bottom: 0px;
	left: 0px;
	width: 100%;
	height: auto;
}

.fc-calendar-container {
	height: auto;
	bottom: 0px;
	width: 100%;
	top: 50px;
	position: absolute;
}

.custom-header {
	padding: 20px 20px 10px 30px;
	height: 50px;
	position: relative;
}

.custom-header h2,
.custom-header h3 {
	float: left;
	font-weight: 300;
	text-transform: uppercase;
	letter-spacing: 4px;
/*	text-shadow: 1px 1px 0 rgba(0,0,0,0.1);*/
}

.custom-header h2 {
	color: rgba(0,0,0,0.9);
	width: 60%;
}


.custom-header nav {
	position: absolute;
	right: 20px;
	top: 20px;
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.custom-header nav span {
	float: left;
	width: 30px;
	height: 30px;
	position: relative;
	color: transparent;
	cursor: pointer;
	background: rgba(0,0,0,0.3);
	margin: 0 1px;
	font-size: 20px;
	/*border-radius: 0 3px 3px 0;*/
/*	box-shadow: inset 0 1px rgba(0,0,0,0.2);*/
}

/*.custom-header nav span:first-child {
	border-radius: 3px 0 0 3px;
}*/

.custom-header nav span:hover {
	background: rgba(0,0,0,0.5);
}

.custom-header span:before {
	font-family: 'fontawesome-selected';
	color: #fff;
	display: inline-block;
	text-align: center;
	width: 100%;
	text-indent: 4px;
}

.custom-header nav span.custom-prev:before {
	content: '\25c2';
}

.custom-header nav span.custom-next:before {
	content: '\25b8';
}

.custom-header nav span:last-child {
	margin-left: 20px;
	border-radius: 3px;
}

.custom-header nav span.custom-current:before {
	content: '\27a6';
}


.fc-calendar {
	background: rgba(0,0,0,0.1);
	width: auto;
	top: 10px;
	bottom: 20px;
	left: 20px;
	right: 20px;
	height: auto;
/*	border-radius: 20px;*/
	position: absolute;
}

.fc-calendar .fc-head { 
	background: #1c2836;
	color: #fff;
	/*box-shadow: inset 0 1px 0 rgba(0,0,0,0.2);*/
/*	border-radius: 20px 20px 0 0;*/
	height: 40px;
	line-height: 40px;
	padding: 0 20px;
}

.fc-calendar .fc-head > div {
	font-weight: 300;
	text-transform: uppercase;
	font-size: 14px;
	letter-spacing: 3px;
/*	text-shadow: 0 1px 1px rgba(0,0,0,0.4);*/
}

.fc-calendar .fc-row > div > span.fc-date {
	color: rgba(0,0,0,0.9);
	text-shadow: none;
	font-size: 26px;
	font-weight: 300;
	bottom: auto;
	right: auto;
	top: 10px;
	left: 10px;
	text-align: left;
/*	text-shadow: 0 1px 1px rgba(0,0,0,0.3);*/
}

.fc-calendar .fc-body {
	border: none;
	padding: 20px;
}

.fc-calendar .fc-row {
	box-shadow: inset 0 -1px 0 rgba(0,0,0,0.2);
	border: none;
}

.fc-calendar .fc-row:last-child {
	box-shadow: none;
}

.fc-calendar .fc-row:first-child > div:first-child {
	border-radius: 10px 0 0 0;
}

.fc-calendar .fc-row:first-child > div:last-child {
	border-radius: 0 10px 0 0;
}

.fc-calendar .fc-row:last-child > div:first-child {
	border-radius: 0 0 0 10px;
}

.fc-calendar .fc-row:last-child > div:last-child {
	border-radius: 0 0 10px 0;
}

.fc-calendar .fc-row > div {
	box-shadow: -1px 0 0 rgba(0, 0, 0, 0.2);
	border: none;
	padding: 10px;
	cursor: pointer;
}

.fc-calendar .fc-row > div:first-child{
	box-shadow: none;
}

.fc-calendar .fc-row > div.fc-today {
	background: transparent;
	box-shadow: inset 0 0 100px rgba(0,0,0,0.1);
}

.fc-calendar .fc-row > div.fc-today:after { 
	content: ''; 
	display: block;
	position: absolute;
	top: 0; 
	left: 0;
	width: 100%;
	height: 100%;
	opacity: 0.2;
	background: transparent;
}

.fc-calendar .fc-row > div > div {
	margin-top: 35px;
}

.fc-calendar .fc-row > div > div a,
.fc-calendar .fc-row > div > div span {
	color: rgba(0,0,0,0.7);
	font-size: 12px;
	text-transform: uppercase;
	display: inline-block;
	padding: 3px 5px;
	border-radius: 3px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 100%;
	margin-bottom: 1px;
	background: rgba(0,0,0,0.1);
}

.no-touch .fc-calendar .fc-row > div > div a:hover {
	background: rgba(0,0,0,0.3);
}

@media screen and (max-width: 880px) , screen and (max-height: 450px) {
	html, body, .container {
		height: auto;
	}

	.custom-header,
	.custom-header nav,
	.custom-calendar-full,
	.fc-calendar-container, 
	.fc-calendar,
	.fc-calendar .fc-head,
	.fc-calendar .fc-row > div > span.fc-date {
		position: relative;
		top: auto;
		left: auto;
		bottom: auto;
		right: auto;
		height: auto;
		width: auto;
        padding-bottom: 1.2em;
	}

	.fc-calendar {
		margin: 0 20px 20px;
	}

	.custom-header h2,
	.custom-header h3 {
		float: none;
		width: auto;
		text-align: left;
		padding-right: 100px;
	}

	.fc-calendar .fc-row,
	.ie9 .fc-calendar .fc-row > div,
	.fc-calendar .fc-row > div {
		height: auto;
		width: 100%;
		border: none;
	}

	.fc-calendar .fc-row > div {
		float: none;
		min-height: 50px;
		box-shadow: inset 0 -1px rgba(0,0,0,0.2) !important;
		border-radius: 0px !important;
	}

	.fc-calendar .fc-row > div:empty{
		min-height: 0;
		height: 0;
		box-shadow: none !important;
		padding: 0;
	}

	.fc-calendar .fc-row {
		box-shadow: none;
	}

	.fc-calendar .fc-head {
		display: none;
	}

	.fc-calendar .fc-row > div > div {
		margin-top: 0px;
		padding-left: 10px;
		max-width: 70%;
		display: inline-block;
	}

	.fc-calendar .fc-row > div.fc-today {
		background: rgba(255, 255, 255, 0.2);
	}

	.fc-calendar .fc-row > div.fc-today:after { 
		display: none;
	}

	.fc-calendar .fc-row > div > span.fc-date {
		width: 30px;
		display: inline-block;
		text-align: right;
	}

	.fc-calendar .fc-row > div > span.fc-weekday {
		display: inline-block;
		width: 40px;
		color: #fff;
		color: rgba(0,0,0,0.7);
		font-size: 10px;
		text-transform: uppercase;
	}
}

 

but notice it is just a proof of concept - not browsertested - just some fast playing.

Best regard mr-fan

Edited by mr-fan
missing n in fancy...;)
  • Like 3
  • Thanks 1

Share this post


Link to post
Share on other sites

Thanks bernhard for a very informative post.

I do have a follow-up question though: How would i make the new panel / custom admin page accessible to a non-admin role? Currently it works great as superuser / admin using another role, logged in as a non-admin user there's only the "pages" tab, nothing else.
The way I understand it, since it uses the "admin" template, I can't really manage access through template settings (?).

I might overthink this though, anyone got an idea?
 

Edit: Actually, i'm sorry, this is a duplicate of

Which of course i found 2 minutes after posting the question, but not during the hour of research before..

  • Like 1

Share this post


Link to post
Share on other sites

Does anybody know how I can add existing fields to a form?

$form = $this->modules->get('InputfieldForm');
$form->add($this->fields->get('myfieldname'));

Whatever I try I get an error

Quote

Method Field::setParent does not exist or is not callable in this context

 

Share this post


Link to post
Share on other sites
4 hours ago, bernhard said:

Does anybody know how I can add existing fields to a form?


$form = $this->modules->get('InputfieldForm');
$form->add($this->fields->get('myfieldname'));

You can't add a Field object to a form, but rather an Inputfield object. Field::getInputfield() would come in handy here.

  • Like 2

Share this post


Link to post
Share on other sites
4 hours ago, Robin S said:

You can't add a Field object to a form, but rather an Inputfield object. Field::getInputfield() would come in handy here.

I've done it getting the actual Inputfield module, not the Fieldtype, I'd be very curious to see if the getInputfield method works! Get back to us on what works @bernhard :D

  • Like 1

Share this post


Link to post
Share on other sites

It would be easy to give an example using a Page object, but from a module I am curious how to achieve it. Another idea could be creating a page/template with all the fields which will be potentially injected in a form then

$test = $fields->get('test_field');
$field = $test->getInputfield($injectPage); // $injectPage is a Page object
$form->add($field);

 

  • Like 1

Share this post


Link to post
Share on other sites

thx @Robin S @elabx @flydev

This works:

$p = new Page();
$p->template = 'project';
$field = $this->fields->get('title');
$form->add($field->getInputfield($p));

I've had an error in my field's config (requesting dynamic data, getting a page via $this->wire->process->getPage(); and that threw an error. I thought I was doing something wrong but with the title field it worked 🙂

Thank you!

Edit: This also works:

$form->add($this->fields->get('recipients')->getInputfield(new NullPage()));
  • Like 2

Share this post


Link to post
Share on other sites

Not a question but just a thank you @bernhard for your great tutorial.

I was already amazed by Processwire's capabilities (at my beginner level) but this new tool is coming very handy for a current project I have but also future ones.

Actually I may have a question: is there a way to execute a function before you exit a page ? I have a cancel button which brings me back to the previous screen, along with a notification warning that the adding process has been canceled, but would it be possible to do the same when clicking in a tree link for example ?

Thanks !

Edit: I'm using this for the inputfield, works as well: 

$form->add($this->fields->get('field')->getInputfield($this->page));

 

  • Like 1

Share this post


Link to post
Share on other sites

glad it helped you @monollonom

not sure what would be the best approach here - I think it depends on your situation. PW uses the class "InputfieldStateChanged" for inputfields that where changed. You could either check on the client side (by intercepting click events) or if you want to do some checks on the server you could use sessions (set a session flag on beginning of the creation and check for that flag on every page other than your process module). 1000 possibilities here...

Share this post


Link to post
Share on other sites
25 minutes ago, bernhard said:

not sure what would be the best approach here - I think it depends on your situation. PW uses the class "InputfieldStateChanged" for inputfields that where changed. You could either check on the client side (by intercepting click events) or if you want to do some checks on the server you could use sessions (set a session flag on beginning of the creation and check for that flag on every page other than your process module). 1000 possibilities here...

I just need a notification to be shown telling the current action has been cancelled, a very small detail... I will find something 🙂

Thank you

  • Like 1

Share this post


Link to post
Share on other sites

It's all there.

When I was starting with PW years ago, I very soon looked at modules and all the Process modules that make the admin.

So I never really needed a tutorial, just look and learn copy and try. It was so simple and since it's all in the same fashion using the already familiar API and "normal" modules. So you have a ton of modules Ryan has already built that make ProcessWIre backend you can use as examples/learning. Isn't it nice?

  • Like 5

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.