<!DOCTYPE html>
<html>

  <head>
    <link data-require="bootstrap-css@3.3.6" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <link rel="stylesheet" href="style.css" />
    <link rel="stylesheet" href="angular-advanced-searchbox.css" />
  </head>

  <body ng-app="myApp">
    <div ng-controller="testCtrl">
      <h1 ng-bind="hello"></h1>
      <nit-advanced-searchbox ng-model="searchParams" parameters="availableSearchParams" placeholder="Search..."></nit-advanced-searchbox>
      <pre><code>{{searchParams}}</code></pre>
      <a href="https://github.com/dnauck/angular-advanced-searchbox" target="blank">angular-advanced-searchbox on Github</a>
    </div>



    <script data-require="jquery@2.2.4" data-semver="2.2.4" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
    <script data-require="angularjs@1.5.8" data-semver="1.5.8" src="https://opensource.keycdn.com/angularjs/1.5.8/angular.min.js"></script>
    <script data-require="ui-bootstrap@2.2.0" data-semver="2.2.0" src="https://cdn.rawgit.com/angular-ui/bootstrap/gh-pages/ui-bootstrap-tpls-2.2.0.js"></script>
    <script data-require="angular-animate@1.5.8" data-semver="1.5.8" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular-animate.js"></script>
    <script src="app.js"></script>
    <script src="controller.js"></script>
    <script src="factory.js"></script>
    <script src="service.js"></script>
    <script src="angular-advanced-searchbox-tpls.js"></script>
  </body>

</html>
// Code goes here

/* Styles go here */

body {
  font-family: Arial;
  background-color: ivory;
}
angular.module('myApp', ['ui.bootstrap','angular-advanced-searchbox']);

var hellofunction = function(testFctry, testSrvc, $scope){
  $scope.hello = "Hello angular advanced searchbox!";
  $scope.msg_factory = testFctry.sayHello("Controller Param");
  $scope.msg_service = testSrvc.sayHello("Controller Param");
  
  
  $scope.availableSearchParams = [
          { 
            key: "name", 
            name: "Name", 
            placeholder: "Name..." },
          { 
            key: "city", 
            name: "City", 
            placeholder: "City...", 
            restrictToSuggestedValues: true, 
            suggestedValues: ['Berlin', 'London', 'Paris'] },
          { 
            key: "country", 
            name: "Country", 
            placeholder: "Country..." },
          { 
            key: "emailAddress", 
            name: "E-Mail", 
            placeholder: "E-Mail...", 
            allowMultiple: true },
          { 
            key: "job", 
            name: "Job", 
            placeholder: "Job..." }
        ];
};

angular.module('myApp').controller('testCtrl', hellofunction);

var testFactoryFunction = function(){
  return {
    sayHello: function(text){
      return "Factory says \"Hello " + text + "\"";
    }
  };
};

angular.module('myApp').factory('testFctry', testFactoryFunction);
var testServiceFunction = function() {
  this.sayHello = function(text) {
    return "Service says \"Hello " + text + "\"";
  };
};

angular.module('myApp').service('testSrvc', testServiceFunction);
/*!
 * angular-advanced-searchbox
 * https://github.com/dnauck/angular-advanced-searchbox
 * Copyright (c) 2016 Nauck IT KG http://www.nauck-it.de/
 * Author: Daniel Nauck <d.nauck(at)nauck-it.de>
 * License: MIT
 */

