I have found the easiest way to do this is actually not to use the tag form field at all. I know that sounds crazy, but when you want to store the tags and tag associations in your own database table, it is easier to go a different route.
The best way is to use an SQL form field that allows multiples. You will set the SQL to pull from your database table containing the tags. Then, in your models getItem() method, retrieve the IDs of the tags associated with your record and store them as an array in the appropriate property for your object (e.g. $item->tags).
Next, in the template of your view, use some JavaScirpt stolen from /layouts/joomla/html/tag.php to make the field behave like the tags field. There are a couple of variables to replace (selector and minTermLength) but you should find that pretty simple. Here is the code:
jQuery(document).ready(function ($) {
var customTagPrefix = '#new#';
// Method to add tags pressing enter
$('" . $selector . "_chzn input').keyup(function(event) {
// Tag is greater than the minimum required chars and enter pressed
if (this.value && this.value.length >= " . $minTermLength . " && (event.which === 13 || event.which === 188)) {
// Search a highlighted result
var highlighted = $('" . $selector . "_chzn').find('li.active-result.highlighted').first();
// Add the highlighted option
if (event.which === 13 && highlighted.text() !== '')
{
// Extra check. If we have added a custom tag with this text remove it
var customOptionValue = customTagPrefix + highlighted.text();
$('" . $selector . " option').filter(function () { return $(this).val() == customOptionValue; }).remove();
// Select the highlighted result
var tagOption = $('" . $selector . " option').filter(function () { return $(this).html() == highlighted.text(); });
tagOption.attr('selected', 'selected');
}
// Add the custom tag option
else
{
var customTag = this.value;
// Extra check. Search if the custom tag already exists (typed faster than AJAX ready)
var tagOption = $('" . $selector . " option').filter(function () { return $(this).html() == customTag; });
if (tagOption.text() !== '')
{
tagOption.attr('selected', 'selected');
}
else
{
var option = $('<option>');
option.text(this.value).val(customTagPrefix + this.value);
option.attr('selected','selected');
// Append the option and repopulate the chosen field
$('" . $selector . "').append(option);
}
}
this.value = '';
$('" . $selector . "').trigger('liszt:updated');
event.preventDefault();
}
});
});
Lastly, go back to your model and modify the save() method to handle the tag inputs and store the data in your database. Something like this works nicely:
if(isset($data['tags'])){
// store the data locally
$tags = $data['tags'];
// remove the data from the array
unset($data['tags']);
// check whether the provided tag is new or old
$tagIds = array();
$newtags = array();
foreach($tags as $tag){
if(strpos($tag, '#') !== false){
// this is a new tag
$newtags[] = str_replace('#new#', '', strtolower($tag));
}else{
// this is an existing tag
$tagIds[] = (int)$tag;
}
}
// handle any new tags
if($newtags){
// create the new tag records
$query = $db->getQuery(true);
$query->insert($db->quoteName('#__my_component_tags'));
$query->columns($db->quoteName('tag'));
foreach($newtags as $tag){
$query->values($db->quote($tag));
}
$db->setQuery($query);
if($db->execute()){
// get the IDs of these tags
$query = $db->getQuery(true);
$query->select($db->quoteName('id'));
$query->from($db->quoteName('#__my_component_tags'));
$query->where($db->quoteName('tag') . ' IN(' . join(',', $db->quote($newtags)) . ')');
$db->setQuery($query);
if($newtagIds = $db->loadColumn()){
// combine the new IDs with the existing ones
$tagIds = array_merge($tagIds, $newtagIds);
}else{
$this->setError('Unable to retrieve IDs of newly created tags');
return false;
}
}else{
$this->setError('Unable to create new tags');
return false;
}
}
// associate this item with all of its selected tags, ignoring duplicates and removing
if($tagIds){
// craft the SQL value strings
$values = array();
foreach($tagIds as $tagId){
$values[] = '(' . (int)$tagId . ',' . (int)$table->id . ')';
}
// build and set the query
$db->setQuery('INSERT INTO ' . $db->quoteName('#__my_component_tag_map') . ' (' . $db->quoteName('tag') . ',' . $db->quoteName('my_record') . ') VALUES ' . join(',', $values) . ' ON DUPLICATE KEY UPDATE ' . $db->quoteName('my_record') . ' = ' . $db->quoteName('my_record') . ';');
if(!$db->execute()){
$this->setError('Unable to associate tags to landing page');
return false;
}
// remove all tags that are no longer associated with this record
$query = $db->getQuery(true);
$query->delete($db->quoteName('#__my_component_tag_map'));
$query->where($db->quoteName('tag') . ' NOT IN(' . join(',', $tagIds) . ')');
$query->where($db->quoteName('my_record') . '=' . (int)$table->id);
$db->setQuery($query);
if(!$db->execute()){
$this->setError('Unable to remove old tag associations');
return false;
}
}else{
// remove all tags
$query = $db->getQuery(true);
$query->delete($db->quoteName('#__my_component_tag_map'));
$query->where($db->quoteName('my_record') . '=' . (int)$table->id);
$db->setQuery($query);
if(!$db->execute()){
$this->setError('Unable to remove all old tag associations');
return false;
}
}
// delete any tags that no longer have an association
$db->setQuery('DELETE FROM ' . $db->quoteName('#__my_component_tags') . ' WHERE ' . $db->quoteName('id') . ' NOT IN(SELECT DISTINCT ' . $db->quoteName('tag') . ' FROM ' . $db->quoteName('#__my_component_tag_map') . ');');
if(!$db->execute()){
$this->setError('Unable to remove unassociated tags');
return false;
}
}else{
// delete any paths that no longer have an association
$db->setQuery('DELETE FROM ' . $db->quoteName('#__my_component_tags') . ' WHERE ' . $db->quoteName('id') . ';');
if(!$db->execute()){
$this->setError('Unable to remove unassociated tags');
return false;
}
}
I hope this helps.