3
\$\begingroup\$

I have been given a requirement to create a symbol toolbar, a toolbar where each button represents a unique symbol.

This is how the toolbar would work:

  1. A user clicks inside a text field and enters data.

  2. The user would then click a symbol on the toolbar and it will be appended to that specific text field.

  3. The browser will put focus back on that text field.

I have created this toolbar, but I really dislike my implementation because it feels really hacky. In my implementation, I created two functions, addSymbol and setLastFocused in my controller:

$scope.addSymbol = function (field)
{
    if (!$scope.product[$scope.lastFocusedModelKey])
    {
        $scope.product[$scope.lastFocusedModelKey] = "";
    }

    $scope.product[$scope.lastFocusedModelKey] = $scope.product[$scope.lastFocusedModelKey] + field.symbol.SymbolCharacter;

    $('#' + $scope.lastFocusedModelKey).focus();
}

$scope.setLastFocused = function (lastFocusedModelKey)
{
    $scope.lastFocusedModelKey = lastFocusedModelKey;
}

SetLastFocused is called every time a user puts focus on a text field. I pass in a string of the field name like this:

<input type="text" id="Foo" name="FooName" ng-model="product.Foo" class="form-control input-xs" ng-focus="setLastFocused('Foo')">

AddSymbol accepts the button field as a parameter, and appends the symbol to the model that is bound to the text field, and then re-focuses on the text field.

I feel like this is hacky because:

  1. I have to use jQuery to re-focus on the text field.

  2. I'm passing in a hardcoded string representing the field name (meaning if I change the name of the field and forget to change the hardcoded string, then this symbol feature will break for that specific text field).

This works fine now with a few text fields in which the users may want to append symbols, but now I'm learning I need to add the functionality to append symbols to text fields inside a whole Angular Ui-Grid (which exists inside a separate controller). So I am realizing I need to refactor this if I want this to work with a Ui-Grid as well. I know I need to move this code to a separate service, but I would also like to make this cleaner. I'm just not really sure how.

How can I make this code follow best Angular practices?

\$\endgroup\$
2
\$\begingroup\$

With custom directives you could save the focus in a $scope.focusTarget variable and the resetting of the focus can be done with a focus broadcast that have the instanceId (=focusTarget e.g. 1, 2, ...) and the new icon class so each directive called iconInput will receive the broadcast and check if the passed ID is matching. If it matches it applies the focus and sets the new icon class.

The ID is generated with a counter that is incremented at every controller run of the iconInput directive.

Please have a look at the code below (then it's more clear how it works, it's a bit difficult to describe) or in this jsfiddle.

The first focus is only working in jsfiddle if you open the fiddle in full screen mode. Not sure what's wrong.

angular.module('demoApp', [])
    .directive('iconbar', IconbarDirective)
    .directive('iconInput', IconInputDirective)
	.factory('focus', FocusFactory)
    .controller('mainController', MainController);

function IconbarDirective(focus) {
    return {
        restrict: 'EA',
        transclude: true,
        scope: {
            focusTarget: '@'
        },
        template: '<div class="iconbar" ng-transclude></div>',
        link: function (scope, element) {
            var icons = element.find('i');
            console.log(icons);
            icons.addClass('iconbar-icon');
            element.find('i').bind('click', function (evt) {
                console.log('clicked', evt.target, this);
                var icon = angular.element(this),
                    iconClasses = icon[0].className.split('ng-scope'), // third is ng-scope
                	target = scope.focusTarget;

                //console.log(iconClasses);
                //console.log(iconClasses[0]);
                focus('focus' + target, iconClasses[0]);
                scope.$apply();
            });
        }
    }
}