(function() {

'use strict';

angular.module('angular-advanced-searchbox', [])
    .directive('nitAdvancedSearchbox', function() {
        return {
            restrict: 'E',
            scope: {
                model: '=ngModel',
                parameters: '=',
                parametersLabel: '@',
                parametersDisplayLimit: '=?',
                placeholder: '@',
                searchThrottleTime: '=?'
            },
            replace: true,
            templateUrl: function(element, attr) {
                return attr.templateUrl || 'angular-advanced-searchbox.html';
            },
            controller: [
                '$scope', '$attrs', '$element', '$timeout', '$filter', 'setFocusFor',
                function ($scope, $attrs, $element, $timeout, $filter, setFocusFor) {

                    $scope.parametersLabel = $scope.parametersLabel || 'Parameter Suggestions';
                    $scope.parametersDisplayLimit = $scope.parametersDisplayLimit || 8;
                    $scope.placeholder = $scope.placeholder || 'Search ...';
                    $scope.searchThrottleTime = $scope.searchThrottleTime || 1000;
                    $scope.searchParams = [];
                    $scope.searchQuery = '';
                    $scope.setFocusFor = setFocusFor;
                    var searchThrottleTimer;
                    var changeBuffer = [];

                    $scope.$watch('model', function (newValue, oldValue) {

                        if(angular.equals(newValue, oldValue))
                            return;

                        angular.forEach($scope.model, function (value, key) {
                            if (key === 'query' && $scope.searchQuery !== value) {
                                $scope.searchQuery = value;
                            } else {
                                var paramTemplate = $filter('filter')($scope.parameters, function (param) { return param.key === key; })[0];
                                var searchParams = $filter('filter')($scope.searchParams, function (param) { return param.key === key; });

                                if (paramTemplate !== undefined) {
                                    if (paramTemplate.allowMultiple) {
                                        // ensure array data structure
                                        if(!angular.isArray(value))
                                                value = [value];

                                        // for each value in the value array: check for adding a new parameter or update it's value
                                        value.forEach(function(val, valIndex) {
                                            if (searchParams.some(function (param) { return param.index === valIndex; })) {
                                                var param = searchParams.filter(function (param) {return param.index === valIndex; });
                                                if(param[0].value !== val)
                                                    param[0].value = val;
                                            } else {
                                                $scope.addSearchParam(paramTemplate, val, false);
                                            }
                                        });

                                        // check if there're more search parameters active then values and remove them
                                        if (value.length < searchParams.length) {
                                            for (var i = value.length; i < searchParams.length; i++) {
                                                $scope.removeSearchParam($scope.searchParams.indexOf(searchParams[i]));
                                            }
                                        }
                                    } else {
                                        if (searchParams.length === 0) {
                                            // add param if missing
                                            $scope.addSearchParam(paramTemplate, value, false);
                                        } else {
                                            // update value of parameter if not equal
                                            if(searchParams[0].value !== value)
                                                searchParams[0].value = value;
                                        }
                                    }
                                }
                            }
                        });

                        // delete not existing search parameters from internal state array
                        for (var i = $scope.searchParams.length - 1; i >= 0; i--) {
                            var value = $scope.searchParams[i];
                            if (!$scope.model.hasOwnProperty(value.key)){
                                var index = $scope.searchParams.map(function(e) { return e.key; }).indexOf(value.key);
                                $scope.removeSearchParam(index);
                            }
                        }
                    }, true);

                    $scope.searchParamValueChanged = function (param) {
                        updateModel('change', param.key, param.index, param.value);
                    };

                    $scope.searchQueryChanged = function (query) {
                        // updateModel('change', 'query', 0, query);
                        // this line 'll cause a timeout and focus-lose
                        // @ToDo: fix it without commenting...
                    };

                    $scope.enterEditMode = function(e, index) {
                        if(e !== undefined)
                            e.stopPropagation();

                        if (index === undefined)
                            return;

                        var searchParam = $scope.searchParams[index];
                        searchParam.editMode = true;
                        setFocusFor('searchParam:' + searchParam.key);

                        $scope.$emit('advanced-searchbox:enteredEditMode', searchParam);
                    };

                    $scope.leaveEditMode = function(e, index) {
                        if (index === undefined)
                            return;

                        var searchParam = $scope.searchParams[index];
                        searchParam.editMode = false;

                        $scope.$emit('advanced-searchbox:leavedEditMode', searchParam);

                        // remove empty search params
                        if (!searchParam.value)
                            $scope.removeSearchParam(index);
                    };

                    $scope.searchQueryTypeaheadOnSelect = function (item, model, label) {
                        $scope.addSearchParam(item);
                        $scope.searchQuery = '';
                        updateModel('delete', 'query', 0);
                    };

                    $scope.searchParamTypeaheadOnSelect = function (suggestedValue, searchParam) {
                        searchParam.value = suggestedValue;
                        $scope.searchParamValueChanged(searchParam);
                    };

                    $scope.isUnsedParameter = function (value, index) {
                        return $filter('filter')($scope.searchParams, function (param) { return param.key === value.key && !param.allowMultiple; }).length === 0;
                    };

                    $scope.addSearchParam = function (searchParam, value, enterEditModel) {
                        if (enterEditModel === undefined)
                            enterEditModel = true;

                        if (!$scope.isUnsedParameter(searchParam))
                            return;

                        var internalIndex = 0;
                        if(searchParam.allowMultiple)
                            internalIndex = $filter('filter')($scope.searchParams, function (param) { return param.key === searchParam.key; }).length;

                        var newIndex =
                            $scope.searchParams.push(
                                {
                                    key: searchParam.key,
                                    name: searchParam.name,
                                    type: searchParam.type || 'text',
                                    placeholder: searchParam.placeholder,
                                    allowMultiple: searchParam.allowMultiple || false,
                                    suggestedValues: searchParam.suggestedValues || [],
                                    restrictToSuggestedValues: searchParam.restrictToSuggestedValues || false,
                                    index: internalIndex,
                                    value: value || ''
                                }
                            ) - 1;

                        updateModel('add', searchParam.key, internalIndex, value);

                        if (enterEditModel === true)
                            $timeout(function() { $scope.enterEditMode(undefined, newIndex); }, 100);

                        $scope.$emit('advanced-searchbox:addedSearchParam', searchParam);
                    };

                    $scope.removeSearchParam = function (index) {
                        if (index === undefined)
                            return;

                        var searchParam = $scope.searchParams[index];
                        $scope.searchParams.splice(index, 1);

                        // reassign internal index
                        if(searchParam.allowMultiple){
                            var paramsOfSameKey = $filter('filter')($scope.searchParams, function (param) { return param.key === searchParam.key; });

                            for (var i = 0; i < paramsOfSameKey.length; i++) {
                                paramsOfSameKey[i].index = i;
                            }
                        }

                        updateModel('delete', searchParam.key, searchParam.index);

                        $scope.$emit('advanced-searchbox:removedSearchParam', searchParam);
                    };

                    $scope.removeAll = function() {
                        $scope.searchParams.length = 0;
                        $scope.searchQuery = '';

                        $scope.model = {};

                        $scope.$emit('advanced-searchbox:removedAllSearchParam');
                    };

                    $scope.editPrevious = function(currentIndex) {
                        if (currentIndex !== undefined)
                            $scope.leaveEditMode(undefined, currentIndex);

                        if (currentIndex > 0) {
                            $scope.enterEditMode(undefined, currentIndex - 1);
                        } else if ($scope.searchParams.length > 0) {
                            $scope.enterEditMode(undefined, $scope.searchParams.length - 1);
                        } else if ($scope.searchParams.length === 0) {
                            // no search parameter available anymore
                            setFocusFor('searchbox');
                        }
                    };

                    $scope.editNext = function(currentIndex) {
                        if (currentIndex === undefined)
                            return;

                        $scope.leaveEditMode(undefined, currentIndex);

                        //TODO: check if index == array length - 1 -> what then?
                        if (currentIndex < $scope.searchParams.length - 1) {
                            $scope.enterEditMode(undefined, currentIndex + 1);
                        } else {
                            setFocusFor('searchbox');
                        }
                    };

                    $scope.keydown = function(e, searchParamIndex) {
                        var handledKeys = [8, 9, 13, 37, 39];
                        if (handledKeys.indexOf(e.which) === -1)
                            return;

                        var cursorPosition = getCurrentCaretPosition(e.target);

                        if (e.which == 8) { // backspace
                            if (cursorPosition === 0) {
                                e.preventDefault();
                                $scope.editPrevious(searchParamIndex);
                            }

                        } else if (e.which == 9) { // tab
                            if (e.shiftKey) {
                                e.preventDefault();
                                $scope.editPrevious(searchParamIndex);
                            } else {
                                e.preventDefault();
                                $scope.editNext(searchParamIndex);
                            }

                        } else if (e.which == 13) { // enter
                            $scope.editNext(searchParamIndex);

                        } else if (e.which == 37) { // left
                            if (cursorPosition === 0)
                                $scope.editPrevious(searchParamIndex);

                        } else if (e.which == 39) { // right
                            if (cursorPosition === e.target.value.length)
                                $scope.editNext(searchParamIndex);
                        }
                    };

                    function restoreModel() {
                        angular.forEach($scope.model, function (value, key) {
                            if (key === 'query') {
                                $scope.searchQuery = value;
                            } else {
                                var searchParam = $filter('filter')($scope.parameters, function (param) { return param.key === key; })[0];
                                if (searchParam !== undefined)
                                    $scope.addSearchParam(searchParam, value, false);
                            }
                        });
                    }

                    if ($scope.model === undefined) {
                        $scope.model = {};
                    } else {
                        restoreModel();
                    }

                    function updateModel(command, key, index, value) {
                        if (searchThrottleTimer)
                            $timeout.cancel(searchThrottleTimer);

                        // remove all previous entries to the same search key that was not handled yet
                        changeBuffer = $filter('filter')(changeBuffer, function (change) { return change.key !== key && change.index !== index; });
                        // add new change to list
                        changeBuffer.push({
                            command: command,
                            key: key,
                            index: index,
                            value: value
                        });

                        searchThrottleTimer = $timeout(function () {
                            angular.forEach(changeBuffer, function (change) {
                                var searchParam = $filter('filter')($scope.parameters, function (param) { return param.key === key; })[0];
                                if(searchParam && searchParam.allowMultiple){
                                    if(!angular.isArray($scope.model[change.key]))
                                        $scope.model[change.key] = [];

                                    if(change.command === 'delete'){
                                        $scope.model[change.key].splice(change.index, 1);
                                        if($scope.model[change.key].length === 0)
                                            delete $scope.model[change.key];
                                    } else {
                                        $scope.model[change.key][change.index] = change.value;
                                    }
                                } else {
                                    if(change.command === 'delete')
                                        delete $scope.model[change.key];
                                    else
                                        $scope.model[change.key] = change.value;
                                }
                            });

                            changeBuffer.length = 0;

                            $scope.$emit('advanced-searchbox:modelUpdated', $scope.model);

                        }, $scope.searchThrottleTime);
                    }

                    function getCurrentCaretPosition(input) {
                        if (!input)
                            return 0;

                        try {
                            // Firefox & co
                            if (typeof input.selectionStart === 'number') {
                                return input.selectionDirection === 'backward' ? input.selectionStart : input.selectionEnd;

                            } else if (document.selection) { // IE
                                input.focus();
                                var selection = document.selection.createRange();
                                var selectionLength = document.selection.createRange().text.length;
                                selection.moveStart('character', -input.value.length);
                                return selection.text.length - selectionLength;
                            }
                        } catch(err) {
                            // selectionStart is not supported by HTML 5 input type, so jut ignore it
                        }

                        return 0;
                    }
                }
            ]
        };
    })
    .directive('setFocusOn', [
        function() {
            return {
                restrict: 'A',
                link: function($scope, $element, $attrs) {
                    return $scope.$on('advanced-searchbox:setFocusOn', function(e, id) {
                        if (id === $attrs.setFocusOn) {
                            return $element[0].focus();
                        }
                    });
                }
            };
        }
    ])
    .factory('setFocusFor', [
        '$rootScope', '$timeout',
        function($rootScope, $timeout) {
            return function(id) {
                return $timeout(function() {
                    return $rootScope.$broadcast('advanced-searchbox:setFocusOn', id);
                });
            };
        }
    ])
    .directive('nitAutoSizeInput', [
        '$timeout',
        function($timeout) {
            return {
                restrict: 'A',
                scope: {
                    model: '=ngModel'
                },
                link: function($scope, $element, $attrs) {
                    var supportedInputTypes = ['text', 'search', 'tel', 'url', 'email', 'password', 'number'];


                    var container = angular.element('<div style="position: fixed; top: -9999px; left: 0px;"></div>');
                    var shadow = angular.element('<span style="white-space:pre;"></span>');

                    var maxWidth = $element.css('maxWidth') === 'none' ? $element.parent().innerWidth() : $element.css('maxWidth');
                    $element.css('maxWidth', maxWidth);

                    angular.forEach([
                        'fontSize', 'fontFamily', 'fontWeight', 'fontStyle',
                        'letterSpacing', 'textTransform', 'wordSpacing', 'textIndent',
                        'boxSizing', 'borderLeftWidth', 'borderRightWidth', 'borderLeftStyle', 'borderRightStyle',
                        'paddingLeft', 'paddingRight', 'marginLeft', 'marginRight'
                    ], function(css) {
                        shadow.css(css, $element.css(css));
                    });

                    angular.element('body').append(container.append(shadow));

                    function resize() {
                        $timeout(function() {
                            if(supportedInputTypes.indexOf($element[0].type || 'text') === -1)
                                return;

                            shadow.text($element.val() || $element.attr('placeholder'));
                            $element.css('width', shadow.outerWidth() + 10);
                        });
                    }

                    resize();

                    if ($scope.model) {
                        $scope.$watch('model', function() { resize(); });
                    } else {
                        $element.on('keypress keyup keydown focus input propertychange change', function() { resize(); });
                    }
                }
            };
        }
    ]);
})();

