How to Add the Tagging Function to CMS Made Simple Modules Part 1

Tagging is a common and well known feature in Blog Software like WordPress. But unfortunately, it is missing in CMS Made Simple. In this article, I will show you how to add the feature into any module. You can see it in action here.

To understand how it works, I take CGBlog as our desired module in this case. I split the tutorial in two parts because we need new functions in the backend to let the author enter some keywords (tags) and we need the frontend management of the tags. Let’s start with the backend configuration.

Install CGBlog and dependencies first. Then go to CGBlog and add a new “Field Definition”.

Now, our new oneline textinput field will appear when we write new posts. Next, download jQuery Tagit from github. Extract the files and upload them to the “uploads” folder:


Then add a new folder “module_custom” and a new file called editarticle.tpl (it’s just a duplicate from the original file in modules/CGBlog/templates/)

Insert the following code into that file:

{assign var="tagfield_id" value="1"}
{assign var="tagfield_name" value="Tags"}

<link href="{uploads_url}/tags/css/tagit-awesome-orange.css" rel="stylesheet" type="text/css" />
<style type="text/css">
{literal}
ul.tagit input[type="text"] {
	background:none;
    -moz-box-sizing: border-box;
    border: none !important;
    margin: 0 !important;
    padding: 0 !important;
    width: inherit !important;
    outline: none;
    font-size:12px;
}
div.pagecontainer ul.tagit {
    cursor: text;
    overflow: auto;
    width: 97%;
    padding: 4px;
    background: none repeat scroll 0 0 #F0F0F0;
    border: 1px solid #CCCCCC;
    border-radius: 5px 5px 5px 5px;
    font-size: 1.2em;
    margin-right: 5px;
}

ul.tagit li.tagit-choice {
	color:#fff;
    -moz-box-shadow: 0;
    -webkit-box-shadow: 0;
    box-shadow: 0;
    text-shadow: none;
    padding: 4px 20px 4px 7px;
    cursor:move;
}

ul.tagit li.tagit-choice a.tagit-close {
	color:#fff;
    -moz-box-shadow: 0;
    -webkit-box-shadow: 0;
    box-shadow: 0;
    text-shadow: none;
}

div.pagecontainer .ui-autocomplete {
	padding:0;}
	
div.pagecontainer .ui-autocomplete .ui-menu-item {
	border:none;
	display:block;
	padding:0;
	margin:0;
	clear:both;
	float:none;}
	
