Leaderboard
Popular Content
Showing content with the highest reputation on 02/08/2018 in all areas
-
Community wise... I would like to see more examples of **How** people built their Processwire websites. It would be awesome if ever so often we could some how host a Google Hangout or use https://www.crowdcast.io/ to pick volunteers from the community to showcase their Processwire websites and do a site walk through. Here's an example of something similar https://zendev.com/2017/08/31/grids-grids-and-more-grids.html where they talk about Zurb's Foundation CSS/Javascript Framework. They called it Foundation Open Chat https://zendev.com/category/foundation-open-chat.html As you can see they can be embedded into a blog post as well. Well I just saw there is a cost to using crowdcast.io. That might not work, but maybe Google Hangouts or YouTube Live would? I believe they do something similar with CraftCms https://straightupcraft.com/hangouts During those sessions we could: ask questions about the site or Processwire? get to know more about a person behind the avatar? talk about the site's pain points and how the developer was able to work around it? cool module walk throughs or new module showcase? talk about how the site is output? does it use a template engine? Twig, Latte, Mustache, PHP, Processwire's Delay Output Method, etc.. talk about the site's tree structure? talk about the site's hosting environment? ...Just some thoughts I had4 points
-
Here's a document I've been working on https://docs.google.com/document/d/1peY-FUpevKgy87cKOxIVz8jwcv2-3c61zbiJr3QKO6c/edit?pli=1# Flexible Page Layouts in Processwire. It's still rough, but I've been using it to help collect all my thoughts/notes on all the ways to achieve flexible layouts in Processwire. I love hearing about other ways people are structuring their websites. Here is another document that helps me wrap my head around some of the different field types in Processwire. (all on one page) https://docs.google.com/document/d/1VcSluQyjl9AhMBcJA3R4-uZb9IUYdKXGSh7Phclf34Y/edit?pli=1#heading=h.ysvx3k7ssuw7 Hope it helps someone.4 points
-
Here we go .. The basic idea is an textarea fieldtype with a function somewhat similar to an website blocks builder, I know that currently this can be achieved using a Repeater, Repeater Matrix or Hanna Code, but perhaps this way would be easier for the developer/user and more flexible. The idea came after seeing this plug-in: https://formbuilder.online/ ... and the implementation could be based on these modules: https://github.com/blynx/TextformatterFunkyFunctions from @blynx https://github.com/outflux3/SettingsFactory from @Macrura https://github.com/Toutouwai/HannaCodeDialog from @Robin S Based on TextformatterFunkyFunctions we would have a folder for this type of field with the design blocks that we want to make available to the user: These block files would have main three sections: The Settings section for the Block UI could be based on the SettingsFactory code. A mockup of the user interface of the field would be: It would have drag and drop to add and sort the blocks, and display the generated Form by each block in order to enter the block data The field will have three display modes in the backend: BUILD - the first where field content is configured in visual and interactive mode: CODE - The second is the "real" configuration of the field as required by TextformatterFunkyFunctions and is the data that is stored in the Processwire DB. This mode could be hidden for some users: PREVIEW - The third one is the visualization of the field output, it could be done using Ajax requests to the module into an iframe or inserting the response in a DIV using jquery. Maybe some code of this module will work: http://modules.processwire.com/modules/fieldtype-runtime-markup/ from @kongondo I hope that I have been able to explain the idea correctly, but I can not wait for it to be inspired to make a perfect explanation, .. waiting for that moment and it has been in my trunk for a long time .. It's just an idea to debate, maybe it will be an inspiration for other things, or for someone who wants to develop it (as a commercial module or not)3 points
-
Well, since adding and deleting FieldsetOpen fields also affects the closing field, this is a little stumbling block. I've opened an issue with a suggested fix.3 points
-
@ngrmm Your solution looks good, since the mod operator is the fastest way to determine even/ odd in PHP, but I would loop only once. $even = ''; $odd = ''; foreach ($allPages as $k => $v) { if ($k % 2) { $even .= "<div>$v->title</div>"; } else { $odd .= "<div>$v->title</div>"; } } echo "<div class='items'>"; echo "<div class='odd'>"; echo $odd; echo "</div>"; echo "<div class='even'>"; echo $even; echo "</div>"; echo "</div>";3 points
-
Hi @Peter Knight $thumb = $image->size($width, $height, $options); $width and $height are not optional arguments. https://processwire.com/api/ref/pageimage/size/3 points
-
https://thesmartgroup.ie/ We launched this earlier in the year, I'm only just getting round to sharing it. This is the 4th iteration design wise in the past 5 years and personally my favourite in terms of design. The old site was PW based so we had a lot of the content in there but undertook a major re-write and claw back to make everything cleaner and more succinct - relying on more visuals to promote the work we do rather than verbose copy. The site is not using anything out of the ordinary, just standard modules I tend to use for all PW sites: AIOM SEO Markup Sitemap XML AutoSmush MenuBuilder: Markup The site loads pretty darn quick considering it's using a lot of images and many of them are 96DPI for better display on Retina.2 points
-
Yes, you just have to make sure the hook doesn't loop into nirvana. That's one way yes, but maybe the ID isn't always there. It's not so much a problem that the hook get's called multiple times if you restrict it via the template or something else (ie a $this->skip flag you set to true) but maybe some code in there gets executed still, like your message idk. It depends a lot what you do and possibly trigger in your hook. I found myself often tracking and trying to figure out what's going wrong for hours and hours, which is not fun at all. Sometimes it's a side effect or even possibly a bug you never know.2 points
-
Just a quick note on the $page->template to $itemtemplate conversion. You could replace all that logic with: $itemtemplate = rtrim(str_replace(array('event_', 'special', 'business'), array('single-', 'special-', 'business-'), $page->template), 's'); It's really up to you whether this seems nicer or more complicated2 points
-
thanks, that's very good to know, might try and revisit the app with them now that you have enlightened me!2 points
-
Cordova/Phonegap are for building hybrid apps, but there are several "native" cordova plugins for things like access to the camera and various sensors, and in that case, also for displaying notifications. Here's the list of cordova plugins you can use: https://cordova.apache.org/plugins/2 points
-
Not totally sure what you mean - you just want to save having to click "Add Filter" to add an email address row? You can use a hook in /site/ready.php: $wire->addHookBefore('ProcessPageLister::execute', function(HookEvent $event) { if($this->config->ajax) return; $process = $event->object; // Just for the Users lister... if($this->page->name === 'users') { // Add to existing defaultSelector (or alternatively you could overwrite defaultSelector) $process->defaultSelector .= ', email%='; } });2 points
-
What I would really like to see is more expanded multilanguage support in the core as 95% of my sites is ML. Very often I need to have some pages available only in non-default languages and every time I have to reinvent the wheel with custom logic. Almost in all modern CMSs (Statamic, Craft, October ..) you can separately publish or unpublish pages for every language even default. There are @adrian's module and request which intended to solve this issue via a module, but as I said, I think that it should be in core.1 point
-
The idea is get something simple to setup and simple to use.. the Matrix usually is complex to setup and fine tuned, and sometimes complex to the user, mainly when have depth.. and near to impossible to reuse it in other projects.. ah! and is not cheap too.. I was thinking in something simple and useful for build basic landing pages, ..easy to copy to another projects.. sometimes when we have many pages with crazy designs that not fit the regular "basic-page" (main+sidebar) or similar, we start to fill the site folder with several templates files.. but it's only and idea to refresh.. for me make sense.. or perhaps having a Textarea that store unrestricted HTML and can be edited with something like this http://grapesjs.com/1 point
-
1 point
-
Yes, I also think this should be done in core, because it is not only an "unpleasant" workflow, but may lead to bugs in modules.1 point
-
Veto! I think that should be done by the core Also came around this several times and was annoyed. So I think it would be better to open a PR than adding another snippet to AOS.1 point
-
and peeked again in the github repo to find that the setLocale part was added in 2017, so 2.7.2 won't have that (yet). So, yes, my line is likely a no-op.1 point
-
I peeked into the setLocale method in Languages.php ($locale is fetched from the translation in wire--modules--languagesupport--languagesupport-module). } else if(strpos($locale, ';') !== false) { // multi-category and locale string, i.e. LC_CTYPE=en_US.UTF-8;LC_NUMERIC=C;LC_TIME=C foreach(explode(';', $locale) as $s) { // call setLocale() for each locale item present in the string if(strpos($s, '=') === false) continue; list($cats, $loc) = explode('=', $s); $cat = constant($cats); if($cat !== null) { $loc = $this->setLocale($cat, $loc); if($loc !== false) $setLocale .= trim($cats) . '=' . trim($loc) . ";"; } } $setLocale = rtrim($setLocale, ';'); if(empty($setLocale)) $setLocale = false; } LC_ALL=de_DE is a different case, as it contains no semicolon and is therefore passed verbatim as the value to setlocale.1 point
-
Perhaps it would, I will check if this would work as intended in my adminonsteroids module, unless someone has something against.1 point
-
That de_DE could the culprit. You could try putting "LC_ALL=de_DE.utf8;LC_NUMERIC=C" (without the quotes) there instead (through the backend) to use German locale settings for everything but numeric things, for which the system's default locale will be used.1 point
-
Not sure it's actually an optimization - more of a reduction and maybe a simplification depending on how you look at things. It also might handle template names without any additional logic which can also be nice.1 point
-
Thanks I am always glad to read about optimizations!1 point
-
It most likely the locale setting in the de language pack that affects numbers. There was once a fix I thought around that time.1 point
-
Thanks for the hint, but I want to automate it and send these users an email to remind them. So I have to get them via the api.1 point
-
Hence this discussion. I am learning as well, so thanks for your pointers .1 point
-
Thanks for the input guys! An app that you have to pay to use/install . So, I am told...but at the cost of what? You get a slower app? Just what I've read. But the point about using technology you already know (HTML, etc) is a good thing (most times). I think it depends on the language you are using. If developing. If using TypeScript + NativeScript, all you need to know is JavaScript (and CSS and (X)HTML)). Is this still the case or am I misunderstanding the below? https://auth0.com/blog/introduction-to-progressive-web-apps-push-notifications-part-3/ https://dockyard.com/blog/2017/07/13/safari-ios-and-progressive-web-apps Sound advice. I think I'll take this approach, albeit start with native and get into PWA's later.1 point
-
Just a by the way, this code: if($page->template == 'event_businessvacations'){ $itemtemplate = 'single-business-vacation'; } if($page->template == 'event_dates'){ $itemtemplate = 'single-date'; } if($page->template == 'event_events'){ $itemtemplate = 'single-event'; } if($page->template == 'event_specialbusinesshours'){ $itemtemplate = 'single-special-business-hours'; } The conditions you are checking are mutually exclusive. A $page->template can only ever have one name. So, there is no need to check it four times. You can use if, elseif, etc (or case:), to have PHP break out of the condition check once a match has been found. E.g. if($page->template == 'event_businessvacations'){ $itemtemplate = 'single-business-vacation'; } elseif($page->template == 'event_dates'){ $itemtemplate = 'single-date'; } elseif($page->template == 'event_events'){ $itemtemplate = 'single-event'; } elseif($page->template == 'event_specialbusinesshours'){ $itemtemplate = 'single-special-business-hours'; }1 point
-
Thanks dude I will look at setting up Cloudflare that might help speed up the image load, also I was looking at http/2 - our server admins are looking at possibly setting that up.1 point
-
Hi @kongondo I have this site running on two other servers with the same version of PW, PHP. So 2 out of 3 work fine. I'll have to do a little forensic investigating.1 point
-
To furthermore change or sort the columns to be displayed in the list, you can do that here: Modules > Configure > ProcessUser1 point
-
As @Zeka said - it expects a minimum of two arguments, but just to clarify, this is not a PHP 7 issue.1 point
-
some time ago I has an idea to mix some modules and codes that we have in our modules "eco-system" to do something similar.. soon I'll post here my idea1 point
-
Ha, thanks mate. Yeah - the 404 was the only cat / kitten reference I was permitted to leave in the site after removing place(kitten)holders1 point
-
You probably have some field formatters active that strips the tags. Did you try this CKEditor plugin? https://ckeditor.com/cke4/addon/mathjax1 point
-
@Maxplex Did you use this one by any chance? https://modules.processwire.com/modules/duplicator/ Duplicator should work but as @Robin S pointed out, manual cloning of the site should work too. I used to install a few sites at 1&1 without issues.1 point
-
Thanks for all the inputs! Whit it's help, I was able to move the content of the Children Tab to the content Tab and remove the tab, as well as moving the name field below the title or subtitle field. Maybe someone else can use this as well: // Reorder Fields wire()->addHookAfter('ProcessPageEdit::buildForm', function ($event) { // make sure we're editing a page and not a user if ($event->process != 'ProcessPageEdit') { return; } $page = $event->object->getPage(); $form = $event->return; $settingsTab = $form->children->getChildByName('id=ProcessPageEditSettings'); $contentTab = $form->children->getChildByName('id=ProcessPageEditContent'); // move name below title or subtitle foreach ([$settingsTab->getChildByName('_pw_page_name')] as $child) { if ($child) { $child->getParent()->remove($child->name); $child->collapsed = Inputfield::collapsedPopulated; $contentTab->insertAfter($child, $contentTab->get('subtitle|title')); } } // continue only if on certain template if ($page->template->name !== 'ResourceEntry') { return; } // children tab $childrenTab = $form->children->getChildByName('id=ProcessPageEditChildren'); if (!$childrenTab) { return; } // move all content foreach ($childrenTab->children as $child) { if ($child) { // relable "add" buttton if ($child->get('AddPageBtn') instanceof InputfieldButton) { $child->get('AddPageBtn')->attr('value', _('Add New')); } $contentTab->append($child); } } // remove Tab and tab content: $form->remove($childrenTab); $event->object->removeTab('ProcessPageEditChildren'); });1 point
-
I ran a poll about PWA's in #174 of ProcessWire Weekly and included a couple of useful links: https://weekly.pw/issue/174/. Check out https://pwa.rocks/ too, some nice examples there Google calls PWA's "a new way to deliver amazing user experiences on the web", which obviously explains absolutely nothing about them. The A List Apart article mentioned above is a great resource, in my opinion, so definitely check that one out. Ionic also has a pretty good introductory article: https://blog.ionicframework.com/what-is-a-progressive-web-app/. In a nutshell PWA's make use of various Web APIs and technologies, such as JavaScript, Service Workers, etc. They provide a "native like" experience, but are not native apps – so, to answer your questions the best I can, I'd say that ... as far as it's doable with web technologies (yes), as long as you can do it with JavaScript (yes), and I'm pretty sure you can, but I have no idea how, except for the point that since a PWA is essentially a "website with superpowers" (not my quote, but can't remember the source right now) it's probably mostly the same process as with any "regular" premium website. That being said, I've also got native mobile app development on my bucket list, where it'll remain even taking PWA's into consideration. For certain things native apps are still a better choice, and serious game development is one of those.1 point
-
SYNOPSIS A little guide to generating an sitemap.xml using (I believe) a script Ryan originally wrote with the addition of being able to optionally exclude child pages from being output in the sitemap.xml file. I was looking back on a small project today where I was using a php script to generate an xml file, I believe the original was written by Ryan. Anyway, I needed a quick fix for the script to allow me to optionally exclude children of pages from being included in the sitemap.xml output. OVERVIEW A good example of this is a site where if you visit /minutes/ a page displays a list of board meetings which includes a title, date, description and link to download the .pdf file. I have a template called minutes and a template called minutes-document. The first page, minutes, when loaded via /minutes/ simply grabs all of its child pages and outputs the name, description and actual path of an uploaded .pdf file for a visitor to download. In my back-end I have the template MINUTES and MINUTES-DOCUMENT. Thus: So, basically, their employee can login, hover over minutes, click new, then create a new (child) record and name it the date of the meeting e.g. June 3rd, 2016 : --------------------------- OPTIONALLY EXCLUDING CHILDREN - SETUP Outputting the sitemap.xml and optionally excluding children that belong to a template. The setup of the original script is as follows: 1. Save the file to the templates folder as sitemap.xml.php 2. Create a template called sitemap-xml and use the sitemap.xml.php file. 3. Create a page called sitemap.xml using the sitemap-xml template Now, with that done you will need to make only a couple of slight modifications that will allow the script to exclude children of a template from output to the sitemap.xml 1. Create a new checkbox field and name it: sitemap_exclude_children 2. Add the field to a template that you want to control whether the children are included/excluded from the sitemap. In my example I added it to my "minutes" template. 3. Next, go to a page that uses a template with the field you added above. In my case, "MINUTES" 4. Enable the checkbox to exclude children, leave it unchecked to include children. For example, in my MINUTES page I enabled the checkbox and now when /sitemap.xml is loaded the children for the MINUTES do not appear in the file. A SIMPLE CONDITIONAL TO CHECK THE "sitemap_exclude_children" VALUE This was a pretty easy modification to an existing script, adding only one line. I just figure there may be others out there using this script with the same needs. I simply inserted the if condition as the first line in the function: function renderSitemapChildren(Page $page) { if($page->sitemap_exclude_children) return ""; ... ... ... THE FULL SCRIPT WITH MODIFICATION <?php /** * ProcessWire Template to power a sitemap.xml * * 1. Copy this file to /site/templates/sitemap-xml.php * 2. Add the new template from the admin. * Under the "URLs" section, set it to NOT use trailing slashes. * 3. Create a new page at the root level, use your sitemap-xml template * and name the page "sitemap.xml". * * Note: hidden pages (and their children) are excluded from the sitemap. * If you have hidden pages that you want to be included, you can do so * by specifying the ID or path to them in an array sent to the * renderSiteMapXML() method at the bottom of this file. For instance: * * echo renderSiteMapXML(array('/hidden/page/', '/another/hidden/page/')); * * patch to prevent pages from including children in the sitemap when a field is checked / johnwarrenllc.com * 1. create a checkbox field named sitemap_exclude_children * 2. add the field to the parent template(s) you plan to use * 3. when a new page is create with this template, checking the field will prevent its children from being included in the sitemap.xml output */ function renderSitemapPage(Page $page) { return "\n<url>" . "\n\t<loc>" . $page->httpUrl . "</loc>" . "\n\t<lastmod>" . date("Y-m-d", $page->modified) . "</lastmod>" . "\n</url>"; } function renderSitemapChildren(Page $page) { if($page->sitemap_exclude_children) return ""; /* Aded to exclude CHILDREN if field is checked */ $out = ''; $newParents = new PageArray(); $children = $page->children; foreach($children as $child) { $out .= renderSitemapPage($child); if($child->numChildren) $newParents->add($child); else wire('pages')->uncache($child); } foreach($newParents as $newParent) { $out .= renderSitemapChildren($newParent); wire('pages')->uncache($newParent); } return $out; } function renderSitemapXML(array $paths = array()) { $out = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'; array_unshift($paths, '/'); // prepend homepage foreach($paths as $path) { $page = wire('pages')->get($path); if(!$page->id) continue; $out .= renderSitemapPage($page); if($page->numChildren) { $out .= renderSitemapChildren($page); } } $out .= "\n</urlset>"; return $out; } header("Content-Type: text/xml"); echo renderSitemapXML(); // Example: echo renderSitemapXML(array('/hidden/page/')); In conclusion, I have used a couple different processwire sitemap generating modules. But for my needs, the above script is fast and easy to setup/modify. - Thanks1 point
-
@noelboss just posted a comment in this thread and the thread is just one of many examples of people looking for solutions of a proper staging/production strategy. we have some modules that try to close this gap, but imho this is an important part of a professional workflow and therefore should be part of the core. don't know how that could be implemented exactly, but at least it would be great to have a thought-out strategy and maybe some kind of standard/best practise guide of how to keep staging/production in sync, be safe while editing, integrate GIT in this process etc. while other features are nice to have for me, this is really one thing that makes me feel totally unprofessional and is a huge pain. i don't think that the options we have so far are as good as they could be. for example if i had to push a fix to a live system, i wished it would be possible to: pull the latest version from live to dev with one click (excluding a predefined list of files / db tables) work on that dev version locally (having all files on the local computer makes searching all files a lot easier) push the fix to git push the fix to production some parts of this workflow can be done with the migrations module, some with the quite new duplicator module, some could be implemented via githooks, but, hey... we are talking about ProcessWire and where PW really shines is making our lives as devs easier and in this special case i feel that this is not true maybe i'm just too inexperienced in this topic and there are proper solutions out there, but following the forum over the last years i didn't see a solution that felt "processwire-awesome". maybe a blog-post covering this topic could be a first step. and maybe i'm totally alone with this opinion... a feature request-voting system could also help a lot here [pub] that was the tracy-boost1 point
-
A quick tutorial how to create file downloads using pages You will be able to create a new page using template "PDF" (or any you setup), upload a pdf file. You then can select this page using page fields, or links in Wysiwyg. The url will be to the page and NOT the file itself. This will allow to keep a readable permanent unique url (as you define it), unlike /site/assets/files/1239/download-1.pdf, and you'll be able to update/replace the uploaded file without worring about its filename. Further more the file will also have an id, the one of the page where it lives. Clicking those links will download or open the file (when target="_blank") like it would be a real file on server with a path like /downloads/project/yourfile.pdf. You'll be also able to use the "view" action directly in the page list tree to view the file. Further more you'll be able to esaily track downloads simply by adding a counter integer field to the template and increase it every time the page is viewed. Since the file is basicly a page. This all works very well and requires only minimal setup, no modules and best of it it works in the same way for multi-language fields: Just create the language alternative fields like "pdf, pdf_de, pdf_es" and it will work without modifying any code! Still with me? ok PW setup Download folder: Create a template "folder" or "download-folder" with only a title needed. Create pages in the root like /downloads/project/ using this template. Setup the template for the pdf files 1. Create a new template in PW. Name it pdf 2. Goto template -> URLs tab and set the URL end with slash to no. (So we can have /path/myfile.pdf as the URL) 3. Create a new custom file field, name it pdf. Set its maximal count to 1 under -> Details tab. 4. Add the pdf field created to the pdf template. Easy. 5. Create a new "pdf" page using the pdf template under a download folder you created earlier. 6. Give it the title and in the name field add ".pdf" to the end (could also leave as is) Template PHP file for the pdf files 1. Create the template file pdf.php in your /site/templates folder 2. add the following code: <?php // pdf.php if($page->pdf){ wireSendFile($page->pdf->filename); } Done. To see the options you have with PW's wireSendFile() you can also overwrite defaults <?php // pdf.php if($page->pdf){ $options = array( // boolean: halt program execution after file send 'exit' => true, // boolean|null: whether file should force download (null=let content-type header decide) 'forceDownload' => false, // string: filename you want the download to show on the user's computer, or blank to use existing. 'downloadFilename' => '', ); wireSendFile($page->pdf->filename, $options); } Simple and powerful isn't it? Try it out. Some thoughts advanced Create as many file types as you like. It might also be possible to use one "filedownload" template that isn't restricted to one field type but evaluate it when being output using $page->file->ext, or save the file extension to the page name after uploading using a hook. One last thing. You can add other meta fields or preview images to the template and use those to create lists or detail pages. It's all open to goodness. Again all without "coding" and third-party modules. Further more you can use the excellent TemplateDecorator to add icons per template and have a nice pdf icon for those pages. This as a base one could also easily create a simple admin page for mass uploading files in a simple manner, and create the pages for the files automaticly. ImagesManager work in the same way. Cheers1 point
-
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.1 point
-
Hey, thanks for taking the time to read! So I'm new to module development and I'm having trouble understanding how to pass data in my module. The module I am making extends process and implements ConfigurableModule.( A lot of the module aspect of the code is copied from other modules.) It's purpose is to download a report that contains a list of people from a permalink and then parse and create a page for each person on the list. That part works fine, if I just run it as a script or hard code the default values into my module. What I am having a hard time understanding is how do I access the fields that are in the module via the admin page interface? I can't seem to actually get a hold of the data that is saved in the two fields of my module. I have my module show up under the setup tab with a submit button and the module itself with the two configurable fields are under site->modules. When I press submit I need it to get the data from the two fields. Is the best way to create global variables in my module?(Which I have tried) Or is there some other preferred way. I really am stumped on how to get it working. My Module: <?php class MemberImporter extends Process implements Module, ConfigurableModule { public static function getModuleInfo() { return array( 'title' => 'LGL Members Importer', 'author' => 'Tyler Williams, Sam Fleming', 'summary' => 'Import a list of LGL members who have an active website.', 'version' => .001, 'singular' => true, ); } //Get the module config data protected $data; /** * Name used for the page created in the admin * */ const adminPageName = 'member-importer'; /** * Instance of Template, used for imported pages * */ protected $template = null; /** * Instance of Page, representing the parent Page for imported pages * */ protected $parent = null; protected static $defaults = array( 'permalinkURL' => 'link-removed-for-privacy', 'membersPath' => '/Members' ); protected $link = null; protected $memPath = null; static public function getDefaultData() { return array( 'permalinkURL' => 'link-removed-for-privacy', 'membersPath' => '/Members' ); } public function __construct() { //set defaults foreach(self::getDefaultData() as $key => $value) { $this->$key = $value; } } public function init() { parent::init(); ini_set('auto_detect_line_endings', true); } public static function getModuleConfigInputfields(array $data) { $data = array_merge(self::getDefaultData(), $data); $fields = new InputfieldWrapper(); $head = new InputfieldMarkup; $head->label = __('Member Import Configuration'); $head->description = __('Set the permalink to the LGL report file and set the parent page for the Member Profiles'); $fields->add($head); //permalink url for report $field = wire('modules')->get('InputfieldText'); $field->attr('name', 'permalinkURL'); $field->attr('value', $data['permalinkURL']); $field->label = "The permalink URL that downloads the LGL report"; $fields->add($field); //members parent page $field = wire('modules')->get('InputfieldPageListSelect'); $field->attr('name', 'membersPath'); $field->attr('value', $data['membersPath']); $field->label = "Path to Members' profiles"; $fields->add($field); return $fields; } public function getConfig(){ return ($this->get($key)) ? $this->get($key) : self::$defaults[$key]; } protected function getInstalledPage() { $admin = $this->pages->get($this->config->adminRootPageID); $parent = $admin->child("name=setup"); if(!$parent->id) $parent = $admin; $page = $parent->child("name=" . self::adminPageName); if(!$page->id) { $page = new Page(); $page->parent = $parent; $page->template = $this->templates->get('admin'); $page->name = self::adminPageName; $page->title = "Member Importer"; $page->process = $this; $page->sort = $parent->numChildren; $page->save(); } return $page; } public function ___execute() { if($this->input->get->import == "Import"){ //run the importer and then redirect back to page tree $this->ImportMembers(); $this->session->redirect(wire('config')->urls->admin); }else{ $form = $this->modules->get("InputfieldForm"); $form->method = 'get'; $form->action = './'; $head = new InputfieldMarkup; $head->label = __('Import Members from Little Green Light'); $head->description = __('Imports members from the LGL Database that have active website URLs. List from LGL is updated daily. Press submit to update the website Members list to match that of the LGL members list.'); $form->add($head); // add submit button $field = $this->modules->get("InputfieldButton"); $field->type = 'submit'; $field->value = 'Import'; $field->name = 'import'; $form->add($field); return $form->render(); } } public function ___ImportMembers(){ $this->ClearAllMemberProfiles(); $this->GetMemberList(); } private function GetMemberList(){ //download the csv report from the permalink $link = $this->getConfig('permalinkURL'); $f = fopen($this->link, 'r'); $members = stream_get_contents($f); fclose($f); $members = str_replace('"','', $members); $rows = explode("\n", $members); array_shift($rows); foreach($rows as $row => $members) { $row_data = explode(',', $members); if(trim($row_data[0]) !=''){ $info[$row]['id'] = trim($row_data[0]); $info[$row]['name'] = trim($row_data[1]) .' '. trim($row_data[2]); $info[$row]['website'] = trim($row_data[3]); } } $this->GenerateMemberProfiles($info, $memPath); } private function GenerateMemberProfiles($info, $memPath){ foreach($info as $mem) { if(!empty($mem['website'])){ $p = new Page(); $p->template = 'member'; $p->parent = $this->getConfig('membersPath'); $p->name = $mem['name']; $p->title =$mem['name']; $p->save(); $p->memID = $mem['id']; $p->memWebsite = $mem['website']; $p->save(); } } } private function ClearAllMemberProfiles(){ $members = wire('pages')->get('/members')->children(); foreach($members as $mem){ wire('pages')->delete($mem, true); } } public function ___install() { wire('modules')->saveModuleConfigData($this, self::getDefaultData()); $p = new Page(); $p->template = $this->templates->get("admin"); $p->parent = $this->pages->get("template=admin, name=setup"); $p->title = $this->_('Member Importer'); $p->name = __CLASS__; $p->process = $this; $p->save(); } public function ___uninstall() { $p = $this->pages->get('template=admin, name=' . __CLASS__); if ($p->id > 0) { $p->delete(); } } } ?> **Updated to reflect current version1 point
-
I'm not sure what login you mean but for custom login using $session->login(user,password) you'd have to code it like this: //.. try{ $u = $session->login($username, $pass); if($u && $u->id){ $session->redirect("/somepage/"); } } catch(Exception $e) { echo $e->getMessage(); }1 point