angular.module('angular-advanced-searchbox').run(['$templateCache', function($templateCache) {
  'use strict';

  $templateCache.put('angular-advanced-searchbox.html',
    "<div class=advancedSearchBox ng-class={active:focus} ng-init=\"focus = false\" ng-click=\"!focus ? setFocusFor('searchbox') : null\"><span ng-show=\"searchParams.length < 1 && searchQuery.length === 0\" class=\"search-icon glyphicon glyphicon-search\"></span> <a ng-href=\"\" ng-show=\"searchParams.length > 0 || searchQuery.length > 0\" ng-click=removeAll() role=button><span class=\"remove-all-icon glyphicon glyphicon-trash\"></span></a><div><div class=search-parameter ng-repeat=\"searchParam in searchParams\"><a ng-href=\"\" ng-click=removeSearchParam($index) role=button><span class=\"remove glyphicon glyphicon-trash\"></span></a><div class=key data-key={{searchParam.key}} ng-click=\"enterEditMode($event, $index)\">{{searchParam.name}}:</div><div class=value><span ng-show=!searchParam.editMode ng-click=\"enterEditMode($event, $index)\">{{searchParam.value}}</span> <input name=value type={{searchParam.type}} nit-auto-size-input set-focus-on=\"{{'searchParam:' + searchParam.key}}\" ng-keydown=\"keydown($event, $index)\" ng-blur=\"leaveEditMode($event, $index)\" ng-show=searchParam.editMode ng-change=\"searchParam.restrictToSuggestedValues !== true ? searchParamValueChanged(searchParam) : null\" ng-model=searchParam.value uib-typeahead=\"suggestedValue for suggestedValue in searchParam.suggestedValues | filter:$viewValue\" typeahead-min-length=0 typeahead-on-select=\"searchParamTypeaheadOnSelect($item, searchParam)\" typeahead-editable=\"searchParam.restrictToSuggestedValues !== true\" typeahead-select-on-exact=true typeahead-select-on-blur=\"searchParam.restrictToSuggestedValues !== true ? false : true\" placeholder=\"{{searchParam.placeholder}}\"></div></div><input name=searchbox class=search-parameter-input nit-auto-size-input set-focus-on=searchbox ng-keydown=keydown($event) placeholder={{placeholder}} ng-focus=\"focus = true\" ng-blur=\"focus = false\" uib-typeahead=\"parameter as parameter.name for parameter in parameters | filter:isUnsedParameter | filter:{name:$viewValue} | limitTo:parametersDisplayLimit\" typeahead-on-select=\"searchQueryTypeaheadOnSelect($item, $model, $label)\" ng-change=searchQueryChanged(searchQuery) ng-model=\"searchQuery\"></div><div class=search-parameter-suggestions ng-show=\"parameters && focus\"><span class=title>{{parametersLabel}}:</span> <span class=search-parameter ng-repeat=\"param in parameters | filter:isUnsedParameter | limitTo:parametersDisplayLimit\" data-key={{param.key}} ng-mousedown=addSearchParam(param)>{{param.name}} <i ng-class=\"{'glyphicon glyphicon-plus': param.allowMultiple}\"></i></span></div></div>"
  );

}]);
.advancedSearchBox {
    display: block;
    position: relative;
    margin: 5px 0 10px 0;
    border: 1px solid #ccc;
    border-radius: 4px;
    -webkit-border-radius: 4px;
    -moz-border-radius: 4px;
    min-height: 40px;
    padding: 0 9px;
    background-color: #fff;
    cursor: text;
    line-height: 38px;
}

    .advancedSearchBox.active {
        border-color: #66afe9;
    }

    .advancedSearchBox .search-icon {
        float: right;
        padding: 10px 0 0 10px;
    }

    .advancedSearchBox .remove-all-icon {
        float: right;
        padding: 10px 0 0 10px;
        cursor: pointer;
    }

    .advancedSearchBox .search-parameter {
        display: inline-block;
        height: 24px;
        margin: 0 7px 0 0;
        background-color: #5bc0de;
        padding: 0 5px;
        border-radius: 4px;
        -webkit-border-radius: 4px;
        -moz-border-radius: 4px;
        line-height: 24px;
        cursor: default;
        transition: box-shadow 100ms linear;
    }

        .advancedSearchBox .search-parameter:hover {
            box-shadow: 0 3px 3px rgba(0,0,0,0.2);
        }

        .advancedSearchBox .search-parameter div {
            float: left;
            margin: 0 2px;
        }

        .advancedSearchBox .search-parameter .remove {
            color: #fff;
            margin-left: 5px;
            cursor: pointer;
        }

        .advancedSearchBox .search-parameter .key {
            color: #fff;
        }

        .advancedSearchBox .search-parameter .value span {
            color: #fff;
        }

        .advancedSearchBox .search-parameter .value input {
            height: 24px;
        }

    .advancedSearchBox .search-parameter-input {
        display: inline-block;
        width: auto;
        height: 24px;
        border: 0;
        margin: 0;
        padding: 0;
    }

        .advancedSearchBox .search-parameter-input:focus {
            box-shadow: none;
            outline: none;
        }



    .advancedSearchBox .search-parameter-suggestions {
        cursor: auto;
        display: block;
    }

        .advancedSearchBox .search-parameter-suggestions .title {
            display: block;
            float: left;
            height: 25px;
            margin: 7px 7px 0 0;
            background-color: transparent;
            color: #888;
            font-weight: bold;
            padding: 0 5px;
            font-size: 14px;
            line-height: 25px;
        }

        .advancedSearchBox .search-parameter-suggestions .search-parameter {
            cursor: pointer;
            background-color: #bdbdbd;
            color: #fff;
}