Jump to content

ricardo-vargas.com


Sergio
 Share

Recommended Posts

Hi guys, after several months I finally released my most complex ProcessWire project so far: https://ricardo-vargas.com

About the client: Ricardo Vargas is a specialist in project management and strategy implementation. He's an accredited professional and author of several books. Since 2007, he publishes a bilingual podcast about project management with millions of episodes listened. From 2012 to 2016, he worked for the United Nations Office for Project Services (UNOPS) in Copenhagen, Denmark. He currently lives in Portugal. 

The previous site was developed in 2009 using Wordpress by my team at the agency I was working for. In 2011, Ricardo invited me to work with him and I started to maintain the site ever since. The front-end side was good, performance-wise, but the backend started to become horrendous to work with so in 2012 I started my quest to find a better CMS and found PW. :D After developing small projects and messing around with it I convinced my client/boss last year to let me develop his new website with PW. 

I did all the stuff (design, frontend, backend, and server ops) so if you find something buggy or strange, let me know, please! I'll appreciate! ^-^

The site is currently running on Digital Ocean droplet with 2GB of RAM. Nginx and Cloudflare (for SSL mostly). I'm using forge.laravel.com to manage it. Approx. 3,000 unique users per day doing 72,421 requests (Cloudflare stats).

podcasts-page.thumb.png.d9019a184642b1fa338fb0b0f70c7803.png

Key features:

  1. Pages are loaded with ajax using https://github.com/miguel-perez/smoothState.js so the podcast player (Soundmanager ) can continue to play the podcast episode as the user navigates other pages.
  2. Member area where the user can see the books his own (and download their files) and also see his bookmarks and reports. I imported and the users (+ 4k) from the WP database and decided to get rid of passwords (the future!) so after some research and tests, I chose Auth0 login system. 

I've mentioned the member area on this topic (there are a couple of screenshots there):

Modules used

AdminLinksInFrontend
AdminOnSteroids
AdminTemplateColumns
Auth0Login (Custom made)
BatchChildEditor
ClearCacheAdmin
FieldtypeRuntimeMarkup
FlickrAPI (Custom made)
FormBuilder
InputfieldCKEditor
MarkInPageTree
MarkupLoadRSS
PageReferencesTab
PageViews (Custom made)
PduReport (Custom made)
PrevNextTabs
ProCache
ProDrafts
ProcessDatabaseBackups
ProcessJumplinks
ProcessSendyAPI (Custom made)
ProcessWireUpgrade
ReaderAreaManager (Custom made)
SlideshareAPI (Custom made)
TemplateLatteReplace
TextformatterAccordion
TextformatterHannaCode
TextformatterMarkExternalLinks
TextformatterMultiValue
TracyDebugger
UserBookmarks (Custom made)
WireMailMailgun
importPagesCSV

  • Like 15
Link to comment
Share on other sites

Nice work! Must be a good feeling to have such a big task completed and live.

I'm always interested to hear how much time goes into larger jobs like this undertaken by a single developer: any idea how long you spent on it?

I noticed a couple of minor display issues on the Books page. You can see both in the screenshot below:

  • "Book recommendations" item seems to be missing expected content.
  • Side column layout issue at some device widths.
  • Edit: one more on this page... the "Powered by Goodreads" link is malformed.

2017-04-05_155334.thumb.png.c774980802b489d53fb7b7b416d37d69.png

Again, a really great site. :)

 

  • Like 2
Link to comment
Share on other sites

Nice to see a few of my modules in such a big project! FYI with AOS you can replace TemplateAdminColumns, and with TemplateLatteReplace filters/macros TextfomatterMultiValue and even ALIF, provided if you are not using their advanced features.

  • Like 2
Link to comment
Share on other sites

8 hours ago, Robin S said:

Nice work! Must be a good feeling to have such a big task completed and live.

I'm always interested to hear how much time goes into larger jobs like this undertaken by a single developer: any idea how long you spent on it?

I noticed a couple of minor display issues on the Books page. You can see both in the screenshot below:

  • "Book recommendations" item seems to be missing expected content.
  • Side column layout issue at some device widths.
  • Edit: one more on this page... the "Powered by Goodreads" link is malformed.

Again, a really great site. :)

 

 

Thanks, @Robin S! Yes, it's a great feeling! After everything was running apparently fine, it was like removing a heavy weight from the shoulders. :)

The project took more time than as I was expecting for some reasons, it was developed in "waves" as I have to stop it for 2-3 weeks many times to handle some other of our projects. I didn't track the time but I can estimate that if done without stopping, it would have taken at least 9 months. But now that I know more about PW's advanced features, like building modules and how to create a member area, it would take 33% less time I think.

