Leaderboard
Popular Content
Showing content with the highest reputation on 02/12/2021 in all areas
-
A couple weeks ago there were a lot of ideas about page builders and different approaches in ProcessWire. Some really interesting ideas actually, and great proof of concepts as well. The conversation started moving faster than I could keep up with, and I think Teppo's summary in ProcessWire Weekly 351 is a great overview, if you haven't yet seen it. These are so great in fact, that I don't think anything I build could live up to what some of you have already built. In part because my focus is more server-side than client-side development, and I can clearly see most of these page builders are much more client-side based than I have expertise in (though would like to learn). Whereas some of you are clearly amazing client-side developers; I'm blown away by what's been built, and how quickly it happened. One struggle I have with page builders is that I don't have my own good use cases for them, with the projects that I work on. Others here (like Jonathan) already have a lot of good use cases. Clearly there are a lot of benefits. I'm fascinated and enthusiastic by all of it, but can't envision how or if I'd use such a tool in my own projects. And having experience with (and particularly close use cases for) a tool is important in hitting the right notes in building it. Likely page builder support belongs in ProcessWire in some form or another, and ProcessWire can be an excellent platform for it. But the actual page builder needs to be designed and built by the people that have expertise in the tools needed to build it, and by the people that will actually use it. I think it should be a community collaboration. For anything needed to support this on the PHP side, count me in. I don't think I've got the background to "lead" the development of the page builder, but I do think I've got plenty to contribute to it. I'd also like to continue building out existing tools like Repeater/Matrix to further support page builder type use cases. I'd like to rebuild and move the auto-save and Live Preview features from ProDrafts into the core, which should also help support some of these page builder cases. Whether these will eventually apply to the larger page-builder project, I don't know, but they'll be useful either way. What are the next steps? Who's interested in working on this? Let's keep the conversion going and move forward with it.8 points
-
3 points
-
Then the user is your own module. All the points in my post above still apply – your module has to make some assumptions about the structure of the JSON data. Even if the data is only consumed internally, passing unstructred data around can cause unexpected errors at any point. So it's still better to parse the data into a representation that makes guarantees a specific data structure, and raise errors during parsing (as opposed to a random error anywhere in your module's lifecycle that's hard to track down). Also, will your module not allow customization through, for example, custom page builder elements or hooks? As soon as that's the case, developers using your module will have to use your data, so it should provide a uniform interface to accessing that data. Basically, any ProcessWire class representing some sort of collection, so you can check out those. For example, check out Pagefiles, FieldsArray and PageArray which all extend WireArray. See how they make use of all the utility methods WireArray provides and override just the methods they need to customize to adapt the WireArray to a specific type of data? For example, check out FieldsArray::isValidKey only allows Field objects to be added to it, so adding anything else will immediately throw an error. You can use this to map your pagebuilder items to a specific class and then have a class like PageBuilderItems extending WireArray that only accepts this class as items. This will go a long way towards having a uniform data structure. Again, don't mix up storage and representation. For storage, I would prefer the second example you posted, with items being an array instead of a map and the ID being just a regular property. Simply because arrays are more convenient to create, parse and map (in JS especially, but also in PHP). Though again, it doesn't matter as soon as you parse the data. The representation your module (or others, see above) uses to access the data can provide utility methods, like accessing an item by key. Regarding the last point specifically, WireArray can help you with this. The documentation is kind of handwaivy about how WireArrays handle item keys – WireArray::add doesn't take the key as an argument, but WireArray::get allows you to get an item by key, so where does the key come from? If you check the source code, turns out WireArray automatically infers a key based on the item – see WireArray::getItemKey. Your custom class can extend this to use the ID property of a given item as the key. For example, FieldsArray::getItemKey does exactly this. This allows you to use page IDs to access items without having to store your data specifically as a map. On top of that, you can use all the other utility methods WireArray provides to access elements in the any way you like.3 points
-
@jploch You're mixing up storage and public API, which is not a good idea. JSON is a storage / transport format, it's always a string which contains a serialization of data. The purpose of JSON is to store or transmit data in different formats between systems – like your database, your server and the client's browser. JSON is also language-independent – you can decode a JSON-string in JavaScript and in PHP. But as soon as you do that, the result is that language's default representation of that JSON string. For PHP, this means you get either a stdClass object or arrays (depending on the flags you give to json_decode). So you need to ask two different questions: What is the best way to store my data, and what is the best way to present it to a user of your module? Data representation Of course you can just json_decode the JSON string and pass the result to the user of your module. But that's a bad idea: You have zero guarantees regarding the structure of your data. You won't notice if your data is missing a specific field, some field is an integer instead of a string, your items are indexed by page name instead of key or whatever else. This will cause errors that are very hard to track down. The user has no indication how your data is structured – to figure that out, I will either have to refer to the documentation for everything or have to dump stdClass objects and figure things out from there ... You can't have nice things like code completion, proper IDE support etc. Instead, you want to parse that unstructured data into a representation (usually, a set of classes) that guarantees a specific structure and has a proper public API for access and modification of the data. Using proper objects with type-hinting will also allow you to find any errors in the structure and abort the process at parse time with an appropriate error message (see Parse, don't validate for an in-depth explanation). For example, to represent your data, you might use classes like PageBuilderEntry and PageBuilderItem to represent a list of items with meta data (the breakpoint fields, in your example) and individual items, respectively. The you can provide utility methods for different use cases – for example, methods to get a list of items indexed by key or by any other field. If you want to build closer to ProcessWire, you can also use a class that extends WireArray to represent a list of items, this will give you many helpful methods (like getting an item by key) out of the box. For the items themselves, you might just reuse the page itself (if the IDs correspond to 'real' pages in your system) or extend the Page class. Data storage Separating the representation from the storage makes the specific structure you store your data in less important, since only your module will interact with it. Just build a static helper method to build the appropriate representation based on a JSON string (e.g. PageBuilderEntry::fromJSON) and a method to encode that representation back to JSON (PageBuilderEntry::toJSON). The fromJSON method is responsible for parsing the data and throwing the appropriate error when it's not in the expected format. That said, personally I usually prefer regular arrays instead of maps for storage, because it's easier to parse and massage into different formats. But again, it depends on how you want to represent the data.3 points
-
Yes, that is because of the order in which ProcessWire executes its steps when it renders a form. While your template is used for every Inputfield contained in an InputfieldWrapper, each Inputfield's render method is called individually inside InputfieldWrapper::render first, then its rendered return value is then inserted into the item_content template in place of {out}. So the Inputfield doesn't even get to see the template. The "strange" behavior comes up because the surrounding InputfieldWrapper, often an InputfieldForm that extends it, also extends Inputfield and is the last (outermost, as we are talking about recursion) time the render() hook is invoked, this one finally with all the rendered item_content parts that still have the literal {meta_id} strings. Not sure how to accomplish what you're trying to without jumping through some complicated hoops.2 points
-
Awesomplete? https://processwire.com/modules/text-input-awesomplete/2 points
-
The core Page Reference inputfields output only a page ID and expect a page ID as input. Possibly you could create your own custom inputfield module that works with a page path instead of an ID. Or here's a hacky solution that involves adding an extra "path" attribute to your Hanna tag... Hook: $wire->addHookAfter('HannaCodeDialog::buildForm', function(HookEvent $event) { // The Hanna tag that is being opened in the dialog $tag_name = $event->arguments(0); // The form rendered in the dialog /* @var InputfieldForm $form */ $form = $event->return; $config = $event->wire()->config; $modules = $event->wire()->modules; if($tag_name === 'select_page') { /* @var InputfieldPageListSelect $f */ $f = $modules->InputfieldPageListSelect; $f->name = 'selected_page'; $f->id = 'selected_page'; $f->label = 'Selected page'; $f->parent_id = 1268; $form->add($f); // Add JS file to Hanna Code Dialog form $js_file = $config->paths->templates . 'hannas/select_page.js'; if(is_file($js_file)) { $js_url = $config->urls->templates . 'hannas/select_page.js?v=' . filemtime($js_file); $form->appendMarkup = "<script src='$js_url'></script>"; } } }); select_page.js $(document).ready(function() { // Must use mousedown instead of click event due to frustrating event propagation issues across PW core // https://github.com/processwire/processwire-issues/issues/1028 $(document).on('mousedown', '.PageListActionSelect a', function(event) { var path; // Adapt unselect label to suit your language if($(this).text() === 'Unselect') { path = ''; } else { path = $(this).closest('.PageListItem').children('.PageListPage').attr('title'); } // Adapt path field ID to suit $('#path').val(path); }); });2 points
-
From recent discussions around the future of Processwire, I suspect my use case might be a bit different to others, as rather than using it in the website 'builder' type scenario, I'm using it more as a database management system, where structured data is critical, and quite often 'pages' will never be visualised via a template, and if they are, the template file is effectively a 'report'. In my case I'm tending to build sites where the admin IS the site, and there's often little or no front-end. I've wondered whether I'd be better to learn some framework like Yii or ASP.Net that have CRUD code generators that can generate data entry forms for SQL tables, but I like Processwire's admin, and the permissions system is something I rely on a lot. I've started working on a membership app, that will have quite a few users, and I've immediately run into a problem. I want admin users to be able to store first name, last name, address etc for members, but it's quite conceivable with a large enough list that it's possible to have more than one say 'John Smith', living at different addresses. I know how to write a hook to put an arbitrary auto-incrementing number in the title field (which will also populate the name field), but this seems to me to break the DRY principle. There's already a page ID, and if I'm effectively populating two other indexed database fields with an arbitrary number so that I can uniquely identify records and keep Processwire happy, it doesn't feel quite right. In straight SQL I can use an auto-increment numeric surrogate primary key and create a constraint on multiple columns so I could have as many 'John Smiths' as I like as long as they reside at different addresses, but I'm not sure how I'd go about this in Processwire, as each field resides in its own table. Perhaps the new Combo Profield @ryan recently released might be have potential for this, as all the subfields reside in a common database table, and there's already some capability to edit the database schema, although I'm not sure if this extends to creating custom constraints? That would be a really great feature, as it would allow defining unique records based on a combination of sub-fields. The Combo fieldtype is still a fieldtype though which needs to be added to a template with mandatory fields, so I still potentially have page id, name, and title all essentially duplicating each other's functionality as arbitrary numeric fields. I understand why page name is meant to be mandatory, as a page that has a template file needs to be accessible via a URL, and the page name is part of the routing, however I'm not sure whether it's practical for pages that don't have a template file to simply work off the page ID? What might be useful is a template property setting that can indicate that the id should be used as the name as well, so there's still a 'name' but it's populated at runtime as a reference to the page id. I'm guessing it might be possible to do something like this via a hook?1 point
-
Getting OT here, but if the page names are publicly accessible anywhere but the email addresses are supposed to be private then you risk leaking the users' emails because base64 encoding is pretty recognisable and easily decoded by anyone. In that case it would be safer to encrypt the email addresses for the name using something like openssl_encrypt(). Also base64 strings can contain characters that are not URL safe so you'd need to replace those, e.g. "+" and "/".1 point
-
Version 2.0.0 as new master version available (PLEASE READ!) 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. >>>> All information about the changelog and bug fixings in the first post.1 point
-
I have not yet tested the difference, so it is something I need to look into. To tell the truth, I have just started to use page classes, so I am happy to hear how others make use of it.1 point
-
Thx @szabesz the problem is, that this will be called every time a page is loaded (I think?). But we want the init() to fire only once for all instances of Contact, so that all hooks only get added once and not multiple times. RockMigrations has a helper for that to trigger init/ready on a pageclass or even trigger it for all classes in a given folder. But I did not investigate a lot in that approach so if there are better solutions (maybe using PWs ClassLoader that I havent really worked with) I'm happy to hear them ?1 point
-
It was indeed a typo!!! That explains why it worked in $(document).ready --> I fixed the typo by accident ? Thx @7Studio and happy that my tutorial helped. The pw backend is a great playground ?1 point
-
@bernhard in your example you are listening to the modal close event - "pw-modal-closed", also you have linked a modal.js, if you are using panels then "pw-panel-closed" should do the trick I guess ?1 point
-
@markus_blue_tomato I assume job_group is a page field. Page fields in GraphQL return Page interface that has only built-in fields (id. name, url...). If you want to get custom fields like (title, job_group_id...) you need to use fragments. { jobDetail { list { id job_name job_group { list { id name ... on JobGroupPage { // <== you need to use the actual type name here title job_group_id } } } } } }1 point
-
Have you had a look at RockMigrations? You get the best of both your worlds: Easy migrations + version control...1 point
-
I'm using ProcessWire all the time for Backend-only applications. Sometimes I also feel like I'm using it in a "wrong" way... But sometimes it seems that there are more people using PW for database applications. I think it is a great tool for such scenarios - I have to admit though that I did not try any others. What's really missing is a way to present tabular lists. That's why I created RockGrid/RockTabulator/RockFinder. RockFinder3 is a great product, while RockGrid/RockTabulator is better than nothing but I'm working on a better solution ? I don't get your problem though... What's wrong with a list like this? id | forename | surname | address | name 1 | john | doe | exroad123 | 1 2 | john | doe | exroad234 | 2 3 | maria | müller | musterweg | 3 I tend to use the title field for a representation of the page that can be used easily on page lists etc: id | title | forename | surname | address | name 1 | john doe (exroad123) | john | doe | exroad123 | 1 2 | john doe (exroad234) | john | doe | exroad234 | 2 3 | maria müller (musterweg) | maria | müller | musterweg | 3 That's a simple saveReady hook. I'd recommend using custom page classes a lot for such setups. It keeps the code clean and a lot easier to maintain. You'll also get the benefit of code completion in your IDE and your hooks will get a lot cleaner, easier and better maintainable as well: // in site/init.php or an autoload module $contact = new Contact(); $contact->init(); // in site/classes/Contact.php <?php namespace ProcessWire; class Contact extends Page { public function init() { $this->addHookAfter("Pages::saveReady", $this, "setTitle"); } /** * Get page title based on other fields * @return string */ public function getTitle() { return $this->forename." ".$this->surname." ({$this->address})"; } public function setTitle(HookEvent $event) { $page = $event->arguments(0); if(!$page instanceof self) return; $title = $page->getTitle(); $exists = $event->pages->get([ 'template' => $page->template, 'title' => $title, 'id!=' => $page->id, ]); if($exists->id) { // page exists, show error message and do whatever is necessary } else { $page->title = $title; } } } Code completion will help you a LOT when the project grows... Combine that with RockMigrations and you feel like you got superpowers. At least I do ?1 point
-
Hi. Something from this thread has worked for me in the past - https://processwire.com/talk/topic/9322-change-default-language-for-homepage/?do=findComment&comment=899251 point
-
@szabesz - I've taken care of most of your requests. The only ones I haven't done are the separate front/back end visibility for the various server type indicators - this will actually be quite a bit of work which I don't have time for at the moment. The other thing I didn't change is making those checkboxes into columns for those panels you mentioned - the problem is not just the responsive issues, but more importantly, it's that it messes with the order of checkboxes - they end up going across the rows, rather than down the columns which is actually pretty awful. Anyway, let me know if all the other stuff works as you'd hoped, including the scroll to top ?1 point
-
Brilliant - thanks. I was thinking along those lines, but obviously not hacky enough ?1 point
-
You can hook the render of a normal text inputfield and use a <datalist> element. Code example here:1 point
-
Great Website!!! It needs to be listed in the processwire-showcase https://processwire.com/sites/1 point
-
It's too early to access page fields/properties in the constructor. There is a ___loaded() method that's called when the page is loaded and ready. So you could have this: public function ___loaded() { $this->header = $this->title; }1 point
-
Page::__construct() takes a Template object as an argument: /** * Create a new page in memory. * * @param Template $tpl Template object this page should use. * */ public function __construct(Template $tpl = null) { if(!is_null($tpl)) { $tpl->wire($this); $this->template = $tpl; } $this->useFuel(false); // prevent fuel from being in local scope $this->parentPrevious = null; $this->templatePrevious = null; $this->statusPrevious = null; } So I think the constructor in your child class should be: public function __construct(Template $tpl = null) { parent::__construct($tpl); $this->header = 'some text'; }1 point
-
This is the way. Performance is relative. Using a hook to check if a duplicate record exists will always be fast, or at the most have linear complexity. How many users do you expect to add each day? How many users / people entries in total do you have? If it's 10 million, then you'll have to start thinking about the performance of a uniqueness check, but if it's significantly less ... Besides performance, what other benefits do you hope to gain by using a unique index in SQL as opposed to a custom check on the interface-level (i.e. a ProcessWire hook)? The only thing I can think of is that uniqueness is guaranteed at database level, but is that going to matter to you? If you use the right hook, you can prevent duplicate entries from both the CMS interface and ProcessWire's API, so unless someone is going to manually insert records to your database through the command line, the result is identical. By the way, the unique setting for pages guarantees a globally unique page name, not uniqueness of an individual field. Just set the name to include the first name, last name and address (you can do that through the template settings or use a hook if you need more control) and use a hook to set new pages to unique. This will guarantee uniqueness for that combination of fields. Granted, it's only guaranteed on API level, not database level, but again, this might be absolutely enough for your use-case.1 point
-
You could add the datalist to a normal text field using a hook in /site/ready.php: $wire->addHookBefore('InputfieldText::render', function(HookEvent $event) { /* @var InputfieldText $inputfield */ $inputfield = $event->object; $field = $inputfield->hasField; if($field && $field->name === 'my_field') { $inputfield->attr('list', 'my-list'); $inputfield->appendMarkup = <<<EOT <datalist id="my-list"> <option value="One"> <option value="Two"> <option value="Three"> </datalist> EOT; } });1 point
-
So without the croppable image module variations are generated on demand. If your clients upload quite big images and you want to generate a maybe even larger than needed set of variations this is going to take some time and even more memory on the initial request. But that's a whole different problem than "how can I not create the default variation for an image" with completely different ways to get to a solution. I still don't get why you insist on filtering down all available variations wherever they might come from. I expect you have a list of sizes for your srcset defined somewhere, so just use those. You can still check the images dimensions to filter out the sizes you defined greater than the image's dimensions. Something like the following. <?php function responsive_image($image, $widths = null) { $widths = $widths || [200, 400, 800, 1200, 1600, 2000]; $default = 400; $smaller_widths = array_filter($widths, function ($width) use ($image) { return $width <= $image->width; }); $srcset = array_map(function($width) use ($image) { return "{$image->width($width)->url} {$width}w"; }, $smaller_widths); $srcset = implode(", ", $srcset); return <<<HTML <img src="{$image->width($default)->url}" alt="{$image->description}" sizes="(min-width: 640px) 60vw, 100vw" srcset="{$srcset}"> HTML; }1 point
-
I usually go by @rafaoski's approach. I first go to http://www.favicomatic.com/, upload a 500x500 image to generate a full icon pack. Then I place the icons in their own folder inside the templates folder. Usually /site/templates/img/favicon/ Favicomatic gives you a rather large HTML snippet. Slap that on your template, and on each line that points to a file, you'll have to fix the url, like so: <link rel="icon" type="image/x-icon" href="<?= $config->urls->templates ?>img/favicon/favicon.ico">1 point