Leaderboard
Popular Content
Showing content with the highest reputation on 09/22/2024 in all areas
-
v2.0.21 released, which I hope fixes your problem @Flashmaster82. Let me know how it goes. Many other bug fixes are included and particular thanks to @Jonathan Lahijani for many of these.3 points
-
@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.3 points
-
PageListCustomSort Github: https://github.com/eprcstudio/PageListCustomSort Modules directory: https://processwire.com/modules/page-list-custom-sort/ This module enables the use of a custom sort setting for children, using multiple properties. About This module is similar to ProcessPageListMultipleSorting by David Karich but is closer to what could (should?) be in the core as it adds the custom sort setting in both the template’s “Family” tab and in the page’s “Children” tab (when applicable). Usage Once a custom sort is set, it is applied in the page list but also when calling $page->children() or $page->siblings(). You can also apply the custom sort when calling $page->find("sort=_custom") or $pages->find("parent_id|has_parent=$id,sort=_custom"). Unfortunately this won’t work the same way sort=sort does if you only specify the template in your selector.1 point
-
@bernhard Well that's fantastic, I already forgot to do that since jumping back to work on my project 😵💫 many thanks!1 point
-
Hey @FireWire Thx for the great meeting, I created a question in the Nette forum: https://forum.nette.org/en/36655-how-to-get-useful-debug-screens-for-latte-file-errors#p2276201 point
-
@bernhard Thanks for the meeting! Going over everything was great. Problem was on my understanding and not the module itself. RM is a fantastic tool, really appreciate it!1 point
-
I am currently redeveloping a ProcessWire app I wrote a few years back. The app provides integrated website and admin system for a membership organisation and has worked very well. I have other potential users for a similar system, so I am making it more generic and portable, as well as enhancing maintainability and appearance. The system uses quite a large number of custom modules (mostly Process modules but some Lister Page Actions) plus templates and custom Page Classes. I am now writing a ‘wrapper’ module which will hold settings in its config and include all the init and ready scripts. My question is what is the best practice for structuring the directories? I am pretty clear that it would be a good idea to bundle all the custom process modules in the directory for the ‘wrapper’ module. I could also put all the Page Classes there, but is it better to leave them in a separate classes directory? If anyone has done something similar, I would be interested to know how you structured it, and any related thoughts you might have. Thanks1 point
-
Seconding this, of course! However, the WireCache fix reminded me of an old issue that still persists as far as I can tell 😉 No upvotes, but still feels like a bug to me: https://github.com/processwire/processwire-issues/issues/1604 It’s about the preloading feature preventing fresh cache values from being generated.1 point
-
Search Corrections Suggests alternative words for a given input word. This can be useful in a website search feature where the given search term produces no results, but an alternative spelling or stem of the term may produce results. The module has two methods intended for public use: findSimilarWords(): this method suggests corrected spellings or similar alternatives for the given word based on words that exist in the website. stem(): this method returns the stem of the given word, which may give a full or partial match for a word within the website. The module doesn't dictate any particular way of using it in a website search feature, but one possible approach is as follows. If a search produces no matching pages you can take the search term (or if multiple terms, split and then loop over each term) and use the module methods to find alternative words and/or the stem word. Then automatically perform a new search using the alternative word(s), and show a notice to the user, e.g. Your search for "begining" produced no matches. Including results for "beginning" and "begin". findSimilarWords() This method creates a list of unique words (the "word list") that exist on the pages and fields that you define, and compares those words to a target word that you give it. The method returns an array of words that are sufficiently similar to the target word. For multi-language sites, the $user language determines which language populates the word list. Similarity The method ranks similar words by calculating the Levenshtein distance from the target word. Where several results have the same Levenshtein distance from the target word these are ordered so that results which have more letters in common with the target word at the start of the result word are higher in the order. Method arguments $target (string) The input word. $selector (string) A selector string to find the pages that the word list will be derived from. $fields (array) An array of field names that the word list will be derived from. $options (array) Optional: an array of options as described below. minWordLength (int) Words below this length will not be included in the word list. Default: 4 lengthRange (int) Words that are longer or shorter than the target word by more than this number will not be included in the word list. Default: 2 expire (int) The word list is cached for this number of seconds, to improve performance. Default: 3600 maxChangePercent (int) When the Levenshtein distance between a word and the target word is calculated, the distance is then converted into a percentage of changed letters relative to the target word. Words that have a higher percentage change than this value are not included in the results. Default: 50 insertionCost (int) This is an optional argument for the PHP levenshtein() function. See the docs for details. Default: 1 replacementCost (int) This is an optional argument for the PHP levenshtein() function. See the docs for details. Default: 1 deletionCost (int) This is an optional argument for the PHP levenshtein() function. See the docs for details. Default: 1 Example of use // The input word that may need correcting $target = 'dispraxia'; // Get the Search Corrections module $sc = $modules->get('SearchCorrections'); // Define a selector string to find the pages that the word list will be derived from $selector = "template=basic-page"; // Define an array of field names that the word list will be derived from $flds = ['title', 'body']; // Optional: override any of the default options $options = ['maxChangePercent' => 55]; // Get an array of similar words that exist in the pages/fields you defined // The return value is in the format $word => $levenshtein_distance $results = $sc->findSimilarWords($target, $selector, $flds, $options); Example result: stem() This method uses php-stemmer to return the stem of the given word. As an example, "fish" is the stem of "fishing", "fished", and "fisher". The returned stem may be the original given word in some cases. The stem is not necessarily a complete word, e.g. the stem of "argued" is "argu". If using the stem in a search you will probably want to use a selector operator that can match partial words. Method arguments $word (string) The input word. $language (string) Optional: the language name in English. The valid options are shown below. Default: english catalan danish dutch english finnish french german italian norwegian portuguese romanian russian spanish swedish Alternatively, you can use the ISO 639 language code for any of the above languages. Example of use // The input word $word = 'fishing'; // Get the Search Corrections module $sc = $modules->get('SearchCorrections'); // Get the stem of the word $stem = $sc->stem($word); https://github.com/Toutouwai/SearchCorrections https://processwire.com/modules/search-corrections/1 point
-
This week we introduce a new module named Custom Fields. This module provides a way to rapidly build out ProcessWire fields that contain any number of subfields/properties within them. No matter how simple or complex your needs are, Custom Fields makes your job faster and easier. Not only does this post introduce Custom Fields, but also documents how to use them and includes numerous examples— https://processwire.com/blog/posts/custom-fields-module/1 point
-
This is just a quick info that I've built a little module on top of @David Karichs great PageHitCounter to store and present historical data of page views. Note that "historical data" means it can display all pageviews from the time this module has been installed. It can NOT show statistics from before that point of time! It is by no means meant to replace a full analytics software, but for simple sites this might be just what you (or the user) need(s). I release it as imperfect as it is for two reasons: Even if the presentation of the data is not perfect yet it might make sense collecting statistics sooner than later. If anybody wants to work on the presentation side I'd happily merge PRs (or if anybody wants to sponsor updates... PM) Presentation is done by plotly.js in conjunction with the awesome tabulator.js (both loaded via CDN at the moment) - that means easy analytics can already be done by using tabulator's internal filter capabilities! Please see the readme for important notes! https://github.com/baumrock/RockHitCounter1 point