The project has 66 templates and 1849 pages so far, some of them with lots of content like the ones on Consulting and Training sections, so a lot of time was spent importing content. 

--

Very strange you had problems in the book section rendering, could you tell me what browser you used? I'm using a Mac and also tested in Firefox on Ubuntu and Edge and IE11 on Windows 10. 

  • Like 2
Link to comment
Share on other sites

Yes, @szabesz!  Using @tpr's excellent Latte module for the whole site! Such a time saver! Take a peek on the podcast item template (ignore the indentation problems):

Spoiler

{block content}
{include './layouts/partials/_section-header.latte'}

<main class="row main-content">
  <div class="{$detail_main_columns} columns">
    <article>
      <ul class="menu simple">
        <li>
            <small>
                <time itemprop="datePublished" datetime="{$page->published_at|date:'%Y-%m-%d'}">
                    {$page->published_at|date:'%d %b %Y'}
                </time>
            </small>
        </li>
        <li n:ifx="$page->page_views"><small>{$x|number_local:$lang} {_'views', 'General'}</small></li>
      </ul>
      <h1 class="section-h1 color-podcasts">
        {$page->title|noescape}
        <button n:if="$user->isLoggedin()" class="color-podcasts u-op3 js-toggle-bookmark" title="{_'Add to my bookmarks', 'General'}">
          <svg class="icon"><use xlink:href="{$icons}icon-bookmark"></use></svg>
        </button>
      </h1>
      <p n:ifx='$warning' class="warning callout u-mt24">{$x|noescape}</p>
      <p class='u-mt24'>
        {$page->intro}
      </p>

      <noscript>
        <p>
          <audio src="{$mp3_file}" controls preload="auto"></audio>          
        </p>
      </noscript>
      

      <div>
        
           <a href="#" data-id="{$page->id}" data-play="auto" data-mp3="{$mp3_file}" data-title="{$page->title} – {$page->published_at|date:'%d %b %Y'}" class="button small u-text-upper bg-podcasts js-btn-play" style="display: none">Play</a>

          <a href="{$url_download}" class="button small hollow podcasts u-text-upper" aria-label="{_'Download MP3 audio file', 'Podcasts'}">Download</a>

      </div>
      
      
      <aside class="row u-mt24">
        <div class="medium-9 columns">
          {include './partials/_tag_list.latte', tags => $page->tags}
        </div>
        <div class="medium-3 columns last pmi-triangle-label">
          <a href="{$podcasts->url}?filter={$page->pmi_triangle->value|lower}">
            <span title="PMI Talent Triangle: {$page->pmi_triangle->title|noescape}">
              <svg class="icon icon-small">
                <use xlink:href="{$icons}icon-pmi-triangle"></use>
              </svg>
              <small class="pmi-triangle {$page->pmi_triangle->value|lower}">{$page->pmi_triangle->title|noescape|cutAt:' '}</small>
            </span>
          </a>
        </div>
      </aside>

      <aside class="u-mt48">
        {if count($page->related_podcasts)}
        <h4 class="u-pb8 u-bb">
        <svg class="icon icon-medium">
            <use xlink:href="{$icons}icon-headphones"></use>
        </svg>
        {_'More on this series', 'Podcasts'}
        </h4>

       
        <ul n:inner-foreach="$page->related_podcasts as $r" class="no-bullet">
          <li class="u-mb8">
            <article>
              <small class="fade-half">
                    <time datetime="{$r->published_at|date:'%Y-%m-%d'}">
                        {$r->published_at|date:'%d %b %Y'}
                    </time>
                </small>
              <a href="{$r->url}" class="color-podcasts">
                <h5>{$r->title|noescape}</h5>
              </a>
            </article>
          </li>
        </ul>
        {/if}


        <aside n:if="$related_playlists->count() > 0" class="callout u-border u-mt64">
          <h5 class="color-podcasts u-text-upper">
            <svg class="icon">
                <use xlink:href="{$icons}icon-shuffle"></use>
            </svg>
            {_'Playlists', 'Podcasts'}
          </h5>
          <p>
            {__p('This episode is part of the playlist', 'This episode is part of the playlists', $related_playlists->count()), 'Podcasts'}:
          </p>
          <div n:inner-foreach="$related_playlists as $r" class="row medium-up-2 large-up-3">
            <div class="column">
              <a href="{$r->url}" class="u-no-link">
                <h5 class="color-podcasts u-h4">{$r->title|noescape}</h5> {*
                <div n:if='$r->images->count()'>
                  {$r|thumbnail:200,100|noescape}
                </div> *}
                <p class="u-font-small">{trim($r->summary, "<p></p>")|noescape}</p>                
              </a>
            </div>
          </div>
        </aside>


        {include './partials/_disqus.latte', color => 'podcasts'}
      </aside>
    </article>
  </div>
  <div class="{$detail_secondary_columns} columns u-mt8 u-mt-xs-64">
    <div class="media-object u-line-height-small">
      <a href="http://feedpress.me/5pmpodcast_{$lang}" target="_blank" rel="noopener noreferrer" class="">
        <div class="media-object-section small">
          <svg class="icon icon-medium icon-rss u-mr16">
            <use xlink:href="{$icons}icon-rss2"></use>
          </svg>
        </div>
        <div class="media-object-section small">
          <span>{_'Subscribe via RSS or Email', 'Podcasts'}
            <br/><small>{_'and get notified on new episodes', 'Podcasts'}</small>
            </span>
        </div>
      </a>
    </div>

    <div class="media-object u-line-height-small">
        <a href="{$soundcloud_url}" target="_blank" rel="noopener noreferrer" class="color-podcasts">
          <div class="media-object-section small">
            <svg class="icon icon-medium u-mr16">
              <use xlink:href="{$icons}icon-soundcloud2"></use>
            </svg>
          </div>
          <div class="media-object-section small">
            {_'Listen to this episode <br/> on <strong>Soundcloud.com</strong>', 'Podcasts'|noescape}
          </div>
        </a>
      </div>

    <div class="u-mt24 u-mb24 callout secondary">
      {include "./partials/_search-form.latte"}
    </div>

    {include './partials/_sidebar.latte'}
     
    <div class="u-mb24" n:if="$same_tags->count() > 0">
      {include "./partials/_related-by-tag.latte"}
    </div>



  </div> 
