Jump to content

Jonathan Lahijani

  • Content Count

  • Joined

  • Last visited

  • Days Won


Jonathan Lahijani last won the day on August 29

Jonathan Lahijani had the most liked content!

Community Reputation

1,085 Excellent


About Jonathan Lahijani

  • Rank
    Distinguished Member
  • Birthday 04/24/1983

Profile Information

  • Gender
  • Location
    Los Angeles, CA
  • Interests
    Web Development, Snowboarding, Hiking

Contact Methods

  • Skype

Recent Profile Visitors

10,969 profile views
  1. This is a braindump of experiences and considerations that a matrix-based page builder should balance. I'll give more thoughts on this later: heading hierarchy source order div nesting section patterns general fields general fields "2"..."n" ex: image + image_2 ex: list_items + list_items_2 (for example, a section w/ description list + tabs) fields mapping to components ex: title => heading ex: image => image ex: menu_items => subnav items, nav items ex: list_items => description list items, tab items, accordion items, slider items, slideshow items, grid items what to do if field is empty? hide surrounding div structure? list_items field selectively showing/hiding appropriate fields depending on component type ex: accordion items => title + body only ex: slideshow items => image only (note: currently a bug with this using matrix template overrides; utilizing hack workaround instead atm) item templates for use in repeatable components when to use hannacode vs. component section switching (in an easy/quick way) section options (mystique) css framework components 3rd party components component options css framework flexibility (use setting() to store classes) naming approach ex: Grid vs. List of People user friend matrix-type registration visual selector w/ placeholder content instant preview (ideally prodrafts) direct vs. dynamic content saved / reusable sections multilingual weird things like - wrapping links (messes up predictable div nesting?) - image backgrounds using inline css - single line vs multi-line text ex: sometimes a heading should be multi-line instead of having 2 separate fields ability to programmatically include a section template using $files->include css grid vs. flex grid - flex gap? https://coryrylan.com/blog/css-gap-space-with-flexbox A section is literally an HTML <section> (or it can be a div if specified). Imagine a section that has 2 components in the following source order: image + text. A section can be styled independently and map to UIkit section classes: https://getuikit.com/docs/section You could call this section "Image + Text" in repeatermatrix for example and give it the UIkit markup to put the image on the left and the text on the right using the appropriate css classes, and wrap it in a container. This is very easy to understand for content editors. Very important. Now you what if you want flexibility such as having a small container instead of a regular sized container? The "wrong" way to do this would be to make a new matrix-type called "Image + Text (Small Container)". Instead you think of maybe having additional dropdowns to manage such things in the matrix block, but if you think that through and build many sections, it will be overly complex and feel wrong. The "correct" way to do this would be to have various section patterns that modify the div structure and component options of that matrix-type. So for example, if you wanted a small container + animations + rounded corners on the image, you could have a section pattern (would have to be created by the developer) that the user selects that applies all that collectively. Maybe each section has 2-5 section patterns which seems reasonable. With this section pattern approach, you could theoretically have the same section display the image on its own row and the text below. But what if you want each component to be in its own different sized container? If you think this through this requires a different div nesting structure to keep things super flexible: <div class="<?=setting('div-1-class')?>" <?=setting('div-1-attr')?>><!-- container --> <div class="<?=setting('div-1-div-1-class')?>" <?=setting('div-1-div-1-attr')?>><!-- container inner --> <div class="<?=setting('div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-attr')?>><!-- grid --> <div class="<?=setting('div-1-div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-1-attr')?>><!-- col --> <div class="<?=setting('div-1-div-1-div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-1-div-1-attr')?>><!-- component wrapper --> <?=files()->include('./fields/blocks/image.php', [ 'page'=>$page, 'block_options'=>setting('image-1-block-options') ])?> </div> </div> <div class="<?=setting('div-1-div-1-div-1-div-2-class')?>" <?=setting('div-1-div-1-div-1-div-2-attr')?>> <div class="<?=setting('div-1-div-1-div-1-div-2-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-2-div-1-attr')?>> <?=files()->include('./fields/blocks/text.php', [ 'page'=>$page, 'block_options'=>setting('text-1-block-options') ])?> </div> </div> </div> </div> </div> vs. <div class="<?=setting('div-1-class')?>" <?=setting('div-1-attr')?>><!-- container 1 --> <div class="<?=setting('div-1-div-1-class')?>" <?=setting('div-1-div-1-attr')?>><!-- container inner 1 --> <div class="<?=setting('div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-attr')?>><!-- grid 1 --> <div class="<?=setting('div-1-div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-1-attr')?>><!-- col 1 --> <div class="<?=setting('div-1-div-1-div-1-div-1-div-1-class')?>" <?=setting('div-1-div-1-div-1-div-1-div-1-attr')?>><!-- component wrapper 1 --> <?=files()->include('./fields/blocks/image.php', [ 'page'=>$page, 'block_options'=>setting('image-1-block-options') ])?> </div> </div> </div> </div> </div> <div class="<?=setting('div-2-class')?>" <?=setting('div-2-attr')?>><!-- container 2 --> <div class="<?=setting('div-2-div-1-class')?>" <?=setting('div-2-div-1-attr')?>><!-- container inner 2 --> <div class="<?=setting('div-2-div-1-div-1-class')?>" <?=setting('div-2-div-1-div-1-attr')?>><!-- grid 2 --> <div class="<?=setting('div-2-div-1-div-1-div-1-class')?>" <?=setting('div-2-div-1-div-1-div-1-attr')?>><!-- col 2 --> <div class="<?=setting('div-2-div-1-div-1-div-1-div-1-class')?>" <?=setting('div-2-div-1-div-1-div-1-div-1-attr')?>><!-- component wrapper 2 --> <?=files()->include('./fields/blocks/text.php', [ 'page'=>$page, 'block_options'=>setting('text-1-block-options') ])?> </div> </div> </div> </div> </div> You can represent those 2-component div structures like this: (1 2) (1) (2) If you have 3 components: (1 2 3) (1 2) (3) (1) (2 3) (1) (2) (3) 4 components: (1 2 3 4) (1 2 3) (4) (1 2) (3 4) (1) (2 3 4) (1) (2) (3 4) (1) (2 3) (4) (1 2) (3) (4) (1) (2) (3) (4) 5 components: (1 2 3 4 5) (1) (2 3 4 5) (1 2) (3 4 5) (1 2 3) (4 5) (1 2 3 4) (5) (1 2 3) (4) (5) (1 2) (3) (4) (5) (1 2) (3 4) (5) (1) (2 3 4) (5) (1) (2 3) (4 5) (1) (2) (3 4 5) (1) (2 3) (4) (5) (1) (2) (3) (4 5) (1 2) (3) (4 5) (1) (2) (3) (4) (5) (it can actually get more complex than that, but no need to go there) A CSS Grid approach probably removes the complexity required for div nesting (not 100% sure since I haven't used CSS-grid in a long time). UIkit would ideally have a built in solution in version 4. Maybe I'm overthinking this div nesting part. Perhaps the section pattern (which comes in via a json settings file and stores its values in setting()) directly specifies which div nesting layout should be used. Hmm.
  2. I just started playing around with this and I thought I was doing something wrong, but based on the lemmas, "books" will match "source". I thought I was going crazy because I was getting a ton of extra, unexpected results, but I suppose this definition makes that association: // WireWordTools/lemmas/s.json "sourcebooks": "sourcebook", A heads up for anyone diving into Ryan's WireWordTools module.
  3. It's definitely possible. I built a complicated online scholarship application with ProcessWire, while not the same thing as an exam, it did have some similarities. An application would be initiated by a nominator on behalf of the person being nominated, and then there were 3 reference people per application (it got pretty complicated). Notification emails, application statuses, a bunch of other things. In my case, the public interface was mostly the in admin, so it had no "frontend" (other than some templates that would be used for redirecting to the edit url). That meant using roles, permissions and hooks accordingly to lock things down. In your use case, I would say to build a custom front-end for it and not do it the way I did it. It will be easier and I think it makes more sense for your use case (ie, multiple choice questions as opposed to all kinds of various fields from files, rich text, etc.). One crucial thing to test heavily is making sure the test takers don't get logged out for some reason. In my situation, that happened and I could not exactly pinpoint why, but it caused a lot of trouble especially since the application involved people filling out long-form answers, which would then be wiped when they hit the save button and their session had expired. I'll have to figure that out in the next iteration, but that was bad. You may want to have one question per page. This is how online / computer-based tests usually do it anyway. As for structure, maybe this could work: Home Exams Exam 1 Question 1 ('question' template) Answer Option 1 ('answer' template) Answer Option 2 Answer Option 3 Question 2 Answer Option 1 Answer Option 2 Answer Option 3 Submissions Submission 1 ('submission' template) Submission 2 question template title - the question being asked answer template title - the answer text correct - checkbox indicating this is the correct answer submission template (title -- make the field not required and hide it, rather than removing it as a global field... a trick I use) user - page field for parent /admin/access/users/; the person who took the test; maybe use the autocomplete inputfield here since loading 25,000 things into a select list will be problematic exam - page field for parent /exams/; the exam that was taken answers - multi-select page field (asm-select) that has all the answers they chose for the particular exam (any other relevant data you need to store) With some code you can check the correct answers of an exam vs. the submission answers and go from there.
  4. I've probably thought up a dozen different approaches to creating an ideal page builder in ProcessWire but each of them have their pros and cons. It's very difficult to strike a perfect balance for something that suits both developers and editors. There are so many (!) other variables to consider. This isn't just a ProcessWire dilemma, but just the nature of page building itself. There are trade-offs and complexity must live somewhere. In an ideal world, you'd use RepeaterMatrix in a classic way, but if you run that thought experiment, here's what happens: I'll make matrix-types for all the sections in a particular page. I'll write the templates for those matrix-types, so all an editor has to do is fill in the fields and everything just falls into place. I'll drive off into the sunset and be happy. Well, turns out there are some changes that need to be done. MatrixType X needs another field. I'll add that field and update the template. Well now we need another matrix-type, but it's basically identical to MatrixType X except the columns are flipped. Does this warrant a new matrix-type or a toggle option of some sort? ... You start another new project and it needs a page builder like the first site. You realize there's going to be some heavy re-use of matrix-types from the first site, so you now think... hmm maybe I should somehow modularize this approach where there is a shared matrix-type library. (I've done this) ... Many of the matrix-types between site 1 and site 2 are similar, but with slight differences... some have different css class names, some have different field names. Now this shared matrix-type library has a bunch of matrix-types but you feel unsatisfied because you think there is a better way to refactor this. The ideas in your head about how to refactor end up going against the grain of the classic page builder way / repeater matrix approach and now you're frustrated. I've gone through this thought process a bunch of times. I've researched basically every single page builder out there. It's like trying to solve a puzzle without a definitive end result. One thing that happened is reactive frameworks gave rise to the current generation of page builders (think WP Block Editor / Gutenberg, SquareSpace, and few dozen others). This left the classic page builder approach behind (RepeaterMatrix, ACF Flexible Content). But I like the classic approach because it gives more programmatic control and it's the way ProcessWire works... and editors can't really mess things up since it's a more controlled experience. I feel like there hasn't been experimentation and development for the classic approach since the world has "moved on" to the newer generation approach. I think RepeaterMatrix is good, but maybe it can benefit from some experimentation of some sort.
  5. Sometime in the recent past, Chrome and Firefox decided to hide important parts of a URL, like https:// and www., which only appear if you focus the address bar field (and even then it's still finicky). I find that it's important to show the full URL, especially when doing ProcessWire development, and particularly when deploying a website and trying to get .htaccess just right on the production site. How to show full URL in Chrome: https://winaero.com/blog/always-show-full-url-address-in-google-chrome/ How to show full URL in Firefox: https://www.cnet.com/how-to/how-to-show-the-full-url-in-firefox/ How to show full URL in Safari: https://www.laptopmag.com/articles/safari-view-full-url
  6. Looks neat and has JS components which is promising, but needs more variables as the document states and more JS components (no tabs, no accordion, etc.). I'll be following this. UIkit is tough to beat however.
  7. ProcessWire's database structure is unusual when first looking at it... Alien in fact. I had a similar reaction in the beginning. But then as you use the system, especially if coming from other content management systems, you'll see that it's very well thought out, flexible and performant. If you compare it to WordPress which has a more traditional structure combined with the post meta table, you'll see where WordPress' approach breaks down. Menus in WP get shoved into the post and post meta table in a really hacky way... Not good. Woocommerce from what I understand now uses their own table structures as opposed to the default WP ones. Slow Woocommerce sites were a big problem. Regardless of the system, editing data directly in MySQL is a bad idea. If I had a potential client that insisted on this, it would be a huge red flag. If they want to get hands on and edit data programmatically, I'd recommend learning the basics of manipulating pages via the API which may satisfy their need.
  8. I could give you a hand as to how the script works and overall execution, but I can't provide the script. It's honestly nothing complex though... it just grabs the feed and does the processing the site needs to get the data into the site as pages (or deletes old listing pages based on other criteria). Given your experience with ProcessWire, it's nothing too out of the ordinary.
  9. The site hasn't been on ProcessWire for some time, but payment processing was all done "offline". The advertising packages were just pages that had associated prices. I believe I have a page structure called "Advertising Subscriptions" that had a single-select user field, a single-select advertising package field, and a start and end date. This is the PW equivalent of join table (is that the right word?).
  10. An example of how I'm using this in my super module would be like this... My module has different sections (repeatermatrix), and each of those sections contain different blocks like gallery, body copy, etc (typical page-builder stuff). Section A has "body copy" + "gallery" Section B has "headline" + "gallery" A big part of my super module is programmatic control for developers (at the expense of 100% flexibility like with page layout builders... Elementor, Gutenberg, YooTheme Pro). Therefore, a developer would be able to "hook" into any sections that were applied to a page via regions. The gallery block would be peppered with different region IDs like this: <div pw-id="block-gallery" pw-id="section-a-block-gallery">...</div> This essentially provides a developer the ability to run region actions on galleries on the site, --or-- all galleries that were within the context of Section A.
  11. With markup regions, you can assign an HTML-based ID (which gets rendered in final output) or PW's specific 'pw-id' attribute (which does not get rendered in final output) to your tag. For example: <div pw-id='foo'>...</div> Tip #1: Markup Regions do NOT need to be unique! In HTML, IDs should be unique to the page (otherwise it's considered invalid html). However when defining markup regions in PW using the above region method, it does NOT actually have to be unique. The cool part about this is if you apply actions to regions (append/prepend/replace/etc) with that pw-id, it will apply the action to all of them. This is incredibly good. Tip #2: You can assign multiple regions to a tag and run actions on any of of them! I just discovered that you can assign multiple IDs to a region like this: <div pw-id='foo' pw-id='bar'>...</div> If you run an action on the first, the second, or both, it will work! This is also incredibly neat and opens up a lot of possibilities. None of this is mentioned in the documentation on Markup Regions: https://processwire.com/docs/front-end/output/markup-regions/ Enjoy.
  12. What are the general thoughts around a module that creates its own fields, templates and/or pages that the module depends on? For example, let's say an ecommerce module is developed and it needs a coupon feature like other ecommerce systems (each coupon has a code, fixed vs. percent discount, products it applies to, etc.). Is it more 'proper' for the module to create the necessary fields/templates/pages in the page tree, or is this something that should be done in a module config? Where is the line drawn? SnipWire for instance stores its tax settings in module config and it looks like some non-trivial work went into getting that to work nicely, when the fields/templates/pages approach could have been used. With the fields/templates/pages approach, I feel you have a lot of flexibility and it's nice to be able to query the data with $pages. But on the flip-side, you then have to potentially inundate a ProcessWire setup with your own set of fields/templates/pages and conflicts can occur (although this can be worked around which I have successfully done). Then there's the scenario of what you do if the module were to be uninstalled: do you removed the custom fields/templates/pages that the module relies on or should the user do that manually? (or should there be a separate uninstallation / cleanup module?)
  13. Check this out: https://processwire.com/blog/posts/pw-3.0.137/#on-demand-mirroring-of-remote-web-server-files-to-your-dev-environment
  14. Looks great! Cool background on the modals as well. I love seeing PW sites with a vast amount of nested pages with relationships between them as it really shows the power of the system. I cringe when in WordPress land people say "I need a catalog, not an ecommerce site, so let me bring in Woocommerce for that." 🤦‍♂️ Total overkill and loss of flexibility. ProcessWire's got that handled as this site demonstrates, and Snipcart finishes it off. It seems like is this was on a different ecommerce platform previously? If so, what lead to it being re-developed? Any other background you can share?
  15. In terms of making the core smaller, can the old admin system (ie, the contents of /wire/templates-admin/ I believe it is) be done away with? The admin that looks like this: https://i.ytimg.com/vi/eq-9GQCT0lw/maxresdefault.jpg
  • Create New...