div.pagecontainer .ui-autocomplete .ui-menu-item a {
	border:none;
	margin:0;
    border-top: 1px solid #E5E5E5;}
    
ul.tagit li.tagit-choice a.tagit-close {
	padding:0 2px 0 2px;
	display:inline-block;
	color:#f3cf91;
	border:1px solid #f3cf91}
    
ul.tagit li.tagit-choice a.tagit-close:hover {
	color:#fff;
	border-color:#fff;}
{/literal}
</style>
<script src="{uploads_url}/tags/js/tagit.js"></script>
<script type="text/javascript">
var action_id = '{$actionid}';
var article_id = '{$articleid|default:""}';
var ajax_url = '{$ajax_get_url}';
var manually_changed = 0;
var finished_setup = 0;
var ajax_xhr = 0;
var ajax_timeout;
ajax_url = ajax_url.replace(/amp;/g,'') + '&suppressoutput=1';

{literal}
function ajax_geturl()
{
  var form = $('#cgblog_editarticle form');
  var vtitle = $('#article_title').val();
  var vpostdate = form.form_get_datetime(action_id+'postdate_',1);
  ajax_xhr = $.post(ajax_url, { title: vtitle, postdate: vpostdate, articleid: article_id }, function(retdata){
    $('#article_url').val(retdata);
    ajax_xhr = 0;
  });
}

function on_change()
{
  if( manually_changed == 0 && finished_setup == 1)
  {
    // ajax function to get a unique url given a title.
    if( ajax_timeout != undefined ) clearTimeout(ajax_timeout);
    if( ajax_xhr = 0 ) xhr.abort();
    ajax_timeout = setTimeout(ajax_geturl,500);
  }
}

$(document).ready(function(){

  $('#article_url').keyup(function(){
    val = $(this).val();
    if( val == '' )
      {
        manually_changed = 0
      }
    else
      {
        manually_changed = 1;
      }
  });

  $('form').ajaxStart(function(){
    $('*').css('cursor','progress');
  });

  $('form').ajaxStop(function(){
    $('*').css('cursor','auto');
  });

  $('#sel_postdate').change(function(){
    on_change();
  });

  $('#article_title').keyup(function(){
    on_change();
  });

  finished_setup = 1;
  $("#tags").prev().hide();
  
	var availableTags = [{/literal}{cgblog_tagvalues item=$tagfield_id}{literal}];
	$('#tags').tagit({
		tagSource:availableTags,
		tagsChanged:showTags,
		sortable:true,
		highlightOnExistColor:"#000"
	}); 
});
function showTags(tags) {
	var tags = $('#tags').tagit('tags');
	var string = "";
	var taglength = tags.length;
	for (var i in tags){
		string += tags[i].label + ", "
	}
	$("#tags").prev().val(string);
}
</script>
{/literal}

<div id="cgblog_editarticle">
{$startform}
<div class="pageoverflow">
  <p class="pagetext">&nbsp;</p>
  <p class="pageinput">{$hidden}{$submit}{$cancel}{if isset($apply)}{$apply}{/if}</p>
</div>

{if isset($start_tab_headers)}
{$start_tab_headers}
{$tabheader_article}
{$tabheader_preview}
{$end_tab_headers}

{$start_tab_content}
{$start_tab_article}
{/if}

<div class="pageoverflow">
  <p class="pagetext">{$mod->Lang('url')}:</p>
  <p class="pageinput">
    <input id="article_url" type="text" name="{$actionid}url" value="{$url}" size="80" maxlength="255"/>
  </p>
</div>

{if isset($authortext)}
<div class="pageoverflow">
  <p class="pagetext">*{$authortext}:</p>
  <p class="pageinput">{$inputauthor}</p>
</div>
{/if}

<div class="pageoverflow">
  <p class="pagetext">*{$titletext}:</p>
  <p class="pageinput">
    <input type="text" id="article_title" name="{$actionid}title" value="{$title}" size="30" maxlength="255"/>
  </p>
</div>

<div class="pageoverflow" id="sel_postdate">
  <p class="pagetext">{$postdatetext}:</p>
  <p class="pageinput">{html_select_date prefix=$postdateprefix time=$postdate start_year="-10" end_year="+15"} {html_select_time prefix=$postdateprefix time=$postdate display_seconds=false}</p>
</div>

<div class="pageoverflow">
  <p class="pagetext">*{$categorytext}:</p>
  <p class="pageinput">
    {capture assign='tmp'}{$actionid}categories{/capture}
    {html_checkboxes name=$tmp options=$categorylist selected=$sel_categories separator="<br/>"}
  </p>
</div>

{if !isset($hide_summary_field) or $hide_summary_field == '0'}
<div class="pageoverflow">
  <p class="pagetext">{$summarytext}:</p>
  <p class="pageinput">{$inputsummary}</p>
</div>
{/if}

<div class="pageoverflow">
  <p class="pagetext">*{$contenttext}:</p>
  <p class="pageinput">{$inputcontent}</p>
</div>

<div class="pageoverflow">
  <p class="pagetext">{$extratext}:</p>
  <p class="pageinput">{$inputextra}</p>
</div>

{if isset($statustext)}
<div class="pageoverflow">
  <p class="pagetext">*{$statustext}:</p>
  <p class="pageinput">{$status}</p>
</div>
{else}
{$status}
{/if}

<div class="pageoverflow">
  <p class="pagetext">{$useexpirationtext}:</p>
  <p class="pageinput">{$inputexp}</p>
</div>

<div class="pageoverflow">
  <p class="pagetext">{$startdatetext}:</p>
  <p class="pageinput">{html_select_date prefix=$startdateprefix time=$startdate start_year="-10" end_year="+15"} {html_select_time prefix=$startdateprefix time=$startdate display_seconds=false}</p>
</div>

<div class="pageoverflow">
  <p class="pagetext">{$enddatetext}:</p>
  <p class="pageinput">{html_select_date prefix=$enddateprefix time=$enddate start_year="-10" end_year="+15"} {html_select_time prefix=$enddateprefix time=$enddate display_seconds=false}</p>
</div>

{if isset($custom_fields)}
{foreach from=$custom_fields item='field'}
{if $field->prompt == $tagfield_name}
<div class="pageoverflow">
	<p class="pagetext">{$field->prompt}</p>
	<div class="pageinput">
		{$field->field}
		<ul id="tags">
			<li>{$field->value}</li>
		</ul>
	</div>
</div>
<div class="pageoverflow">
	<p class="pagetext">Tag-Suggestion:</p>
	<p class="pageinput">{cgblog_tagvalues item=$tagfield_id}</p>
</div>
{else}
  <div class="pageoverflow">
    <p class="pagetext">{$field->prompt}</p>
    {if isset($field->thumb_name)}
      <p class="pagetext">
      {if isset($field->preview_name)}
        <a class="fancybox" rel="images" href="{$field->fileurl_base}/{$field->preview_name}">
          <img src="{$field->fileurl_base}/{$field->thumb_name}">
        </a>
      {elseif $field->value != ''}
        <a class="fancybox" rel="images" href="{$field->fileurl_base}/{$field->value}">
         <img src="{$field->fileurl_base}/{$field->thumb_name}">
        </a>
      {/if}
      </p>
    {/if}
    <p class="pageinput">{$field->field}</p>
  </div>
{/if}
{/foreach}
{/if}
{if isset($end_tab_article)}{$end_tab_article}{/if}

{if isset($start_tab_preview)}
{$start_tab_preview}
<script type="text/javascript">{literal}
jQuery(document).ready(function(){
  jQuery('[name=m1_apply]').live('click',function(){
    if( typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
    var data = jQuery('form').find('input:not([type=submit]), select, textarea').serializeArray();
    data.push({'name': 'm1_ajax', 'value': 1});
    data.push({'name': 'm1_apply', 'value': 1});
    data.push({'name': 'showtemplate', 'value': 'false'});
    var url = jQuery('form').attr('action');
    jQuery.post(url,data,function(resultdata,text){
      var resp = jQuery(resultdata).find('Response').text();
      var details = jQuery(resultdata).find('Details').text();
      var htmlShow = '';
      if( resp == 'Success' && details != '' )
      {
	 htmlShow = '<div class="pagemcontainer"><p class="pagemessage">'+details+'<\/p><\/div>';
      }
      else
      {
	 htmlShow = '<div class="pageerrorcontainer"><ul class="pageerror">';
	 htmlShow += details;
	 htmlShow += '<\/ul><\/div>';
      }
      jQuery('#editarticle_result').html(htmlShow);
    },'xml');
    return false;
  });

  function cgblog_dopreview()
  {
    if( typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
    var data = jQuery('form').find('input:not([type=submit]), select, textarea').serializeArray();
    data.push({'name': 'm1_ajax', 'value': 1});
    data.push({'name': 'm1_preview', 'value': 1});
    data.push({'name': 'showtemplate', 'value': 'false'});
    data.push({'name': 'm1_previewpage', 'value': jQuery('#preview_returnid').val()});
    data.push({'name': 'm1_detailtemplate', 'value': jQuery('#preview_template').val()});
    var url = jQuery('form').attr('action');
    jQuery.post(url,data,function(resultdata,text){
      var resp = jQuery(resultdata).find('Response').text();
      var details = jQuery(resultdata).find('Details').text();
      var htmlShow = '';
      if( resp == 'Success' && details != '' )
      {
	 // preview worked... now the details should contain the url
         details = details.replace(/amp;/g,'');
         jQuery('#previewframe').attr('src',details);
      }
      else
      {
	 if( details == '' ) details = 'An unknown error occurred';

	 // preview save did not work.
	 htmlShow = '<div class="pageerrorcontainer"><ul class="pageerror">';
	 htmlShow += details;
	 htmlShow += '<\/ul><\/div>';
         jQuery('#editarticle_result').html(htmlShow);
      }
    },'xml');
  }

  jQuery('#preview').click(function(){
    cgblog_dopreview();
    return false;
  });

  jQuery('#preview_returnid,#preview_template').change(function(){
    cgblog_dopreview();
    return false;
  });
});
{/literal}</script>

{* display a warning *}
<div class="pagewarning">{$warning_preview}</div>
<fieldset>
  <label for="preview_template">{$prompt_detail_template}:</label>&nbsp;
  <select id="preview_template" name="preview_template">
  {html_options options=$detail_templates selected=$cur_detail_template}
  </select>&nbsp;

  <label for="preview_returnid">{$prompt_detail_page}:</label>&nbsp;
  {$preview_returnid}
</fieldset>
<br/>
<iframe id="previewframe" style="height: 800px; width: 100%; border: 1px solid black; overflow: auto;" src=""></iframe>
{$end_tab_preview}
{$end_tab_content}
{/if}

<div class="pageoverflow">
  <p class="pagetext">&nbsp;</p>
  <p class="pageinput">{$hidden}{$submit}{$cancel}{if isset($apply)}{$apply}{/if}</p>
</div>
{$endform}
</div>{* #cgblog_editarticle *}

<div id="busy" style="display: none;"></div>

Modify the first two lines if you need to! To get the right field_id, simply hover over the custom fields in the module settings:

All in all, our file structure should look like this now:

Last step is to add a “User defined Tag”(UDT) in CMS Made Simple. Go to “Extensions”->”User Defined Tags” and add a new one called “cgblog_tagvalues” with the code:

global $gCms;
$db = $gCms->GetDb();
$tagfield = $params['item'];
$abfrage= "SELECT value FROM ".cms_db_prefix()."module_cgblog_fieldvals WHERE fielddef_id =". $tagfield;
$ergebnis= $db->Execute($abfrage);

$tags = array();
while($ergebnis && $eintrag = $ergebnis->FetchRow() )
{
$tags[] = strtolower($eintrag["value"]);
}

foreach($tags as $tag)
{
$value = $tag.", ".$value;
}
$value = substr($value,0,strlen($value)-2);
$value = str_replace(",,",",",$value);

$value = explode(",", $value);
$value = array_unique($value);
foreach($value as $tag)
{
if(trim($tag) != "")
{
$ausgabe = "'".addslashes(ucfirst(trim($tag)))."', ".$ausgabe;
}
}
$ausgabe = substr($ausgabe,0,strlen($ausgabe)-2);
echo $ausgabe;

Note: if you are using cmsms 1.11, then you should delete the UDT and use this plugin instead:
function.cgblog_tagvalues

  1. download it
  2. rename it to function.cgblog_tagvalues.php
  3. move it to /plugins/ folder

That is all! Now try to insert a few keywords! In the second part of the article, I will describe how to print out the keywords in the frontend and make them clickable.
Final Result:

Looking for quality CMS Made Simple Hosting? Look no further than Arvixe Web Hosting!

Tags: , , , , , , , , , , | Posted under CMS Made Simple | RSS 2.0

Author Spotlight

Nic Bug

I am a freelance web designer from Germany and CMS Made Simple is my every day tool. I have been using it since 2007 and I love it because it’s so easy to use. Even my clients understand and use it daily. I also have my own CMS Made Simple project called www.TemplateMadeSimple.com. There, you will find premium templates for made for CMS Made Simple.

9 Comments on How to Add the Tagging Function to CMS Made Simple Modules Part 1

  1. Michael says:

    This is really excellent Nic. Thank you so much for your detailed information. It seems to have worked first time!

    Have you done the instructions for adding the clickable keywords to the front end yet? I can’t wait :)

  2. Georg says:

    Sehr Good!

  3. max says:

    Hello, man!
    One question.
    In your post i see this line
    SELECT value FROM “.cms_db_prefix().”module_cgblog_fieldvals WHERE fielddef_id

    but i not have this table in my cms db. How i can get a install sql file or listing.

    Thx, man

  4. max says:

    Oh, sorry i’m find them. But on edit blog post screen i have a “white” screen. May be version mistmatch ? I’m use CMS Made Simple™ 1.11.2 “Isabela”

  5. Nic Bug says:

    ive updated the entry and you will find a plugin below the UDT now! have fun.

  6. GJ says:

    Works in 1.11.5 :)

    One thing though. After entering tags and submitting the cgblog article, in the database you find an extra space and comma after the last tag. In the representation this is translated to a tag with no text.

    Do you have any clue on how this cna be solved?

Leave a Reply

Your email address will not be published. Required fields are marked *


4 + = 6

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>