Leaderboard
Popular Content
Showing content with the highest reputation on 04/30/2017 in all areas
-
After this tutorial you'll have learned how to: Build a Process module Make an AJAX request to backend Serve JSON as response Let's say you want to display the latest orders in a dashboard that you can access from admin panel. And you want it to refresh its content with a button click. Most straightforward and proper way (that I know of) is to create a Process module, as they're built for this purpose. First, create a directory under /site/modules/, call it ProcessDashboard, and create a file named ProcessDashboard.module under that directory. Following is about the least amount of code you need to create a Process module. <?php namespace ProcessWire; class ProcessDashboard extends Process { public static function getModuleInfo() { return [ 'title' => 'Orders Dashboard', 'summary' => 'Shows latest orders', 'version' => '0.0.1', 'author' => 'abdus', 'autoload' => true, // to automatically create process page 'page' => [ 'name' => 'order-dashboard', 'title' => 'Orders', 'template' => 'admin' ] ]; } public function ___execute() { return 'hello'; } } Once you refresh module cache from Modules > Refresh, you'll see your module. Install it. It will create an admin page under admin (/processwire/) and will show up as a new item in top menu, and when you click on it, it will show the markup we've built in execute() function. All right, now let's make it do something useful. Let's add create a data list to display latest orders. We'll change execute() function to render a data table. public function ___execute() { /* @var $table MarkupAdminDataTable */ $table = $this->modules->MarkupAdminDataTable; $table->setID($this->className . 'Table'); // "#ProcessDashboardTable" $table->headerRow([ 'Product', 'Date', 'Total' ]); // fill the table foreach ($this->getLatest(10) as $order) { $table->row([ $order['title'], $order['date'], $order['total'] ]); } // to refresh items $refreshButton = $this->modules->InputfieldSubmit; $refreshButton->name = 'refresh'; $refreshButton->id = $this->className . 'Refresh'; // "#ProcessDashboardRefresh" $refreshButton->value = 'Refresh'; // label of the button return $table->render() . $refreshButton->render(); } where getLatest() function finds and returns the latest orders (with only title, date and total fields) protected function getLatest($limit = 5, $start = 0) { // find last $limit orders, starting from $start $orders = $this->pages->find("template=order, sort=-created, limit=$limit, start=$start"); // Only return what's necessary return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total ]; }); } When you refresh the page, you should see a table like this Now we'll make that Refresh button work. When the button is clicked, it will make an AJAX request to ./latest endpoint, which will return a JSON of latest orders. We need some JS to make AJAX request and render new values. Create a JS file ./assets/dashboard.js inside the module directory. window.addEventListener('DOMContentLoaded', function () { let refresh = document.querySelector('#ProcessDashboardRefresh'); let table = document.querySelector('#ProcessDashboardTable'); refresh.addEventListener('click', function (e) { // https://developer.mozilla.org/en/docs/Web/API/Event/preventDefault e.preventDefault(); // Send a GET request to ./latest // http://api.jquery.com/jquery.getjson/ $.getJSON('./latest', { limit: 10 }, function (data) { // check if data is how we want it // if (data.length) {} etc // it's good to go, update the table updateTable(data); }); }); function renderRow(row) { return `<tr> <td>${row.title}</td> <td>${row.date}</td> <td>${row.total}</td> </tr>`; } function updateTable(rows) { table.tBodies[0].innerHTML = rows.map(renderRow).join(''); } }); And we'll add this to list of JS that runs on backend inside init() function public function init() { $scriptUrl = $this->urls->$this . 'assets/dashboard.js'; $this->config->scripts->add($scriptUrl); } Requests to ./latest will be handled by ___executeLatest() function inside the module, just creating the function is enough, PW will do the routing. Here you should notice how we're getting query parameters that are sent with the request. // handles ./latest endpoint public function ___executeLatest() { // get limit from request, if not provided, default to 10 $limit = $this->sanitizer->int($this->input->get->limit) ?? 10; return json_encode($this->getRandom($limit)); } Here getRandom() returns random orders to make it look like there's new orders coming in. protected function getRandom($limit = 5) { $orders = $this->pages->find("template=order, sort=random, limit=$limit"); return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total ]; }); } And we're done. When refresh button is clicked, the table is refreshed with new data. Here it is in action: 2017-04-29_19-01-40.mp4 (227KB MP4, 0m4sec) Here's the source code: https://gist.github.com/abdusco/2bb649cd2fc181734a132b0e660f64a2 [Enhancement] Converting page titles to edit links If we checkout the source of MarkupAdminDataTable module, we can see we actually have several options on how columns are built. /** * Add a row to the table * * @param array $a Array of columns that will each be a `<td>`, where each element may be one of the following: * - `string`: converts to `<td>string</td>` * - `array('label' => 'url')`: converts to `<td><a href='url'>label</a></td>` * - `array('label', 'class')`: converts to `<td class='class'>label</td>` * @param array $options Optionally specify any one of the following: * - separator (bool): specify true to show a stronger visual separator above the column * - class (string): specify one or more class names to apply to the `<tr>` * - attrs (array): array of attr => value for attributes to add to the `<tr>` * @return $this * */ public function row(array $a, array $options = array()) {} This means, we can convert a column to link or add CSS classes to it. // (ProcessDashboard.module, inside ___execute() method) // fill the table foreach ($this->getLatest(10) as $order) { $table->row([ $order['title'] => $order['editUrl'], // associative -> becomes link $order['date'], // simple -> becomes text [$order['total'], 'some-class'] // array -> class is added ]); } Now, we need to get page edit urls. By changing getLatest() and getRandom() methods to return edit links in addition to previous fields protected function getLatest($limit = 5, $start = 0) { // find last $limit orders, starting from $offset $orders = $this->pages->find("template=order, sort=-created, limit=$limit, start=$start"); return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total, 'editUrl' => $order->editUrl ]; }); } protected function getRandom($limit = 5) { $orders = $this->pages->find("template=order, sort=random, limit=$limit"); return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total, 'editUrl' => $order->editUrl ]; }); } and tweaking JS file to render first column as links function renderRow(row) { return `<tr> <td><a href="${row.editUrl}">${row.title}</a></td> <td>${row.date}</td> <td>${row.total}</td> </tr>`; } we get a much more practical dashboard.3 points
-
If you set an array to openPageIDs then it must be an array of page IDs as strings. For example: $this->wire('config')->js('ProcessPageList', array( 'openPageIDs' => array('1045','1046'), )); If you would like the convenience of passing multiple page IDs as a GET parameter you could do this in /site/ready.php: $this->addHookBefore('ProcessPageList::execute', function($event) { $branches = $this->sanitizer->intArray(json_decode($this->input->get->branches)); if(count($branches)) { $this->wire('config')->js('ProcessPageList', array( 'openPageIDs' => array_map('strval', $branches) )); } }); Then you would link to the page tree like this: /processwire/page/?branches=[1045,1046]3 points
-
Thanks for the suggestion, I've added it to the original post.2 points
-
In PW3 all the core PW classes (e.g. Page, PageArray, or in your current case HookEvent) are in the ProcessWire namespace. So where in PW2 you can write the type hinting as... function menu_post_process(HookEvent $event) ...in PW3 you need the ProcessWire namespace, like this... function menu_post_process(ProcessWire\HookEvent $event) But rather than use the namespace every time you create a new instance of a class you can just declare it once at the top of the PHP file. namespace ProcessWire; And the idea of the file compiler is that you don't even need to do this - the file compiler takes care of it for you. But there are some files that the file compiler does not process. As LostKobrakai said in the other thread you linked to: And it sounds like in your case you have deliberately disabled the file compiler in certain files with: # FileCompiler=0 So therefore you need to manually insert the namespace declaration yourself wherever you instantiate a PW class.2 points
-
Your textarea tag isn't valid. Instead of... <input type='textarea' id='comment' name='comment'> ...it should be... <textarea id='comment' name='comment'></textarea>2 points
-
FacebookEvents ProcessWire module to get Facebook Page Events using the Graph API. Create an app on Facebook developers website You have to create an app to get appId and appSecret. Those keys are required. Go to Facebook Developers and add a new app. Congrats! Now you can copy your Facebook appId and appSecret. Get your Facebook page ID You can either enter your facebook page ID or the facebook page name. If you enter the Facebook page name, this module will get the page ID for you! Call Module $events = $modules->get('FacebookEvents')->getEvents(); Output Events {% for event in events|reverse %} {% if event.start_time|date('U') > date().timestamp %} <div> {% set dts = modules.get('FacebookEvents').getDates(event) %} <a href="https://www.facebook.com/events/{{event.id}}/" title="Facebook">{{dts.dates}}:</a> {{event.name}} <em>{{dts.times}}</em> </div> {% endif %} {% endfor %1 point
-
The code I have posted is taken from a frontend login template. The frontend has a complete suite of register, login and logout templates. I use core processes in the frontend. To prevent redirects to the backend in case of successfull login/ logout or an expired session I set login and logout URLs before I execute the Process. The customers are PW users with very limited permissions. I gave the admin area a very strange name (default: processwire) like '6gt0klw5a14' to hide it from guests or frontend users. You can additionally protect the login with a custom cookie or IP filter in your .htaccess file. Calling any target in the admin area without a valid session will cause render of the Login page instead. Its not recommended to change this.1 point
-
1 point
-
Thanks @abdus for this useful tutorial! If you could also add how to turn the values of the first column into links, then it would be a 100% perfect example.1 point
-
I know this thread was old, but I want to build a web server that is using official package from the distribution repo. So I write this Dockerfile and if anyone wants to test/use it jus look at https://github.com/mriza/docker-processwire1 point
-
It's a namespace issue. The file where function menu_post_process() is defined is not being processed by the file compiler, maybe because it is in an include. So add... namespace ProcessWire; ...at the top of that PHP file.1 point
-
Thanks! This just works, although this has a 0.5 second delay due to separate ajax call.1 point
-
Thanks Juergen and a great suggestion re the anchor. Achieved it with Ryan's snippet: // scroll to form submitted message jQuery(document).ready(function($) { var f = $('#FormBuilderSubmitted'); if(!f.length) return; var y = f.offset().top; $('body').animate( { scrollTop: y }, 'slow'); });1 point
-
@skylundy, turns out ProcessWire has a built-in class loader. It uses spl_autoload_register() internally. Given a folder structure: /site/templates/ components/ Composer.php Writer.php app.php where Composer.php and Writer.php are classes under \ProcessWire namespace You can autoload them using // /site/templates/app.php $loader = new WireClassLoader($wire); // or use $classLoader since it's already an API variable. // autoload classes inside /site/templates/components/ $componentsPath = wire()->config->paths->templates . 'components/'; $loader->addNamespace('ProcessWire', $componentsPath); // then you can reference classes just fine. $b = new Composer(); $w = new Writer(); $w->write($b->compose('hello')); http://processwire.com/api/ref/class-loader/1 point
-
Your module submitted fine and I have approved it, but please check your author details.1 point
-
Great stuff, Ryan! In SA, I've actually picked up a considerable speed improvement, especially on the forums. Not sure if that difference would affect you or Europe as much as it does here. Very snappy.1 point
-
BTW, if you have GhostScript installed on your server, you can use it to compress PDFs locally using a command-line call... gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dNOPAUSE -dQUIET -dBATCH -sOutputFile=out.pdf in.pdf Source: SuperUser.com (See 2nd answer.)1 point
-
Media Manager version 010 (released 28/04/2017) Happy to announce the latest release of Media Manager. Changelog As per this request, added option to confirm duplicate media overwrite on upload. This, obviously, only works when the setting Duplicate Media is set to overwrite existing media with the one being uploaded. If you have that option selected, you will see a new option 'Always confirm when replacing/overwriting duplicate media' in the Duplicate Media sub-section. Tick that and save if you want to use this option. On the Uploads Tab you should now see a 'Confirm Overwrite' checkbox toward the top right of that page. Unless that checkbox is ticked, you will not see 'Start' upload buttons. Made various strings used in success/error messages in JavaScript translatable This latest version is now available for download Screens 'Always Confirm Overwrites' setting 'Confirm Overwrites' checkbox unchecked 'Confirm Overwrites' checkbox checked1 point
-
@Slav Welcome to the forum. Take this as a first approach $homeUrl = $pages->get(1)->url; // Prevent redirect to admin login. Redirect to homepage instead if (!$user->isLoggedin() && isset($input->get->login)) $session->redirect($homeUrl); if ($user->isLoggedin() && isset($input->get->login)) $session->redirect($customLoginUrl); // set custom login/ logout urls $login = $modules->get('ProcessLogin'); $login->setLoginURL($customLoginUrl); $login->setLogoutURL($homeUrl);1 point
-
Rob, welcome to the forums! Diogo is right that we'll be building a module to handle this natively without having to create your own Fieldtype. But for now you would have to create your own Fieldtype. The image fieldtype may be a bit much to look at because there's so much stuff in there specific to images as opposed to the actual functionality you are looking for. If I'm understanding correctly, you are wanting a field that supports any number of entries with 3 text fields each. Regarding documentation, ProcessWire is still kind of a new project and so I've tried to cover the areas that would be helpful to the most people, and am going back and filling in the rest of the gaps over time. Documentation on how to do things like create your own Fieldtype is something that will be coming. It's basically just a matter of time and resources. We're just 1 year old project and the documentation will continue to grow with the age of the project. So I'm glad to guide people through how to do everything as much as possible, and I'll be glad to assist you with creating the fieldtype you want. My experiences here help me to write documentation as well. Fieldtype vs. Inputfield Creating your own Fieldtype will be a matter of working in PHP and extending ProcessWire's classes. You will need both a Fieldtype and an Inputfield. The two work together. It's good to understand what both are. The Fieldtype defines the data's type and handles loading, saving and sanitizing the data that is added to a page. Whereas an Inputfield does nothing other than provide the HTML form inputs to collect the data, and then retrieve the posted data when it's been submitted. As a result, an Inputfield only comes into play in the admin when you are entering data. Whereas the Fieldtype is always active any time the data is loaded. To put it another way, if you only ever populated data in ProcessWire with the API, then you wouldn't even need an Inputfield. In many cases there is a specific Inputfield for each Fieldtype. But there are also many Fieldtypes that can work with an existing Inputfield too. For instance, the FieldtypeTextarea can utilize InputfieldTextarea or InputfieldTinyMCE, according to your need. In your case, I think you'll want to create both a Fieldtype and an Inputfield specific to it. Fieldtype vs. FieldtypeMulti In ProcessWire Fieldtypes fall into a class of either Fieldtype or FieldtypeMulti. The only difference between the two is that Fieldtype supports one entry per field per page and FieldtypeMulti supports any number of entries per field per page. An example of a regular Fieldtype is FieldtypeTextarea, which would simply be one textarea input. Examples of FieldtypeMulti are FieldtypeFile, FieldtypeImage, FieldtypePage, and FieldtypeComments. Regardless of whether a Fieldtype or a FieldtypeMulti, ProcessWire reserves one database table per field. This means you can build complex Fieldtypes containing multiple fields of data per entry. While not many fieldtypes demonstrate this, Fieldtypes are specifically designed to represent complex data types in the database and in the API. FieldtypeComments is currently the most complex public example as it's schema contains many fields per entry. Creating your own FieldtypeMulti If you are still interested, here are some links to get started. Let me know if this stuff makes some sense to you (you don't need to understand it all), and then we'll use this thread to start building a simple FieldtypeMulti. Once we've got something working, hopefully we can take parts of this and turn it into a Fieldtype tutorial. Discussion and example module of how to expand the schema of the Image fieldtype http://processwire.com/talk/index.php/topic,466.0.html The Fieldtype base class (read the comments for each function): https://github.com/ryancramerdesign/P21/blob/master/wire/core/Fieldtype.php The FieldtypeMulti base class (read comments, but not as important as Fieldtype): https://github.com/ryancramerdesign/P21/blob/master/wire/core/FieldtypeMulti.php (ignore the commented out code, I've just kept some things commented out for future reference) The Inputfield base class (don't spend too much time, just take a brief look): https://github.com/ryancramerdesign/P21/blob/master/wire/core/Inputfield.php Simple Inputfield example implementation (integer field): https://github.com/ryancramerdesign/P21/blob/master/wire/modules/Inputfield/InputfieldInteger.module1 point