Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 01/16/2019 in all areas

  1. Just celebrated the creation of my 25000th page on the first website www.birthfactdeathcalendar.net I made with ProcessWire, about five years ago. 25000 pages means over 17000 individual public pages and a few thousand as selectlists (cities, states, countries, occupations,....). All of this would never have been possible with the great support of members here. Thank you all! And the lucky winner is Sabine Pass, Texas, United States, where Jiles Perry Richardson aka The Big Bopper was born on 24 October 1930.
    7 points
  2. OT, but though to mention it here. I have been working on a site profile (still in alpha >>> it's been ages! other things got in the way) inspired by the Electron API Demos app that will do this and more. Essentially, it teaches you to use the API. On install, you get nothing but the 'blank profile'. You choose a topic and It teaches how to accomplish what is covered in that topic, e.g. the AP to create/edit/delete fields, templates, pages, etc.
    6 points
  3. Sorry for the confusion @Robin S - it was a failed attempt at humor ? I was trying to make the point that there are way too many options - some are intentionally provided and some are just due to class inheritance etc, but I personally think we should be very careful to document just one default approach for all examples and then properly explain the other options and where they can/need to be different to the chosen default approach.
    5 points
  4. Right – I might've misunderstood those posts, thought you were referring to the demo site. Probably just me not paying enough attention ? I've been doing JS related work recently, and found it really helpful how some libraries provide these interactive code blocks on promo sites and/or docs: "here's our example, here's what it does – try modifying it and see how that'll change things." Another use case are incomplete (or even empty) code blocks urging the user to figure out how to produce some predefined output from them. Personally I dislike learning from docs alone, so this approach provides the best of both worlds: docs so I don't feel like I have to figure everything out myself, and editable/executable code to give me the opportunity to learn things by doing (and making mistakes along the way) ? Now that you mention this, I've done that on a few occasions too. While this may be a bit of an edge case, it's a good example of how something that may seems like an issue at first (overriding API variables by accident) can also be a good thing once you know what you're doing ? (By the way, it seems that the wire-prefixed versions of these were missing from those lists of "all the different ways to access one API variable". Not sure how often people actually use wirePage() etc... but they've probably found their way to some projects out there. And, as is explained in the blog post introducing Functions API and in the FunctionsWireAPI.php file, this version has the advantage (over regular Functions API) of always being available.)
    4 points
  5. I've suggested this a couple of times above - making use of the Tracy Console panel - either as is, or more likely using the code behind it to have something that can be embedded in an iframe on the page itself.
    4 points
  6. Hello, we found the following solution that is quite similar to the suggestion from Autofahrn. Our setup includes two languages - de (german) as default for *.de domain (default language) - en (english) for *.com domain In _init.php there is just: if (preg_match("/(:2284|\.com)/", $config->httpHost)) { $user->language = $languages->get('englisch'); } Your question no. 2: It is important that you configure the settings for your root page to "/" for each language. In that case PW doesn't prefix your URL paths for the children. Be careful! In case you use the front page editor - when a child page has the same path for every language, the front page editor is not able to save the page correctly. You have to use the backend. But if there are differences, for example *.com/imprint and *.de/impressum - the front page editor works fine.
    3 points
  7. Clever idea. I've just test-implemented that on one of my multi-language sites with .de and .com domains and it works pretty smooth following these steps: all DNS entries point to the same PW instance root in site/config.php I've added all allowed domain names in _init.php I've quickly added this (only for com, should match a field in language setting): if(strpos($config->httpHost, ".com") > 0) { foreach($languages as $lang) { if($lang->name == 'en') { $user->language = $lang; break; } } } That's it, now all language fields follow the selected language. Switching between languages is now switching between domain names. Thanks for that hint!
    3 points
  8. Me too. I also think that "mixing it up" for a newcomer the various ways to use the API is not a good idea, even the tabbed interface idea (which is great) feels a bit too much to present to a beginner. If the function API is the preferable one on the frontend, then all frontend examples should use that one only and only module related docs should use another one (which one is preferable in that case). There should be an article explaining all the other ways to do the very similar, and only in the "advanced" category. BTW, some docs tag pages/tutorials as beginner/intermediate/advanced. I think ProcessWire docs could use those too.
    3 points
  9. I have used these functions in the past for search excerpts: https://github.com/boyter/php-excerpt Together with this jQuery library for highlighting: http://bartaz.github.io/sandbox.js/jquery.highlight.html You might like to consider maintaining a hidden "index" field in your templates that merges the text content from other fields in a Page::saveReady hook. Then you use this field for searching/excerpting/highlighting.
    3 points
  10. New version committed and it is now named Admin Tools.
    2 points
  11. @kongondo First. Thank you for this great module! I'm writing this in the hope it can help someone. I'm using the latest PW version. Using the module I received this error when switching to a different language from the default one. No menu items found! To solve the issue I went in the admin and find the Menu Builder page (under Admin>Setup) then I edited both the "Menu Builder" page and "mainmenu" page (this a menu that I built in Menu Builder) and set the other language to active. It seems the module doesn't set as active new pages in languages different from default. My PW installation by default set it to false. Hope this helps. Ciao
    2 points
  12. On the issue of page() vs $page it's actually unfortunate that page() can't be overwritten because I often use the Tracy console in the admin when editing a page. $page is automatically set to the page you are editing, but of course page() will return the "/admin/page/edit/" page. I don't think this is a valid reason to NOT promote the use of page() as the default approach, but it is something to be aware of.
    2 points
  13. @psy, Thanks for the info. I have not been able to replicate this. I've used your example HTML strings and it works just fine. Page Edit renders fine. I even tried with invalid/broken HTML and nothing breaks; it works OK. I missed this earlier. What do you mean 'format it'? I'm wondering if your string is maybe returning invalid HTML that somewhere trips the browser? Just guessing. Other than that, RuntimeMarkup's CSS and JS files are blank; they never return anything. I need to remove them actually as they don't do anything. If all else fails, I'm happy to have a look around your install if that's possible.
    2 points
  14. I am relativelay new to Processwire and started about 3 weeks ago with it. I tried all tutorials, read a lot in the forums and played around with several provided code snippets here and I must say: Wow! I used to use Wordpress as I could get things done with plugins all the way through but with Processwire I feel like I have everything in control as I "have to" take care of everything I want to accomplish. I spend every free minute learning Processwire and learned more about PHP (and Processwire) than ever before. I wasn't and still be not a programmer but Processwire gives me the ability to become one. I read people saying "Processwire is your CMS of choice" but I think i also helps you to understand the basic of PHP programming. if/else or foreach or whatever... Processwire give me what PHP tutorials are missing. Maybe in a week or two I already can publish my very first and "self programmed" website ever. Without Wordpress or any plugins from other people. Thank you all for giving those verbose code snippets that every newbie understands! P.S. I hope this is the right place to post this. Sorry if not.
    2 points
  15. I've been following this topic from the sidelines with mixed feelings. I've always considered ProcessWire's API exceptionally clean and easy to understand, and technically that hasn't changed, but I do find it confusing as well that there are so many ways to achieve essentially the same thing – except that some will only work on certain situations (bootstrap, templates, modules, multi-instance, ...) or may have performance benefits or perhaps won't be available on all sites depending on config settings, etc. I'm very eager to hear what you've got to say about this, and I'm hoping that it'll make everything crystal clear again ? --- Either way, the tab idea (different approaches in different tabs) is, in my opinion, awesome. Combined with per-tab instructions on where and how to use each approach (could be a tooltip with short summary and a link to full documentation page) this would be a huge improvement. If functions API calls are shorter, then show those by default – it won't take anything away from the marketing / functions API promotion perspective, and it may in fact give users a better understanding of what they're missing if they're not using the functions API. --- On a completely different topic, one thing I've been wondering is if it would make sense to provide some kind of "interactive editor" for the API features – "tweak the code and see the result here". I know, ProcessWire isn't a JavaScript app, and the API has loads of stuff that wouldn't make any sense to implement in JavaScript, but perhaps as some kind of a virtual box (not sure how one would set that up securely, though) or a limited scope JavaScript mock API with a set of mock data behind it? Just an idea for now.
    2 points
  16. I don't think that there's an existing feature for this, but even if you could do this, I would still suggest not doing it. Usually it would be a better idea to display a smaller image in a bigger size in the front-end, rather than to make the end-user download a bigger image with essentially the same content ?
    2 points
  17. Absolutely this needs to be covered in the official documentation. It's foundational stuff and too important leave to a scattering of forum discussions. I'd hazard to say there are many here who have been working with PW for years and are not 100% clear on this stuff (myself included).
    2 points
  18. Context Tools or Context Actions? Contextions? ?
    2 points
  19. In the template context, how can $page be any page unless you have overridden it, which we always discourage. Otherwise, $page is always the current page. I do understand that page() can't be overridden like $page can, so maybe going forward the functions API should be the recommended approach, but we are going to confuse the hell out of beginners if homepage examples show both approaches. But the save() example won't work as is and will confuse the user to see an error about outputformatting which they will not understand anything about yet. We have setAndSave() - why not use that example? Same goes for my observation about the image example and the issue with the need for first() on the site profiles which I think mostly come with "images" fields, rather than an "image" field. As a new user to a system all it often takes is one unexpected error early on to send them packing - I know I have done that myself. Sorry to be so blunt but I feel like many of us here have expresses our concerns regarding this - either through lots of likes on my posts above about it, or through their own comments. Please accept these comments in the spirit they are intended, which is to help make PW easy for beginners to fall in love with.
    2 points
  20. It's been mentioned a couple of times. I think functions API should lead the examples, but I understand the concern. Per the mention in last week's post, I'll be writing up all the details this coming week. Take a look, and if I haven't convinced you guys at that point, I'll be happy to change the examples. The only thing is, we'll probably need to come up with new examples because they get too long without the functions API. And at that point, they no longer serve the intended purpose at least on the marketing aspect. Favicons now added. The same could be said of using an API variable inside of a function or method. These are short examples to get you interested, they lack surrounding context regardless of what style API you are using with PW, and there will be cases where they work and where they don't either way. We're just trying to give people a small sip to see if it tastes good, not show them how to brew the beer. However, with that said, the intention is that they will work out of the box. They do work on the dev branch right now (which is the branch we recommend for new installs). This will also merge to master again soon. The examples have always worked on the core Regular site profile as well. Error messages have also been added to detect when you use a functions API call without them enabled, and it then tells you exactly what you need to do to enable it. As far as the core goes, the values set in /wire/config.php have to be settings that work on all existing installations. Whereas the values set in /site/config.php are those intended for new installations. We can't enable the functions API on existing installations—only new installations. The reason is that someone may have defined their own pages() function (for example) years before we even had a functions API, and upgrading to a version of PW that has it pre-enabled would then break their installation. I make an effort to ensure anything that gets added in PW is done so in a backwards compatible manner, and this is why there are properties like $config->installed and independent config files. The mix of approaches is actually relevant here because anything using a function refers to something where only one instance can exist, and anything using a variable refers to something that can have any number of instances. So I would use a variable like $page in an example when the intention is to say "can be any page", whereas I would use page() when the intention is to say "page being viewed", as there can only be one. That is one benefit, but definitely not the only one. Read what I have to say later this week. Context is important here and the statement above skips that. I'm recommending the functions API for front-end templates, where PW manages the instance for you. But this is also where our audience is going to be 99% of the time. Of course if you are developing modules, then neither pages() or $pages is right for you, as you are going to need to ask for them from $this. I'm not trying to appeal to module developers, they already have a good handle on this stuff. I went back and forth on this one a couple weeks ago, but settled on the centered version after a lot of testing. Centered felt much more balanced, perhaps because the lines are so short that all the weight ends up on the left side. These are just short snippets of text, but if it were any longer then no doubt we'd want to go left aligned. Good one and I agree. I think Robin S. also mentioned this if I recall, and I do have it on my list to add this capability. Uikit 3 doesn't come with an autocomplete component (Uikit 2 did), and we don't have one to use from jQuery UI like we do in the admin (since not using jQuery UI on this site). So the current autocomplete search is completely homegrown. I don't really know how to add the arrow key nav to this at the moment but do plan to hopefully figure it out soon. Between the existing wire highlight pointer, and the existing darkened text, and then add on that the text usually matches that of the headline, to me this just seems like way too much emphasis and redundancy if we also add a color change to it as well? At least my opinion is I don't want my eyes to be drawn to that unless I'm specifically looking for it.
    2 points
  21. The Page Hit Counter module for ProcessWire implements a simple page view counter in backend. Page views of visitors are automatically tracked on defined templates, with monitoring of multiple page views. This gives you a quick overview of how many visitors have read a news or a blog post, for example, without first having to open complex tools such as Google Analytics. This module quickly provides simple information, e.g. for editors. Or, for example, to sort certain news by most page views. For example for "Trending Topics". Works with ProCache and AdBlockers. With a lightweight tracking code of only ~320 bytes (gzipped). And no code changes necessary! In addition GDPR compliant, since no personal data or IP addresses are stored. Only session cookies are stored without information. In addition, there are some options, for example filtering IP addresses (for CronJobs) and filtering bots, spiders and crawlers. You can also configure the lifetime of the session cookies. Repeated page views are not counted during this period. It is also possible to exclude certain roles from tracking. For example, logged in editors who work on a page are not counted as page views. Sort by hits and access page views (hit value) Each trackable template has an additional field called phits. For example, you want to output all news sorted by the number of page views. // It is assumed that the template, e.g. with the name "news", has been configured for tracking. $news = $pages->find("template=news, sort=-phits"); To output the page views of a tracked page, use: echo $page->phits; Example: Reset counter per API $modules->get("PageHitCounter")->resetPageViews("template=whatever", false); Example: Tracking a page hit via API and jQuery If you want to track a template that does not represent a full page to automatically inject a tracking script, you can define allowed API templates in the module that you can track. Below is an example of how you can track a click on news tag using jQuery. This will allow you to find out which keywords are clicked the most. For example, you can sort and display a tag cloud by the number of hits. Suppose your keywords have the template "news_tag". The template "news_tag" was also configured in the Page Hit Counter Module as a trackable API template. Example PHP output of keywords / tags: // Required: the data attribute "data-pid" with the ID of the template to be tracked. echo $pages->find("template=news_tag, sort=-phits")->each("<a href='{url}' class='news_tag' data-pid='{id}'>{title}</a>"); Example Tracking Script with jQuery: /** * Required: Data attribute "data-pid" with the ID of the news tag template * Required: Send the POST request to the URL "location.pathname.replace(/\/?$/, '/') + 'phcv1'" * Required: The POST parameter "pid" with the ID of the template */ $(function(){ if($('a.news_tag').length > 0) { $('a.news_tag').each(function(){ var tPID = $(this).data("pid"); if(tPID) { $(this).on("click", function(){ $.post(location.pathname.replace(/\/?$/, '/') + 'phcv1', {pid: tPID}); }); } }); } }); So simply every click on a tag is counted. Including all checks as for automatic tracking. Like Bot Filtering, Session Lifetime, etc. Notice: Tracking with URL segments If the option "Allow URL Segments" is activated on a template, the hits are only counted if the base URL of the page is called. If you want the hit to be counted even when a segment is requested, you MUST configure the segments in the template configuration. How to do this can be found here. If you use dynamic segments, configure them as RegEx. There is currently no other option. The problem is that the Page Hit Counter hooked into the PageNotFound process. If URL segments are allowed but not defined, a 404 is never triggered. This means that the Page Hit Counter cannot be called. New since 2.0.0: Ignore URL segments If a template has URL segments configured, each hit on a different segment is counted as a new hit. Enable "Ignore URL segments" so that dynamic segments are not counted individually on the base template / page. New since 2.0.0: Use cookieless tracking (Experimental) Enable this option to not use individual cookies for tracking or if you have many different pages you want to track. The limit for cookies is 50 per domain for all cookies on the page. If the option is enabled, PHP session storage is used. Downside: you can't set the lifetime higher than configured in your PHP.ini and the session will be terminated as soon as the browser is closed. Upgrade note for 2.0.0 from previous versions! Version 2.0.0 requires an update in the database schema, so that additionally the date of the last access / hit on the page can be displayed ($page->lastPageHit). To make this possible, you have to do the update via the upgrade module, upload the ZIP itself and do an update directly via the backend AND DO A MODULE REFRESH DIRECTLY AFTER UPLOAD/UPDATE. If you do not do this, you will get an error that a column is missing in the database table. _______________________________________________________ Background: This module is the result of a customer requirement, where the editors are overwhelmed with analytics or no tracking tools were allowed to be used. However, a way had to be found to at least count page views in a simple form for evaluations. Furthermore, by using ProCache, a way had to be found to count views of a page without clearing the cache. _______________________________________________________ Pros Automatic Page View Tracking Lightweight tracking code, only ~320 bytes (gzipped) No code or frontend changes necessary Works with ProCache! Even if no PHP is executed on the cached page, the tracking works Works with browser AdBlockers No cache triggers (for example, ProCache) are triggered. The cache remains persistent GDPR compliant, session-based cookie only, no personal information Filtering of IPs and bots possible Exclude certain roles from tracking Ability to reset Page Views Works with all admin themes Counter database is created as write-optimized InnoDB API to track events for templates that are not viewable No dependencies on libraries, pure VanillaJS (Automatic tracking script) Works in all modern browsers Pages are sortable by hits Cons Only for ProcessWire version 3.0.80 or higher (Requires wireCount()) Only for PHP version 5.6.x or higher No support for Internet Explorer <= version 9 (Because of XMLHttpRequest()) No historical data, just simple summation (Because of GDPR) Segment URLs can only be counted if the segments are defined Planned Features / ToDos API access to hit values Since version 1.2.1 Possibility to sort the pages by hits (Request by @Zeka) Since version 1.2.0 Don't track logged in users with certain roles (Request by @wbmnfktr) Since version 1.1.0 Possibility to reset the counter for certain pages or templates (Request by @wbmnfktr) Since version 1.1.0 Better bot filter Since version 1.1.0 Disable session lifetime, don't store cookies to track every page view (Request by @matjazp) Since version 1.2.1 Option to hide the counter in the page tree (Request by @matjazp) Since version 1.2.1 Option to hide the counter in the page tree on certain templates Since version 1.2.1 API to track events for templates that are not viewable Since version 1.2.2 Cookieless tracking Since version 2.0.0 Show last hit Since version 2.0.0 Ignore URL segments (Request by @bernhard) Since version 2.0.0 Add hookable method after pageview was tracked (Request by @bernhard) Since version 2.0.0 Changelog 2.0.0 Feature request: Add hookable method after pageview was tracked (___pageViewTracked($pageID)) (Requested by @bernhard) Feature request: Ignore URL segments option (Requested by @bernhard) New: Cookieless tracking New: Show date of last hit Update: Botlist Enhancement: Documentation improvement 1.2.7 Feature request: make buildPageListHitCounter-Function public (Requested by @bernhard) 1.2.6 Bug-Fix: Set the counter of a cloned page to 0 Enhancement: The function for resetting counters is now available in the module as a public function to reset counters via own scripts on the API side (Request by @VeiJari) Enhancement: Documentation improvement API reset 1.2.5 Bug-Fix: When counting 404 hits, cookies are no longer set. The session lifetime is deactivated for the 404 page Enhancement: Documentation improvement regarding URL segments 1.2.4 Bug-Fix: Resetting the counters on system pages (e.g. 404) does not work (Reported by wbmnfktr) Bug-Fix: Tracking endpoint is logged as 404 if module "Jumplinks" is installed (Reported by wbmnfktr) Enhancement: Corrected few typos (Merged from Sergio #6 – THX!) 1.2.3 Bug-Fix: Tracking script triggers 404 if pages are configured without slash (#3) Reported by @maxf5 Enhancement: Reduction of the tracking script size if it's gzipped (~320 bytes) Enhancement: Documentation improvement Enhancement: Corrected few typos 1.2.2 New feature: API to track events for templates that are not viewable Enhancement: Documentation improvement 1.2.1 API access to hit values Use $page->phits Bug-Fix: No tracking on welcomepage (Reported by wbmnfktr; Thx to matjazp) Bug-Fix: Tracking script path on subfolders (Reported by matjazp) Bug-Fix: Tracking on pages with status "hidden" Enhancement: Change database engine to InnoDB for phits field Enhancement: Option to disable session lifetime set session lifetime to 0, no cookies Enhancement: Better installation check Enhancement: AJAX Request asyncron Enhancement: Reduction of the tracking script size by ~20% Enhancement: Option to hide the counter in the page tree You can output the counter with the field name "phits" Enhancement: Option to hide the counter in the page tree on certain templates Enhancement: Option for activate general IP validation Enhancement: Reduction of tracking overhead up to ~30ms Enhancement: Better bot list for detection 1.2.0 New feature: Sort pages by hits – New field phits Migrate old counter data to new field 1.1.0 New feature: Exclude tracking of certain roles New feature: Reset Page Views Better bot filter and detection 1.0.0 Initial release Notes By default, the page views are stored as INT in the database. This allows a maximum counter value of 4.2 billion views (4,294,967,295) per page. If you need more, change the type to BIGINT directly in the database. But I recommend to use Google Analytics or similar tools if you have such a large number of users. _______________________________________________________ Download GitHub: ProcessWire Page Hit Counter (Version 2.0.0) PW Module Directory: ProcessWire Page Hit Counter (Version 2.0.0) Install via ProcessWire (Classname): PageHitCounter _______________________________________________________ Update information If you have used version 1.2.1 from the DEV branch, please replace it completely with the new master version. Old stable version Download GitHub: ProcessWire Page Hit Counter (Version 1.2.7)
    1 point
  22. RockLESS Download: https://modules.processwire.com/modules/rock-less/ Docs: https://github.com/BernhardBaumrock/RockLESS
    1 point
  23. Hello, I searched the forum but couldn't find posts about the exact same scenario that I am facing. Instead of serving the different languages through a language specific URL, Each language should have their own domain. Everything is served from one single PW installationh, no multisite setup. So instead of domain.com/ domain.com/de/ domain.com/pl/ etc. the language versions should be available under domain.com domain.de domain.pl etc. I found how to switch the language based on the http hostname. But there are a few more questions: How to properly implement the redirection in .htaccess How to configure PW so it doesn't append the language to the URL? (just leave the language names out on the home page settings tab?) I hope someone has had that scenario before and can help out with some hints. Thank you.
    1 point
  24. This will be more of a quick tip, and maybe obvious to many of you, but it's a technique I found very useful when building display options. By display options I mean fields that control how parts of the page are displayed on the frontend, for example background colors, sizing, spacing and alignment of certain elements. I'll also touch on how to make those options intuitive and comfortable to use for clients. It basically involves setting up option values that can be used directly in CSS classes or as HTML elements and mapping those to CSS styling (which can be quickly generated in a couple of lines using a pre-processor such as SASS). Another important aspect is to keep the option values seperate from their corresponding labels; the former can be technical, the latter should be semantically meaningful. The field type that lends itself to this this seperation of concerns is the Selectable Options field, the following examples mostly use this field type. Note that this module is part of the ProcessWire core, but not installed by default. The following examples all come from real projects I built (though some are slightly modified to better demonstrate the principle). #1: Headline levels & semantics For a project that had many pages with long texts, I used a Repeater field to represent sections of text. Each section has a headline. Those sections may have a hierarchical order, so I used a Selectable Option field to allow setting the headline level for each section (you can guess where this is going). The definition of the options looks something like this (those are all in the format value|label, with each line representing one option, see the blogpost above for details): h2|Section headline h3|Sub-section headline Of course, the PHP code that generates the corresponding HTML can use those values : // "sections" is the repeater field foreach ($page->sections as $section) { // create an h2 / h3 tag depending on the selected option (called headline_level here) echo "<{$section->headline_level->value}>{$section->headline}</{$section->headline_level->value}>"; echo $section->body; } That's a pretty obvious example, but there are two important takeaways: I only used two options. Just because there are six levels of headlines in HTML, doesn't mean those are all relevant to the client. The less options there are, the easier it is to understand them, so only the options that are relevant should be provided. In this case, the client had provided detailed, structured documents containing his articles, so I could determine how many levels of hierarchy were actually needed. I also started at h2, since there should be only one h1 per page, so that became it's own field separate from the repeater. The two options have a label that is semantically relevant to the client. It's much easier for a client who doesn't know anything about HTML to understand the options "Section headline" and "Sub-section headline" than "h2" and "h3". Sure, it can be cleared up in the field description, but this way it goes from something that's quickly explained to something that needs no explanation at all. #2: Image width and SASS In the same project, there was also an image section; in our layout, some images spanned the entire width of the text body, others only half of it. So again, I created an options field: 50|Half width 100|Full width In this case, I expected the client to request different sizes at some point, so I wanted it to be extensible. Of course, the values could be used to generate inline styles, but that's not a very clean solution (since inline styled break the cascade, and it's not semantic as HTML should be). Instead, I used it to create a class (admittedly, this isn't strictly semantic as well): <img src="..." class="w-<?= $section->image_width->value ?>"> With pure CSS, the amount of code needed to write out those class definitions will increase linearly with the number of options. In SASS however, you only need a couple of lines: @each $width in (50, 100) { .w-#{$width}{ max-width: percentage($width/100); } } This way, if you ever need to add other options like 25% or 75%, you only need to add those numbers to the list in parenthesis and you're done. You can even put the definition of the list in a variable that's defined in a central variables.scss file. Something like this also exists in Bootstrap 4 as a utility, by the way. It also becomes easier to modifiy those all at once. For example, if you decide all images should be full-width on mobile, you only need to add that once, no need to throw around !important's or modify multiple CSS definitions (this is also where the inline styles approach would break down) : # _variables.scss $image-widths: (25, 50, 75, 100); $breakpoint-mobile: 576px; # _images.scss @import "variables"; @each $width in $image-widths { .w-#{$width}{ max-width: percentage($width/100); @media (max-width: $breakpoint-mobile) { max-width: 100%; } } } One important gotcha: It might be tempting to just use an integer field with allowed values between 10 - 100. In fact, the amount of SASS code would be identical with a @for-directive to loop throuh the numbers. But that's exactly what makes point-and-click page builders so terrible for clients: too many options. No client wants to manually set numerical values for size, position and margins for each and every element (looking at you, Visual Composer). In fact, having too many options makes it much harder to create a consistent layout. So in those cases, less is more. #3: Multiple options in one field Another example for repeatable page sections, this time for a two-column layout. The design included multiple variants regarding column-span and alignment. Using a 12-column grid, we needed a 6-6 split, a centered 5-5 split, a left-aligned 6-4 split and a right-aligned 4-6 split. I didn't want to litter the repeater items with options, so I decided to put both settings in one field (called something like Column layout) : center_6_6|6 / 6 (Centered) center_5_5|5 / 5 (Centered) left_6_4|6 / 4 (Left-aligned) right_4_6|4 / 6 (Right-aligned) As long as the value format is consistent, the individual options can be quickly extracted and applied in PHP: [$alignment, $width['left'], $width['right']] = explode('_', $section->column_layout->value); echo '<section class="row justify-content-' . $alignment . '">'; foreach (['left', 'right'] as $side) { echo '<div class="col-lg-' . $width[$side] . '">'; echo $section->get("body_{$side}"); echo '</div>'; } echo '</section>'; If you don't recognize the syntax in the first line, it's symmetric array destructuring, introduced in PHP 7.1. For older versions you can use list() instead. This example uses Bootstrap 4 grid classes and flexbox utility classes for alignment. The corresponding CSS can be quickly generated in SASS as well, check the Bootstrap source code for a pointer. Again, I'm limiting the options to what is actually needed, while keeping it extensible. With this format, I can easily add other column layouts without having to touch the code at all. #4: Sorting page elements A final example. In this case I was working on a template for reference projects that had three main content sections in the frontend: A project description, an image gallery and embedded videos (each using their own set of fields). The client requested an option to change the order in which those sections appeared on the page. Had I known this earlier, I maybe would have gone for a Repeater Matrix approach once again, but that would have required restructuring all the fields (and the corresponding code), so instead I used a Selectable Option field (labelled "Display order"). My approach is similar to the one from the last example: body_gallery_embeds|Description - Gallery - Videos body_embeds_gallery|Description - Videos - Gallery gallery_body_embeds|Gallery - Description - Videos gallery_embeds_body|Gallery - Videos - Description embeds_body_gallery|Videos - Description - Gallery embeds_gallery_body|Videos - Gallery - Description Since there are six possibilities to sort three items, this is the expected number of options. So I decided to include them all, even though some are probably never going to be used. I also tried to use a predictable order for the options (i.e. the options come in pairs, depending on what element is first). And here is the code used on the frontend: // render the template files for each section and store the result in an associative array $contents = [ 'body' => wireRenderFile('partials/_section-body.php', $page), 'gallery' => wireRenderFile('partials/_section-gallery.php', $page), 'embeds' => wireRenderFile('partials/_section-embeds.php', $page), ]; // e.g. 'gallery_body_embeds' => ['gallery', 'body', 'embeds']; $order = explode('_', $page->display_order->value); // echo the contents in the order defined by the option value foreach ($order as $item) { echo $contents[$item]; } You can see how it will be easy to add an additional section and integrate it into the existing solution. Though a fourth item would result in 4! = 24 possibilities to sort them, so at that point I'd talk to my client about which layouts they actually need ? Conclusion I always try to keep my code and the interfaces I create with ProcessWire extensible and intuitive. Those are a couple of solutions I came up with for projects at work. They are certainly not the only approach, and there is nothing super special about those examples, but I found that putting a little more effort into defining options with meaningful labels and using option values that I can use directly in my templates makes the result less verbose and more maintainable. Some or most of this tutorial may be immediately obvious to you, but if you made it this far, hopefully you got something out of it ? Feel free to share your own methods to create display options, or how you would've approached those problems differently. Thanks for reading!
    1 point
  25. @kongondo, thank you! New day here and taking another look. I noticed in the code that even though I'm only calling FormBuilder to get the entries, all the FB scripts & css are loading into the <head>. Digging deeper to see if this is causing a conflict. Grateful for your offer to look around the code if I can't resolve it. Will keep you posted. More: Yep, FormBuilder files seem to be the problem. Now to figure out how to use FB in admin area to get the entries without the scripts loading. SOLVED: One of the things my custom function 'formsMultiple' did was render the form and add the output to the final returned array. Works great on the front end but not needed here. Made the form rendering optional in the 'formsMultiple' function and in the RTM code, said don't do it. End result is RTM field and rest of the admin page display as expected. Thanks for the help & pointing me in the right direction.
    1 point
  26. @horst That looks interesting as well! Of course with pages it's easier to maintain, and clients can their own options. I usually only use pages for larger structures such as taxonomies, and Selectable Options for simpler display options, since it's a bit faster to set up. Maybe I'll try a "pure" setup with only pages for options next time ^^ Hm, this opens up some possibilities, something like creating setting groups or presets containing multiple other options .. I'll definitely play around with that one ?
    1 point
  27. 1 point
  28. I also have made some simple customizations to this module that I feel are worth putting into the official version, and for at least 1 site that run this on, these changes are critical for the module to work. If anyone were to take it over, I could submit a pull request, or describe the change/requirement and let the maintainer implement in their interpretation of best practice. for one, you might notice that this module's config provides a field for the 'Domain Name'. However, in my case, the 'from' email may use different domains. My Mailgun account has many domains configured. So instead of the $options array item called CURLOPT_URL using the module's entered domain name, i need it to use the domain from the $mail instance's from/sender. To this end i created a method getDomainName() as follows: private function getDomainName() { return substr(strrchr($this->mail['from'], "@"), 1); } then replace the CURLOPT_URL with CURLOPT_URL => "{$this->apiUrl}" . $this->getDomainName() . "/messages", I guess for this module to be flexible enough going forward, there may need to be an additional setting, where you either enter a domain name in the module config, or you tell the module to 'auto' populate the domain name from the sender domain.
    1 point
  29. Do I understand this correctly: A counter is displayed and tracked for your homepage template even though you have not configured the template? Or do you use a template for your homepage which is also used for other pages? Also a quick update: Version 1.2.0 with Sortable selector Version 1.2.0 is now available on the Dev-Branch, which makes it possible to sort the pages by hits. Please check the upgrade for test environments first, because the DB structure will be changed. Previously tracked data will be taken over, a new fieldtype will be installed. Simply replace all files and trigger a module refresh in the PW. Infos and download: https://github.com/FlipZoomMedia/PageHitCounter/tree/dev
    1 point
  30. $out = ''; if(count($story->galleria)) { $firstImage = $story->galleria->first(); $out = "<img src='{$firstImage->url}' alt='{$firstImage->description}'>"; } else { // no images; do something else } echo $out;
    1 point
  31. Hi Moritz, big thanks for this one too! ? I use a similar setup, but with page references instead of option fields.
    1 point
  32. Sorry if I wasn't clear - the idea was to have it one the PW homepage instead of (or in addition to) the API examples so potential users can actually play with them and generate output returned via AJAX - just like the Console panel. This sounds really great, but is there actually an interactive code console that can run code, or do you need to edit template files with your code editor?
    1 point
  33. That's correct. Full multi-lingual support has been on my todo list for a long time. Hopefully I'll get some time soon to look into it. Thanks for sharing your workaround with others.
    1 point
  34. Good suggestions. ? Both functionalities are implemented in the current Dev branch. You can test them here: https://github.com/FlipZoomMedia/PageHitCounter/tree/dev
    1 point
  35. Yeah, the only concern of mine is just this, so that I (we) can search google like "Admin Actions" and "Admin Tools" separately ? Haha ? it would be okay for an application or a module since it is a made up word but not so good for something descriptive as much as possible like a panel's name.
    1 point
  36. What about Admin Tools? I think if all we are trying to do is not match the name of the Admin Actions module, then this is probably this simplest and most descriptive.
    1 point
  37. Quick Actions Utility Actions Shortcut Actions Tracy Shortcuts Utilities
    1 point
  38. Wow... that's indeed really nice. Two things came up while playing around with it. Is it planned to exclude logged-in users from tracking? I know that several of my clients publish new content and therefore check the new site over and over again. Right now they would create page counts that are kind of fake. Is it planned to reset all or some counters? Right now I created ~50 page counts and would like to reset those to zero again.
    1 point
  39. A little solution that maybe helps someone... Often when I'm writing documentation for a site I want to get the label text from an inputfield header so I can paste it into the document. But the problem is that you cannot drag a selection around the header label in order to copy the text. So as a solution I used a bit of jQuery to get the label text when the inputfield header is Alt+clicked. $(function() { // If an InputfieldHeader is clicked... $(document).on('click', '.InputfieldHeader', function(event) { // And the Alt key is down... if(event.altKey) { // Get the header text, excluding that within the AdminOnSteroids field edit link var text = $(this).clone().find('.aos_EditField').remove().end().text(); // Copy the text to the clipboard copyToClipboard(text); } }); }); // Copy a string to the clipboard function copyToClipboard(string) { var $temp = $('<input type="text" value="' + string + '">'); $('body').append($temp); $temp.select(); document.execCommand('copy'); $temp.remove(); } I added this to the PW admin using the Asset Paths feature of the AdminOnSteroids module: The solution in action: Okay, so those are short labels and I could have just typed them out by hand. But some labels are longer, copy/pasting reduces the chance of typos, I'm lazy, time is money, etc.
    1 point
  40. I personally see just a single benefit in the functions api: It's beginner friendly as it's keeping you from needing to understand how scoping and classes work in php/pw. But it brings the the big downside of relying on a global processwire instance, which is a well known code smell. It might not matter for many simple websites, but especially in modules one should never use those as it will simply prevent the module from being usable in a multi instance setup. Currently I feel like the tradeoffs and why one would use one API access method over the other just isn't very well described. Maybe something of this post could be incorporated into the current explanations:
    1 point
  41. Here is another status report for development (early state) of my GroupMailer module: I've setup the dashboard to list all GroupMailer messages found in pages tree. The question is how messages (pages) should be managed in general. I plan to make the whole thing as flexible as possible. You can create messages wherever you want, in the root - directly under "Home" or within a container page. In the upper area of the dashboard you can select the desired container and all messages of this container will be listed. Messages are identified by the template "groupmailer-message". A field containing meta data for the message (dispatch status, number of recipients, number of mails sent, etc.) is attached to this template. The Messages grid will show these values. The Messages grid itself is a rewrite of the ProcessPageLister module matching the requirements of GroupMailer. It will provide all features of the original PageLister module + specific GroupMailer functions. It will be a live view of the current state of all Newsletters (Messages). That means you can watch how mails are sent out and immediately stop/restart etc. the sending process. Here is a screenshot of the current state: (The columns are not the final ones and will be adjusted according to my needs.) What do you think? Am I on the right way or do you have any hints?
    1 point
  42. Ryan has just modified all included site profiles to turn on the functions API by default although I am not sure why the change wasn't made in the wire/config.php file so it works for all profiles even if they come from another source - I am sure I am overlooking a good reason though. Regardless I still don't like the mix of approaches used in those examples, but that has been discussed above ?
    1 point
  43. I forgot to mention that I also use the free https://www.spectacleapp.com/ "window-size manager" application. In the case of the Finder I use it in a semi-automatic way to quickly setup a "two-pane layout" for the Finder. I create a new window with half of the vertical size of the desktop ('cos I prefer landscape/horizontal windows for column view) and right away following that I create a new window because the Finder clones the last not-yet-resized window's dimensions, and I only need to reposition the two, similarly sized windows. Since I rarely move Finder windows around I do not need to do it too often.
    1 point
  44. How about a new favicon of the ProcessWire logo instead of being lost in the clouds? I attached two 512x512 PNG files (PNGs can be used for Favicons by the way and all browsers support it; worth ditching .ico).
    1 point
  45. I really like the new design Ryan! I have only one suggestion; would it be better to move "Getting Started" to somewhere a little easier to find for new users? Thus far, I have only been able to access it here, and it is tucked away from the main sidebar on the right.
    1 point
  46. @adrian Probably yes, I've changed that, thanks. Yesterday I added a "translatorModal" tweak to Misc that allows editing translations in a modal, hope you find it useful.
    1 point
  47. Update 2018-07-09: ProcessNetteTester module is available in the Modules Directory and on GitHub. This is a short tutorial on how to use Nette Tester with ProcessWire. As you will see it's very easy to setup and use and it's perfect for testing your code's functionality. With bootstrapping ProcessWire it's also possible to check the rendered markup of pages using the API, checking page properties, etc. It's also a great tool for module developers for writing better code. While there will be nothing extraordinary here that you couldn't find in Tester's docs this can serve as a good starting point. Prerequisites: PHP 5.6+ 01 Download Tester Go to https://github.com/nette/tester/releases and download the latest release (currently 2.0.2). Download from the link reading "Source code (zip)". You can use composer also if you wish. 02 Extract Tester files Create a new directory in your site root called "tester". Extract the zip downloaded here, so it should look like this: /site /tester/src /tester/tools /tester/appveyor.yml /tester/composer.json /tester/contributing.md /tester/license.md /tester/readme.md /wire ... 03 Create directory for test files Add a new directory in "/tester" called "tests". Tester recognizes "*.Test.php" and "*.phpt" files in the tests directory, recursively. 04 Create your first test In the "tests" directory create a new "MyTest.php" file. The first test is a very simple one that bootstraps ProcessWire and checks if the Home page name is "Home". This is not the smartest test but will show you the basics. Add this to "/tester/tests/MyTest.php": <?php namespace ProcessWire; use \Tester\Assert; use \Tester\DomQuery; use \Tester\TestCase; use \Tester\Environment; require __DIR__ . '/../src/bootstrap.php'; // load Tester require __DIR__ . '/../../index.php'; // bootstrap ProcessWire Environment::setup(); class MyTest extends TestCase { // first test (step 04) public function testHomeTitle() { $expected = 'Home'; // we expect the page title to be "Home" $actual = wire('pages')->get(1)->title; // check what's the actual title Assert::equal($expected, $actual); // check whether they are equal } // second test will go here (step 06) // third test will go here (step 07) } // run testing methods (new MyTest())->run(); I've added comment placeholders for the second and third tests that we will insert later. 05 Run Tester Tester can be run either from the command line or from the browser. The command line output is more verbose and colored while in the browser it's plain text only (see later). Running from the command line Navigate to the "/tester" directory in your console and execute this: php src/tester.php -C tests This will start "/tester/src/tester.php" and runs test files from the "/tester/tests" directory. The "-C" switch tells Tester to use the system-wide php ini file, that is required here because when bootstrapping ProcessWire you may run into errors (no php.ini file is used by default). You may load another ini file with the "-c <path>" (check the docs). If the title of your Home page is "Home" you should see this: If it's for example "Cats and Dogs", you should see this: Running from the browser First we need to create a new PHP file in ProcessWire's root, let's call it "testrunner.php". This is because ProcessWire doesn't allow to run PHP files from its "site" directory. The following code runs two test classes and produces a legible output. IRL you should probably iterate through directories to get test files (eg. with glob()), and of course it's better not allow tests go out to production. <?php ini_set('html_errors', false); header('Content-type: text/plain'); echo 'Starting tests.' . PHP_EOL; echo '--------------------------' . PHP_EOL; $file = __DIR__ . '/PATH_TO/FirstTest.php'; echo basename($file) . ' '; require $file; echo '[OK]' . PHP_EOL; $file = __DIR__ . '/PATH_TO/SecondTest.php'; echo basename($file) . ' '; require $file; echo '[OK]' . PHP_EOL; echo '--------------------------' . PHP_EOL; echo 'Tests finished.'; exit; Navigate to "DOMAIN/testrunner.php" in your browser to execute the file. If every test succeeds you should get this: If there are failed tests the execution stops and you can read the error message. If there were more tests (eg. ThirdTest), those won't be displayed under the failed test. 06 DOM test This test will check if a page with "basic-page" template has a "h1" element. We will create the page on the fly with ProcessWire's API. To keep things simple we will add the new test as a new method to our MyTest class. Add this block to the MyTest class: public function testBasicPageHeadline() { $p = new Page(); $p->template = 'basic-page'; $html = $p->render(); $dom = DomQuery::fromHtml($html); Assert::true($dom->has('h1')); } This will most likely be true but of course you can check for something more specific, for example "div#main". Note that we have used the DomQuery helper here (check the "use" statement on the top of the file). 07 Custom function test You will probably want to make sure your custom functions/methods will work as they should so let's write a test that demonstrates this. I don't want to complicate things so I'll check if the built-in "pageName" sanitizer works as expected. Add this to the myTest class: public function testPageNameSanitizer() { $expected = 'hello-world'; $actual = wire('sanitizer')->pageName('Hello world!', true); Assert::equal($expected, $actual); } This should also be true. Try to change the expected value if you are eager to see a failure message. 08 Next steps You can add more methods to the MyTest class or create new files in the "tests" directory. Check out the range of available Assertions and other features in the docs and see how they could help you writing more fail-safe code. Once you make a habit of writing tests you'll see how it can assist making your code more bulletproof and stable. Remember: test early, test often ? If you find out something useful or cool with Tester make sure to share.
    1 point
  48. When using setUp and tearDown methods it's good to keep in mind that Tester runs tests in parallel threads. That is, if you except that a Page you create in the setUp method will be deleted in tearDown before the next test method begins, you may be wrong. For example I've created and saved a new Page with the same title in setUp and deleted it in tearDown. I randomly got a ProcessWire error saying it could not generate a unique name for the page, and that was because the other tests have been started before the page could be deleted in the tearDown method. The actual thread number can be retrieved, so appending it to the title solved the issue (or by adding a random suffix): $p->title = 'Test ' . getenv(\Tester\Environment::THREAD); Alternatively you can reduce the number of threads to 1 with the "-j 1" switch (runtime will increase a lot).
    1 point
  49. Thanks bitpoet. Must have overlooked the clone function. And yes you understood it correctly that I wanted to create those virtual instance only on rendering. In the end I however decided to create real pages kind of like MuchDev suggested via module by hooking into the save function of the single-event pages. The admin for the single-event template now looks like this: https://monosnap.com/file/t187kaF2xI7WxF79blA3D982uMT1ko The code for the module is as follows. Adapt it for your needs <?php class recurringEvents extends WireData implements Module { public static function getModuleInfo() { return array( 'title' => 'Recurring Events', 'summary' => 'Adds a function to create recurring events and add pages to Processwire when a checkbox is checked on page save', 'version' => 1, 'autoload' => true, ); } public function init() { $this->pages->addHookBefore('save', $this, 'hookSave'); } public function hookSave($event) { $page = $event->arguments[0]; # check if recurring is checked on the page beeing saved. if ($page->recurring==1) { $intervallString=$page->intervall->title; for ($i=1; $i <= $page->n_repeats; $i++) { // create new page $k= new Page(); $k->template = 'single-event'; // set template $k->parent = wire('pages')->get('/events/'); // set the parent $intervall='+'.$i.' '.$intervallString; // construct the $intervall selector for strtotimestamp +1 week or +1day, increase with iteration by 1 $k->setOutputFormatting(false); // Copy page fields from current page to newly created one $k->title=$page->title; $k->organisation=$page->organisation; $k->type=$page->type; $k->location->address=$page->location->address; $k->body=$page->body; $k->recurring=0; $k->dateStart= strtotime($intervall, $page->dateStart); $k->dateEnd = strtotime($intervall, $page->dateEnd); $k->save(); } $this->message("Sucessfully created ".($i-1)." copies of recurring event {$k->title}"); // on the current page set recurring to 0 again, no page->save() needed because the page will be saved after the hook automatically $page->recurring=0; } } }?>
    1 point
×
×
  • Create New...