</main>
{/block}
{block pagescripts}
<script>
  $(document).ready(function(){
    $('.js-btn-play').show();
  });
</script>
{/block}

 

 
 
 

And I forgot to mention that for the frontend I'm using Foundation framework with a couple of custom spacing and helpers styles like the ones you see on some tags like "u-mt48" (utility- margin-top of 48 pixels)

 

Edited by Sérgio
Add spoiler and changed code block again
Link to comment
Share on other sites

7 hours ago, tpr said:

Nice to see a few of my modules in such a big project! FYI with AOS you can replace TemplateAdminColumns, and with TemplateLatteReplace filters/macros TextfomatterMultiValue and even ALIF, provided if you are not using their advanced features.

1

Many thanks for all your modules, @tpr! They are excellent and helped a lot! 

Yep, I was planning to replace TemplateAdminColumns in all templates but had to release the project before that. :) I do it later. 

The same thing about ALIF, I started using it before AOS I think, so I just kept it there. :) 

But what can I use to replace TextfomatterMultiValue ?

  • Like 1
Link to comment
Share on other sites

11 minutes ago, Sérgio said:

Yes, @szabesz!  Using @tpr's excellent Latte module for the whole site! Such a time saver! Take a peek on the podcast item template (ignore the indentation problems):

Thanks for Sharing! (You Might Want to Put the Code in a Spoiler Though ) the Template File's Code Reveals More That Can Currently Be Seen or May It Just Me Who Cannot Find the "Related Stuff"

What happened to your reply BTW? In the meantimne it was deleted, just the code part remaining...

Link to comment
Share on other sites

8 minutes ago, szabesz said:

Thanks for Sharing! (You Might Want to Put the Code in a Spoiler Though ) the Template File's Code Reveals More That Can Currently Be Seen or May It Just Me Who Cannot Find the "Related Stuff"

What happened to your reply BTW? In the meantimne it was deleted, just the code part remaining...

 
 

I think I deleted it by mistake but thanks to you I put it back. And thanks for the tip of using spoiler! Much better indeed!

I also changed the link so the new page shows all the code blocks with related stuff: https://ricardo-vargas.com/podcasts/cost-estimating-part-1-of-3/

Edited by Sérgio
  • Like 1
Link to comment
Share on other sites

Beautiful code! Nice to see somebody else using Latte and in such an advanced level :) 

Am I right you're using custom filters, like |thumbnail or |number_local? If you think you have filters/macros to share, please share :) 

TextformatterMultiValue: I meant the getlines filter as a replacement. This is very new and allows only 1 or 2 items per lines (value or key+value) so if you need more items per line it will not do.

ALIF: it's not AOS (which is strictly loaded in the admin) but the editlink macro. Again, it's very basic compared to ALIF or FEEL or Fredi.

