Leaderboard
Popular Content
Showing content with the highest reputation on 05/23/2016 in all areas
-
Back in 2013 we at X-com built our very first ProcessWire empowered website: A-Z barbecue & gourmet. It has been running very well for the past 3 years. Technology caught up on us however, and in 2016 it was time for a radical redesign. The old website was not very mobile friendly and the actual shopping feature was iframed and delivery by a third party. Shopping was not very userfriendly either, which led us to believe that an improvement in design and usability was in order. All actual functionality is now handled by performing API calls and Iframes were removed. The new design is simple and straightforward and the first results look promising. The website went live about 3 weeks ago and since then conversion increased on all devices: Desktop 6,08% =>7,27% Tablet 2,48% => 4,01% Mobile 0,5% => 1,36% Go check 'm out! http://www.barbecue.nl http://www.gourmetten.nl http://www.a-z.nl8 points
-
Adapting Soma's code from here, in /site/ready.php: $this->addHookAfter('InputfieldPage::processInput', function($event) { $field = $event->object; if($field->name == "featured_products"){ $count = count($field->value); if( $count < 4 || $count > 6 ) { $page = $this->modules->ProcessPageEdit->getPage(); $old_value = $page->get($field->name); $field->value = $old_value; $field->error("You must select more than 4 and less than 7 products"); } } });8 points
-
I also hate empty paragraphs so gave this a try. Works well for me. This is an interesting one. There is a setting, but it doesn't work as intended. With the help of Tracy Debugger I did a bit of investigating as to why but haven't got to the bottom of it yet. The line intended to replace empty paragraphs in InputfieldCKEditor is this: $value = str_replace(array('<p><br /></p>', '<p> </p>', '<p></p>', '<p> </p>'), '', $value); But it doesn't match the empty paragraphs because $value has already passed through HTML Purifier where gets replaced with some mystery space character. So neither 'nbsp;' nor ' ' match the space character between the paragraph tags. I haven't been able to work out what this space character is because it's rendered like a normal space in the variable dump. --- Update: the mystery character is a UTF-8 encoded non-breaking space character. So the code above should instead be: $value = str_replace(array('<p><br /></p>', '<p> </p>', "<p>\xc2\xa0</p>", '<p></p>', '<p> </p>'), '', $value); Double quotes are needed around the string with the UTF-8 non-breaking space. I'll submit a pull request for this fix.6 points
-
5 points
-
In addition to this, I also recommend reading up about the very basics of Object Oriented Programming, http://www.tutorialspoint.com/php/php_object_oriented.htm at least the concepts behind it. Although in the case of ProcessWire frontend development you are not "forced" to implement anything in OOP, knowing the fundamentals helps a lot to understand the API and its usage. An often used technique in the OOP world is method chaining. You do not have to use it, but when you want to look up someone else's code, you need to how and why it works: http://www.xpertdeveloper.com/2010/09/method-chaining-in-php/ BTW I highly recommend bookmarking http://www.tutorialspoint.com/php/ You can use this method before you jump into your trial-and-error experiment: Whenever you start working on something unfamiliar, say sending emails, study some basics in pure PHP, say: http://www.tutorialspoint.com/php/php_sending_emails.htm After that, look for alternatives in the ProcessWire world: API support, module support Of course, it is recommended to use the API and/or modules whenever you can, but understanding why is so makes working with them easier. Hope this helps, good luck, and never give up!5 points
-
see also this post and my javascript solution: https://processwire.com/talk/topic/11538-pagefield-–-limit-number-of-choices/?p=107504 (and also tpr's modification) should not be too hard to adapt the code. putting everything into a modules would also be nice... it was beyond my head then and now i'm really busy, sorry3 points
-
Hey Soma and Charles, Sorry about the issue with the master/dev version of the Tracy core - this is a recent problem that started 3 days ago: https://github.com/nette/tracy/issues/185 - I was hoping I would get a response that would help me get it going again, but still no luck, but hopefully it will be up and running again soon. Soma - I think the panel positioning/moving issues have all been fixed in the master/dev version. I was contemplating backporting to stable, but the master version has been tagged as RC1, so I think it will be released as stable pretty shortly. In summary, depending on your server you will probably need Stable for the moment - I'll update here as soon as master is reliable again because there are some great improvements in the new version.3 points
-
OK...sigh...because you asked nicely... @note: Using this method, it makes no sense to create a menu with more than 1 level in Menu Builder. The levels will be built using natural child-parent relationship in MSN The CSS attritubes that can be set in MB have no meaning. I see no way of passing those to MSN This was a one-time exercise. Currently, I am not able to support more features for this technique Step 1: Create a 1-level menu in MB Step 2: Grab menu items from the menu created in MB in Step #1 $itemIDs = ''; $mb = $modules->get("MarkupMenuBuilder"); // options 1: get menu items as array $menuItemsArray = $mb->getMenuItems('Ivan Gretsky', 1); foreach($menuItemsArray as $m) $itemIDs .= $m['pages_id'] . '|'; $itemIDs = rtrim($itemIDs, '|'); #### OR #### //option 2: get menu items as object (WireArray) $menuItemsObj = $mb->getMenuItems('Ivan Gretsky'); // implode the WireArray the cool way (PW 2.4+) $itemIDs = $menuItemsObj->implode('|', 'pagesID'); // get a PageArray using menu item IDs $itemsPageArray = $pages->find('id=' . $itemIDs); Step 3: Output the menu using MSN // pass the PageArray to MSN (optionally passing it MSN $options) to output your menu $menu = $modules->get("MarkupSimpleNavigation"); echo $menu->render($options, null, $itemsPageArray);3 points
-
Pagefields afaik are always unique in that they cannot hold a page multiple times. But enforcing the number of items is not possible. Personally I code such things to allow for 0-6 items in my template and just add a note to the field that it'll look bad without at least 4 items and that every access item after the first 6 will not be shown.3 points
-
I have now added a method to MarkupMenuBuilder that makes this quite easy, thanks to your challenge . @Beluga, I have used your example to make a demo using Menu Builder, thanks. @Peter, you will find full examples in Menu Builder's support forum here.3 points
-
One more thing to mention: personally I've really enjoyed the courses from Codecademy. Their system isn't exactly perfect, but it's still pretty awesome, and "learning by doing" has always worked better for me personally than learning from books. If you enjoy a hands-on experience, I'd suggest giving them a try. It's free and they've got an easy-to-understand PHP course for beginners On a related note I wrote a blog post about PHP templating a few years ago. It's mostly about PHP vs. Twig, but could also work as a kind of an introduction to templating in PHP.2 points
-
Thanks for your feedback. You're right, we haven't done any effort in making this site work without javascript turned on. Figures show that the amount of visitors without javascript (excluding bots and crawlers) is pretty much non-existent, so that was a calculated tradeoff.2 points
-
@kongondo should be a little horizontal animation, will check (I use FF). @Ivan Gretsky I'm currently writing a tutorial about ajax and PW so I thought I would make a simple artist profile as a base for the article. Use the profile if you bascially want whats on the demo (lots of artists mainly need simple templates that are clean and show of their works). Seems to work well in Chrome and FF but not great on the animation in safari - will fix soon2 points
-
Quick FYI - I just committed support for using the Validator behind htaccess protected sites. There are new config settings for entering the username and password to be sent with the request. @tpr has just tested for me and it's working great for him, but because I am using wireHttp->get first, and then falling back to file_get_contents, I am not sure if it will work for everyone as is. Not enough time right now to test thoroughly, so let me know if anyone using htaccess protection has any problems with this new one. To quote @tpr - "I can no more hide behind htpasswd and say that "Everything is valid, trust me" :)"2 points
-
Interesting - I was just dealing with something similar with AdminRestrictBranch but solved it a different way by hooking into executeNavJSON Here's the approach just it's helpful for you or someone else: https://github.com/adrianbj/AdminRestrictBranch/blob/master/AdminRestrictBranch.module#L110 PS - I am certainly not suggesting mine is better in anyway - just a different approach to share2 points
-
Check what is $i (use TracyDebugger). Does: $pagesWithImages = wire('pages')->find($fieldname.">0, sort=title"); make a difference (removed .count)? You can also use: $imagedata = $p->getUnformated($fieldname); then you don't need is_array check.2 points
-
Some improvements in the JS: $(window).load(function () { var fieldName = 'tags_recipe', // add field name here limit = 5, // set limit here asmSelector = '#Inputfield_' + fieldName; $(asmSelector).on('change limitItems', function () { var obj = $(this).parent().find('.asmSelect'); $(this).find(':selected').length >= limit ? obj.attr('disabled', 1) : obj.removeAttr('disabled'); }); $(asmSelector).trigger('limitItems'); }); What would be nice is to add the "limit" in the backend to the asmSelect using a "data-limit" attribute and then let JavaScript do its part on all asmSelects which have data-limit set.2 points
-
nice challenge for the evening with a happy end create a javascript file scripts/limitpageselect.js: // my field is called "pageselect" $(window).load(function() { if($('#Inputfield_pageselect :selected').length > 4) $('#Inputfield_pageselect').siblings('select').hide(); $('#Inputfield_pageselect').change(function() { if($('#Inputfield_pageselect :selected').length > 4) { $(this).siblings('select').hide(); } else { $(this).siblings('select').show(); } }); }); and inject this file via hook in /site/ready.php $wire->addHookAfter("ProcessPageEdit::buildForm", function(HookEvent $event){ $process = $event->object; $page = $process->getPage(); if($page->template != 'basic-page') return; // set your template name here $config = wire('config'); $config->scripts->append($config->urls->templates . 'scripts/limitpageselect.js'); });2 points
-
HELLO! Finished a simple Artist Profile for Processwire similar in scope to Indexhibit. On github: https://github.com/benbyford/artist-profile Demo: http://artist.nicegrp.com/1 point
-
I like the idea of turning this module into an issue tracker with debug support Jokes aside: If we could somehow list both php code TODOs and these Notes in an aggregated view, that would be great. When I cannot list all of them, those notes/todos are are kinda useless. Also, I always put my todo list in priority order, my todo items are even further organized into "categories" such as: asap, before launch, after launch, "nice to have in the future", etc... since an unorganized list is not too useful either. But the coolest thing would be to be able to indicate if a Note is only for the developer or for the client too. This way we could place Notes on the pages where they belong to, so that clients checking the site during development can instantly see what is done and what is still under construction, so they do not start reporting bugs, typos, missing elements, etc... in the first place. It could save a lot of time (fewer and shorter discussion/emails exchanged). Better yet, what if clients could also take Notes, I mean open issues belonging to that page? Ok, I stop it here, this is too much already...1 point
-
1 point
-
Thanks for the nice responses. @Sérgio: Unfortunately our hoster currently doesn't support http/2. Not even PHP7, which bothers me. I will definitely look into it, because I haven't tried http/2 out yet, but it sounds promising.1 point
-
1 point
-
https://processwire.com/api/selectors/#or-groups1 point
-
An File field configured as single (max 1), will by default be a single object on front-end (where outputformatting is on), BUT in the backend where no outputformatting is on the field will be an WireArray, regardless of if single or multiple.1 point
-
+1 for the future compatibility between LivePreview and RepeaterMatrix. For me the two go together, the possibility to allow users to create longf-orm content and build pages by adding and reordering of blocks is a great combination of power and usability (in my mind). Thanks again for all the hard work Ryan!1 point
-
Collapsing the PageList tree was introduced in PW 3.0.13 - see this Blog entry: I would like to suggest that collapsing the tree should work stepwise - in that way, that in each step only the lowest level is affected. Imagine this partially unfolded tree: Home - One-A 3 -- Two-A1 2 --- Three-A11 --- Three-A12 -- Two-A2 2 -- Two-A3 2 - One-B 2 -- Two-B1 4 -- Two-B2 3 - One-C 3 -- Two-C1 -- Two-C2 1 --- Three-C21 -- Two-C3 2 1st click on "Home" would result in Home - One-A 3 -- Two-A1 2 -- Two-A2 2 -- Two-A3 2 - One-B 2 -- Two-B1 4 -- Two-B2 3 - One-C 3 -- Two-C1 -- Two-C2 1 -- Two-C3 2 2nd click: Home - One-A 3 - One-B 2 - One-C 3 I'd find this very handy - what do you think?1 point
-
It works well with PW3. This is a twig issue. You have to use: {% if page['images'] %} Have a look at this page: How to check if a variable or value exists, this helped me a lot. Or try to change the Formatted value in the field settings to Array of items. Then you should be able to test using length because this way it returns an empty array.1 point
-
The modules limit is set to 350 currently. But there seems to be more than that already. If I set the config to 400 Tracy shows up. There's a hard limit on the webservice in modules.processwire.com for how many modules can be listed in one go, but can't remember how high Ryan set. ModulesManager would need some recoding regarding the limit, but haven't got time and a good idea how to handle it using pagination. However it's always possible to use the built in module installer.1 point
-
For PW 2.x it's difficult to avoid caching problems for your linked CKEditor JS and CSS files. One solution, albeit tedious, is to append a cachebusting query string to your URLs and update it every time you change the files. So in your field settings something like: /site/modules/InputfieldCKEditor/contents.css?2 This has been fixed in one of the PW 3.x releases.1 point
-
Just for the info: latest Nette/Latte plugin for PhpStorm works fine - syntax highlight, autocomplete and such thing works like charm. I will update the module readme later.1 point
-
Maybe i don't see it but how can i delete pages with admin template? PW 3.0.18 logged in as superadmin - advanced mode on... status: System: Non-deleteable and locked ID, name, template, parent (status not removeable via API) is checked...but i wanna remove this page it's from a failed installation of a module...i've deleted module files and db entries...so far. But the admin page stays.. Regards mr-fan1 point
-
1 point
-
Great - thanks for testing and letting me know! Back to the authorization issue - it is possible to send the username and password so it's possible to actually get the html of the page so it can be sent to the validator - let me know if you think that would be a worthwhile addition. Speaking of new functionality, I have just had an idea for a "Notes" panel. The content would be unique to each page and the idea is that it would be a place to store notes about changes that are still needed for the page. I would store the info either in a dedicated sql table, or perhaps in JSON in a text file that can be parsed on load. I know most of us make use of services like trello or similar for keeping track of tasks during development, but I am wondering if this might also be useful because the notes are available in the context of the viewed page. I am also working on a TODO panel which parses out //TODO, //FIXME etc from your php scripts. Not sure if there is too much overlap between these two panels. I know that when I use //TODO in my code it's usually about code improvements/refactoring etc, whereas I think the Notes panel would be more about content/layout/functionality changes. Anyway, I'd really appreciate hearing everyone's thoughts on whether they would find either of these useful and what the ideal approach for your workflow might be.1 point
-
I think Emogrifier can be handy but should it be part of the module? Why not a different module, so it can work without latte too?1 point
-
Depending on which method of adding the frontend editing you're using you migh need to specify the field and the pageid: https://processwire.com/blog/posts/front-end-editing-now-in-processwire-3.0-alpha-4/1 point
-
Update: version 0.1.5 Changelog Added new method getMenuItems() to MarkupMenuBuilder that greatly simplifies creation of custom complex menus. Read more about it in previous (long) post.1 point
-
Been getting questions on how to build complex/custom menus. Even with the ability to pass it custom options, some menus are a bit more complex than what Menu Builder offers out of the box. My answer to such questions has, to date, been get the JSON and use that to build your menu. Not very easy for some....but no more...thanks to @Beluga, @Peter and @Webrocker + others for the inspiration/challenges. I've now added a method in MarkupMenuBuilder to make building custom complex menus a breeze. Don't get me wrong, you still have to know your foreach loops or even better grab one of the many recursive list/menu functions in the forums (or StackOverflow) and adapt it to your needs. Don't worry though, below, I provide a couple of complete code examples using recursive functions. For a 2-level deep menu, even a nested foreach would do. Going forward, this new method is what I recommend for building complex menus if using Menu Builder. The new method is called getMenuItems($menu, $type = 2, $options = null) takes 3 arguments. Before I show you the cool things you can do with this method, let's get familiar with the arguments. $menu: This is identical to the first argument in MarkupMenuBuilder's method render(): Use this argument to tell getMenuItems() the menu whose items you want returned. The argument takes a Page, id, title, name or array of menu items $type: Whether to return a normal array or a Menu object (WireArray) of menu items $options: Similar to render() method options but please note that only 3 options (from the list of those applicable to render()) apply to getMenuItems(). These are default_title, default_class and current_class_level. default_class is applied to the item's property $m['ccss_itemclass']. Probably not many know that MarkupMenuBuilder ships with a tiny but useful internal class called Menu. MenuBuilder uses it internally to build Menu objects that are finally used to build menus. Menu objects are WireArrays. If $type == 2, this is what is returned. Array vs WireArray So, which type of output should you return using getMenuItems()? Well, it depends on your needs. Personally, I'd go for the Menu object. Here's why: Although you can still easily build menus by using getMenuItems() to return a normal PHP Array, it's not nearly as powerful as returning and using a WireArray Menu object instead. Whichever type of items you return using getMenuItems(), it means you can manipulate or apply logic before or within a recursive function (or foreach loop) to each of your menu items. For instance, show some parts of the menu only to users who are logged in, or get extra details from a field of the page represented by the menu item, add images to your menu items, etc. Grabbing the Menu object means you can easily add some runtime properties to each Menu object (i.e. each menu item). If you went with a normal array, of course, you can also manipulate it, but not as easily as working with an object. A Menu object also means you have access to the powerful WireArray methods (don't touch sort though!). For instance, $menuItems->find("parentID=$m->id"). With a Menu object, you also get to avoid annoying isset(var) that come with arrays . Here are the properties that come with each Menu object. Use these to control your logic and output menu items values. In that block of code, to the left are the indices you'd get with a normal array. The values (to the right) are the Menu object properties. Below are examples of building the W3Bits 'CSS-only responsive multi-level menu' as illustrated in the tutorial by @Beluga. We use 3 different recursive functions to build the menu using items returned by getMenuItems(). I will eventually expound on and add the examples to my Menu Builder site. Meanwhile, here's a (very colourful) demo. Examples @note: The CSS is the one by @Beluga in the tutorial linked to above. @note: Clearer examples can be found in these gists. First, we grab menu items and feed those to our recursive functions. $mb = $modules->get('MarkupMenuBuilder');// get Menu Builder // get menu raw menu items. $menu can be a Page, an ID, a name, a title or an array #$menu = $pages->get(1299);// pass a Page #$menu = 1299;// pass an ID #$menu = 'main';// pass a name $jsonStr = $pages->get(1299)->menu_items; $arrayFromJSON = json_decode($jsonStr, true); #$menu = $arrayFromJSON;// pass an array $menu = 'Main';// pass a title /** grab menu items as WireArray with Menu objects **/ // for examples 1a, 2 and 3 $menuItems = $mb->getMenuItems($menu, 2, $options);// called with options and 2nd argument = 2 {return Menu (WireArray object)} #$menuItems = $mb->getMenuItems($menu);// called without options; 2nd argument defaults to 2 /** grab menu items as Normal Array with Menu items **/ // only for example 1b below menuItems2 = $mb->getMenuItems($menu, 1);// called without options; 2nd argument is 1 so return array Example 1a: Using some recursive function and a Menu object /** * Builds a nested list (menu items) of a single menu. * * A recursive function to display nested list of menu items. * * @access private * @param Int $parent ID of menu item. * @param Array $menu Object of menu items to display. * @param Int $first Helper variable to designate first menu item. * @return string $out. * */ function buildMenuFromObject($parent = 0, $menu, $first = 0) { $out = ''; $has_child = false; foreach ($menu as $m) { $newtab = $m->newtab ? " target='_blank'" : ''; // if this menu item is a parent; create the sub-items/child-menu-items if ($m->parentID == $parent) {// if this menu item is a parent; create the inner-items/child-menu-items // if this is the first child if ($has_child === false) { $has_child = true;// This is a parent if ($first == 0){ $out .= "<ul class='main-menu cf'>\n"; $first = 1; } else $out .= "\n<ul class='sub-menu'>\n"; } $class = $m->isCurrent ? ' class="current"' : ''; // a menu item $out .= '<li' . $class . '><a href="' . $m->url . '"' . $newtab . '>' . $m->title; // if menu item has children if ($m->isParent) { $out .= '<span class="drop-icon">▼</span>' . '<label title="Toggle Drop-down" class="drop-icon" for="' . wire('sanitizer')->pageName($m->title) . '" onclick>▼</label>' . '</a>' . '<input type="checkbox" id="' . wire('sanitizer')->pageName($m->title) . '">'; } else $out .= '</a>'; // call function again to generate nested list for sub-menu items belonging to this menu item. $out .= buildMenuFromObject($m->id, $menu, $first); $out .= "</li>\n"; }// end if parent }// end foreach if ($has_child === true) $out .= "</ul>\n"; return $out; } Example 1b: Using some recursive function and a Menu array /** * Builds a nested list (menu items) of a single menu from an Array of menu items. * * A recursive function to display nested list of menu items. * * @access private * @param Int $parent ID of menu item. * @param Array $menu Array of menu items to display. * @param Int $first Helper variable to designate first menu item. * @return string $out. * */ function buildMenuFromArray($parent = 0, $menu, $first = 0) { $out = ''; $has_child = false; foreach ($menu as $id => $m) { $parentID = isset($m['parent_id']) ? $m['parent_id'] : 0; $newtab = isset($m['newtab']) && $m['newtab'] ? " target='_blank'" : ''; // if this menu item is a parent; create the sub-items/child-menu-items if ($parentID == $parent) {// if this menu item is a parent; create the inner-items/child-menu-items // if this is the first child if ($has_child === false) { $has_child = true;// This is a parent if ($first == 0){ $out .= "<ul class='main-menu cf'>\n"; $first = 1; } else $out .= "\n<ul class='sub-menu'>\n"; } $class = isset($m['is_current']) && $m['is_current'] ? ' class="current"' : ''; // a menu item $out .= '<li' . $class . '><a href="' . $m['url'] . '"' . $newtab . '>' . $m['title']; // if menu item has children if (isset($m['is_parent']) && $m['is_parent']) { $out .= '<span class="drop-icon">▼</span>' . '<label title="Toggle Drop-down" class="drop-icon" for="' . wire('sanitizer')->pageName($m['title']) . '" onclick>▼</label>' . '</a>' . '<input type="checkbox" id="' . wire('sanitizer')->pageName($m['title']) . '">'; } else $out .= '</a>'; // call function again to generate nested list for sub-menu items belonging to this menu item. $out .= buildMenuFromArray($id, $menu, $first); $out .= "</li>\n"; }// end if parent }// end foreach if ($has_child === true) $out .= "</ul>\n"; return $out; } For example 1a and 1b we call the respective functions to output the menu <div id="content"> <nav id="mainMenu"> <label for='tm' id='toggle-menu' onclick>Navigation <span class='drop-icon'>▼</span></label> <input id='tm' type='checkbox'> <?php // build menu from Menu object (example 1a) echo buildMenuFromObject(0, $menuItems); // OR build menu from array (example 1b) #echo buildMenuFromArray(0, $menuItems2); ?> </nav> </div> Example 2: Using a modified version of @mindplay.dk's recursive function /** * Recursively traverse and visit every child item in an array|object of Menu items. * * @param Menu item parent ID $parent to start traversal from. * @param callable $enter function to call upon visiting a child menu item. * @param callable|null $exit function to call after visiting a child menu item (and all of its children). * @param Menu Object|Array $menuItems to traverse. * * @see Modified From mindplay.dk https://processwire.com/talk/topic/110-recursive-navigation/#entry28241 */ function visit($parent, $enter, $exit=null, $menuItems) { foreach ($menuItems as $m) { if ($m->parentID == $parent) { call_user_func($enter, $m); if ($m->isParent) visit($m->id, $enter, $exit, $menuItems); if ($exit) call_user_func($exit, $m); } } } For example 2, we call the function (@note: a bit different from example 1 and 3) this way to output the menu <div id="content"> <nav id="mainMenu"> <label for='tm' id='toggle-menu' onclick>Navigation <span class='drop-icon'>▼</span></label> <input id='tm' type='checkbox'> <?php echo "<ul class='main-menu cf'>"; visit( 0// start from the top items , // function $enter: <li> for a single menu item function($menuItem) { echo '<li><a href="' . $menuItem->url . '">' . $menuItem->title; if ($menuItem->isParent) { echo '<span class="drop-icon">▼</span>' . #'<label title="Toggle Drop-down" class="drop-icon" for="' . wire('sanitizer')->pageName($menuItem->title) . '" onclick>▼</label>' . '<label title="Toggle Drop-down" class="drop-icon" for="sm' . $menuItem->id . '" onclick>▼</label>' . '</a>' . #'<input type="checkbox" id="' . wire('sanitizer')->pageName($menuItem->title) . '"><ul class="sub-menu">' . '<input type="checkbox" id="sm' . $menuItem->id . '"><ul class="sub-menu">'; } else echo '</a>'; }// end function 1 ($enter) , #function $exit: close menu item <li> and sub-menu <ul> tags function($menuItem) { if ($menuItem->isParent) echo '</ul>'; echo '</li>'; }, $menuItems// the menu items (Menu objects in this example) ); ?> </nav> </div> Example 3: Using a modified version of @slkwrm's recursive function /** * Recursively traverse and visit every child item in an array|object of Menu items. * * @param Menu Object|Array $menuItems to traverse. * @param Int $parent ID to start traversal from. * @param Int $depth Depth of sub-menus. * @param Int $first Helper variable to designate first menu item. * @see Modified From @slkwrm * @return string $out. */ function treeMenu($menuItems, $parent, $depth = 1, $first = 0) { $depth -= 1; if ($first == 0){ $out = "\n<ul class='main-menu cf'>"; $first = 1; } else $out = "\n<ul class='sub-menu'>"; foreach($menuItems as $m) { if ($m->parentID == $parent) { $sub = ''; $out .= "\n\t<li>\n\t\t<a href='" . $m->url . "'>" . $m->title; if($m->isParent && $depth > 0 ) { $sub = str_replace("\n", "\n\t\t", treeMenu($menuItems, $m->id, $depth, $first)); $out .= '<span class="drop-icon">▼</span>' . '<label title="Toggle Drop-down" class="drop-icon" for="sm' . $m->id . '" onclick>▼</label>' . '</a>' . '<input type="checkbox" id="sm' . $m->id . '">' . $sub . "\n\t"; } else $out .= "</a>\n\t"; $out .="\n\t</li>"; } }// end foreach $out .= "\n</ul>"; return $out; } For example 3, we call the function to output the menu <div id="content"> <nav id="mainMenu"> <label for='tm' id='toggle-menu' onclick>Navigation <span class='drop-icon'>▼</span></label> <input id='tm' type='checkbox'> <?php //parameters: menuItems, menu item parent ID, depth, first (helper variable) echo treeMenu($menuItems, 0, 4); ?> </nav> </div>1 point
-
1 point
-
as a follow up to this, i have a module for image tags, using selectize.js; 1.) install the shared js lib: https://github.com/outflux3/JquerySelectize 2.) install the image tags module https://github.com/outflux3/SelectizeImageTags i have it working on several sites without issues, but this module should be considered Alpha. Documentation is also at an early stage. please report any issues or questions here. Once these are all stable and have had some testing/documentation, they will be added to the modules directory. (note these represent the start of a series of modules that utilize selectize.js, including an awesome page select Inputfield)1 point
-
Partly yes - it says Validated but the panel contains too much info - or is this normal? It seems that the two validators have different markups or perhaps the html5 validator page markup was changed. The green bordered line is a "p" tag with "success" class. Maybe I see this because I'm the only one here writing valid markup?1 point
-
Well that's the main thing, figuring out a system that integrates well into PW and is simple enough. See the images below of my current newsletter page in the admin and the final outcome. There's a dedicated Newsletters page in the root page tree, newsletters are listed below them. This is useful because newsletters have their own URLs so "View in browser" is easy to implement. The subscribers have another page in the root, each subscriber is a page. They can be active or inactive, based on the page status published or not. I use simpe subscription, no confirmation emails. I do not use subscriber lists, this should be also implemented somehow. I use wireMail() for sending (with MailGun), but queing would be also nice. It would be nicer to have a new nav item in the admin "Newsletter", and move the newsletters and subscribers there. Anyway, this is what I've got, maybe these can generate some ideas.1 point
-
That makes such a huge difference - I have a query (that I was obviously caching) which was taking around 4-5 minutes, that now happens in 45seconds! Obviously I still need to optimize some other components, but this is a huge improvement - thanks to everyone involved in this!1 point
-
Very nice and clean, congratulations! My only suggestion, as you are already using https, is to enable http/2, it will be faster!1 point
-
In other words: processwire does not limit you as a developer. So obvious, but didn't cross my mind so far to put it in these words. Added this one to my list of processwire arguements.1 point
-
Very impressive (on mobile at least). I loved the "negative color" effect between sections.1 point
-
From a noob on the outside looking in, I feel a need to state the obvious. I have been spending a fair amount of my time lately installing and working with a few of the 'popular' CMS offerings. I haven't honestly decided what product I'm going to go with yet. For the type of sites I hope to be doing, PW might be overkill. But that's off topic here. Just speaking from my own observations so far.... The Processwire forums and community easily outshines the others ... overwhelmingly. I am not going to name names and I'm just guessing that the accompanying argument from the competition would be something like 'Well , there's only so many hours in a day, and we're a small group of developers with other jobs, family , etc , so whatever time we can spare, we put it into development , patching, etc.'. And that argument is a completely valid one. The point is though, that it appears a number of people involved with PW, with I'm sure the same obligations, ARE taking the time to make the community here what it is - very knowledgeable , very friendly and VERY valuable to those who otherwise might be Googling for hours on end looking for a solution, and we all know how much fun that can be. Well maybe not all of us, but I know I have been there on more than one occasion because the so called documentation was outdated, couldn't find an answer in the 'forum' and I wasn't in the mood to walk through the code with XDebug that day. Anyway, I have rambled enough. Thanks again to ALL. You ALL have a GREAT community here.1 point
-
Fantastic. Been waiting very long for something that takes me back to the days of Rails migrations! I will be doing a screencast on this in my WP vs. PW series. Just in time!1 point
-
Workers are separate processes, which run alongside your webserver stack on the host system. They can process things they get out of the queue without being reliant on the Request->Process->Response cycle of the webserver. E.g. You want to have a way for users to request a big report on your website. As a big report sounds like, it take some time to generate the pdf for it (say ~1 Min). You wouldn't want your user to sit around and watch the nice browser spinner, waiting for the next page to pop up. You can now write the request in a queue and instantly return a message stating that the report will be generated and emailed to the user when ready. To generate that report you may know the option of using a cron to trigger website logic, without an actual user requesting a website, but that's also flawed with things like timeouts and such, also it's only starting in intervals. That's where workers come in. These are php (and other script) processes, which often run as services like e.g. your apache server does. These scripts contain some way to not exit early (e.g. infinite while loop) – which is the biggest difference to website request – and periodically check their queue if there's work to do. If your report request is in it, they generate the pdf, email it and if done delete the queue item. If there are other reports to do they'll move on to them, otherwise they sleep until another queue item is available.1 point
-
Usually this is a good and simple method: In a autoload module you hook into the processInput of InputfieldTextarea. public function init(){ $this->addHookAfter("InputfieldTextarea::processInput", $this, "validateText"); } public function validateText($event){ $field = $event->object; if($field->name == "body"){ $page = $this->modules->ProcessPageEdit->getPage(); $oldValue = $page->get("$field->name"); $newValue = $field->value; echo "old value: " . $oldValue; echo "new value: " . $newValue; if($newValue !== $oldValue){ $field->value = $oldValue; $field->error("Go away!"); } } } For a error message you simply add error("text") to the field. It will get showed after saving. What this examples also shows nicely is how to get the old value and new value. The code may slightly varies for different type of fields but the principle is always the same. There's also a method to hook into Pages::saveReady() but I'm not sure what the advanced really are atm.1 point