Friday, August 29th, 2008 (updated 2 Mar ’10)

Adam Khan | Brighton, England

How Subtraction.com was converted to EE

 
B

eing a blog, Khoi Vinh’s renowned Subtraction.com isn’t as complex a web site as one for an entire organization. But being a standard-bearer of design excellence, everything has to be just so. As Khoi switched to EllisLab’s ExpressionEngine web publishing system, some of the details he wanted fell through the cracks of the software’s out-the-box functionality. This article explains how those details were implemented and how the site was architected in EE.

  • Sector
  • Service

For the conversion of the renowned Subtraction.com web site to the renowned ExpressionEngine web publishing system, the key architectural insight was that all except one of the major screens are either single-entry or multi-entry. The various multi-entry screens are therefore powered by just one multi-entry template and the single-entry screens by just one single-entry template, reducing the number of templates required, making the site easier to maintain.

The Index Template

In turn, both the single- and the various multi-entry screens spark from a single template, /site/index:

{embed="site/header"}

{if segment_1
=="" }
{
!-- Display multi-entry as homepage --}
{embed
="site/multi-entry" pagetype="homepage"}

{if
:elseif
 
segment_1 != "" && 
 
segment_1 != "archives" && 
 
segment_1 != "categories" && 
 
segment_1 != "about" && 
 
segment_4==""
}
{
!-- Display multi-entry as monthly --}
{if segment_1 
2000 && segment_2 && segment_2 13}
{embed
="site/multi-entry" pagetype="monthly"}
{
/if}

{if
:elseif segment_1 == "categories"}
{
!-- Display multi-entry as category --}
{embed
="site/multi-entry" pagetype="category"}

{if
:elseif segment_4}
{
!-- Display single-entry --}
{embed
="site/single-entry"}
{
/if}

