-
Posts
733 -
Joined
-
Last visited
-
Days Won
9
Everything posted by SamC
-
This is something that I've been trying to figure out for quite awhile. At the moment I do this: 1) Create template banner-index 2) Create template banner-entry 3) Create page 'Banners' using banner-index template 4) Create child page of whatever the banner will be, let's say 'Sounds good'. So you now have: ... and the template for 'Sounds good': (link to page is a Page type which enables me to pick ANY page on the site with a PageListSelect+ as input field type and 'Home' set as parent) So I create a file which is going to output the actual banner: // includes/_banner.php <?php namespace ProcessWire; // get the banner page determined by the id passed to this template // from wireIncludeFile additional array, see next snippet $banner = $pages->get($Id); ?> <div class="jumbotron jumbotron-fluid py-6 text-center bg-repeat"> <div class="container"> <h2 class="display-4"><?= $banner->headingMain; ?></h2> <p class="lead"><?= $banner->headingSubtitle; ?></p> <a href='<?= $banner->linkToInternalPage->url; ?>' class='btn btn-primary'>Go to <?= $banner->linkToInternalPage->title; ?></a> </div> </div> ...then I grab this file using 'wireIncludeFile' so I can pass an additional value into this template which is the id of the page: // views/about.php <?php wireIncludeFile('./includes/_banner', array('Id' => 1212)); ?> So the page id of 'Sounds good' is '1212'. I pass this manually at the moment. So now, when /about/ is viewed, it displays '_banner.php' wherever I want, where you can see the PHP is populated with values from the 'Sounds good' page: So, what about making this a bit more dynamic instead of manual IDs in a template. Create a new field 'bannerToDisplay' as a 'Page' field type: ...then select it to return just a single page: ...and choose the parent of selectable pages to be the 'Banners' page you made earlier using the 'banners-index' template with an input field type of 'Select': ...go add this bad boy to a template! ...then go edit your page: ...choose a banner, save it. Go edit the code which renders '_banners.php' and get rid of that manual ID: // views/about.php <?php // if something has been selected from the 'Choose a banner' dropdown if ($page->bannerToDisplay) { // get the ID of the selected page $bannerPageId = $page->bannerToDisplay->id; // pass this ID to the _banner.php template wireIncludeFile('./includes/_banner', array('Id' => $bannerPageId)); } ?> ...create another banner and then change the dropdown and refresh the page! This is obviously just an example which enables adding of a single block but once you get the idea of the Page field type and how you could use it, your options will increase a fair bit. The thing I found with repeaters is that you still can't add a content block above, say , a body field, then another one below it. Your template would literally just have to have the repeater on it and you add item after item. Of course, with a repeater, every item will be the same (defined once, then just repeated with alternative content in the same field types). I'm going to try the pro fields repeater matrix next for something similar. I'd love to be able to make entire pages simply from blocks and to be able to drag them around. It takes a fair bit of forethought though. Just experiment, have fun! NOTE: This still means you have to define WHERE the banner will go on the page via a template, but you can change WHICH banner to display on the page edit inside admin it doesn't solve your problem but hopefully helps a bit and gets you on your way. Good luck!
-
Hi, I'm looking at ways that make it easier for a user to add inline images with a caption in an RTE field. At present it's a bit weird as there's no central media store so in order to add images to a text field on a template, I have to: 1) Create an 'images' field 2) Stick that above the RTE field in the template 3) Then when editing the page, a user has to add images that they 'might use' into this field (or upload into this field after clicking image toolbar button) 4) Then the image is available to add in the RTE Its pretty long winded for every RTE field that 'might' have an image in it. Anyway, this isn't my problem (although try explaining the above to a client). If you have any better ways of doing this, I'm all ears! Anyway, I have a CKEditor issue: Problem When I add an image and select to add a caption: ...I cant add the text at this screen, which isn't too user friendly. Instead, a handy popup tells you to add in the next step of the process. So I click 'insert image' and: ...wtf?? Then I save the page and view it the browser: ...and BOOM, normal size again! The resulting HTML is: <figure> <img alt="" src="/site/assets/files/1112/pw.png" width="636" /> <figcaption>WOW, this image is TINY!</figcaption> </figure> Where do I edit the image alt text? I filled in the description field but seems this isn't used for the alt text. I tracked down the CSS culprit for the tiny image in: http://mysite.com/wire/modules/Inputfield/InputfieldCKEditor/contents.css So, in short: 1) How can I override this without editing anything inside the wire folder? 2) How can I get an alt text onto this image? Any advice would be awesome thanks!
-
Love a bit of Armin.
-
My original code: $m->to($contactFormRecipient) ->from($email) ->subject('Contact form submission') ->bodyHTML($message) ->send(); // SEND #1 if ($m->send()) { // SEND #2 I totally didn't notice the parenthesis in the if block. Doh! It sends the mail, then returns true/false presumably. You were right in your first reply. Thanks @Harmen All working now Full code here (bootstrap 4 alpha 6): <?php namespace ProcessWire; wireIncludeFile("./vendor/vlucas/valitron/src/Valitron/Validator.php"); $captcha = $modules->get("MarkupGoogleRecaptcha"); $contactPageID = '1020'; $contactFormRecipient = 'MY_EMAIL_ADDRESS'; $name = $sanitizer->text($input->post->name); $email = $sanitizer->email($input->post->email); $message = $sanitizer->text($input->post->message); $v = new \Valitron\Validator(array( 'name' => $name, 'email' => $email, 'message' => $message ) ); $v->rule('required', ['name', 'email', 'message']); $v->rule('email', 'email'); if ($input->post->sendMe) { if ($v->validate()) { if ($captcha->verifyResponse() === true) { $message = " <html> <body> <p><b>From:</b></p> <p>{$name}</p> <p><b>Email:</b></p> <p>{$email}</p> <p><b>Message:</b></p> <p>{$message}</p> </body> </html> "; $mail = wireMail(); $mail->to($contactFormRecipient) ->header('Reply-To', $email) ->subject('JS Electrician form submission') ->bodyHTML($message); if ($mail->send()) { $session->flashMessage = "<h2>Thank you for your message! I will get back to you shortly.</h2>"; $session->sent = true; $session->redirect($pages->get($contactPageID)->url); } else { $session->flashMessage = "<h2>Sorry, an error occured. Please try again.</h2>"; } } else { $session->flashMessage = '<h2>Recaptcha must be complete.</h2>'; } } else { $session->flashMessage = 'Please fill out the fields correctly.'; } } ?> <div class="container"> <div class="row justify-content-between"> <div class="col-sm-12 col-lg-8 py-3"> <?php if($session->flashMessage):?> <div class="alert <?php echo $session->sent ? 'alert-success' : 'alert-danger'?>" role="alert"> <?php echo $session->flashMessage;?> </div> <?php endif;?> <form id="contact-form" method="post"> <div class="row"> <div class="form-group col-sm-12 col-lg-6 py-2 <?php echo $v->errors('name') ? 'has-danger' : ''?>"> <label for="name">Name (required)</label> <input class="form-control" name="name" id="name" type="text" value="<?php echo $sanitizer->text($input->post->name); ?>"> </div> <div class="form-group col-sm-12 col-lg-6 py-2 <?php echo $v->errors('email') ? 'has-danger' : ''?>"> <label for="email">Email (required)</label> <input class="form-control" name="email" id="email" type="text" value="<?php echo $sanitizer->text($input->post->email); ?>"> </div> </div> <div class="form-group py-2 <?php echo $v->errors('message') ? 'has-danger' : ''?>"> <label for="message">Message (required)</label> <textarea class="form-control" name="message" id="message" rows="8"><?php echo $sanitizer->text($input->post->message); ?></textarea> </div> <div> <!-- Google Recaptcha code START --> <?php echo $captcha->render(); ?> <!-- Google Recaptcha code END --> </div> <div class="form-group"> <button type="submit" class="btn btn-primary" name="sendMe" value="1">Send message</button> </div> </form> </div> <div class="col-sm-12 col-lg-3 py-3"> <h2>Have a question?</h2> <p class="mb-0"><i class="fa fa-phone" aria-hidden="true"></i> <?php if($clientPhone) echo $clientPhone; ?></p> </div> </div> </div> <?php $session->remove('flashMessage'); $session->sent = false; echo $captcha->getScript(); ?>
-
Thanks @Harmen but the next block relies on the return value of send() to show the flash message like so: if ($m->send()) { $session->flashMessage = "<h2>Thank you for your message! I will get back to you shortly.</h2>"; $session->sent = true; $session->redirect($pages->get($contactPageID)->url); } else { $session->flashMessage = "<h2>Sorry, an error occured. Please try again.</h2>"; } Would you mind sharing the code for your form so I can have a look at how you approach this?
-
I can't work out what the problem here is. I have a simple contact form: <?php namespace ProcessWire; wireIncludeFile("./vendor/vlucas/valitron/src/Valitron/Validator.php"); $captcha = $modules->get("MarkupGoogleRecaptcha"); $contactPageID = '1020'; $contactFormRecipient = 'MY_EMAIL_ADDRESS'; $name = $sanitizer->text($input->post->name); $email = $sanitizer->email($input->post->email); $message = $sanitizer->text($input->post->message); $v = new \Valitron\Validator(array( 'name' => $name, 'email' => $email, 'message' => $message ) ); $v->rule('required', ['name', 'email', 'message']); $v->rule('email', 'email'); if ($input->post->sendMe) { if ($v->validate()) { if ($captcha->verifyResponse() === true) { $message = " <html> <body> <p><b>From:</b></p> <p>{$name}</p> <p><b>Email:</b></p> <p>{$email}</p> <p><b>Message:</b></p> <p>{$message}</p> </body> </html> "; $m = $mail->new(); $m->to($contactFormRecipient) ->from($email) ->subject('Contact form submission') ->bodyHTML($message) ->send(); if ($m->send()) { $session->flashMessage = "<h2>Thank you for your message! I will get back to you shortly.</h2>"; $session->sent = true; $session->redirect($pages->get($contactPageID)->url); } else { $session->flashMessage = "<h2>Sorry, an error occured. Please try again.</h2>"; } } else { $session->flashMessage = '<h2>Recaptcha must be complete.</h2>'; } } else { $session->flashMessage = 'Please fill out the fields correctly.'; } } ?> <div class="container"> <div class="row justify-content-between"> <div class="col-sm-12 col-lg-8 py-3"> <?php if($session->flashMessage):?> <div class="alert <?php echo $session->sent ? 'alert-success' : 'alert-danger'?>" role="alert"> <?php echo $session->flashMessage;?> </div> <?php endif;?> <form id="contact-form" method="post"> <div class="row"> <div class="form-group col-sm-12 col-lg-6 py-2 <?php echo $v->errors('name') ? 'has-danger' : ''?>"> <label for="name">Name (required)</label> <input class="form-control" name="name" id="name" type="text" value="<?php echo $sanitizer->text($input->post->name); ?>"> </div> <div class="form-group col-sm-12 col-lg-6 py-2 <?php echo $v->errors('email') ? 'has-danger' : ''?>"> <label for="email">Email (required)</label> <input class="form-control" name="email" id="email" type="text" value="<?php echo $sanitizer->text($input->post->email); ?>"> </div> </div> <div class="form-group py-2 <?php echo $v->errors('message') ? 'has-danger' : ''?>"> <label for="message">Message (required)</label> <textarea class="form-control" name="message" id="message" rows="8"><?php echo $sanitizer->text($input->post->message); ?></textarea> </div> <div> <!-- Google Recaptcha code START --> <?php echo $captcha->render(); ?> <!-- Google Recaptcha code END --> </div> <div class="form-group"> <button type="submit" class="btn btn-primary" name="sendMe" value="1">Send message</button> </div> </form> </div> <div class="col-sm-12 col-lg-3 py-3"> <h2>Have a question?</h2> <p class="mb-0"><i class="fa fa-phone" aria-hidden="true"></i> <?php if($clientPhone) echo $clientPhone; ?></p> </div> </div> </div> <?php $session->remove('flashMessage'); $session->sent = false; echo $captcha->getScript(); ?> So validation works fine, the flash messages work, the only thing that's wrong is in my gmail app (or gmail in the browser - this form is sending to a gmail address). I receive it twice, but the second one is like it's being forwarded back to me? Is weird, here's what it looks like once I've submitted it: ...then if I expand the [...] quoted text bit... Can anyone see or suggest why this might be happening? Thanks for any advice
-
Template associated to an _include.php page: possible???
SamC replied to ARG's topic in Getting Started
Have some fun with it. There's no magic, it's all in the docs. Go to the docs, read, test, read some more, test again. This was a total boost for me at the beginning. The docs are excellent and explain a lot of things. For example, you could start here: https://processwire.com/api/variables/pages/ What I'm saying is, the help pages link so well together that every time new questions are kicked up, the answer is just a few clicks away. And this forum is phenomenal for support. Stick with it, you'll be able to make some cool things in no time. Once you find a way that suits you in terms of organising your data in your site, the templates, fields etc... then it's just a matter of getting stuck into the docs to find a way to print these values to a webpage. Further in, you'll probably want to do some more complex things with the data, but deal with that when you get there. -
I'm not sure either, but did you try it?
-
Out of curiosity, would this work? $articles = $pages->find("template=project, sort=-date "); foreach ($articles as $article) { // from https://processwire.com/api/modules/select-options-fieldtype/#outputting-selected-options-on-a-page $n = $article->number->title; // <<<< the value of the selected option $thumbs = $article->images->slice(0, $n); foreach($thumbs as $thumb) { echo "<img src='$thumb->url' >"; } }
-
Some good suggestions, thanks. Just gonna crack on with a test site and see if I can put this together. Can I just clarify something, when we talk of creating new users, are we saying all users will be listed at '/access/users/'? Or do they sometimes get created in the page tree? (I'm talking generally, not edge case) Also, (in @LostKobrakai's approach) if having minimal fields in the user template means having a second template, wouldn't deleting a user in the admin (when logged in as superuser) mean also having to delete the associated profile page manually? Everything seems to be doubled up. I'm sure my preferred method will become clear once I've played around with it.
-
I can only see a missing '}' at the end to close the first loop (presuming an incomplete copy/paste), the rest looks ok to me. I just tried slice in something similar and works fine. // home.php with images field called 'images' <?php $toRender = $page->images->slice(0, 2); // 2 of 4 available images foreach($toRender as $image) { echo "<img src='{$image->url}' />"; } ?> Be interested to see what's wrong here.
-
@Robin S ah I see, I've never used that. Thanks for the info.
-
Thanks @LostKobrakai This sounds like a sensible way to organize, Ilike it. So I'd do this? User (template) - name - email - password Profile (template) - avatar - social media link - page ref (to user) ...how would I link the two? A page ref field on profile to user? I'd like to manually create a few users to test this. But if you had 100,000 users, that'd be hell of a long dropdown list on the page ref field. I'm guessing it would be better to do this programmatically in a template when a user signs up to create a matching profile when the user is created. Is this what's meant here?
-
Thanks @arjen that's a decent explanation but I'm still not 100% clear on this. I think it would be helpful if we could define what is meant by a user (or use different terminology to show what type of user they are). This confuses me a bit when people talk about 'users'. To me, there are different types of users (of the logged in to your site variety). For example, an admin could have superuser role, staff could have a 60% permissions client role (maybe excluding config/permissions sections or something), and members (i.e. people who are a member of your site, who can only change their email/password/avatar) with a member role. So in the above case, how would you handle something like this: 1) An admin may only need name, email, password. 2) Staff may have fields, email, phone, address, department, name, email, password. 3) A member may have avatar, email, social media profiles, name, email, password. Is it just a case of putting EVERY possible required field onto the user template, then users (I mean anyone who logs in) would just fill in the fields they require? Or did I just describe an edge case? I've seen situations on the forum which describe having users as part of the page tree. Would this be a normal way to do things if you had 100,000 members? Or would they all be listed in '/access/users/'? I've set up a local site in which I am going to experiment with user profiles to see if I can get this sorted in my head.
-
I wanted to create a client profile with fields: profilePicture clientEmail clientAddress clientPhoneNumber profileFacebook profileTwitter profileLinkedIn profileGooglePlus ...etc. At the moment, this lot sits in a template called 'site-settings' but they should really be associated with a user. Ok, so this site only has one user so it doesn't matter, but I'm looking at future practice and if the site had multiple users, assigning the above to each user would be impossible. I created a new role called 'client'. So I have a couple of questions. Hypothetically, my new site will have 10 client accounts, each with the same fields as above: 1) Would I assign these fields to a ROLE (rather than a user, where name, email, password and role fields seem sufficient), then create new user with this role? 2) How do I add fields to a role? I'm confused between 'modules/configure/processRole' and 'modules/core/processRole'. Both of these urls have a way to add fields, but what's the difference? Any advice would be awesome, thanks. --EDIT-- Are roles just for permissions? --EDIT-- Wait, I just saw there's 'processProfile' and 'processUser' (in core and config dropdowns). Even more confused now.
-
@Robin S I just checked this out and confused now as to where the mystyles.js fits in with this. Could you elaborate please?
-
ERROR Call to undefined function ..from an include file
SamC replied to Pixrael's topic in General Support
Exact problem I just had with an undefined function, damn namespace! Keep forgetting it. Thanks @Zeka -
I do something similar to @szabesz but I found that that I had to use a different name (anything other than 'mystyles') to get this working. Whatever I did, the original one at: /wire/modules/inputfield/inputfieldCKEditor/mystyles.js would always take precedence over my custom one at: /site/modules/InputfieldCKEditor/mystyles.js So for my body field found at: Admin > SetupFields > Edit Field: body > [select input tab] ...I did this: ...and in mystyles.js: /** * mystyles.js */ CKEDITOR.stylesSet.add( 'customstyles', [ // <<<< MY CUSTOM NAME { name: 'Generic List', element: 'ul', attributes: { 'class': 'generic-list'} }, { name: 'Inline Code', element: 'code' }, { name: 'Inline Quotation', element: 'q' }, { name: 'Left Aligned Photo', element: 'img', attributes: { 'class': 'align_left' } }, { name: 'Right Aligned Photo', element: 'img', attributes: { 'class': 'align_right' } }, { name: 'Centered Photo', element: 'img', attributes: { 'class': 'align_center' } }, { name: 'Small', element: 'small' }, { name: 'Deleted Text', element: 'del' }, { name: 'Cited Work', element: 'cite' } ] ); Now I can create a list in CKeditor, then once I select this newly created list, an extra option is now in the styles dropdown to let me add a class of 'generic-list' that persists after page save without being stripped out. As I said, I tried many combinations and it just wouldn't work unless I didn't use the term 'mystyles'. Regarding that, like in my example, my custom class only appears in that dropdown after I've actually selected a list. Regarding this part, if you get the first part above working (i.e. a custom mystyles.js working), you can then chose whatever class names you want for the image align.
-
Exactly what I was looking for, thanks
-
This is what I was after. $testimonials = $pages->get(1019)->testimonials->find("limit=5, sort=random"); Credit to @adrian:
- 1 reply
-
- 3
-
I've got a testimonials page, now I wanted to print a few (say 4) to the homepage. So I managed to grab them all: <div class='container'> <div class='row py-5'> <div class='col-sm-12 col-md-6 px-lg-5'> <?php $testimonials = $pages->get(1019)->testimonials; // how to limit here? $testimonials->shuffle(); $firstTestimonial = $testimonials->shift(); ?> <?php foreach($testimonials as $index => $testimonial): ?> <?php if($index == ceil(count($testimonials) / 2)): ?> </div> <div class='col-sm-12 col-md-6 px-lg-5'> <?php endif; ?> <h2 class='py-3'><?= $testimonial->title; ?></h2> <p><?= $testimonial->testimonialBody; ?></p> <p class="font-weight-bold"> <i><?= $testimonial->testimonialReviewee; ?> - Review from <a href='<?= $testimonial->reviewSitePageRef->reviewSiteURL; ?>' target='_blank'><?= $testimonial->reviewSitePageRef->title; ?></a> </i> </p> <?php endforeach; ?> </div> </div> </div> Problem is I've been reading through the docs and can't see a way of limiting the returned results (when using get, it returns a single page so I guess there's nothing to filter). If I were to use $pages->find I can use 'limit=4' in the selector but the testimonials aren't pages, they are a repeater on a single page, hence I thought $pages->get was a good idea. Would it be better to use $page->get("field") or something like that instead, rather than getting the testimonials page itself? Or this? http://cheatsheet.processwire.com/templates/template-properties/template-fields/ Any advice would be awesome, thanks.
-
Splitting PageArray into two new arrays (with half the items each)
SamC replied to SamC's topic in General Support
Thanks for the explanation @Robin S, you're a gent! I understand it now, final code: <?php namespace ProcessWire; ?> <?php $testimonials = $page->testimonials; $testimonials->shuffle(); $firstTestimonial = $testimonials->shift(); ?> <div class="jumbotron jumbotron-fluid bg-color"> <div class="container"> <h2><?= $firstTestimonial->title; ?></h2> <p><?= $firstTestimonial->testimonialBody; ?></p> <p class="font-weight-bold"> <i><?= $firstTestimonial->testimonialReviewee; ?> - Review from <a href='<?= $firstTestimonial->reviewSitePageRef->reviewSiteURL; ?>' target='_blank'> <?= $firstTestimonial->reviewSitePageRef->title; ?></a> </i> </p> </div> </div> <div class='container'> <div class='row py-5'> <div class='col-sm-12 col-md-6 px-lg-5'> <?php foreach($testimonials as $index => $testimonial): ?> <?php if($index == ceil(count($testimonials) / 2)): ?> </div> <div class='col-sm-12 col-md-6 px-lg-5'> <?php endif; ?> <h2 class='py-3'><?= $testimonial->title; ?></h2> <p><?= $testimonial->testimonialBody; ?></p> <p class="font-weight-bold"> <i><?= $testimonial->testimonialReviewee; ?> - Review from <a href='<?= $testimonial->reviewSitePageRef->reviewSiteURL; ?>' target='_blank'> <?= $testimonial->reviewSitePageRef->title; ?></a> </i> </p> <?php endforeach; ?> </div> </div> </div> I see that shift() removes the first item of an array and returns it so seems a neat way to extract the first item leaving the rest in the original array. Found the guide here: https://processwire.com/api/arrays/page/ Anyway, thanks again On a side note, I also found a 404 on the PW site where I would have expected a page: 1) Go to https://processwire.com/api/ref/ 2) Choose something from dropdown, say $page, takes you here: https://processwire.com/api/ref/page/ 3) Back to dropdown, choose 'API Reference...' at the very top (I wanted to go back to the root at https://processwire.com/api/ref/ ) 4) Takes you here instead: https://processwire.com/api/ref/class-loader/API Reference … Maybe someone can have a look. Might be intended behaviour but thought I'd mention it. -
Splitting PageArray into two new arrays (with half the items each)
SamC replied to SamC's topic in General Support
Thanks I was thinking of a way avoid repeating that block twice when only the array differs between the two. Not 100% on what these lines do though, using a key=>value instead of the way I did it: What's the value of $index here for each loop? This code confuses me somewhat with the div placement. Nice and short though, I like it! I also extended it now to include a page ref field in the repeater: ...so you can choose which site the review came from (with the site title and URL stored in a different template)... ...works quite well, and having the reviews out of the standard 3 across grid looks so much better to me. Glad to be back using PW. Paid job this one. -
Splitting PageArray into two new arrays (with half the items each)
SamC replied to SamC's topic in General Support
Actually, this seems to work: <?php namespace ProcessWire; ?> <?php // get PageArray of all testimonials (which is a repeater: title, testimonialReviewee, testimonialReviewee) $arr = $page->testimonials; // shuffle them $arr->shuffle(); // grab the first one $firstTestimonial = $arr->first(); // grab the others $otherTestimonials = $arr->not("$firstTestimonial"); // get total of half the array $numberInFirstHalf = ceil(count($otherTestimonials) / 2); // return new PageArray (half of $otherTestimonials) $firstHalfArr = $otherTestimonials->slice(0, $numberInFirstHalf); // return new PageArray (halfway to end) $secondHalfArr = $otherTestimonials->slice($numberInFirstHalf, $numberOfItems); ?> <div class="jumbotron jumbotron-fluid bg-color"> <div class="container"> <h2><?= $firstTestimonial->title; ?></h2> <p><?= $firstTestimonial->testimonialBody; ?> - <i><?= $firstTestimonial->testimonialReviewee; ?></i></p> </div> </div> <div class='container'> <div class='row'> <div class='col-6'> <?php foreach($firstHalfArr as $testimonial): ?> <h2 class='py-3'><?= $testimonial->title; ?></h2> <p><?= $testimonial->testimonialBody; ?></p> <p class="font-weight-bold text-muted">- <i><?= $testimonial->testimonialReviewee; ?></i></p> <?php endforeach; ?> </div> <div class='col-6'> <?php foreach($secondHalfArr as $testimonial): ?> <h2 class='py-3'><?= $testimonial->title; ?></h2> <p><?= $testimonial->testimonialBody; ?></p> <p class="font-weight-bold text-muted">- <i><?= $testimonial->testimonialReviewee; ?></i></p> <?php endforeach; ?> </div> </div> </div> I get one main one featured at top, 3 in left column and 2 in right column, total of 6 testimonials. -
I'm building a testimonials page, and currently have this (one review will always be at the top, the first one, whichever that may be after shuffling): // views/testimonials.php <?php namespace ProcessWire; ?> <?php $arr = $page->testimonials; $arr->shuffle(); $firstTestimonial = $arr->first(); $otherTestimonials = $arr->not("$firstTestimonial"); ?> <div class="jumbotron jumbotron-fluid bg-color"> <div class="container"> <h2><?= $firstTestimonial->title; ?></h2> <p><?= $firstTestimonial->testimonialBody; ?> - <i><?= $firstTestimonial->testimonialReviewee; ?></i></p> </div> </div> <div class='container'> <div class='row py-6'> <?php foreach($otherTestimonials as $testimonial): ?> <div class='col-sm-12 col-md-6 py-3 px-lg-5'> <h2 class='py-3'><?= $testimonial->title; ?></h2> <p><?= $testimonial->testimonialBody; ?></p> <p class="font-weight-bold text-muted">- <i><?= $testimonial->testimonialReviewee; ?></i></p> </div> <?php endforeach; ?> </div> </div> ... the problem I have is from a design perspective. Using a flexbox grid means columns are equal height. Each row contains three columns on desktop. Now, if one column has shorter content than the next one, there is large white space under it because it doesn't fill the column vertically (but the longer ones do). You end up with this: ------------------------------------------------------------------------------ Title Title Title ngjkagas ndjskagdasj njgksgnjsk gdjkgasjk gdjskgbjksa gdasjgkads gndjaskgnjdasnjg ds gbdjskgbdjkab dgasnjk jgfksjgfds jgdksajkgds njdkajda njdasxlhs gf jkdgjsagjdajdasdga fdjakfdbjak fhdjkahf fdbjabfdj fdjsjkakjka ------------------------------------------------------------------------------ Title Title Title ngjkagas ndjskagdasj njgksgnjsk gdjkgasjk gdjskgbjksa gdasjgkads gndjaskgnjdasnjg ds gbdjskgbdjkab dgasnjk jgfksjgfds jgdksajkgds njdkajda njdasxlhs gf jkdgjsagjdajdasdga fdjakfdbjak fhdjkahf fdbjabfdj fdjsjkakjka fnfsjsjs dhhdsjsjds fdnsjkfdjs fdhj dhksd ------------------------------------------------------------------------------ So I thought I'd get the original PageArray, split it into two equal parts, then just loop the two new arrays into two columns. to get this: -------------------------------- -------------------------------- Title Title ngjkagas ndjskagdasj gdjskgbjksa gdasjgkads gndjaskgnjdasnjg ds gbdjskgbdjkab dgasnjk njdkajda njdasxlhs gf jgfksjgfds jgdksajkgds jkdgjsagjdajdasdga fdjakfdbjak fhdjkahf Title fdbjabfdj fdjsjkakjka jfdksahfjka hfdjkasfhjdafalk fnfsjsjs dhhdsjsjds ndjskagjka hdsk hjkdsahjkgd fdnsjkfdjs fdhj dhksd jgdskgksa hgdjskahgjdashgkdas Title Title ngjkagas ndjskagdasj gdjskgbjksa gdasjgkads gndjaskgnjdasnjg ds gbdjskgbdjkab dgasnjk njdkajda njdasxlhs gf jgfksjgfds jgdksajkgds jkdgjsagjdajdasdga fdjakfdbjak fhdjkahf Title fdbjabfdj fdjsjkakjka jfdksahfjka hfdjkasfhjdafalk fnfsjsjs dhhdsjsjds ndjskagjka hdsk hjkdsahjkgd fdnsjkfdjs fdhj dhksd jgdskgksa hgdjskahgjdashgkdas -------------------------------- -------------------------------- So my new code is sketchy and I could do with some help to possibly refactor and also to fill in the gaps where my knowledge fails. Here it is so far: <?php namespace ProcessWire; ?> <?php // get PageArray of all testimonials (which is a repeater: title, testimonialReviewee, testimonialReviewee) $arr = $page->testimonials; // shuffle them $arr->shuffle(); // grab the first one $firstTestimonial = $arr->first(); // grab the others $otherTestimonials = $arr->not("$firstTestimonial"); // get total items in array $numberOfItems = count($otherTestimonials); // return new PageArray (half of $otherTestimonials) $firstHalfArr = $otherTestimonials->slice(0, ($numberOfItems / 2)); // return new PageArray (half + 1) to end $secondHalfArr = $otherTestimonials->slice(($numberOfItems / 2) + 1, $numberOfItems); ?> <div class="jumbotron jumbotron-fluid bg-color"> <div class="container"> <h2><?= $firstTestimonial->title; ?></h2> <p><?= $firstTestimonial->testimonialBody; ?> - <i><?= $firstTestimonial->testimonialReviewee; ?></i></p> </div> </div> <div class='container'> <div class='row'> <div class='col-6'> <?php foreach($firstHalfArr as $testimonial): ?> <h2 class='py-3'><?= $testimonial->title; ?></h2> <p><?= $testimonial->testimonialBody; ?></p> <p class="font-weight-bold text-muted">- <i><?= $testimonial->testimonialReviewee; ?></i></p> <?php endforeach; ?> </div> <div class='col-6'> <?php foreach($secondHalfArr as $testimonial): ?> <h2 class='py-3'><?= $testimonial->title; ?></h2> <p><?= $testimonial->testimonialBody; ?></p> <p class="font-weight-bold text-muted">- <i><?= $testimonial->testimonialReviewee; ?></i></p> <?php endforeach; ?> </div> </div> </div> Sure you guys don't need the comments but it helps me as I go along to build the logic. The obvious problem is what happens when the PageArray contains an odd number of items. Is there a round method or something? If I round incorrectly though, I think maybe one testimonial item would be missed i.e. [0,1,2,3] and [5,6,7,8]. Anyway, the problem I'm having right now in PHP and JS as I learn is thinking like a programmer. I'm getting better at reading technical docs and using methods from instructions etc. but the thinking like a programmer bit (logic included) eludes me somewhat. I'll get there in the end. Any advice is appreciated as always. Thanks. (p.s. I am aware of CSS3 column-count)