2
\$\begingroup\$

I have implemented some extra functionality for the tag-it JavaScript plugin, where I need an option where availableTags are displayed in one box, and the user can click these options to add them to the actual tag input (selection box is on the right, real input on the left):

enter image description here

The user can then click on the selected tags to move them back to the selection box and remove them from the input.

My implementation works fine, but I am pretty sure it can be greatly improved, perhaps by integrating it with a fork of the plugin (I am unsure of how to do this).

HTML

First function adds the selected tag to the input and styles it

<script type="text/javascript">
    function addSelectedColourToInput(colour) {
        // add tag to input
        $('#colourTagInput').tagit('createTag', colour);

        // get the newly created tag
        var colourTagLi = $('#colourTagInput').find('.tagit-label:contains("' + colour + '")').parent();
        processNewColourTag(colourTagLi, colour);
    }

    function processNewColourTag(tagLi, colour) {
        // remove from selection box
        $('#colourTagSelection').find('.tagit-label:contains("' + colour + '")').parent().remove();
        // style
        tagLi.css({ 'background': colour }).addClass('colour-tag');
        // click to remove
        tagLi.click(function () { $('#colourTagInput').tagit('removeTagByLabel', colour); });
    }

document.ready retrieves the available tags from a c# List (using Razor)

    $(document).ready(function () {

        // Set available colour tags
        var availableColourTags = [];
        @{
            foreach (var colourTag in MasterTags.Where(t => t.Type == "Colour"))
            {
                @:availableColourTags.push("@colourTag.Name");
            }
        }

Initialise tagit with my custom clickSelector option

        // after remove colour tag event
        var removeColour = function (colour) {
            $('#colourTagSelection')
                .append('<li onclick="addSelectedColourToInput(\''+colour+'\')" class="tagit-choice ui-widget-content ui-state-default ui-corner-all tagit-choice-read-only selector-tag colour-tag" style="background:' + colour + ';"><span class="tagit-label">' + colour + '</span></li>');
        };

        // tag-it init
        var colourTags = $('#colourTagInput');
        colourTags.tagit({
            availableTags: availableColourTags,
            singleField: true,
            singleFieldNode: $('#colourTagField'),
            clickSelector: $('#colourTagSelection'),

            afterTagRemoved: function (evt, ui) {
                removeColour(colourTags.tagit('tagLabel', ui.tag));
            }
        });

Prevents selected items resetting on refresh (this only seems to work in FireFox)

        // keep existing items on page refresh (only seems to work in Firefox)
        $('#colourTagInput li').each(function () {
            var colour = $(this).find('.tagit-label').text();
            if (colour == '') { return true; }
            processNewColourTag($(this), colour);
        });

Without this, the tag input value is not passed to the controller

        // Allow validation on hidden fields!
        $.validator.setDefaults({
            ignore: [],
        });
    });
</script>

The form input area:

