Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 10/12/2024 in all areas

  1. This week there’s new $pages->saveFields() and $page->saveFields() methods on the core dev branch. You might already be familiar with the $pages->saveField($page, $field); method which lets you save one field from a page, or $page->save($field); which does the same. This is useful when you only need to save one field from a page, rather than the entire page. Now we have a plural version of that method, which lets you specify multiple fields on a page to save: $pages->saveFields($page, [ 'title', 'body', 'summary' ]); Below is the same thing, but on a $page object, so you don't need to specify a $page argument: $page->saveFields([ 'title', 'body', 'summary' ]); You can also use a string if you prefer: $page->saveFields('title,body,summary'); In my case, I needed this method for a project I'm working on, and I also needed it to save without updating the 'modified' time or user, which you can achieve by specifying the 'quiet' argument. Though note, the 'quiet' argument is available for all the page saving methods and has been around a long time. But I'm not sure how widely used it is, so I'll mention it. $page->saveFields('title,body,summary', [ 'quiet' => true ]); This week the API methods for Select Options fields have also been updated to add more convenience for getting, adding and removing options to an existing selection. Let's say we have an Options field of checkboxes named "colors". Each selectable option has an ID, optional value, and title. Now you can get, add, or remove by any of those properties. Previously you had to work directly with SelectableOption instances, which wasn't as convenient. // add by title of option 'Blue' $page->colors->addByTitle('Blue'); // remove the option with value 'orange' from colors field $page->colors->removeByValue('orange'); // get SelectableOption instance of option with title 'Red' $red = $page->colors->getByTitle('Red'); echo "ID, value, title: $red->id, $red->value, $red->title"; // check if colors has an option with value 'purple' if($page->colors->hasValue('purple')) { // it has purple } The methods added to SelectableOptionArray (not yet reflected in linked docs page) include: getByID($id) getByValue($value) getByTitle($title) addByID($id) addByValue($value) addByTitle($title) removeByID($id) removeByValue($value) removeByTitle($title) That's all for this week. There likely won't be any core updates next week, as I'll be out of town again. Thanks for reading and I hope that you all have a great weekend and great week ahead.
    12 points
  2. I got great input from @BrendonKoz and @monollonom (and others) in the thread about RockCalendar development, so I thought I'd start a thread for RockCommerce too, as this module is finally taking shape 🚀 Today I implemented automatic image changing when a color option of the product is changed. This is so great, I had to share that!!! 😍 Now what is so great about this? The implementation! It will work with ANY frontend. Really. No matter if you are using Tailwind, UIkit, Bootstrap, whatever. All you have to do is to add a simple hook: // set image according to selected option ProcessWire.addHookAfter("RcVariationOption::enable", (event) => { // get the RcVariationOption instance const option = event.object; // get the image number from client input that is stored in rc-image attribute const image = parseInt(option.$rcattr("rc-image")); if (isNaN(image)) return; // find the thumbnail link and click it const index = image - 1; const link = document.querySelector( ".shop-gallery [uk-slideshow-item='" + index + "'] a" ); if (link) link.click(); }); Isn't that JavaScript code, you might ask? YES! 😎 I have totally refactored the JS implementation over the last week so that RockCommerce now uses the powerful concepts of Hooks that we know from PHP but in the JS world!! (See Let's bring hooks to JavaScript) I am in contact with Ryan to add this to the core once it is tested more. But so far it is really a pleasure to work with on both sides: First, when developing RockCommerce all I had to do to make the above shown hook possible is to make the enable() method of the RcVariationOption hookable by simply renaming it to ___enable() Second, it's also great for anybody using this module, as you can not only LISTEN to those events but you can also totally change the return value or even completely replace it's implementation. For example this is what I use to show custom prices based on the taxrate of the user visiting the website: ProcessWire.addHookAfter("RcProduct::priceTotal", (event) => { // get the money object // it's not a float, because 0.1 + 0.2 !== 0.3 in binary world! const money = event.return; // return the initial price plus the added tax event.return = money.times(1 + RockCommerce.getTaxrate() / 100); }); I've only been working with this new concept for a few days, but I already love it and can't believe I haven't implemented before! The next part to tackle is the cart 🤯😅 I'll keep you posted! PS: Anybody having an e-commerce project coming up or anybody interested in beta testing? Let's get in touch!
    4 points
  3. Indeed, I do. And I'd be very interested to try your module
    2 points
  4. @MarkE I know this is a late answer, but I think this is a great question and something that can feel daunting. Hopefully it's still helpful for you and possibly others. I originally wrote my translation module back in 2020 and when it came time to build upon it, add new features, and respond to feedback from the community that first version felt like a straightjacket in terms of structure. I rewrote it last year and have been able to iterate on it much more easily and confidently. This is really a decision based on how you view your project and what "feels good". When you get to writing modules as complex as the one you're describing, as always, the best way to reason about your approach is consistency and maintainability. This is more critical than trying to fit your code into someone else's structure. The most important way to think about organization is considering where you would first look to find a specific feature or behavior and if there are enough of these to warrant their own directory. My translation module probably isn't as complex as yours is, but it may not matter because we're both writing software applications that must be understood and maintained. Anyone can view the repo I shared above, but here are some notes about my reasoning. The purpose of this isn't meant to be a set of instructions or a roadmap, but more of a way to help you think about what is best for your application. [app] - This contains everything that supports what the module must do. If it has logic or supports logic, it goes here. [Caching] - Caching is a performance requirement and required abstraction and logic to tailor PW's caching feature for my use EngineLanguagesCache.php TranslationCache.php [Components] - Fieldsets used in one or more places where abstracting helped keep clean code in other files, like the main Module Traits - Shared behaviors used by Component files FluencyApiUsageTableFieldset.php FluencyStandaloneTranslatorFieldset.php [DataTransferObjects] - All DTOs that encapsulate data provided to and returned by methods in different classes Traits - Shared behaviors used by DTOs AllConfiguredLanguagesData.php ConfiguredLanguageData.php DEVDOC.md - A guide for me and others about how to use DTOs and their purpose as general data objects EngineApiUsageData.php .... [Engines] - Self contained "submodules" that interact with 3rd party services, both current and future. [DeepL] [GoogleCloudTranslation] [Traits] DEVDOC.md FluencyEngine.php FluencyEngineConfig.php FluencyEngineInfo.php [Functions] - General functions imported to support specific sub-behaviors that serve a specific purpose in more than one-two places fluencyEngineConfigNames.php typeChecking.php [Services] - Features that are added to Fluency that extend beyond the primary function of translating page content FluencyProcessWireFileTranslator.php FluencyErrors.php FluencyLocalization.php FluencyMarkup.php [assets] - Files that contain admin-facing UI files and compiled code, Gulp compiles and outputs to this directory [img] [scripts] [styles] [src] - Source JS and SASS that gets compiled and output to the assets directory [scripts] [scss] ...(editor/dotfiles) CHANGELOG.md - Semantic versioning is your friend, this is as important for you as it is for others Fluency.info.php Fluency.module.php FluencyConfig.php LICENSE README.md - This goes into high detail of all features because it also organizes my understanding of everything Fluency does ...(JS module configs and Composer file) I spent a lot of time on this decision. Probably overthought it, and since then there are things that I would do differently- however, how it exists now has been great to work with. My approach was entirely based on what this module does now, and what it may do in the future. The focal point and goals of this approach serve these purposes: - Fluency.info.php - Not required to separate, but helped keep the main module file cleaner and purpose-driven - Fluency.module.php - Aims to primarily contain methods needed to integrate with ProcessWire and any methods available via the global $fluency object - FluencyConfig.php - ProcessWire needs this to be configurable, but the amount of logic it contains certainly benefits from its own file Things I like having lived with it for over a year now. The directory structure is great and supports future expansion since it organizes by purpose. The specific way I structured my subfolders may not make sense for most others, but the message here is the logic behind the approach. Like I said, it seemed overengineered at the time, but now I know exactly where everything is and where everything should be in the future. This module has heavy UI integration and the way the JS files in src/ are broken down makes it clear what everything does. Because JS can import modules from other files, it really doesn't matter how many you have. The JS files for each Inputfield has on more than one occasion enabled me to add features or fixes that would have taken much longer without the granularity I put in place. Initially this module only supported DeepL for translation, but because of how Engines is structured as individual units, it was relatively easy to come back and add Google Cloud Translation because of how the individual units of behavior were organized and supported. What would I do differently having lived with it for over a year now? I would change the file names in app/ and remove the 'Fluency' prefix. That's why we have namespaces. Name collisions are handled by PHP so a simple file naming approach would have been best. This is something that I could change though without impacting the end user. FluencyErrors, FluencyMarkup, and FluencyLocalization might be better located in a 'Support' directory or something, but that would just be applying my approach to organization rather than needing to serve a hard purpose. It doesn't cause any confusion or problems as is. Probably some little things here or there, but not nagging enough to mention or even remember. Anyway. I chose a directory heavy structure with nesting which was a personal choice. There are a lot of great modules out there with different structures. A mark of good organization is asking the question "if I've never looked at this codebase before, would it be something I could get up to speed quickly and contribute without difficulty?". This may seem like a disconnected and impersonal approach, but inevitably you will feel like you need to relearn your own codebase in the future if you've spent enough time away from it. This is a great place to start. In a complex enough project, it may be worth considering separate modules that are installed as dependencies. This would let you group like-kind behavior where it may also be possible that some separate functionality could stand on its own. If you can organize things to separate concerns enough, it's trivial to access other installed modules from within modules using $this->wire('modules')->get('YourOtherModule'); and access all of its features and functionality. Since you can configure your module to have required dependencies, you can be sure that the modules you need will always be available. Organizing by purpose and features is really helpful. If the behavior doesn't provide anything useful as a standalone, then consider keeping it within the "wrapper" module. It depends on how portable you need individual behaviors to be separate from others. I'm not sure too much on the specifics of the implementation, but these are files that perform a specific type of work and would be candidates for their own directory. There's no rule for this, but it makes sense because it gives you a single place to look for these files. Long story short, trust yourself now and think about future you who will work on this later. You'll thank yourself for it. Hope this was relevant enough to be helpful even though it's a late-in-game response.
    2 points
  5. In the last couple of weeks I’ve been to several cities in Spain, France and Italy. I’d never been to any of those countries before (or Europe for that matter), so it was an exciting trip. Though the goal was for my kids to broaden their horizons and experience other parts of the world, as well as spend time with my parents and family. We got back this week and have been recovering from jet lag (another thing I’d not experienced before). The 6 hour difference was no problem getting there, but coming back, it’s a little harder to adjust! Next week I turn 50 years old (ugh), and then the following week I’m back in Europe again, except this time in the Netherlands on a bike trip with a client, and without my kids and parents. I’m not sure I’ll be able to do many core updates during the 10 day trip but do expect to have internet access this time, so will at least be online regularly and hope to be here in the forums. After that trip, I won’t be traveling again for a long time, and the focus will be on getting our next main/master version out. I noticed this week that @Robin S is now beating me as our most prolific module developer, with 72 modules! Great job and thanks for all the great modules Robin S.!
    1 point
  6. 1 point
  7. {\ProcessWire\__text('foo')} works (but it didn't solve my problem hahaha). But guess whose module did the trick
    1 point
  8. What do you think of something like this? // somewhere let HelloWorld = ProcessWire.hookable( { foo: "foo world!", ___hello: () => { return "hello world!"; }, }, "HelloWorld" ); console.log(HelloWorld.hello()); // output: hello universe! console.log(HelloWorld.foo); // output: foo universe! // somewhere else ProcessWire.addHookAfter("HelloWorld::hello", (e) => { e.return = "hello universe"; }); ProcessWire.addHookAfter("HelloWorld::foo", (e) => { e.return = "foo universe!"; }); I built a proof of concept module here, but it's already outdated and I'll work on a new version shortly. The reason why I'm building this is because I needed a way to modify some parts of my JS frontend setup on my RockCommerce project. So I added two methods RockCommerce.after(...) and RockCommerce.before(...) where we as a developer and user of RockCommerce customise every part of the Frontend (for example adding custom taxes or pricing calculations). Now while working on RockCalendar I wanted something similar. And while working on RockGrid I had the same need. For example adding custom locales or adding custom callbacks. What is easy as cake in PHP is not so easy to do in JS. So I thought, wouldn't it be nice to have hooks in JS as well? I asked Ryan and he seems to like the idea, so I'll work on that over the next few weeks. As you can see here I will most likely change the module soon to work with JS proxy objects. This is what provides the magic of AlpineJS behind the scenes. Now I'm wondering if some basic Alpine magic would make sense here as well, for example watching a property and firing a callback can be extremely helpful on its own! let demo = ProcessWire.magic({ foo: "foo", init() { this.$watch("foo", (newVal, oldVal) => { console.log(`foo property changed from ${oldVal} to ${newVal}`); }); }, }); demo.foo = "bar"; // will log "foo property changed from foo to bar" I've renamed ProcessWire.hookable() to .magic() in this example, as it does more than just adding hook support. Not sure about "magic", maybe you have better ideas? What do you think? Would that be helpful? What would be a good name? ProcessWire.rock(...) would be my favourite I guess 😄 ProcessWire.proxy(...) would also be an option. Not sure if adding basic apline magic to PW would be a good idea of if adding alpinejs itself would be better? Any JS experts here that want to help with that project?
    1 point
  9. Hey @BrendonKoz and anyone else following this thread, you might find interesting insights and examples in this thread:
    1 point
  10. The question prompted me to put FieldtypeSketchyDate on GitHub. It's a small module I started writing for a solution that never went anywhere, so its pretty basic. It may be a starting point though. In short, it lets you enter 2024 or 2024/10 or 2024/10/11 in the date value, and it also lets you search for 2024/10 and returns all pages with a date between 2024/10/01 and 2024/10/31 as well as those where 2024/10 was entered. So far, it just validates the formatting through the HTML pattern attribute. Proper date validation and calendar UI are still on the todo list.
    1 point
  11. This thread is #1 on HN at the moment: https://news.ycombinator.com/item?id=41805391 Do a search for processwire on the page. Looks like an old forum member.
    1 point
  12. My pleasure. The hooks and inputfields APIs in ProcessWire make module development an absolute dream. It's very satisfying seeing the results you can get with just a little code, and working on modules provides a motivation to dig into the core code so I learn a lot in the process. Also, another shout out to @adrian's amazing Tracy Debugger, which has been transformative for my coding. I have several more modules in progress so watch this space. 🙂
    1 point
  13. Hi. Thanks for the clarifications, now I understand the basic idea better. As I said, I would fork your module to work on it in my free time: I can't assure you of short times, or the completion of my ideas, but I will definitely let you know if there will be any developments. Best regards.
    1 point
  14. I recently discovered Pinkary.com which I would describe as a Twitter clone, built by one of the Laravel team members with all the latest and greatest of Laravel and its ecosystem (the project is open-source). Right now it's got about 1000 members after being launched earlier this year and it's almost all web developers, which reminds me of the early Twitter days. I don't get excited about social media or microblogging much, but having a concentrated community of like-minded folks is intriguing and a place to find interesting things going on and nuggets of information, without all the noise, bots and other nonsense you'd see on Twitter/X. I think it's worth a join.
    1 point
  15. That's a very good question 😅 I think that should work as well. One benefit I see with the approach above is that we can define the $schema[] array in one place and then reuse this for the ALTER TABLE statement. Ok you could extract that array into a dedicated method as well, then you'd have basically the same with a Module::upgrade() approach. Another thing is that I find it easier to read like this: if($schemaVersion < 2) ... compared to this: if(version_compare($oldVersion, $newVersion) < 1) ... I never know which operator to use here 🙂 And module versions can be confusing, as they can be integers like 104 or version strings like 1.0.4 And last but not least this approach is what I found in FieldtypeComments which is built by Ryan, which should be argument enough 😄 But thanks for the question! If you want to try another route please let us know what you find out 🙂
    1 point
  16. Oh excellent, thanks! I’ve been asking myself this, but been too lazy to actually figure it out instead of just changing the table manually for project-specific modules 😄
    1 point
  17. Has thinking around this issue changed at all? Support for .webp as an upload format for images would be a game-changer for my current project (and has been requested by several previous clients).
    1 point
×
×
  • Create New...