IPS Staff
  • Content count

  • Joined

  • Last visited

About Rikki

  • Rank
    data-type='member title'
  • Birthday 08/17/1983

Contact Methods

IPS Marketplace

  • Resources Contributor Total file submissions: 3

Profile Information

  • Gender Male
  • Location Lynchburg, VA :o
  1. We can now tie everything together with some Javascript. It's with the Javascript that everything will feel snappy and interactive. Begin by heading over to the template editor, and editing the Javascript file we created earlier. The Javascript controller we write will do two things: Load the first record when the page is loaded Handle click events on the other records to load the ones the user clicks Here's the final Javascript code you need to insert into the file. We'll explain it shortly: Spoiler /** * IPS Social Suite 4 * (c) 2013 Invision Power Services - http://www.invisionpower.com * * ips.releaseNotes.main.js - Release notes controller * * Author: Rikki Tissier */ ;( function($, _, undefined){ "use strict"; ips.controller.register('pages.front.releaseNotes.main', { _ajaxObj: null, initialize: function () { this.on( 'click', '[data-releaseID]', this.showRelease ); this.setup(); }, setup: function () { // Find the current release if available var showFirst = this.scope.find('[data-role="releases"] [data-currentRelease]'); if( !showFirst.length ) { showFirst = this.scope.find('[data-role="releases"] [data-releaseID]').first(); } if( showFirst.length ){ showFirst.click(); } }, showRelease: function (e) { e.preventDefault(); var self = this; var link = $( e.currentTarget ).attr('href'); var infoPanel = this.scope.find('[data-role="releaseInfo"]'); // Cancel any current requests if( this._ajaxObj && _.isFunction( this._ajaxObj.abort ) ){ this._ajaxObj.abort(); } // Set panel to loading infoPanel .css({ height: infoPanel.height() + 'px' }) .html( $('<div/>').addClass('ipsLoading').css({ height: '100px' }) ); // Unhighlight all others, then highlight this one this.scope.find('[data-releaseID]').removeClass('cRelease_active'); $( e.currentTarget ).addClass('cRelease_active'); this._ajaxObj = ips.getAjax()( link, { data: { rating_submitted: 1 } }) .done( function (response) { var responseContent = $("<div>" + response + "</div>"); var content = responseContent.find('#elCmsPageWrap'); infoPanel.html( content ).css({ height: 'auto' }); $( document ).trigger( 'contentChange', [ infoPanel ] ); }); } }); }(jQuery, _));     This is a standard IPS4 javascript controller; refer to the controller documentation for more information. In our initialize method (called automatically when the controller is initialized), we set up an event handler for clicking on elements with the data-releaseID attribute (if you remember, we gave items in our record listing this attribute). We bind this handler to the showRelease method. We then call the setup method. The logic in this method selects a record to load automatically, so that the page has content in it when the user first loads it. It tries to find a record with the data-currentRelease attribute, but if one doesn't exist, it will load the first item in the list instead. Finally, there's the showRelease method which has the job of loading and displaying the record view, pulled dynamically via AJAX. To do this, we find the relevant URL from the record row and then fire an AJAX request. In the done handler, we update the div that holds our record content. When a Pages URL is called via AJAX (whether it's a normal page, or a database), it will only return the page content - it won't include the complete site wrapper. This makes it a little easier to implement dynamic page loading. However, in our case, the HTML we need is actually even narrower - we want just the record content, not any of the surrounding page from Pages. For this reason, in the done handler we select just the contents of the #elCmsPageWrap element, and use that as our record content.
  2. Record display - record template (See diff) This is the main template for the display portion of our database. Here's the final code: Spoiler <div class='ipsClearfix'> <h1 class='ipsType_pageTitle ipsType_largeTitle ipsType_break'> IPS Community Suite {wordbreak="$record->_title"} {{if $record->isFutureDate() || $record->mapped('pinned') || $record->mapped('featured') || $record->hidden() === -1 || $record->hidden() === 1}} {{if $record->isFutureDate()}} <span class="ipsBadge ipsBadge_icon ipsBadge_warning" data-ipsTooltip title='{$record->futureDateBlurb()}'><i class='fa fa-clock-o'></i></span> {{elseif $record->hidden() === -1}} <span class="ipsBadge ipsBadge_icon ipsBadge_warning" data-ipsTooltip title='{$record->hiddenBlurb()}'><i class='fa fa-eye-slash'></i></span> {{elseif $record->hidden() === 1}} <span class="ipsBadge ipsBadge_icon ipsBadge_warning" data-ipsTooltip title='{lang="pending_approval"}'><i class='fa fa-warning'></i></span> {{endif}} {{if $record->mapped('pinned')}} <span class="ipsBadge ipsBadge_icon ipsBadge_positive" data-ipsTooltip title='{lang="pinned"}'><i class='fa fa-thumb-tack'></i></span> {{endif}} {{if $record->mapped('featured')}} <span class="ipsBadge ipsBadge_icon ipsBadge_positive" data-ipsTooltip title='{lang="featured"}'><i class='fa fa-star'></i></span> {{endif}} {{endif}} </h1> <p class='ipsType_reset ipsType_large'>{$record->customFieldDisplayByKey('release-date', 'display')|raw}</p> {$record->customFieldDisplayByKey('security-release', 'display')|raw} </div> <hr class='ipsHr'> {{if count( $record->tags() )}} {template="tags" group="global" app="core" params="$record->tags()"} {{endif}} <article class='ipsContained ipsSpacer_top'> <div class='ipsClearfix'> <section class="ipsType_richText ipsType_normal" data-controller='core.front.core.lightboxedImages'> <h2 class='ipsType_pageTitle'>Key Changes</h2> {$record->_content|raw} </section> {{if $record->fieldValues()['field_164']}} <section class="ipsSpacer_top ipsSpacer_double ipsType_richText ipsType_normal" data-controller='core.front.core.lightboxedImages'> <h2 class='ipsType_pageTitle'>Additional Information</h2> {$record->customFieldDisplayByKey('additional-information', 'display')|raw} </section> {{endif}} </div> <hr class='ipsHr ipsClear ipsClearfix'> {{if $record->isFutureDate() or $record->canPin() or $record->canUnpin() or $record->canFeature() or $record->canUnfeature() or $record->canHide() or $record->canUnhide() or $record->canMove() or $record->canLock() or $record->canUnlock() or $record->canDelete()}} <a href='#elentryActions_menu' id='elentryActions' class='ipsButton ipsButton_light ipsButton_verySmall' data-ipsMenu>{lang="content_record_actions" sprintf="$record::database()->recordWord( 1, TRUE )"} <i class='fa fa-caret-down'></i></a> <ul id='elentryActions_menu' class='ipsMenu ipsMenu_auto ipsHide'> {{if $record->isFutureDate() and $record::canFuturePublish( NULL, $record->container() )}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'publish' ) )}' data-confirm title='{lang="publish_now"}'>{lang="publish"}</a></li> {{endif}} {{if $record->canFeature()}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'feature' ) )}' title='{lang="feature_title_record" sprintf="$record::database()->recordWord(1)"}'>{lang="feature"}</a></li> {{endif}} {{if $record->canUnfeature()}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'unfeature' ) )}' title='{lang="unfeature_title_record" sprintf="$record::database()->recordWord(1)"}'>{lang="unfeature"}</a></li> {{endif}} {{if $record->canPin()}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'pin' ) )}' title='{lang="pin_title_record" sprintf="$record::database()->recordWord(1)"}'>{lang="pin"}</a></li> {{endif}} {{if $record->canUnpin()}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'unpin' ) )}' title='{lang="unpin_title_record" sprintf="$record::database()->recordWord(1)"}'>{lang="unpin"}</a></li> {{endif}} {{if $record->canHide()}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'hide' ) )}' title='{lang="hide_title_record" sprintf="$record::database()->recordWord(1)"}' data-ipsDialog data-ipsDialog-title="{lang="hide"}">{lang="hide"}</a></li> {{endif}} {{if $record->canUnhide()}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'unhide' ) )}' title='{{if $record->hidden() === 1}}{lang="approve_title_record" sprintf="$record::database()->recordWord(1)"}{{else}}{lang="unhide_title_record" sprintf="$record::database()->recordWord(1)"}{{endif}}'>{{if $record->hidden() === 1}}{lang="approve"}{{else}}{lang="unhide"}{{endif}}</a></li> {{endif}} {{if $record->canLock()}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'lock' ) )}' title='{lang="lock_title_record" sprintf="$record::database()->recordWord(1)"}'>{lang="lock"}</a></li> {{endif}} {{if $record->canUnlock()}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'unlock' ) )}' title='{lang="unlock_title_record" sprintf="$record::database()->recordWord(1)"}'>{lang="unlock"}</a></li> {{endif}} {{if $record->canMove()}} <li class='ipsMenu_item'><a href='{$record->url('move')->csrf()}' data-ipsDialog data-ipsDialog-title="{lang="move"}" title='{lang="move_title_record" sprintf="$record::database()->recordWord(1)"}'>{lang="move"}</a></li> {{endif}} {{if $record->canDelete()}} <li class='ipsMenu_item'><a href='{$record->url('moderate')->csrf()->setQueryString( array( 'action' => 'delete' ) )}' data-confirm title='{lang="delete_title_record" sprintf="$record::database()->recordWord(1)"}'>{lang="delete"}</a></li> {{endif}} </ul> {{endif}} {{if $record->canEdit()}}   <a href='{$record->url('edit')->csrf()}' title='{lang="edit_title"}'>{lang="edit"}</a> {{endif}} {{if $record->canManageRevisions()}}   <a href='{$record->url('revisions')}' title="{lang="content_view_revisions"}">{lang="content_view_revisions"}</a> {{endif}} </article> <br> {{if $updateForm}} <div class='ipsAreaBackground_light ipsPad'> <h2 class='ipsType_sectionHead'>{lang="cms_front_update_fields" sprintf="$record::database()->recordWord( 1 )"}</h2> <ul class='ipsForm ipsForm_vertical'> {$updateForm|raw} </ul> </div> {{endif}}   There isn't too much going on here that hasn't already been explained. We're using the customFieldDisplayByKey method of the record to output our formatted fields, although we're using the display type this time: {$record->customFieldDisplayByKey('security_release', 'display')|raw} We also check the raw field value of the Additional Information field to determine whether there's any content to display for it. As before, you'll need to replace the ID number with the correct one from your own database: {{if $record->fieldValues()['field_164']}} ... {{endif}} We also strip out features we know aren't needed in this case: following, reputation, pager controls for moving between records (the latter isn't needed because all of our records are shown in the listing on the same page!).
  3. How the Release Notes section is structured Before we start editing the templates, it would be helpful to understand how the finished section is going work. By default in Pages database, you see a record listing (which uses the Listing templates), you click through to a record which loads a new page to show it (which uses the Display templates). In our Release Notes database however, there's no page reload - the record is loaded dynamically via AJAX. This is achieved by mixing both the Listing and Display templates. The wrapper of the page and the version list on the left is built using the Listing templates. However, when a record is clicked and loaded dynamically, that portion of the page is using the Display template: Now that the structure is clear, it's finally time to start editing templates! Tip Each template discussed below and in the next step includes a link to a diff report so that you can easily compare the changes we've made to the default template.   Listing header - categoryHeader template (See diff) The first template we'll work on is the category header. This shows the header of the category (which in our case is actually the header of the whole section, since the user doesn't leave this page while using the database). The categoryHeader template handles the title, the follow button, the Add Record button and more. Here's the final code for this template bit, which I'll explain below.  Spoiler {{if !\IPS\Request::i()->advancedSearchForm}} <div class="ipsType_center ipsSpacer_bottom ipsSpacer_top"> <h1 class="ipsType_veryLarge ipsType_reset">{$category->_title}</h1> <div class="ipsType_richText ipsType_large ipsType_light ipsSpacer_bottom"> {$category->_description|raw} </div> <div class='ipsResponsive_noFloat ipsResponsive_hidePhone'> {template="follow" app="core" group="global" params="'cms','categories' . $category->database_id, $category->_id, \IPS\cms\Records::containerFollowerCount( $category )"} </div> </div> {{endif}} {{if $category->hasChildren() AND ! isset( \IPS\Request::i()->advancedSearchForm )}} <div class="ipsBox ipsSpacer_bottom"> <h2 class='ipsType_sectionTitle ipsType_reset'>{lang="content_subcategories_title"}</h2> <ol class="ipsDataList"> {{foreach $category->children() as $cat}} {template="categoryRow" group="category_index" location="database" app="cms" params="$cat"} {{endforeach}} </ol> </div> {{endif}} {{if $category->can('add')}} {{if ! \IPS\Request::i()->isAjax() AND ! isset( \IPS\Request::i()->advancedSearchForm ) AND $category->show_records}} <ul class="ipsToolList ipsToolList_horizontal ipsClearfix ipsSpacer_both ipsResponsive_hidePhone"> <li class='ipsToolList_primaryAction'> <a class="ipsButton ipsButton_medium ipsButton_important ipsButton_fullWidth" href="{$category->url()->setQueryString( array( 'do' => 'form', 'd' => \IPS\cms\Databases\Dispatcher::i()->databaseId ) )}">{lang="cms_add_new_record_button" sprintf="\IPS\cms\Databases::load( $category->database_id )->recordWord( 1 )"}</a> </li> </ul> {{endif}} {{endif}} {{if count( $activeFilters ) AND ! isset( \IPS\Request::i()->advancedSearchForm )}} {template="filterMessage" app="cms" location="database" group="release_notes" params="$activeFilters, $category"} {{endif}}   This template is pretty similar to the default. The key things that have changed: Positioning/styling of the title, description and follow button I have removed the list item that contained the 'mark as read' link, since we don't use this functionality I have wrapped the tool list bar in {{if $category->can('add')}} since only those with permission to add records need to see these elements.   Listing table - categoryTable template (See diff) The categoryTable template is the 'meat' of the listing view; it generates the table into which record rows are displayed. This template is modified quite heavily from the default, in part due to a lot of unused code being removed. Note: templates often contain a lot of code that is used when a certain option is enabled for the database. This code is wrapped in a logic check so it's only displayed if that option is enabled. It is my preference to remove these unused portions of code when I'm certain I don't need the feature they display; it makes the template more succinct and easier to read. Here's the final code for this template bit: Spoiler <div class='ipsAreaBackground ipsPad_half' data-baseurl='{$table->baseUrl}' data-resort='{$table->resortKey}' data-controller='core.global.core.table{{if $table->canModerate()}},core.front.core.moderation{{endif}}'> <div class='ipsAreaBackground_reset ipsColumns ipsColumns_collapsePhone' data-controller='pages.front.releaseNotes.main'> <div class='ipsColumn ipsColumn_wide ipsAreaBackground cReleaseColumn' data-role='releases'> {{if ! count($rows)}} <div class="ipsPad"> {lang="cms_no_records_to_show" sprintf="\IPS\cms\Databases::load( \IPS\cms\Databases\Dispatcher::i()->databaseId )->recordWord()"} </div> {{else}} <ol class='ipsDataList ipsDataList_zebra ipsClear cCmsListing {{foreach $table->classes as $class}}{$class} {{endforeach}}' id='elTable_{$table->uniqueId}' data-role="tableRows"> {template="$table->rowsTemplate[1]" params="$table, $headers, $rows" object="$table->rowsTemplate[0]"} </ol> {{endif}} {{if $table->pages > 1}} <div data-role="tablePagination"> {template="pagination" group="global" app="core" location="global" params="$table->baseUrl, $table->pages, $table->page, $table->limit"} </div> {{endif}} </div> <div class='ipsColumn ipsColumn_fluid'> <div data-role='releaseInfo' class='ipsPad_double'></div> </div> </div> </div>   Here's the key changes: There's some simple style changes to get the output to look how we want Note that on the wrapper element, we initialize a Javascript controller, pages.front.releaseNotes.main. This controller will be created in the Javascript file we set up earlier. A lot of code we don't need for our use is removed; the moderation tools and sorting/filter options. An empty div is added in the fluid column, with a data-role attribute. This is the div that will hold a record when it is loaded into the page.   Listing row - recordRow template (See diff) This template generates each row of the listing table. It's the first template where we're doing something special, so I'll cover those parts below. Here's the final code: Spoiler {{$rowIds = array();}} {{foreach $rows as $row}} {{$idField = $row::$databaseColumnId;}} {{$rowIds[] = $row->$idField;}} {{endforeach}} {{$iposted = ( $table AND method_exists( $table, 'container' ) AND $table->container() !== NULL ) ? $table->container()->contentPostedIn( null, $rowIds ) : array();}} {{foreach $rows as $row}} {{$idField = $row::$databaseColumnId;}} <li class="cCmsRecord_row {{if $row->hidden()}}ipsModerated{{endif}}" data-rowID='{$row->$idField}'> <a href='{$row->url()}' class='cRelease' data-releaseID='{$row->$idField}' {{if $row->fieldValues()['field_163']}}data-currentRelease{{endif}}> {$row->customFieldDisplayByKey('security-release', 'listing')|raw} <h3 class='ipsType_sectionHead ipsType_break'> {{if $row->_title}}{$row->_title}{{else}}<em class="ipsType_light">{lang="content_deleted"}</em>{{endif}} {$row->customFieldDisplayByKey('current-release', 'listing')|raw} {$row->customFieldDisplayByKey('beta-release', 'listing')|raw} </h3> {{if $row->isFutureDate() || $row->mapped('pinned') || $row->mapped('featured') || $row->hidden() === -1 || $row->hidden() === 1}} <span> {{if $row->isFutureDate()}} <span class="ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_warning" data-ipsTooltip title='{$row->futureDateBlurb()}'><i class='fa fa-clock-o'></i></span> {{elseif $row->hidden() === -1}} <span class="ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_warning" data-ipsTooltip title='{$row->hiddenBlurb()}'><i class='fa fa-eye-slash'></i></span> {{elseif $row->hidden() === 1}} <span class="ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_warning" data-ipsTooltip title='{lang="pending_approval"}'><i class='fa fa-warning'></i></span> {{endif}} {{if $row->mapped('pinned')}} <span class="ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_positive" data-ipsTooltip title='{lang="pinned"}'><i class='fa fa-thumb-tack'></i></span> {{endif}} {{if $row->mapped('featured')}} <span class="ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_positive" data-ipsTooltip title='{lang="featured"}'><i class='fa fa-star'></i></span> {{endif}} </span> {{endif}} {{if count( $row->customFieldsForDisplay('listing') )}} <div class='ipsDataItem_meta'> {{foreach $row->customFieldsForDisplay('listing') as $fieldId => $fieldValue}} {{if $fieldValue && $fieldId != 'current-release' && $fieldId != 'beta-release' && $fieldId != 'security-release'}} {$fieldValue|raw} {{endif}} {{endforeach}} </div> {{endif}} </a> </li> {{endforeach}}   As with the previous template, a lot of unused code has been removed. The first few lines are setup that also exist in the default. Following those, we go into the main loop which iterates over each of our rows and generates the HTML for each. The first part I want to highlight is the <a> element. Notice we've applied a custom classname which we'll use for styling. We've also added a data-releaseID attribute, the value of which is the ID of the record. This is important because we'll use it to load the correct record later when the user clicks this record in the listing.   Accessing raw field values On the same line, you'll see this: {{if $row->fieldValues()['field_163']}}data-currentRelease{{endif}} What's happening here is we are calling $row->fieldValues() (which returns an array of all the field values for the row), and using it to determine if field_163 is true. If it is, we mark this as our current release with a data attribute. You would be correct to assume then that field 163 is the current release field we set up. Unfortunately we have to refer to it by ID rather than key when using the fieldValues() method, which does complicate this check slightly. Note: In your own database, the current release field will have a different ID. Replace field_163 with whatever the field ID is in your setup.   Accessing formatted field output A few times within this template, you'll see a call like this: {$row->customFieldDisplayByKey('security-release', 'listing')|raw} This is returning the output of one of our custom fields (the security_release toggle in this case). It returns the generated HTML from our formatting options that we set up for the field. This is what makes custom formatters so useful - we can keep the output for the field neatly managed on the field edit screen, and simply insert it into our templates with the simple tag above. Notice we pass the listing value for the second parameter. This is indicating we want the listing format (if you recall, you can create formatting for both the listing and display templates).   
  4. About database templates In Pages, there are four kinds of basic database template: Listing The templates that show the list of records. If you use categories, this is the template used when you click into a category. if you don't use categories but do use a record listing, this is the template that forms the index/homepage of the database. Form A template that allows you to customize the add/edit form for records in your database. Display Templates used when displaying a record, including the comments & reviews portions. Category Index If you use categories, these categories generate the category listing. This forms the index/homepage of the database, but also renders any sub-categories once you click through. In addition, there's some other templates that are used when you use a database in its 'article' mode. You can create new versions of the above templates and assign them to your database. Each database can use a different set of templates, so it's possible to have very unique areas of your site, each different from the others. Templates are also extremely powerful. You have access to a large amount of data in them, and all of the usual IPS Community Suite template syntax is available including HTML logic, template tags and raw PHP (see the Template Syntax guide for more information). With some creativity, and knowledge of HTML and template syntax, you can create databases that don't at all feel like the usual 'list of records' approach.   Creating our database templates Since we don't use categories in our Release Notes database, we don't need to create a set of those templates. Similarly, we aren't going to be concerned with the record form template; the default will be fine for our use. That leaves us with the Listing and Display templates, and we'll create new sets so we can customize them without impacting any other databases. To create them, head over to Pages -> Templates. In the left-hand panel of the template editor, you can click to expand the existing database templates and get a feel for how they are structured. Click the New button in the editor, and then select New Database Template.  For the name, we'll enter ReleaseNotes so that we can easily identify it later. Ensure that Record Listing is selected as the template type. Conveniently, this popup window allow allows us to assign the templates to a database right now, so choose the Release Notes database for this field. Note: if you don't assign the template to the database here, you can always do so later by editing the database itself and selecting this template set under the Options tab. Click Save to create the template set. Repeat this process to create a Record Display type. Note that the name should still be ReleaseNotes - by using the same name, all of the template bits for both template types will exist in one group, which (in my opinion) makes it a little easier to use. You can create a separate group for each type if you prefer, however.   Exploring the templates If you now expand the Database Templates category in the editor, you'll see the two new sets we've created. If you expand those, you'll see a dozen or so template bits that make up the set. We won't be editing all of these in later steps; some won't be used in what we're doing, and others are fine as default. When later steps refer to editing a database template bit, this is where you'll go to do so.
  5. It is possible to use arbitrary PHP in your templates. Generally this is discouraged, but in some situations a simple statement can be of benefit to initialize a variable or aid debugging (for example). Note: templates also support a special expression template tag; consider using this tag in favor of arbitrary PHP. We cover the tag in a later step of this guide. To use PHP, you can enclose your statement in double-curly parenthesis, like so: {{$myVar = 0;}} Be sure to include the semi-colon so that your statement is valid. This syntax allows for one-line statements. If you have a larger block, each line must contain its own double-curly parenthesis. Note: templates use output buffering; attempting to echo, print_r or similar in the middle of a template is likely to cause errors. If you need to do this, we recommend following the statement with an {{exit;}} so that script execution ends immediately.
  6. Variables

    Each template bit can have variables passed into it by the backed PHP code, and these variables can be used by the template bit to control the display. Consult either the template editor or designer's mode guides (depending on your preference) to find out how to determine which variables are available to a template. As well as these local variables, you can access the various objects created by the IPS4 PHP framework.   Variables are escaped It's important to note that by default, all variable values are HTML-escaped when you output them in templates. This is for security, and ensures you don't inadvertently output some malicious HTML that is then processed by the browser and displayed. If a variable $value contained: <strong>Example</strong> Then outputting it in a template like so: Here's the variable value: {$value} Would actually send: Here's the variable value: &lt;strong&gt;Example&lt;/strong&gt; This is safe for the browser to display. Bypassing this protection Of course, in some situations, you want the raw HTML to be output, and not escaped. To do so, you can use the raw modifier on the variable: Here's the variable value: {$value|raw} Warning Using this modifier on untrusted content is a security risk. You should not output raw user-supplied HTML unless it has been properly sanitized and you are certain it is safe. Content that comes from IPS4's rich text editor is safe to output with this modifier.  
  7. Loops

    Standard PHP loop types are supported as HTML logic tags.   Foreach {{foreach [expression]}} ... {{endforeach}} Expression is anything PHP would support in a loop. The variables the loop defines are available within the body of the loop: <ul> {{foreach $arrayOfItems as $id => $item}} <li>{$id}: {$item}</li> {{endforeach}} </ul>   For {{for [expression]}} ... {{endfor}} For example: <ul> {{for $i = 0; $i < count( $arrayOfItems ); $i++}} <li>{$i}</li> {{endfor}} </ul>   Breaking and continuing If you need to break or continue a loop, you can use the relevant PHP statement to do so. For example, using break: {{foreach $arrayOfItems as $id => $item}} {{if $id > 15}} {{break;}} {{endif}} {{endforeach}}  
  8. As mentioned in the first step, any valid PHP expression can be used in HTML logic tags. You'll often just be checking if an expression is true or false: {{if $value}} ... {{endif}} ...but there are many other possibilities. You can also use normal PHP functions in your expressions. For example, you may need to determine if an array has any items, so PHP's count function is appropriate: {{if count( $items ) > 0}} ... {{endif}} Consult the full PHP documentation for the functions and language syntax.   Getting values from IPS4 You'll often need to compare values in the software in your expressions. For example, is a particular setting enabled, or does the current user have a member ID. While you can use the standard PHP approach to get these values, IPS4 contains a number of shortcut 'constants' you can use to simplify your logic. They are used like so: {{if settings.reputation_enabled}} ... {{endif}} Behind the scenes, this shortcut gets expanded to the PHP equivalent, meaning it gives you access to all of the available methods and properties of the object it translates to. The available shortcuts are:   request Translates to \IPS\Request::i(). Access to the request variables. e.g. {{if request.some_param}}   member Translates to \IPS\Member::loggedIn().  The member object for the current user. e.g. {{if member.language()->isrtl}}   settings Translates to \IPS\Settings::i(). Obtains system setting values (by setting key). e.g. {{if settings.auto_polling_enabled}}   output Translates to \IPS\Output::i(). The output object, containing the methods/properties the suite uses to output content. e.g. {{if count( output.contextualSearchOptions )}}   theme Translates to \IPS\Theme::i()->settings. Access to the theme settings available for the currently-used theme. e.g. {{if theme.sidebar_position == 'right'}}   cookie Translates to \IPS\\Request::i()->cookie. Access to the cookie object. e.g. {{if isset( cookie.hasJS )}}   Consult the PHP framework documentation for the full range of properties and methods available for each class. 
  9. if/elseif/else logic

    The most basic logic check is a simple if/else. This allows you to put HTML if a condition matches, or something else if it doesn't. The syntax is simply: {{if [expression]}} HTML to output if expression is true {{else}} HTML to output if expression was not true {{endif}}   There's also an elseif tag which allows you to specify other conditions to check if earlier conditions didn't match. {{if [expression]}} HTML to output if expression is true {{elseif [expression]}} HTML to output if expression is true {{else}} HTML to output if other expressions were not true {{endif}}  
  10. What problem does HTML logic solve? In the IPS Community Suite, our templates are the 'view', i.e. how the data is rendered as HTML to the screen. However, basic HTML has no logic capabilities; what is in the HTML file is what is sent to the browser. In a complex application like ours, we need some way to make decisions about what is output. These decisions could potentially be made in the PHP backend, but that isn't appropriate in most cases; the backend should be focused on processing the data, while the view (our templates) control how the data is displayed. HTML logic allows us to make these decisions inside our templates. It mixes standard HTML with some special tags and flow-control statements, most of which are very similar to PHP.   What other features do templates have? The result is that in one template, we can have logic that says output some HTML if a certain condition is met, or different HTML otherwise. We can also do loops on data, reducing repetition. We also have some special tags that call output plugins to transform values in some way (for example, to render dates from a timestamp).    Basic syntax There's three basic types of syntax you'll see. We will explore these in more detail in later steps. Logic tags Double-curly parenthesis. Controls flow in a template. In these tags, the expressions can be any valid PHP expression. Some examples: /* Basic structure */ {{if $condition}} ... {{else}} ... {{endif}} /* Examples of other expressions */ {{if !$condition}} {{if ( $color == 'green' && $size == 'big' ) || $condition}} {{if count( $value ) > 2}}   Variables Single-curly parenthesis. Outputs values passed into the template (or values from elsewhere, e.g. a loop). {$value}   Output plugins Pass the provided value through the specified output plugin. {pluginName="value"}  
  11. Managing emoticons

    The IPS Community Suite ships with a collection of our iconic emoticons, and from version 4.1 onwards, they are retina-compatible (meaning high-resolution versions are used on displays that support them). It's also possible to add new emoticons, either individually or as new sets, to customize your community. Emoticons are used either by clicking the emoticon button in the editor and clicking the emoticon you'd like to use, or by typing the short code, which is then converted to the emoticon on the fly.   Managing existing emoticons To manage your site's emoticons, navigate to Customization -> Editor -> Emoticons in the AdminCP. The screen you'll see will show you the currently-installed emoticons:   Underneath each emoticon is the code that users can type to use the emoticon; you can change this by editing the value and pressing enter (old posts will continue to show the emoticon correctly). To the right of the textbox is an HD indicator to let you know whether the emoticon has a high-res version. Users don't need to worry about this - they will automatically see the high-res version when compatible with their screen. An emoticon can be deleted by clicking the X in the corner. However, deleting an emoticon will remove the image from the server, meaning old posts that used it will show a broken image to the user. You can reorder emoticons simply by dragging and dropping them. You can also move them between sets in the same way. Finally, you can rename the emoticon set by clicking the  icon in the title bar. By default, there's only one set (named Default), but if you have several, the name helps to identify them when the user browses the emoticon list in the editor.   Adding new emoticons To add new emoticons to your site, click the Add Emoticons button in the header. You'll see the following:   Simply drag and drop your emoticon images to the upload field to attach them. You can also choose whether to create a new group or add them to an existing group. Once you click Save and the emoticons have finished uploading, you'll be returned to the overview screen. Emoticon codes will have been added for each emoticon based on the filename, but you can edit these now if you wish. Retina emoticons To add HD versions of your emoticons, a special naming format is used. Simply add @2x to the end of the filename, making sure the rest of filename matches that of the 'low res' version. For example, if you're uploading an emoticon named smile.png, the high-res version should be named smile@2x.png. The software will automatically combine these two images into one emoticon for you.
  12. A few global editor settings are available for you (as the administrator) to configure for your community. To edit them, navigate to Customization -> Editor -> Settings in the AdminCP. General settings Paste Behavior By default, in IPS 4.1 onwards, when a user pastes formatted content (that is, content copied from elsewhere that contains HTML formatting such as bold, italics etc.), a bar appears in the editor asking them if they would like to retain this formatting, or remove it and keep the plain text:   Some administrators prefer that no formatted content is posted, and the Paste Behavior setting allows you to control this.   Return Key Behavior By default, when the user presses the return key, a new paragraph is started (and there's there's a space between the new line, and the line above), in common with many modern web apps. However, some administrators prefer that when the return key is pressed, a simple line break occurs and the same paragraph continues (this is how IPB3 handled the return key, too). The Return Key Behavior setting allows you to control this.   Advanced settings When a post is submitted, the post parser strips out certain HTML tags, attributes and values that are potentially undesirable for security or visual reasons. However, in some cases, you may want to alter this behavior and allow certain values through. The Advanced tab allows you to add exceptions for: CSS classnames By default, other than certain 'known' CSS classnames that IPS4 needs for things such as quotes, classnames in elements will be stripped out. If you need to retain certain classnames for editor plugins or buttons, you can add them here. Javascript controllers IPS4's Javascript framework allows for controllers to be defined on elements using the data-controller attribute. These are stripped out when posted. If you have a block that requires some Javascript controller, you can add an exception to allow it here. Allowed iframe bases By default, iframes are usually stripped from posts. However, if you have a need to allow them, you can add URLs here. If the URL of an iframe matches one listed, it will be allowed through the parser.   Warning It goes almost without saying that you should be careful about what you add exceptions for on the Advanced tab. Allowing undesirable values through the post parser could lead to visual abnormalities, or worse, security issues.
  13. If an existing CKEditor plugin isn't available that meets your needs, another alternative that may be suitable is to create a custom button.   What can custom buttons do? Custom buttons allow you to create blocks of HTML that are inserted by clicking a button on the editor toolbar. The blocks you create can accept content that users can enter. Custom buttons don't have the capabilities of a full CKEditor plugin - they can't be dynamic or use Javascript, for example. But for formatting text the user enters, a custom button may be the best choice. Note: although custom buttons tend to be simple, we recommend you have knowledge of HTML, or seek assistance from our peer-to-peer forums.   Creating a custom button To create a custom button, navigate to Customization -> Editor -> Toolbars. Click the Add Button button in the header, and then the Custom tab. The form you see has the following fields: Button title The title seen by users when they hover over the button in the editor Icon A small image file that will act at the button icon on the editor. For retina support, upload an icon twice as big as you'd like it to display; it will be resized down by the browser and therefore show high-res. Type Three types of content are supported, and they roughly mimic the three types of element display in HTML: Inline - used when the inserted HTML exists somewhere in a line of text. Does not create a new line for the content. Single line block - designed for single lines (e.g. headers), and puts the block on a new line Block - used for multiple lines, and puts the block on a new line Use option A custom button can optionally accept a value from the user (aside from what they can type as normal inside the block itself). This option will appear as a popup dialog when the user clicks the button in the editor, and the value they enter is passed into the block when it is rendered. With this field enabled, you'll see an additional setting: Option label The text shown to the user requesting a value for the option. HTML The actual HTML the button will render in the editor when used. Generally, any HTML is supported but it must be valid. Within this HTML, a couple of special tags can be used: {option} If the option is used, this tag is replaced with the value the user entered, as-is. {content} If your button will allow the user to type within the generated HTML, insert this tag where the user should be able to type. Click the Save button to create the button. Your icon will be shown on the Buttons Not On Editor toolbar, and from here you can drag it to your live toolbars and configure it as normal.   Using custom styles We don't recommend using inline styles in your HTML, because it will be almost impossible for you to update later (posts are built at save-time, not display-time, so if you update a custom button, old posts won't reflect those changes). Instead, we suggest using classnames, and adding styles for those classnames in your custom.css theme file. This way, you can update the styles later, and old posts will also reflect the changes.   An example Within this documentation we have tip boxes like this: Tip This is a tip This is a custom editor button we've created that is available to staff. Here's the configuration we used to create this button: Button title Tip Icon Type Block Use option No HTML <div class='docsBox docsBox_tip'> <div class='docsBox_header'>Tip</div> <div class='docsBox_body'> <div class='ipsType_richText ipsType_break ipsContained'> {content} </div> </div> </div>   We then add these styles to our custom.css CSS file: .docsBox_header { padding: 5px 10px; color: #fff; font-weight: 500; font-size: 15px; } .docsBox_body { padding: 10px; font-size: 13px; line-height: 1.4; } .docsBox_tip .docsBox_header { background: #2E7D32; } .docsBox_tip .docsBox_body { background: #E8F5E9; } .docsBox_tip .docsBox_body .ipsType_richText { color: #1B5E20; }  
  14. Since the editor in IPS4 uses CKEditor, the full range of plugins is available for you to install. If there's an editor feature you wish you had, check out their plugin repository and see if a ready-made plugin is available. Note: Currently we only support plugins that add a button to the CKEditor toolbar. Please don't install plugins that add other kinds of functionality or they may cause problems for your community.   Installing a CKEditor plugin To start, navigate to Customization -> Editor -> Toolbars in the AdminCP. Click the Add Button button, and from the next screen select the CKEditor Plugin tab.  Note: The plugin you install needs to be compatible with the CKEditor version used in your current version of the IPS Community Suite. We display your current version on this form so that you can cross-check the compatibility with the CKEditor website. Next, choose the plugin zip file you downloaded, and then submit the form. If everything installed successfully, you will see the new button shown on the Buttons Not On Editor toolbar at the bottom of the management screen. You can now drag it to your active toolbars and manage it as normal.   If plugin installation fails Occasionally, installing a CKEditor plugin won't work. This might be because: It doesn't add a button We currently only support editor plugins that add a button to the toolbar; other kinds of plugin aren't supported. It doesn't support the installed version of CKEditor Ensure the plugin you want to use supports the version of CKEditor used in the IPS Community Suite version you have installed Your CKEditor directory doesn't have write permissions The directory /applications/core/interface/ckeditor/ckeditor/plugins needs to be writable (CHMOD 777) in order to install plugins
  15. Toolbars on the IPS4 editor are completely customizable - you can rearrange buttons or remove them entirely, and set permissions for which group can use which buttons. To manage the editor toolbars, navigate to Customization -> Editor -> Toolbars in the AdminCP. On the screen you'll see a dummy editor showing your current button configuration for desktop view.      Responsive support IPS4 has a responsive theme, which means the same theme works on desktops, tablets and phones, and adjusts what it displays based on the available space. Naturally, on a desktop monitor you might want to show a full range on editing options, while on a phone you might just show the essential formatting choices. We facilitate this by offering three editor configurations that you can independently change, ensuring the appropriate buttons are shown depending on device. The tabs shown allow you to configure these sizes. Large = Desktop Medium = Tablet/Small Desktop Small = Phone   Reordering editor buttons On the main screen, you see a dummy editor that shows you your current button configuration. At the bottom of the page is another toolbar that shows you the available buttons that you are not currently using. Drag and drop buttons between these two locations to add or remove them from your toolbars. Changes you make are available instantly to users.   Changing button permissions To restrict areas in which a button is available or to whom it is available to, click the button you want to edit on the toolbar. You'll see a simple form: By default, buttons are available to "Everyone" and "Everywhere", but by toggling the checkboxes and selecting either groups or areas, you can change this here. Note: some buttons, such as bold, italics and underline, have old-style BBCode equivalents. Removing the buttons from the toolbar does not prevent these BBCodes from being used in content.   Adding toolbars & separators You can also add new toolbars if you find the single toolbar doesn't offer enough space, and new separators if you want to keep buttons grouped by type. Simply click the buttons above the toolbar editor to add them. Separators can be dragged and dropped just like buttons once you have added them.   Reverting changes to your editor If you want to revert back to the configuration that we provide when you install the software, click the Restore Default Configuration button at the top of the page. This resets all positioning and permissions on all editor sizes, not just the one you are currently viewing.