1

I'm trying to create a custom form field type for my component. Functionally it should operate the same as the standard tag form field type, except I want to store the tags in my own database table (rather than the default #__tags table Joomla uses) and I want to force all entered tags to be unique so there are no duplicates in the database.

I've started out by creating a new class that extends JFormFieldTag but not really sure how to proceed from there.

Any help or guidance would be appreciated.

1
  • I'm inclined to vote this question as Too Broad, however Zachary has gone to great effort to try to answer this question for you. If the answer has sufficiently served to resolve your question, please mark the answer with the green tick. If you still require assistance with this old question, please update your question. All posted questions should progress to a system-recognized resolution. Thank you in advance for your good JSE citizenship in this matter. Commented Mar 12, 2019 at 1:54

2 Answers 2

2

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.

0

Just for the record: I was facing the same problem, but in the end it seemed to be much easier to simple create a multiple select form field and use Tokenize2 to create the tag input.

1
  • Welcome to JSE and thank you for contributing. Please take our tour. Commented Mar 24, 2021 at 9:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.