But if you have those modules at place I think better to leave them there to avoid unexpected side effects :) 

What do you think about adding a module option to TLR to get rid of the "noescape" filter? At least for projects where myself is the only dev/editor I don't expect malicious code :) 

Link to comment
Share on other sites

11 minutes ago, tpr said:

Beautiful code! Nice to see somebody else using Latte and in such an advanced level :) 

Am I right you're using custom filters, like |thumbnail or |number_local? If you think you have filters/macros to share, please share :) 

TextformatterMultiValue: I meant the getlines filter as a replacement. This is very new and allows only 1 or 2 items per lines (value or key+value) so if you need more items per line it will not do.

ALIF: it's not AOS (which is strictly loaded in the admin) but the editlink macro. Again, it's very basic compared to ALIF or FEEL or Fredi.

What do you think about adding a module option to TLR to get rid of the "noescape" filter? At least for projects where myself is the only dev/editor I don't expect malicious code :) 

 
 

Thanks! But I know that there's a lot of places where I can improve it. I had some problems with the plural translation on some other pages but got a workaround. I still have to update the module and start using your news filter and macros. :)

About my custom filters/macros, I have to refactor some of them, and I guess you probably have almost all of them but with different names. Thumbnail is just a dumb filter to get the page image and resize it, I actually not using it anymore, forgot to remove the commented code from the page.

Here's the number_local and cutAt: 

Spoiler

/**
* Usage: {$file->download_count|number_local:$lang}
**/
$view->addFilter('number_local', function ($number, $lang, $decimal=0) {

    if ($lang == 'pt') {
        return number_format($number, $decimal, ',', '.');
    } else {
        return number_format($number, $decimal, '.', ',');
    }

});

/**
* Extract string part at defined $character
* Example: 
* $thumb = 'https://farm8.staticflickr.com/7494/27395968195_c50c13dd0b_q.jpg'
* <img src="{$thumb|cutAt:'_q.jpg'}_m.jpg" />
**/
$view->addFilter('cutAt', function ($string, $character, $reverse = false) {
    if($character != '') {
        $phrase = explode($character, $string, 2);        
        $word = $phrase[0];
        if($reverse) {
            $word = $phrase[1];
        }
    } else {
        $word = $string;
    }
    return $word;
}); 

 

 

And yes, I'm planning to get rid of the "noescape" filter in the future. :)

  • Like 1
Link to comment
Share on other sites

34 minutes ago, Zeka said:

Hi, @Sérgio Really nice work. 

Maybe you have some tips to share about ajax page load. Maybe you meet some challenges while implementing this feature. If so, could you please share them.   

 

Hi Zeka, thank you!

The solution I used, the aforementioned Smoothstate.js, it's a jQuery plugin to handle page transitions, so not a fully Ajax solution. If you disable JS, the site main navigation will work just fine, what the plugin does is to only load the main content of the page between transitions. It's the quick and easiest way I know to have a nice transition without worrying about Ajax complications, you can build a normal PW site and have the plugin on top of it.

But I did use a bit more Ajax on the website to have things like page views and user bookmarks being recorded in a db table. Do you want to know more about it?

Link to comment
Share on other sites

Oh.. I have been thinking that pages are loaded via ajax and Smoothstate is used only for animation. I have to look closely to this plugin as it looks awesome.

Offcourse I want. If you have time to share some details, it would be great!!!

Link to comment
Share on other sites

Yep, it's a great plugin, but I'm not using its animations capabilities at all. :)
Sure thing, I'll find a time to write about how to handle page views in a procached static page then! 

Link to comment
Share on other sites

8 hours ago, Sérgio said:

Very strange you had problems in the book section rendering, could you tell me what browser you used? I'm using a Mac and also tested in Firefox on Ubuntu and Edge and IE11 on Windows 10. 

I tested in Firefox and Chrome on Windows. But I would expect the same for all browsers because the issues are in the source code. Odd that you don't see them at your end - maybe I'm seeing cached content and you're not.

1. "Book recommendations" item missing expected content - probably just needs some if() conditions.

2017-04-06_090007.png.9410cbd42f8ce9d3082fca2548032b25.png

2017-04-06_090034.png.0bdc5020535109ae0d0c9d9ebac887e1.png

2. Side column: you have a mismatch between large/medium column classes, meaning the side column wraps under the main column but remains narrow at medium device width.

2017-04-06_090103.png.14bd4c8b4abe9b9dafeacf17cb3429a4.png

3. Goodreads link

2017-04-06_090157.png.078b8ba36e87d97b0d36b65d5dfb030e.png

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...