function IconInputDirective() {
    var instanceCounter = 0;
    return {
        restrict: 'EA',
        scope: {
            target: '='
        },
        template: '<div class="icon-input"><i class="icon-placeholder" ng-class="placeholder()"></i>' +
            '<input type="text" placeholder="enter text..."/>' +
            '</div>',
        controller: function ($scope) {
            instanceCounter++;
            this.instanceId = instanceCounter;
            console.log('instance no.', instanceCounter);
            $scope.placeholder = function() {
            	return $scope.icon || 'fa fa-bars icon-invisible';
            };
        },
        link: function (scope, element, attr, ctrl) {
            var inputBox = element.find('input');
            inputBox.bind('focus', function () {
                // clear other acitve classes
                element.parent().find('input').removeClass('active');
                inputBox.addClass('active');
                scope.target = ctrl.instanceId;
                scope.$apply();
            })
            /*   .bind('blur', function () {
                inputBox.removeClass('active');
            });*/
			
            // code for focusOn from here http://stackoverflow.com/questions/14833326/how-to-set-focus-on-input-field
            scope.$on('focusOn', function (e, name, iconClass) {
                console.log('focuson triggered', name, element[0]);
                if (name === 'focus' + ctrl.instanceId) {
                    console.log('focus now!!', element.find('input')[0]);
                    element.find('input')[0].focus();
                    inputBox.addClass('active');
                    scope.icon = iconClass;
                }
            });
        }
    }
}

function MainController($scope, focus) {
    $scope.focusTarget = 1; // last acitve input box --> sets first focus
    focus('focus' + $scope.focusTarget); // activate focus with-out class
}

function FocusFactory($rootScope, $timeout) {
    return function (name, iconClass) {
        $timeout(function () {
            $rootScope.$broadcast('focusOn', name, iconClass);
        });
    }
}
.iconbar-icon {
  cursor: pointer;
}
.icon-placeholder {
  width: 16px;
  padding: 5px;
}
[iconbar] {
  border: 1px solid;
  padding: 5px;
  margin-bottom: 5px;
}
.active {
  border: 2px solid blue;
}
i {
  padding-right: 5px;
}
.icon-invisible {
  visibility: hidden;
}
/* spacer not working */

.icon-spacer::before,
.icon-blank::before {
  width: 1em;
  content: ' ';
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="mainController">
  <div iconbar focus-target="{{focusTarget}}"> <i class="fa fa-bars"></i>
    <i class="fa fa-bus"></i>
    <i class="fa fa-bug"></i>
    <i class="fa fa-beer"></i>

  </div>
  <!--{{focusTarget}}-->
  <icon-input target="focusTarget"></icon-input>
  <icon-input target="focusTarget"></icon-input>
</div>

\$\endgroup\$
  • \$\begingroup\$ I appreciate your answer. While this is a nice implementation, I should have been more clearer in my question. I meant I wanted to append a symbol to the data within the text field, not make an icon appear next to the text field. By symbol I mean characters like © (the copyright symbol). However I'm sure it wouldn't be too difficult to change your code accordingly, correct? \$\endgroup\$ – Sam Parsons Aug 19 '15 at 2:29
  • \$\begingroup\$ Ah, OK, then it's a bit wrong with my symbol before the input. That wasn't clear. But adding the symbol to the text field data is also possible with my code. It took a while to add because I had troubles with ngModel formatters / parsers (see here). But now it is working. Here is an updated fiddle. \$\endgroup\$ – AWolf Aug 23 '15 at 0:16
  • \$\begingroup\$ That is closer, but I was wanting the symbol to be appended to the right of the text in the input, not to the left. And I should have the ability to continue to append more and more symbols if I continue to click the toolbar icon. Sorry lol, but really appreciate your help. \$\endgroup\$ – Sam Parsons Aug 23 '15 at 1:15
  • \$\begingroup\$ Nevermind, I figured it out. I just changed the one line appending the symbol to "scope.inputText = tempString + $sanitize(iconClass);" and that solved my problem. Thanks! \$\endgroup\$ – Sam Parsons Aug 23 '15 at 1:27

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.