{embed
="site/footer"

Here the first and last lines show that the header and footer are shunted out of the way from the get-go; any header variations among the screens are handled within the /site/header template. Into these site-wide standards is shoveled as much as possible in order to reduce the main templates.

The heart of /site/index is a conditional to test various URL structures. If for instance our URL has four segments then we’re on a single-entry page, as the site uses the blog-standard /year/month/date/dirified-title URL format for entries and no other screens have so many URL segments.

The Single-Entry Template

EE is not, despite its blogging software pedigree, designed for blog-standard URLs. Instead, its URLs use a /template-group/template/dirified-title format. Khoi was adamant however that his blog-standard URLs must stay. So this is handled in /site/single-entry:

{exp:weblog:entries weblog="subtraction|lens|elsewhere" disable="category_fields|pagination|trackbacks" year="{segment_1}" month="{segment_2}"  url_title="{segment_4}" dynamic="off" limit="1" status="not closed"}
{if member_group
=="1" || status=="open"}

<div id="posts">

<
div class="day">

 <
class="date">{entry_date format='%D %d %M'}<br /> {entry_date format='%Y'}</p>
 <
h2{if status=="Hold"} style="color: red"{/if}>{title}</h2>
 <
div class="post-info">
  <
span class="info-label">Posted</span>
  <
span class="info-data">{entry_date format='%h&#58;%i %a'}</span>
 </
div>
 <
div class="post-info">
  <
span class="info-label">Author</span>
  <
span class="info-data"><a href="{url}">{author}</a></span>
 </
div>

 
{if weblog_short_name == "subtraction"}
 
<div class="post-info">
  <
span class="info-label">Categories</span>
  <
span class="info-data">{categories backspace="1" style="linear"}<a href="{path=/}">{category_name}</a>, {/categories}</span>
 </
div>
 <
div class="body-lead">
  <
div class="info-label">Body</div>
  <
div class="first">
   
{body}
  
</div>
  
{extended}
 
</div><!-- /.body-lead -->
 <
div id="terminator">+</div>
 <
div class="clear"></div>
 
{/if}

 {if weblog_short_name 
== "elsewhere"}
 
<div class="post-info">
  <
span class="info-label">Rating</span>
  <
span class="info-data">
   <
div class="stars">
    <
a href="" class="stars-{elsewhere-rating}"><img src="{site_url}assets/1-star.gif" alt="Stars" width="14" height="14" /></a>
   </
div>
   <
div class="clear"></div>
  </
span>
 </
div>
 <
div class="body-lead">
  <
div class="info-label">Body</div>
  <
div class="first">
   
{elsewhere-body}
  
</div>
 </
div><!-- /.body-lead -->
 <
div id="terminator">+</div>
 <
div class="clear"></div>
 <
div class="post-info post-info-elsewhere">
  <
span class="info-label">Link</span>
  <
span class="info-data">
   <
div class="elsewhere-link"><a href="{elsewhere-url}">Jump to This Link</a></div>
  </
span>
 </
div>
 
{/if}

 {if weblog_short_name 
== "lens"}
 
<div class="post-info">
  <
span class="info-label">Categories</span>
  <
span class="info-data">&nbsp;
  
{categories backspace="2" style="linear"}<a href="{path=weblog/index}">{category_name}</a>, {/categories}
  
</span>
 </
div>
 <
div class="body-lead {lens-orientation}">
  
{lens-image} 
  {if lens
-caption != "" }
  {lens
-caption}
  {
/if}
  {if lens
-caption-extended != "" }
  {lens
-caption-extended}
  {
/if}
 
</div><!-- /.body-lead -->
 <
div id="terminator">+</div>
 <
div class="clear"></div>
 
{/if}

</div><!-- /.day -->

<?php
$still_allow_comments
="no";
$post_time=strval("{entry_date format='%U'}");
$current_time="{current_time}";
$comments_allowed_from $current_time 5184000;
if ( 
$post_time >= $comments_allowed_from 
{
 $still_allow_comments
="yes";
}
?>

{if allow_comments 
&& comment_total == 0}
{if
:elseif allow_comments && comment_total >= 1}
<a name="remarks"></a>
<
div id="remarks">
 <
h3>
  <
span class="notation">Remarks</span>
  
<?php if ( $still_allow_comments == "yes" ) : ?>
  {comment_total} total remarks on this post
. <a href="#add-form">Add your own remarks below.</a>
  
<?php else: ?>
  {comment_total} total remarks were added before the post was closed
.
  
<?php endif; ?>
 
</h3>
{/if}

{embed
="site/display-comments" thisentry_id="{entry_id}"}

{if member_group
=="1" || status=="open"}
</div><!-- /.remarks -->
{/if}

<?php
if ( $still_allow_comments == "yes" ) :
?>

<div class="add-remarks">

 
{exp:comment:form preview="site/comment_preview" url_title="{segment_4}"}
 
<h3>Add Remarks<a name="add-form"></a></h3>
 <
div class="guidelines">Please be nice.</div>
 
{if logged_in}
 
<div class="add-lead">
  <
label for="name">Name</label>
  <
div><input type="text" size="3" id="name" name="name" value="{name}" /><span>Your name is required.</span></div>
 </
div><!-- /.add-lead -->
 <
div class="clear"></div>

 <
div class="add-lead">
  <
label for="email">Email</label>
  <
div><input type="text" size="3" id="email" name="email" value="{email}" /><span>An email address is required.</span></div>
 </
div><!-- /.add-lead -->
 <
div class="clear"></div>

 <
div class="add-lead">
  <
label for="url">Web Site</label>
  <
input type="text" size="3" id="url" name="url" value="{url}" />
 </
div><!-- /.add-lead -->
 <
div class="clear"></div>
 
{/if}

 {if logged_out}
 
<div class="add-lead">
  <
label for="remember">Your Info</label>
  <
input type="checkbox" name="save_info" value="yes" {save_info} />
<
span>Save my personal information for next time</span>
 </
div><!-- /.add-lead -->
 <
div class="clear"></div>
 
{/if}

 
<div class="add-lead">
  <
label for="remember">Follow-up</label>
  <
input type="checkbox" name="notify_me" value="yes" {notify_me} />
<
span>Notify me of further remarks on this post</span>
  <
div class="clear"></div>
 </
div><!-- /.add-lead -->

 <
div class="add-lead">
  <
label class="your-remarks">Your Remarks</label>
  <
textarea name="comment" rows="10" cols="40" class="your-remarks">{comment}</textarea>
 </
div><!-- /.add-lead -->

 <
div class="add-last-lead">
  <
label>Submit</label>
  <
span><input type="submit" name="preview" value="Preview"  /></span>
  <
span><input type="submit" name="submit" value="Submit" /></span>
 </
div><!-- /.add-lead -->
 
{/exp:comment:form}
 
</div><!-- /.add-remarks -->

<?php endif; ?>

</div><!-- /#posts -->

{embed="site/sidebar" pagetype="article" articletype="{lens-orientation}" }

{
/if}
{
/exp:weblog:entries} 

Into the template’s sole weblog:entries tag (always a good goal for performance reasons) a few extra parameters have been added:

year="{segment_1}" month="{segment_2}" url_title="{segment_4}" dynamic="off" limit="1" 

The tag’s “dynamic” parameter switches off EE’s default method of retrieving the entry from the URL. Instead, the year is tested against the contents of segment #1, the month against segment #2 and the dirified URL against segment #4. (The date isn’t tested against segment #3 because this seems to filter out entries posted between 12:00am and 1:00am.)

The “weblog” parameter contains all three weblogs — subtraction, lens and elsewhere. Initially the template handles entries similarly regardless of their weblog, displaying the title, date and author. Then entries fork depending on their weblog.

After the “day” div are a few lines of PHP beginning with

still_allow_comments="no"

Khoi wants each post’s comments to close after 60 days. While EE does have a comment expiration field in the Publish/Edit Form, it’s a pity to set each post’s expiration date manually when a single 60-day rule covers them all. So the PHP here tests whether the entry is less than 60 days old, and if it is, the variable $still_allow_comments is set to “yes”, displaying the comment area’s header as an invitation to comment rather than a notice that comments are closed, followed later by the comment form.

Near the end of the template is the call to the sidebar. This call could have been made from the footer except for the sidebar needing to know if our page is a Lens entry with a landscape-shaped image, and if so to not display certain sidebar modules since they conflict with the image. (The sidebar template could also find out what it needs by running the weblog:entries tag itself.)

The comments are displayed in a sub-template /site/display-comments because we need them to be outside of the weblog:entries tag in order to display Khoi’s comments differently (though in retrospect that could probably be handled with a PHP conditional):

{exp:comment:entries weblog="subtraction|lens|elsewhere" dynamic="off" entry_id="{embed:thisentry_id}"}
<div class="remarks-lead{if author_id=="1"} owner{/if}">
 <
div class="remarks-date">
  <
b>{comment_date format='%D %d %M %Y'}</bat {comment_date format='%h&#58;%i %a'}<br />
  
{url_as_author}
 
</div>
 
{comment}
 
<div class="clear"></div>
</
div><!-- /.remarks-lead -->
{/exp:comment:entries} 

The Sidebar Template

/site/sidebar:

<div id="sidebar">

 
{!-- QUICK ACCESSDisplay on HomepageMonthlyCategoryArticle --}

 {if 
  
"{embed:pagetype}" == "homepage"
  
|| "{embed:pagetype}" == "monthly"
  
|| "{embed:pagetype}" == "category" 
  
|| "{embed:pagetype}" == "article"
  
|| "{embed:pagetype}" == "about"
 
}
 
<div class="sidebar-module">
  <
h3>Quick Access</h3>
  
{exp:stats weblog="subtraction|elsewhere|lens"}
  <?php $total_entries 
number_format("{total_entries}"); ?>
  {
/exp:stats}
  
<class="first"><a href="http://www.subtraction.com/archives/"><?php echo $total_entries?></aposts since July 2000.</p>
  <
div class="full-archives">
   <
label>Dates</label>
   <
div class="pulldown">
    <
select name="date-archive" onchange="jumpMenu('parent',this,0)">
     <
option value="" selected="selected">Select&#8230;</option>
     
{exp:weblog:month_links limit="12"}
     
<option value="{path=/}">{month} {year}</option>
     
{/exp:weblog:month_links}
     
<option value="http://www.subtraction.com/archives/">All Older Posts</option>
    </
select>
   </
div><!-- /.pulldown -->
  </
div><!-- /.full-archives -->
  <
div class="full-archives">
   <
label>Categories</label>
   <
div class="pulldown">
    <
select name="date-archive" onchange="jumpMenu('parent',this,0)">
     <
option value="" selected="selected">Select&#8230;</option>
     
{exp:weblog:categories weblog="subtraction" category_group="1" 
  
style="linear" orderby="title" disable="category_fields" }
     
<option value="{path=/}">{category_name}</option>
     
{/exp:weblog:categories}
    
</select>
   </
div><!-- /.pulldown -->
  </
div><!-- /.full-archives -->
 </
div><!-- /.sidebar-module -->
 
{/if}

 {
!-- RECENT POSTSDisplay on MonthlyCategoryArticle providing not Landscape --}

 {if 
  
"{embed:pagetype}" == "monthly" 
  
|| "{embed:pagetype}" == "category" 
  
|| "{embed:pagetype}" == "article" 
  
&& "{embed:articletype}" != "landscape" 
 
}
 
<div class="sidebar-module">
  <
h3>Recent Posts</h3
  
{exp:weblog:entries orderby="date" sort="desc" limit="5" weblog="subtraction" dynamic="off"
  
disable="pagination|categories|member_data|trackbacks"}
  
<class="date">{entry_date format='%d %M %Y'}</p>
  <
h4><a href="{path={entry_date format=">{title}</a></h4>
  
{summary}{lens-caption}
  {
/exp:weblog:entries}
 
</div><!-- /.sidebar-module -->
 
{/if}

 {
!-- ABOUT MEDisplay on Homepage --}

 {if 
"{embed:pagetype}" == "homepage"}
 {
!-- Display on homepage --}
 
<div class="sidebar-module">
  <
h3>About Me</h3>
  <
class="first">I work as the Design Director at <a href="http://www.nytimes.com">NYTimes.com</a>, 
 and 
I have a dog named <a href="http://www.misterpresident.org/">Mister President</a>. 
 <
a href="http://www.subtraction.com/about/">Read more</a>.</p>
 </
div><!-- /.sidebar-module -->
 
{/if}

 {
!-- PLEASE NOTEDisplay on Homepage --}

 {if 
"{embed:pagetype}" == "homepage"}
 
<div class="sidebar-module">
  <
h3>Please Note</h3
  <
class="first">Some things that may interest you.</p>
  <
a href="http://www.abriefmessage.com/"><img src="http://www.subtraction.com/images/tout_abm.gif" 
 
alt="A Brief Message" width="190" height="170" /></a>
  <
a href="http://www.misterpresident.org"><img src="http://www.subtraction.com/images/tout_mr_president.gif" 
 
alt="MisterPresident.org" width="190" height="50" /></a>
  <
a href="http://www.subtraction.com/archives/2007/0827_a_subway_sys.php">
<
img src="http://www.subtraction.com/images/tout_subway_maps.gif" alt="Subway Maps for iPhone" width="190" height="50" />
</
a>
 </
div><!-- /.sidebar-module -->
 
{/if}

 {
!-- GET YOUR XML ONDisplay on Homepage --}

 {if 
"{embed:pagetype}" == "homepage"}
 {
!-- Display on homepage --}
 
<div class="sidebar-module">
  <
h3>Get Your XML On</h3>
  <
ul class="xml">
   <
li><a href="http://feeds.feedburner.com/subtraction" class="atom">Subscribe to the Feed</a></li>
  </
ul>
 </
div><!-- /.sidebar-module -->
 
{/if}

 {
!-- COLOPHONDisplay on Homepage --}

 {if 
"{embed:pagetype}" == "homepage"}
 {
!-- Display on homepage --}
 
<div class="sidebar-module">
  <
h3>Colophon</h3>
  <
ul class="colophon">
   <
li><a href="http://www.apple.com" class="mac">Made with a Macintosh</a></li>
   <
li><a href="http://www.expressionengine.com" class="ee">Published with Expression Engine</a></li>
   <
li><a href="http://creativecommons.org/licenses/by-nc-sa/1.0" class="cc">Creative Commons License</a></li>
  </
ul>
 </
div><!-- /.sidebar-module -->
 
{/if}

 {
!-- RECENT REMARKSDisplay on Homepage --}

 {if 
"{embed:pagetype}" == "homepage"}
 
<div class="sidebar-module">
  <
h3>Recent Remarks</h3
  
<?php $count=""?>
  {exp
:weblog:entries sort="desc" orderby="date" limit="10" dynamic="off"}
  {if comment_total 
0}
  <?php
  $comments 
"{comment_total}";
  
$commentsShow $comments -1;
  if (
$count ) :
  
$count++;
  
?>
  {embed
="embeds/recent-remarker" thisentry_urltitle="{url_title}" 
 
commentsShow="<?php echo $commentsShow?>" datepath="{entry_date format="%Y/%m/%d/"}"}
  <?php 
endif; ?>
  {
/if}
  {
/exp:weblog:entries}
 
</div><!-- /.sidebar-module -->
 
{/if}
 
</div><!-- /sidebar --> 

The sidebar is comprised of a series of modules — Quick Access, Recent Posts, About Me, Please Note, Get Your XML On, Colophon and Recent Remarks — some static, some dynamic, each displayed or not depending on the value of the pagetype variable, which is always passed to the sidebar template by its parent.

For recent remarks, a template is embedded to display the comment:entries tag, with data passed to it from the weblog:entries tag. This is required because the comment:entries tag is missing some vital variables, such as entry_date and url_title. The embedded template is /embeds/recent-remarker:

{exp:comment:entries url_title="{embed:thisentry_urltitle}" 
  
disable="categories|custom_fields|member_data|pagination|trackbacks" limit="1"}
<class="date">{comment_date format="%d %M %Y"}</p>
<
h4>{name} {if embed:commentsShow 0}(and {embed:commentsShow} others{/if}on <a href="{path=/}{embed:datepath}{url_title}">{title}</a></h4>
{/exp:comment:entries} 

The Multi-Entry Template

That’s it for a single entry, now for the multi-purpose /site/multi-entry template:

<div class="clear"></div>

{if embed:pagetype == "homepage"}
<div id="cover">&nbsp;</div>
{if:elseif embed:pagetype == "monthly" }
{embed
="embeds/top-monthly"}
{if
:elseif embed:pagetype == "category"}
{embed
="embeds/top-categories"}
{
/if}

<div id="posts">

 
{if segment_1 == ""}
 {embed
="site/multi-entry-inner" entrieslimit="60"}
 {if
:else}
 {embed
="site/multi-entry-inner" entrieslimit="999"}
 {
/if}

 {
!-- if "{embed:pagetype}" != "category"}
 {embed
="embeds/posts-more"}
 {
/if --}

</div><!-- /#posts -->

{embed="site/sidebar" pagetype="{embed:pagetype}" articletype=""

The multi-entry template contains only three parts: its top, the posts, and the call to the sidebar. The top varies depending on whether the template has been called to display the homepage, a monthly or a category page.

The Multi-Entry Inner Template

If we’re on the homepage, the /site/multi-entry-inner template is called with a limit of 60 entries:

<?php 
$daylimit
=""
$daycount=""
?>

{exp
:weblog:entries weblog="subtraction|elsewhere|lens" orderby="date" sort="desc" 
 
limit="{embed:entrieslimit}" disable="member_data|trackbacks" relaxed_categories="yes"}
{date_heading display
="daily"}
<?php $daylimit
++; if ($daylimit=="1"{ $daylimit="2"} ?>
{
/date_heading}
{
/exp:weblog:entries}
 
<?php 
if ("{embed:entrieslimit}" != "60"{ $daylimit="999"} ?>

{exp
:weblog:entries weblog="subtraction|elsewhere|lens" orderby="date" sort="desc" 
 
limit="{embed:entrieslimit}" disable="member_data|trackbacks" relaxed_categories="yes"}

{date_heading display
="daily"}
<?php $daycount
++; if ($daycount $daylimit) : ?>
<div class="day">
 <
class="date">
  
{entry_date format='%D %d %M'}<br />
  
{entry_date format='%Y'}
 
</p>
 
<?php endif; ?>
 {
/date_heading}

 <?php 
if ($daycount $daylimit) : ?>

 <?php
 $still_allow_comments
="no";
 
$post_time=strval("{entry_date format='%U'}");
 
$current_time="{current_time}";
 
$comments_allowed_from $current_time 5184000;
 if ( 
$post_time >= $comments_allowed_from 
 
{
  $still_allow_comments
="yes";
 
}
 ?>

 {if weblog_short_name 
== "elsewhere"}
 
<h2 id="{entry_id}" class="elsewhere"><a href="{elsewhere-url}">{title}</a></h2>
 <
div class="post-lead">
  <
div class="time-remarks">
   <
div class="stars">
    <
a href="{path={entry_date format=" class="stars-{elsewhere-rating}">
<
img src="{site_url}assets/1-star.gif" alt="Stars" width="14" height="14" />
</
a>
   </
div><!-- /.stars -->
  </
div><!-- /.time-remarks -->
  
{if elsewhere-body != "" }
  {elsewhere
-body}
  {if
:elseif elsewhere-body == ""}
  
<p>&nbsp;</p>
  
{/if}
 
</div><!-- /.post-lead -->
 <
div class="clear"></div>
 
{/if}

 {if weblog_short_name 
== "lens"}
 
<h2 id="{entry_id}">
 <
a href="{path={entry_date format=" format="%m" format="%d">{title}</a>
</
h2>
 <
div class="post-lead lens">
  <
div class="time-remarks">
   
{entry_date format="%g:%i %A"}<br />
   
{if allow_comments && comment_total == 0}
   <?php 
if ( $still_allow_comments == "yes" ) : ?>
   
<a href="{path={entry_date format=">Add Remarks</a>
   
<?php endif; ?>
   {if
:elseif allow_comments && comment_total >= 1}
   
<a href="{path={entry_date format=">Remarks ({comment_total})</a>
   
{/if}
  
</div><!-- /.time-remarks -->
  <
a href="{path={entry_date format=">{lens-preview}</a
  
{if lens-caption != "" }
  {lens
-caption}
  {
/if}
  
<div class="full">
   <
a href="{path={entry_date format=">View Full Image</a>
  </
div>

  
{if allow_comments && comment_total == 0}
  <?php 
if ( $still_allow_comments == "yes" ) : ?>
  
<div class="add"><a href="{path={entry_date format=">Add First Remarks</a></div>
  
<?php endif; ?>
  {if
:elseif allow_comments && comment_total >= 1}
  <?php 
if ( $still_allow_comments == "yes" ) : ?>
  
<div class="add"><a href="{path={entry_date format=">Add Remarks ({comment_total} so far)</a></div>
  
<?php endif; ?>
  {
/if}
 
</div><!-- /.post-lead -->
 <
div class="clear"></div>
 
{/if}

 {if weblog_short_name 
== "subtraction"}
 
<h2 id="{entry_id}"><a href="{path={entry_date format=" format="%m" format="%d">{title}</a></h2>
 <
div class="post-lead">
  <
div class="time-remarks">
   
{entry_date format="%g:%i %A"}<br />
   
{if allow_comments && comment_total == 0}
   <?php 
if ( $still_allow_comments == "yes" ) : ?>
   
<a href="{path={entry_date format=">Add Remarks</a>
   
<?php endif; ?>
   {if
:elseif allow_comments && comment_total >= 1}
   
<a href="{path={entry_date format=">Remarks ({comment_total})</a>
   
{/if}
  
</div><!-- /.time-remarks -->
  
{if "{embed:pagetype}" != "category"}
  {body}
  {if extended 
!= "" }
  
<div class="continue">
   <
a href="{path={entry_date format=">Continue Reading</a>
  </
div>
  
{/if}
  {if
:else}
  {summary}
  {
/if}
  <?php 
if ( $still_allow_comments == "yes" ) : ?>
  {if allow_comments 
&& comment_total == 0}
  
<div class="add">
 <
a href="{path={entry_date format=">Add First Remarks</a>
</
div>
  
{if:elseif allow_comments && comment_total >= 1}
  
<div class="add">
 <
a href="{path={entry_date format=">Add Remarks ({comment_total} so far)</a>
</
div>
  
{/if}
  <?php 
endif; ?>
 
</div><!-- /.post-lead -->
 <
div class="clear"></div>
 
{/if}

{date_footer display
="daily"}</div><!-- /.day -->{/date_footer}

<?php 
endif; ?>

{
/exp:weblog:entries} 

Why is an inner template necessary? A choices of two values is required for the weblog:entries “limit” parameter because the homepage displays only the newest 60 entries whereas the other multi-entry pages — months and categories — display an unlimited number of entries. The problem is that a variable generated within a template doesn’t work as a weblog:entries tag’s limit parameter value due to the template parsing order. But variables passed from a parent template can be used there.

The first thing to notice is that the weblog:entries tag is called twice. While the homepage limit is 60 entries, Subtraction.com is organized daily, so if the oldest day contains say 5 entries but 57 have already been displayed, then only three of the final day’s entries will be shown. Khoi wanted the homepage’s final day to appear completely. So in the first call of the weblog:entries tag the number of days covered is counted as $daylimit, then in the second weblog:entries tag $daycount is incremented for each day, the day only displayed if the $daycount is smaller than the $daylimit:

<?php $daycount++; if ($daycount $daylimit) : ?>
...
<?php endif; ?> 

The result is that the final day, whether incomplete or not, is truncated from the homepage. (If we’re not on the homepage, the daylimit is set to 999 because we don’t need to apply it.)

Unlike on the single-entry template, each of the three weblogs is displayed differently enough here to warrant its own fork. But like the single-entry template, PHP code calculates whether comments are still allowed, determining whether the links read “Add Remarks” or just “Remarks”.

The Top Categories Template

Back up to the /site/multi-entry template, if we are not on the homepage but on a categories page then the conditional embeds the template /embeds/top-categories:

<div id="category-header">
 
<?php $catposts=""?>
 {exp
:weblog:entries weblog="subtraction|elsewhere|lens" orderby="date" sort="desc" limit="999" 
 
disable="category_fields|custom_fields|member_data|pagination|trackbacks" 
 
relaxed_categories="yes" status="not closed|Hold"}
 {if count
=="1"}
 <?php $catposts
="{total_results}"?>
 {
/if}
 {
/exp:weblog:entries} 
 {exp
:weblog:category_heading weblog="subtraction|lens|elsewhere" relaxed_categories="yes"}
 
<h1>{category_name}</h1>
 <
h3><?php echo $catposts?> posts</h3>
 
{if category_description}
 
<p>{category_description}</p>
 
{/if}
 {
/exp:weblog:category_heading} 
</div

Here the weblog:entries tag is run before the weblog:category_heading tag because category_heading lacks a count feature, and Khoi wasn’t doing without that. Using a PHP variable the weblog:entries tag enables us to remember the number of entries as $catposts. Since we’re in a /categories URL, which is what we’ve set as EE’s Reserved Category URL Indicator, the weblog:entries tag does not require a specified category as it’s already active. Then the weblog:category_heading tag displays the category’s name and description, if there is one, together with our $catposts.

The Top Monthly Template

If however we’re on a monthly page then instead of /embeds/top-categories the top is /embeds/top-monthly:

{exp:weblog:entries weblog="illustrate_me" limit="1" disable="member_data|trackbacks"}
<div id="illustration">
 <
img src="{illustrate_me_illustration}" alt="" />
</
div>
{/exp:weblog:entries}

<?php
$monthcount 
"";
$total_subtraction="";
$total_lens="";
$total_elsewhere="";
?>

{exp
:weblog:entries weblog="subtraction|elsewhere|lens" disable="member_data|trackbacks" limit="999" 
 
disable="categories|category_fields|custom_fields|member_data|pagination|trackbacks" status="not closed|Hold"}
{if count
=="1"}
<?php 
$monthcount 
"{total_results}";
?>
{
/if}
{
/exp:weblog:entries}

<?php $row
=""?>
{exp
:weblog:calendar weblog="subtraction|lens|elsewhere" leading_zeroes="yes"}
<div id="calendar">
<
h1 class="page-title">{date format="%F %Y"}

<span><?php if ($monthcountecho $monthcount " posts"} ?></span>
</
h1>

<
table border="0" cellspacing="0" cellpadding="0">

<
tr>
<
th>Sunday</th>
<
th>Monday</th>
<
th>Tuesday</th>
<
th>Wednesday</th>
<
th>Thursday</th>
<
th>Friday</th>
<
th class="last">Saturday</th>
</
tr>

 
{calendar_rows}
 {row_start}
<tr>{/row_start}

  {if entries}
  
<td>
   <
p>{day_number}</p>
   
{entries}
   
<a href="{path={entry_date format=" title="{title}"><img src="{site_url}assets/calendar_markers.gif" alt="{title}" width="95" height="10" /></a>
   
{/entries}
   
  
</td>
    
  
{/if}

  {if not_entries}
  
<td>
   <
p>{day_number}</p>
   <
img src="{site_url}assets/calendar_markers.gif" alt="" width="95" height="10" />
  </
td>
  
{/if}

  {if blank}
  
<td><p></p></td>
  
{/if}

 {row_end}
</tr>{/row_end}
 {
/calendar_rows}

</table>
</
div>
{/exp:weblog:calendar}

{exp
:weblog:entries weblog="illustrate_me" limit="1" disable="member_data|trackbacks"}
{exp
:replace find="</p<" replace=""}{illustrate_me_caption}{/exp:replace} {related_entries id="illustrate_me_subtraction"}Read the <a href="{path={entry_date format=">interview about this illustration</a>.</p>{/related_entries}
{
/exp:weblog:entries}

<div class="clear"></div

Here too links get an assist from EE’s natural functioning: in this template’s three weblog:entries tags neither the year nor month need be specified because the system already grabs them from the numerical /year/month format of the URL. Yes, parameters could be added explicitly to the weblog:entries tags:

year="{segment_1}" month="{segment_2}" dynamic="off" 

But it’s nice to let her sail as she was designed.

The first weblog:entries tag displays the discontinued Illustrate Me image if it exists for that month. Although Khoi does not plan to revive this feature, it’s easier to let EE handle the scheduling of discontinued content types just the same way it handles ongoing ones. So Illustrate Me has a weblog just as do Subtraction, Elsewhere and Lens.

The next weblog:entries instance is similar to the one in the /embeds/top-categories template that counts entries in the category, except this time we’re counting entries in the month. (Note that here all options are disabled while in /embeds/top-categories categories was still enabled.)

Then within Khoi’s sleek-looking calendar we list any Subtraction, Lens or Elsewhere entries, linking as always to their blog-style URLs:

{path={entry_date format="%Y/%m/%d"}}{url_title} 

At the end of the template the Illustrate Me weblog is called again to retrieve and display its caption, which appears after the calendar. (To save server processsing we could have grabbed this data from the earlier call to Illustrate Me, setting it there as a PHP variable to display later down below.)

The Archives Template

Just one major screen remains, the Archives, which differs from all the others so requires its own template:

{if segment_2 == ""}

{embed
="site/header"}

<div id="column-a">
 <
h1>Archives<br />by Date</h1>

 
<?php $x=""?>
 {exp
:yearly_archives weblog="subtraction|lens|elsewhere" status="not closed" monthsort="desc" show_future_entries="no"}
 {months}
 {if num_entries}
  <?php 
  $x
++;
  if ( 
$x 4) : 
  
?>
  
<div class="archive-unit">
  <
h2><a href="{path=/}{year}/{month_num}"><span>{month} {year}</span>{num_entries} posts</a></h2>
  
{embed="embeds/archives-date" year="{year}" month="{month_num}"}
  <?php 
else : ?>
  {if month_num
=="12"}<h2 style="clear: both; text-align: left">{year}</h2>{/if}
  
<div class="archive-subunit">
  <
h2><a href="{path=/}{year}/{month_num}"><span>{month}<!-- {year}--></span><br />{num_entries} posts</a></h2>
  
<?php endif; ?>
 
</div><!-- /.archive-unit -->
 
{/if}
 {
/months}
 {
/exp:yearly_archives}

</div><!-- /#column-a -->

<div id="column-b">

 <
h1>Archives<br />by Category</h1>
 
 
{exp:query sql="SELECT count(exp_category_posts.entry_id) 
 AS post_count, exp_category_posts.cat_id, exp_categories.cat_name, 
 exp_categories.cat_url_title, exp_categories.cat_description 
 FROM exp_categories, exp_category_posts, exp_weblog_titles 
 WHERE exp_category_posts.cat_id = exp_categories.cat_id 
 AND (exp_weblog_titles.weblog_id = '1' || exp_weblog_titles.weblog_id = '2' || exp_weblog_titles.weblog_id = '4') 
 AND exp_weblog_titles.entry_id = exp_category_posts.entry_id GROUP BY exp_categories.cat_name"
}
 
<div class="archive-unit">
  <
h2><a href="{path=tags}{cat_url_title}"><span>{cat_name}</span>{post_count} posts</a></h2>
  
{if cat_description}<p>{cat_description}</p>{/if}
 
</div><!-- /.archive-unit -->
 
{/exp:query}

</div><!-- /#column-b -->

{embed="site/footer"}

{
/if}
{if segment_2 
!= ""}

<?php
$original_segment2 
"{segment_2}";
$year $original_segment2;
$original_segment3 "{segment_3}";
$month substr($original_segment302);
$date substr($original_segment322);
$urltitle substr($original_segment35);
$urltitle rtrim($urltitle".php");
$urltitle strtr($urltitle"_""-");
$path $year "/" $month "/" $date;
header("HTTP/1.1 301 Moved Permanently");
header("Location: http://www.subtraction.com/" $path "/" $urltitle);
exit();
?>

{
/if} 

Archives by Date relies on the Yearly Archives plugin by Lodewijk Schutte. For the most recent three months the titles of the posts are displayed as well as the post count, after which the listing recedes to just a post count. These first three are separated from the others using a counter and a conditional, and only if the counter is less than 4 do we embed the template /embeds/archives-date (it must be embedded because the weblog:entries tag won’t work within the yearly_archives plugin):

<p>{exp:weblog:entries weblog="subtraction|elsewhere|lens" dynamic="off" year="{embed:year}" month="{embed:month}" disable="categories|category_fields|custom_fields|member_data|pagination|trackbacks"}
<a href="{path={entry_date format=">{title}</a>&nbsp;+&nbsp;
{/exp:weblog:entries}</p

This small template, having received the year and month, displays the month’s post titles and links to them. Since only the posts’ titles and URL titles are used, everything else in the weblog:entries tag is disabled. Each but the latest post is preceded by a “+”. Again the links to individual entries are in the blog-standard year/month/date format.

The rest of the months are tested to determine if they are December, and if so a yearly heading is inserted. Note that all the action is within a conditional that tests whether the month actually has any entries, this to omit empty months.

Over on the right Archives by Category are displayed using the Category Count technique from the EE Wiki.

Legacy URLs

All this is set to display only if the URL’s segment #2 is empty. If it’s not then we’re on a legacy URL from MoveableType, such as /archives/2005/1025_european_vac.php, and some PHP string manipulation at the end of the template converts this URL into one of our new ones. The year is retrieved from the original segment #2, the month from the first two characters of the original segment #3, the date from the next two characters, and the URL title from the 5th character on. The URL Title’s “.php” is chopped off then any underscores are converted to dashes. The result is /2005/10/25/european-vac, to which the browser is redirected. It’s great that the site had a consistent URL structure in its previous incarnation otherwise this would have been a lot harder.

The Footer Template

Now to the footer:

<div class="clear"></div>

{if segment_1=="" || segment_4 != ""}
<div id="cat-table">
 <
h3>Categories</h3>
 <
table cellspacing="0" cellpadding="0" border="0">
  <
tr>

   
{!-- Get the number of categories --}
   {exp
:query sql="SELECT count(cat_id) FROM exp_categories WHERE group_id='1' GROUP BY cat_id"}
   <?php $count
="{count}" ?>
   {
/exp:query}

   {
!-- Calculate the length of each column based on a manually-set number of columns --}
   <?php
    $cols 
4;
    
$thiscol 0;
    
$collengthround($count $cols);
    
$offset=0;
    while (
$offset $count):
   
?>

   {
!-- Display a column --}
   <?php $thiscol
++; if ($thiscol 5): ?>
   
<td>
    <
ul>
     
{embed="embeds/footer-categories" count="<?php echo $collength; ?<" offset="<?php echo $offset; ?<"}
    
</ul>
   </
td>
   
<?php endif; ?>
   <?php 
if ($thiscol 4): ?><td class="shim"></td><?php endif; ?>

   {
!-- Increase the offset by the length of a column and reiterate until done --}
   <?php
    $offset 
$offset $collength;
    endwhile;
   
?>

  
</tr>
 </
table>
</
div><!-- /#cat-table -->
{/if}

<div id="footer">
 <
p><a href="http://www.subtraction.com">Subtraction.com</a> and all contents copyright 1998-{current_time format="%Y"
 by {encode
="[unencoded email address]?subject=Regarding Subtraction.com" title="Khoi Vinh"}unless otherwise noted.<br />
 
Contents under <a href="http://creativecommons.org/licenses/by-nc-sa/1.0/">Creative Commons License</a>. 
 
Visual designlayout and Cascading Style Sheets may not be reused without permission.</p>
</
div><!-- /#footer -->

</div><!-- /home or articles -->
</
body>
</
html

Khoi wanted his list of categories to be divided equally across four columns. First the number of categories is retrieved using a query tag and assigned to the PHP variable $count. Then the length of the columns, $collength, is calculated by dividing the number of categories by the number of columns, four. A loop displays each column. An offset value is also required so that the subsequent columns don’t merely repeat the first one. Since a PHP variable can’t be passed into a query tag due to the template parsing order, each column is displayed within an embedded template:

{exp:query sql="SELECT count(exp_category_posts.entry_id) AS post_count,
exp_category_posts.cat_id, exp_categories.cat_name, exp_categories.cat_url_title
FROM exp_categories, exp_category_posts, exp_weblog_titles
WHERE exp_category_posts.cat_id = exp_categories.cat_id
AND exp_weblog_titles.weblog_id = '1'
AND exp_weblog_titles.entry_id = exp_category_posts.entry_id
GROUP BY exp_categories.cat_name LIMIT {embed:count} OFFSET {embed:offset}"
}
<li><a href="{path=site_index}categories/{cat_url_title}/"><span>{cat_name}</span>{post_count}</a></li>
{/exp:query} 

Here again the Category Count technique is taken from the EE Wiki, with a limit and offset appended to the query, populated with values passed from the parent template.

We’re almost done, everything covered except the header:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

{assign_variable:my_template_group="site"}
{assign_variable
:my_weblogs="subtraction|lens|elsewhere"}
<?php $css
=""?>

<html>
<
head>
<
title>
 
Subtraction on Expression Engine{if "{segment_1}" == ""} 7.0{if:else}:{/if}
 {if segment_1 
!= "categories"}
 {exp
:weblog:entries limit="1" dynamic="off" url_title="{segment_4}" disable="categories|category_fields|member_data|pagination|trackbacks"}
 {
!-- Article --}{if "{segment_4}" != ""}{title}{/if}
 {
!-- Monthly --}{if "{segment_2}" != "" && "{segment_1}" != "categories" 
 
&& "{segment_1}" != "archives" && "{segment_3}" == ""}
{entry_date format
="%F %Y"} Archives
{
/if}
 <?php 
 $css
="{css}{elsewhere-css}"
 
$entryid="{entry_id}";
 
?>
 {
/exp:weblog:entries}
 {
/if}
 {
!-- Category --}{if "{segment_1}" == "categories"}{exp:weblog:category_heading weblog="subtraction|lens|elsewhere" relaxed_categories="yes"}{category_name}{/exp:weblog:category_heading} Archives{/if}
 {
!-- Archives --}{if "{segment_1}" == "archives"}Archives{/if}
</title>
<
link rel="stylesheet" href="{stylesheet=styles/main}" type="text/css" />
<
link rel="stylesheet" href="{stylesheet=styles/nav}" type="text/css" />
<
link rel="shortcut icon" href="favicon.ico" type="image/x-icon" /> 
<?php if ($css != ""): ?>
<style>
<?php echo $css?>

</style>
<?php endif; ?>
<link rel="alternate" type="application/atom+xml" title="Atom" href="http://feeds.feedburner.com/subtraction" />
<!-- 
script src="/mint/?js" type="text/javascript">
 
//create jump menu
function jumpMenu(targ,selObj,restore)//v3.0
eval(targ+".location='"+selObj.options[selObj.selectedIndex].value+"'");
if (
restoreselObj.selectedIndex=0;
}

</head>

<
body>
<
div id="{if segment_1 == ""}home{if:elseif "{segment_2}" != "" && segment_4 == "" && "{segment_1}" != "categories
 && "
{segment_1}" != "archives" && "{segment_1}" != "about" }
 calendar
 {if:elseif "
{segment_1}" == "categories"}
 category
 {if:elseif "
{segment_1}" == "archives"}
 archives
 {if:elseif "
{segment_1}" == "about"}
 about
 {if:elseif segment_4 != ""}
 articles
 {/if}
"
>
<
div id="header">
 <
h1><a href="http://www.subtraction.com">Subtraction</a></h1>
 <
p><img src="http://www.subtraction.com/assets/subtraction_logo_black.gif" alt="+S" width="23" height="23" />
 
Version 7.1 Beta This dev server page {elapsed_time} secs<br /><a href="{path=about}">Khoi Vinh</a>&#8217;s Web Site</p>
 
<h4>Search <span>via Google</span></h4>
 <
div>
  <!-- 
Google CSE Search Box Begins -->
  <
form id="searchbox_005794356726363419020:mq1sdd86pfu" action="http://www.subtraction.com/search/">
  <
input type="hidden" name="cx" value="005794356726363419020:mq1sdd86pfu" />
  <
input type="hidden" name="cof" value="FORID:9" />

  <
input name="q" class="search-field" type="text" />
  <
input type="submit" name="sa" value="Go" />
  </
form>
  <!-- 
Google CSE Search Box Ends -->
  </
form>
 </
div>
</
div><!-- /#header -->

<div id="navcontainer">
 <
ul id="navlist">

  <
li class="home"><a href="{path=/}">Home</a></li
  <
li><a href="{path=archives}">Archives</a></li>
  <
li><a href="{path=about}">About</a></li>

  
{if segment_1 != "" && segment_1 != "archives" && segment_4=="" && segment_1 != "categories" } {!-- Monthly index --}

  
<li class="short"></li>

  
{exp:weblog:calendar weblog="subtraction|lens|elsewhere" leading_zeroes="yes"}
  
<li id="prev"><a href="{previous_path=/}">Previous</a></li>
  <
li id="next"><a href="{next_path=/}">Next</a></li>
  
{/exp:weblog:calendar}

  {if
:elseif segment_1 == "" OR segment_1 == "archives" OR segment_1 == "categories"}
  {
!-- HomepageArchives and Categories pages --}

  
<li id="empty"></li>

  
{if:elseif segment_4 != ""} {!-- Article --}

  {embed
="embeds/next-previous" thisentry_entryid="<?php echo $entryid; ?<"}
  
  {
/if}

 
</ul>
</
div><!-- #navcontainer -->

<div class="clear"></div

Within the window title is a weblog:entries tag. If we’re on an article the tag displays the article title and captures the entry ID and entry-specific CSS, if any, to insert later in the template (for performance reasons we only want to invoke the weblog:entries tag once in the header). If we’re on a monthly page then the month and year are displayed in long format. Otherwise the weblog:entries tag is skipped, and if we’re on a categories page then the category_heading tag is called to display its name, or if we’re on the archives page then that is stated.

Using PHP the article’s unique CSS is displayed if it has any. Then after the body is declared the outer div is given its id based on the URL.

The Next and Previous links differ depending on the page type. If we’re on a monthly page they display the next and previous month using the calendar tag. If we’re on an article they display the next and previous article. The next and previous links for articles are a casualty of our blog-style URLs: they simply don’t work. After some trial and error we gave up and used the Query module instead:

<li class="short"></li>
<
li id="prev">
 
{exp:query sql="SELECT title, url_title, entry_date FROM exp_weblog_titles 
 WHERE entry_date < '{embed:thisentry_entrydate}' AND status='open' 
 AND (weblog_id='1' OR weblog_id='2' OR weblog_id='4' OR weblog_id='5')
 ORDER BY entry_date DESC LIMIT 1"
}
 {if title}
 
<a href="{path={entry_date format=">Previous</a>
 
{/if}
 {
/exp:query}
</li>
<
li id="next">
 
{exp:query sql="SELECT title, url_title, entry_date FROM exp_weblog_titles 
 WHERE entry_date < '{embed:thisentry_entrydate}' AND entry_date <= '{current_time}' AND status='open' 
 AND (weblog_id='1' OR weblog_id='2' OR weblog_id='4' OR weblog_id='5')
 LIMIT 1"
}
 {if title}
 
<a href="{path={entry_date format=">Next</a>
 
{/if}
 {
/exp:query}
</li

Since the writing of this article I’ve developed a plugin, Nearby Entries, that solves the problem of displaying next/previous entries when using the year/month/date URL format.

Stepping back, I’m honored to have worked on this site that I admire so much. Thanks Khoi for the opportunity.

Add-ons

The site uses the following EE add-ons:

Plugins:

Extensions

What’s with all the excellent Dutchmen?

Tue 30 Sep ’08
10:58am

dave

excellent post. Did you have to make a htaccess file as well for removing the EE url structure (use of index.php) etc., ?

It would be interesting to know the structure you used for a posts entry form. If you have multiple images split in the body of a text page I find Mark Huot’s File doesn’t really help. Image uploading is still a bit slow using EE I find. If you have any tips that would be interesting to read.

Tue 30 Sep ’08
3:06pm

mattresses

this was pretty interesting, thanks for puttin it up.

Wed 1 Oct ’08
11:51am

Steven Hambleton

I don’t know about you but you don’t realise just how much you end up coding until you sit back and print it all out like that!

Good job!

Wed 1 Oct ’08
12:59pm

Adam Khan

Dave, yes, I almost always use the htaccess file method for removing index.php, so it’s so par for the course that I didn’t think to mention it here. I generally use the “File and Directory Check” method documented in the “Remove index.php From URLs” entry in the EE WIki.

For multiple images split in the body of a text I’ve often just created Body, Body2, Body3 fields so the site author can control where the split happens. It seems clunky but there isn’t too much downside. Depends on the user as well. That’s not what happens on Subtraction—Khoi handcodes HTML within his entries and uploads images separately. We only used File for his defunct Illustrations section. That’s how he’s used to working (though I’m going to suggest he use File for his new Lens weblog for photos).

Thanks for the feedback, Mattresses and Steven.

Wed 1 Oct ’08
6:01pm

Christopher L. Jorgensen

Way cool write up. I wish I had more time to actually learn EE. Or I wish I had the cash to outsource the things I don’t understand.

Vinh?s site looks pretty much the same, but I liked it before, so glad to see it’s functional changes, and not cosmetic ones.

Also, in your code examples above some shoot way off the page in Safari on 10.5.5.

Sun 5 Oct ’08
5:25pm

Devon Shaw

Bookmarked for the EE Jedi tricks. This is great stuff. I’ve used EE for nearly two years now but never gone so far as to exercise the robustness of the conditionals. I learned a lot from this post, thanks for putting it up.

Fri 31 Oct ’08
5:34pm

allgood2

Great post Adam. You just gave me the ability to streamline a days-past PHP code, I created for a client, that performs a similar function to yours?removing access to comments and a featured image at a specific time frame. Excellent.

I think this is an excellent instructional on what needs to happen when not using EE’s dynamic handling for URLs. Thanks for the write-up.

Sat 10 Jan ’09
3:29pm

Ryan Irelan

Great write-up, Adam.

Did you know that EE has a built-in setting for expiring the comments after a certain number of days? It’s under the Comment Posting Preferences section of Weblog Management.

Sat 10 Jan ’09
5:06pm

Adam Khan

Ryan, how embarrassing, I didn’t know about that setting. Thanks for the kind words and the heads-up.

Tue 30 Jun ’09
12:25am

Victor

hi - excellent work. I just recently started learning EE and looking at your code, it seems I have a long way to go.

I have a question in regards to your implementation of the footer, especially the category section. I was able to follow the logic until the second block of code where you talk about “Since a PHP variable can?t be passed into a query tag due to the template parsing order, each column is displayed within an embedded template:”

where exactly is that code implemented in relation to the footer code above it?  I am thinking of implementing a similar solution in a sidebar with only 2 columns in this case. if you can provide gudance on which code goes on the page itself and which block is an include element that will be great! thx!

Tue 30 Jun ’09
8:58am

Adam Khan

Victor, the template that begins with the query tag is the embedded one, ie, /embeds/footer-categories.

Regarding having a long way to go, well, this article is the solution in retrospect, but when working on a site you just bang on through tackling each problem as it comes until they’re all tackled and next time it’s possibly easier because you’ve done something similar previously.

Wed 26 May ’10
4:44am

Willy Schwenzfeier

Hey Adam, super helpful post. Especially the explanation of the blog-standard URLs, a technique I’ve studied very closely and used a few times. Thanks much.

I’m wondering how you’d handle adding pagination to the home page if Khoi wanted sequential pages of 60 posts each? It seems like the segment queries in your /site/index template would have a tough time testing for the “P1” type segments that the paginate tag creates. Any strategies make sense to you?

Thanks again for the great post.

Wed 26 May ’10
4:51am

Adam Khan

Funny you should mention that, Willy, as I’m just about to do it somewhere else. I was vaguely thinking of doing pagination non-natively, with a URL setup of /pages/1, pages/2, etc, and just passing an offset parameter to the template that contains the weblog:entries tag.

Wed 26 May ’10
6:20am

Willy Schwenzfeier

Wow that was quick!

Interesting. That might just do it.

Alternately, i was just poking around in the EE forums and docs and discovered that perhaps we could try using paginate_base=“site/paging-template” in the weblog:entries tag to force EE to skip index.php altogether and go straight to a template that’s just for pagination. In Khoi’s case, that could hold {embed=“site/header”}{embed=“site/site/multi-entry”}{embed=“site/footer”} and deliver a URL like /site/paging-template/P60/. Perhaps a little awkward, but if you came up with a good name for that template (maybe even with its own template group) it might be fine.

I’m doing a site now where the blog-standard URLs will follow the the “news” template group (ie /news/2010/05/26/news-story/). If I used this method then, I think I could end up with pagination URLs like /news/stories/P15 which might not be half bad.

Thanks again.

Wed 26 May ’10
6:32am

Adam Khan

Yep, that should do it. Re speedy response—freak occurrence.

-Adam

Tue 9 Nov ’10
6:30am

Adrian

According to this link, embeded templates are still parsed after advanced conditionals, so you should be fine in this instance.

http://expressionengine.com/forums/viewthread/168268/

Wed 8 Feb ’12
7:21am

StephanieMartus

Just starting to learn EE… so your codes going to help me. Looking forward for more such posts!!!
bridge

Post a comment

Name:

Email:

Location:

URL:

Your comment:

Please answer the following question to assure your intentions are honorable: Name a movie that's set "A long time ago in a galaxy far away..." (9 characters required)

Remember my personal information
Notify me of follow-up comments?