0

I'm trying to build an Angular Tag Directive that functions not too dissimilarity from Stack Overflow's Tag input form. It uses a div with a border to create the illusion of a form, and then a div containing a list of tags to the left of an input field where you can type:

<div class="wrapper">
    <div class="tag-wrapper">
        <div class="tags" ng-repeat="tag in selectedTags">
            <div>
                [[ tag.name ]]
                <span class="remove" ng-click="removeTag(tag)"></span>
            </div>
        </div>
    </div>
    <input type="text" class="tag-input"
           ng-model="tagInput"
           ng-style="{ width: inputLength + 'px'}"
           ng-keypress="tagInputKeyPress($event)"
           ng-keyup="updateSuggestionList()"
           ng-focus="toggleSuggestionVisibility()"
           ng-blur="toggleSuggestionVisibility()" />
</div>

Please note I'm using [[]] as my interpolation provider for Angular because I have another templating engine already using {{}}.

When a key is pressed in the input, it runs a function to check if the key is a backspace or enter or a space to create/remove a tag:

$scope.tagInputKeyPress = function(event) {
    // Currently using jQuery.event.which to detect keypresses, keyCode is deprecated, use KeyboardEvent.key eventually:
    // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key

    // event.key == ' ' || event.key == 'Enter'
    if (event.which == 32 || event.which == 13) {
        event.preventDefault();

        // Remove any rulebreaking chars
        var tag = $scope.tagInput;
        tag = tag.replace(/["']/g, "");
        // Remove whitespace if present
        tag = tag.trim();

        $scope.createTag(tag);

    // event.key == 'Backspace'
    } else if (event.which == 8 && $scope.tagInput == "") {
        event.preventDefault();

        // grab the last tag to be inserted (if any) and put it back in the input
        if ($scope.selectedTags.length > 0) {
            $scope.tagInput = $scope.selectedTags.pop().name;
        }
    }
    $scope.inputLength = $(element).find('input.tag-input').parent().innerWidth() - $(element).find('.tag-wrapper').outerWidth() - 1;

    return true;
};

The bit I'm having problems with is the last line before the return statement. What it is meant to do is recompute the width of the .tag-wrapper element and adjust the width of the input element to suit via this ng-style property:

ng-style="{ width: inputLength + 'px'}"

However, as it currently stands, the input length is always one step behind the UI, causing the input to overflow. This is because:

  1. It firstly adds a new tag onto the selectedTags list.
  2. It computes the width of .tag-wrapper.
  3. It returns true letting the original key event pass through
  4. Angular's digest cycle runs and appends a new tag into the DOM. The width of .tag-wrapper is now bigger, and the input overflows.

Here is an example of this:

enter image description here

Right now, step 4 is happening after step 2. I need step 2 to happen after step 4.

How can I achieve this?

4

1 回答 1

1

You modify your createTag method to have a callback, and set the inputLength within the callback.

Alternative hack: wrap the setting of inputLength within a $timeout, to force the the line to be executed in the following digest call.

于 2015-08-29T23:32:29.593 回答