<div class="form-group">
    @Html.LabelFor(model => model.ColourTags, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-5">
        @Html.HiddenFor(model => model.ColourTags, new  { @class = "form-control", @name = "tags", @id = "colourTagField" } )
        <ul id="colourTagInput" class="form-control"></ul>
        @Html.ValidationMessageFor(model => model.ColourTags, "", new { @class = "text-danger" })
    </div>

    <div class="col-md-5">
        <ul id="colourTagSelection" class="form-control tagit ui-widget ui-widget-content ui-corner-all"></ul>
    </div>
</div>

tag-it.js

added option:

clickSelector : null

in _create

// Add options to selection box
if (this.options.clickSelector != null) {
    var selectionBox = this.options.clickSelector;
    var availableTags = this.options.availableTags;

    for (i = 0; i < availableTags.length; i++) {
        selectionBox.append('<li onclick="addSelectedColourToInput(\'' + availableTags[i] +'\')" class="tagit-choice ui-widget-content ui-state-default ui-corner-all tagit-choice-read-only selector-tag colour-tag" style="background:' + availableTags[i] + ';"><span class="tagit-label">' + availableTags[i] + '</span></li>');
    }
}

I would really appreciate any tips or guidance, and let me know if I can add anything to the question!

\$\endgroup\$
1
\$\begingroup\$

After working on this for a while, I think I have improved it quite a bit in that it is now more generic, has a cleaner usage, and is entirely contained in its own file.

I still think it can be improved upon, so I am thinking I should ask a new question with the improved code.

Usage

in document.Ready

// Build Colour selector
initialiseTagSelector({
    tagArray: availableColourTags,
    readOnly: true,
    tagInput: $('#colourTagInput'),
    tagField: $('#colourTagField'),
    tagSelector: $('#colourTagSelection'),
    colourTags: true
});

HTML

<div class="form-group">
    @Html.LabelFor(model => model.ColourTags, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-5">
        @Html.HiddenFor(model => model.ColourTags, new { @class = "form-control", @name = "tags", @id = "colourTagField" })
        <ul id="colourTagInput" class="form-control"></ul>
        @Html.ValidationMessageFor(model => model.ColourTags, "", new { @class = "text-danger" })
    </div>

    <div class="col-md-5">
        <ul id="colourTagSelection"></ul>
    </div>
</div>

TagSelector.js

tagAdded

This method is called whenever a tag is added to either the selection area or the input area. It basically just finds the required tag with selectTag and then calls styleTag to add some styling (currently just sets the background colour for colour tags).

var tagAdded = function (options, tagArea, tagName) {
    var tagLi = selectTag(tagArea, tagName);
    styleTag(options, tagLi, tagName);
}

selectTag

returns the element for the given tag name in the given area. It would be great if thigs was not needed, but can't find a way of finding the tag using tag-it.

function selectTag(tagArea, tagName) {
    return tagArea.find('.tagit-label:contains("' + tagName + '")').parent();
}

styleTag

Styles the given tag (tagLi) based on the options

function styleTag(options, tagLi, tagName) {
    if (options.colourTags) { tagLi.css({ 'background': tagName }).addClass('colour-tag'); }
}

initialiseTagSelector

initialises our inputs. One tag area for selection, and one for the inputting through the form.

Once initialised, we add our tag selection to the selection area, ignoring any which already exist in the input area (this is to handle page refreshes).

function initialiseTagSelector(options) {
    options.tagInput.tagit({
        readOnly: options.readOnly,
        onlyAvailableTags: options.readOnly,
        availableTags: options.tagArray,
        singleField: true,
        singleFieldNode: options.tagField,
        afterTagAdded: function (evt, ui) {
            if (!ui.duringInitialization) {
                tagAdded(options, options.tagInput, options.tagInput.tagit('tagLabel', ui.tag));
            }
        },
        onTagClicked: function (evt, ui) {
            options.tagInput.tagit('removeTag', ui.tag);
        },
        afterTagRemoved: function (evt, ui) {
            options.tagSelector.tagit('createTag', options.tagSelector.tagit('tagLabel', ui.tag));
        }
    });

    options.tagSelector.tagit({
        readOnly: true,
        onlyAvailableTags: true,
        availableTags: options.tagArray,
        singleField: true,
        afterTagAdded: function (evt, ui) {
            if (!ui.duringInitialization) {
                tagAdded(options, options.tagSelector, options.tagSelector.tagit('tagLabel', ui.tag));
            }
        },
        onTagClicked: function (evt, ui) {
            options.tagSelector.tagit('removeTag', ui.tag);
        },
        afterTagRemoved: function (evt, ui) {
            options.tagInput.tagit('createTag', options.tagSelector.tagit('tagLabel', ui.tag));
        }
    });

    for (i = 0; i < options.tagArray.length; i++) {
        var tagName = options.tagArray[i];
        var tagLi = selectTag(options.tagInput, tagName);

        if (tagLi.length) {
            styleTag(options, tagLi, tagName);
        } else {
            options.tagSelector.tagit('createTag', tagName);
        }
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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