http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/falcon-ui/app/js/directives/ng-tags-input.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/ng-tags-input.js b/falcon-ui/app/js/directives/ng-tags-input.js new file mode 100644 index 0000000..1039011 --- /dev/null +++ b/falcon-ui/app/js/directives/ng-tags-input.js @@ -0,0 +1,1148 @@ +/*! + * ngTagsInput v2.3.0 + * http://mbenford.github.io/ngTagsInput + * + * Copyright (c) 2013-2015 Michael Benford + * License: MIT + * + * Generated at 2015-03-24 00:49:44 -0300 + */ +(function() { + 'use strict'; + + var KEYS = { + backspace: 8, + tab: 9, + enter: 13, + escape: 27, + space: 32, + up: 38, + down: 40, + left: 37, + right: 39, + delete: 46, + comma: 188 + }; + + var MAX_SAFE_INTEGER = 9007199254740991; + var SUPPORTED_INPUT_TYPES = ['text', 'email', 'url']; + + var tagsInput = angular.module('ngTagsInput', []); + + /** + * @ngdoc directive + * @name tagsInput + * @module ngTagsInput + * + * @description + * Renders an input box with tag editing support. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} [displayProperty=text] Property to be rendered as the tag label. + * @param {string=} [keyProperty=text] Property to be used as a unique identifier for the tag. + * @param {string=} [type=text] Type of the input element. Only 'text', 'email' and 'url' are supported values. + * @param {number=} tabindex Tab order of the control. + * @param {string=} [placeholder=Add a tag] Placeholder text for the control. + * @param {number=} [minLength=3] Minimum length for a new tag. + * @param {number=} [maxLength=MAX_SAFE_INTEGER] Maximum length allowed for a new tag. + * @param {number=} [minTags=0] Sets minTags validation error key if the number of tags added is less than minTags. + * @param {number=} [maxTags=MAX_SAFE_INTEGER] Sets maxTags validation error key if the number of tags added is greater than maxTags. + * @param {boolean=} [allowLeftoverText=false] Sets leftoverText validation error key if there is any leftover text in + * the input element when the directive loses focus. + * @param {string=} [removeTagSymbol=Ã] Symbol character for the remove tag button. + * @param {boolean=} [addOnEnter=true] Flag indicating that a new tag will be added on pressing the ENTER key. + * @param {boolean=} [addOnSpace=false] Flag indicating that a new tag will be added on pressing the SPACE key. + * @param {boolean=} [addOnComma=true] Flag indicating that a new tag will be added on pressing the COMMA key. + * @param {boolean=} [addOnBlur=true] Flag indicating that a new tag will be added when the input field loses focus. + * @param {boolean=} [addOnPaste=false] Flag indicating that the text pasted into the input field will be split into tags. + * @param {string=} [pasteSplitPattern=,] Regular expression used to split the pasted text into tags. + * @param {boolean=} [replaceSpacesWithDashes=true] Flag indicating that spaces will be replaced with dashes. + * @param {string=} [allowedTagsPattern=.+] Regular expression that determines whether a new tag is valid. + * @param {boolean=} [enableEditingLastTag=false] Flag indicating that the last tag will be moved back into + * the new tag input box instead of being removed when the backspace key + * is pressed and the input box is empty. + * @param {boolean=} [addFromAutocompleteOnly=false] Flag indicating that only tags coming from the autocomplete list will be allowed. + * When this flag is true, addOnEnter, addOnComma, addOnSpace, addOnBlur and + * allowLeftoverText values are ignored. + * @param {boolean=} [spellcheck=true] Flag indicating whether the browser's spellcheck is enabled for the input field or not. + * @param {expression} onTagAdding Expression to evaluate that will be invoked before adding a new tag. The new tag is available as $tag. This method must return either true or false. If false, the tag will not be added. + * @param {expression} onTagAdded Expression to evaluate upon adding a new tag. The new tag is available as $tag. + * @param {expression} onInvalidTag Expression to evaluate when a tag is invalid. The invalid tag is available as $tag. + * @param {expression} onTagRemoving Expression to evaluate that will be invoked before removing a tag. The tag is available as $tag. This method must return either true or false. If false, the tag will not be removed. + * @param {expression} onTagRemoved Expression to evaluate upon removing an existing tag. The removed tag is available as $tag. + */ + tagsInput.directive('tagsInput', ["$timeout","$document","$window","tagsInputConfig","tiUtil", function($timeout, $document, $window, tagsInputConfig, tiUtil) { + function TagList(options, events, onTagAdding, onTagRemoving) { + var self = {}, getTagText, setTagText, tagIsValid; + + getTagText = function(tag) { + return tiUtil.safeToString(tag[options.displayProperty]); + }; + + setTagText = function(tag, text) { + tag[options.displayProperty] = text; + }; + + tagIsValid = function(tag) { + var tagText = getTagText(tag); + + return tagText && + tagText.length >= options.minLength && + tagText.length <= options.maxLength && + options.allowedTagsPattern.test(tagText) && + !tiUtil.findInObjectArray(self.items, tag, options.keyProperty || options.displayProperty) && + onTagAdding({ $tag: tag }); + }; + + self.items = []; + + self.addText = function(text) { + var tag = {}; + setTagText(tag, text); + return self.add(tag); + }; + + self.add = function(tag) { + var tagText = getTagText(tag); + + if (options.replaceSpacesWithDashes) { + tagText = tiUtil.replaceSpacesWithDashes(tagText); + } + + setTagText(tag, tagText); + + if (tagIsValid(tag)) { + self.items.push(tag); + events.trigger('tag-added', { $tag: tag }); + } + else if (tagText) { + events.trigger('invalid-tag', { $tag: tag }); + } + + return tag; + }; + + self.remove = function(index) { + var tag = self.items[index]; + + if (onTagRemoving({ $tag: tag })) { + self.items.splice(index, 1); + self.clearSelection(); + events.trigger('tag-removed', { $tag: tag }); + return tag; + } + }; + + self.select = function(index) { + if (index < 0) { + index = self.items.length - 1; + } + else if (index >= self.items.length) { + index = 0; + } + + self.index = index; + self.selected = self.items[index]; + }; + + self.selectPrior = function() { + self.select(--self.index); + }; + + self.selectNext = function() { + self.select(++self.index); + }; + + self.removeSelected = function() { + return self.remove(self.index); + }; + + self.clearSelection = function() { + self.selected = null; + self.index = -1; + }; + + self.clearSelection(); + + return self; + } + + function validateType(type) { + return SUPPORTED_INPUT_TYPES.indexOf(type) !== -1; + } + + return { + restrict: 'E', + require: 'ngModel', + scope: { + tags: '=ngModel', + onTagAdding: '&', + onTagAdded: '&', + onInvalidTag: '&', + onTagRemoving: '&', + onTagRemoved: '&' + }, + replace: false, + transclude: true, + templateUrl: 'ngTagsInput/tags-input.html', + controller: ["$scope","$attrs","$element", function($scope, $attrs, $element) { + $scope.events = tiUtil.simplePubSub(); + + tagsInputConfig.load('tagsInput', $scope, $attrs, { + template: [String, 'ngTagsInput/tag-item.html'], + type: [String, 'text', validateType], + placeholder: [String, 'Add a tag'], + tabindex: [Number, null], + removeTagSymbol: [String, String.fromCharCode(215)], + replaceSpacesWithDashes: [Boolean, true], + minLength: [Number, 3], + maxLength: [Number, MAX_SAFE_INTEGER], + addOnEnter: [Boolean, true], + addOnSpace: [Boolean, false], + addOnComma: [Boolean, true], + addOnBlur: [Boolean, true], + addOnPaste: [Boolean, false], + pasteSplitPattern: [RegExp, /,/], + allowedTagsPattern: [RegExp, /.+/], + enableEditingLastTag: [Boolean, false], + minTags: [Number, 0], + maxTags: [Number, MAX_SAFE_INTEGER], + displayProperty: [String, 'text'], + keyProperty: [String, ''], + allowLeftoverText: [Boolean, false], + addFromAutocompleteOnly: [Boolean, false], + spellcheck: [Boolean, true] + }); + + $scope.tagList = new TagList($scope.options, $scope.events, + tiUtil.handleUndefinedResult($scope.onTagAdding, true), + tiUtil.handleUndefinedResult($scope.onTagRemoving, true)); + + this.registerAutocomplete = function() { + var input = $element.find('input'); + + return { + addTag: function(tag) { + return $scope.tagList.add(tag); + }, + focusInput: function() { + input[0].focus(); + }, + getTags: function() { + return $scope.tags; + }, + getCurrentTagText: function() { + return $scope.newTag.text; + }, + getOptions: function() { + return $scope.options; + }, + on: function(name, handler) { + $scope.events.on(name, handler); + return this; + } + }; + }; + + this.registerTagItem = function() { + return { + getOptions: function() { + return $scope.options; + }, + removeTag: function(index) { + if ($scope.disabled) { + return; + } + $scope.tagList.remove(index); + } + }; + }; + }], + link: function(scope, element, attrs, ngModelCtrl) { + var hotkeys = [KEYS.enter, KEYS.comma, KEYS.space, KEYS.backspace, KEYS.delete, KEYS.left, KEYS.right], + tagList = scope.tagList, + events = scope.events, + options = scope.options, + input = element.find('input'), + validationOptions = ['minTags', 'maxTags', 'allowLeftoverText'], + setElementValidity; + + setElementValidity = function() { + ngModelCtrl.$setValidity('maxTags', scope.tags.length <= options.maxTags); + ngModelCtrl.$setValidity('minTags', scope.tags.length >= options.minTags); + ngModelCtrl.$setValidity('leftoverText', scope.hasFocus || options.allowLeftoverText ? true : !scope.newTag.text); + }; + + ngModelCtrl.$isEmpty = function(value) { + return !value || !value.length; + }; + + scope.newTag = { + text: '', + invalid: null, + setText: function(value) { + this.text = value; + events.trigger('input-change', value); + } + }; + + scope.track = function(tag) { + return tag[options.keyProperty || options.displayProperty]; + }; + + scope.$watch('tags', function(value) { + scope.tags = tiUtil.makeObjectArray(value, options.displayProperty); + tagList.items = scope.tags; + }); + + scope.$watch('tags.length', function() { + setElementValidity(); + }); + + attrs.$observe('disabled', function(value) { + scope.disabled = value; + }); + + scope.eventHandlers = { + input: { + change: function(text) { + events.trigger('input-change', text); + }, + keydown: function($event) { + events.trigger('input-keydown', $event); + }, + focus: function() { + if (scope.hasFocus) { + return; + } + + scope.hasFocus = true; + events.trigger('input-focus'); + }, + blur: function() { + $timeout(function() { + var activeElement = $document.prop('activeElement'), + lostFocusToBrowserWindow = activeElement === input[0], + lostFocusToChildElement = element[0].contains(activeElement); + + if (lostFocusToBrowserWindow || !lostFocusToChildElement) { + scope.hasFocus = false; + events.trigger('input-blur'); + } + }); + }, + paste: function($event) { + $event.getTextData = function() { + var clipboardData = $event.clipboardData || ($event.originalEvent && $event.originalEvent.clipboardData); + return clipboardData ? clipboardData.getData('text/plain') : $window.clipboardData.getData('Text'); + }; + events.trigger('input-paste', $event); + } + }, + host: { + click: function() { + if (scope.disabled) { + return; + } + input[0].focus(); + } + } + }; + + events + .on('tag-added', scope.onTagAdded) + .on('invalid-tag', scope.onInvalidTag) + .on('tag-removed', scope.onTagRemoved) + .on('tag-added', function() { + scope.newTag.setText(''); + }) + .on('tag-added tag-removed', function() { + // Sets the element to its dirty state + // In Angular 1.3 this will be replaced with $setDirty. + ngModelCtrl.$setViewValue(scope.tags); + }) + .on('invalid-tag', function() { + scope.newTag.invalid = true; + }) + .on('option-change', function(e) { + if (validationOptions.indexOf(e.name) !== -1) { + setElementValidity(); + } + }) + .on('input-change', function() { + tagList.clearSelection(); + scope.newTag.invalid = null; + }) + .on('input-focus', function() { + element.triggerHandler('focus'); + ngModelCtrl.$setValidity('leftoverText', true); + }) + .on('input-blur', function() { + if (options.addOnBlur && !options.addFromAutocompleteOnly) { + tagList.addText(scope.newTag.text); + } + element.triggerHandler('blur'); + setElementValidity(); + }) + .on('input-keydown', function(event) { + var key = event.keyCode, + isModifier = event.shiftKey || event.altKey || event.ctrlKey || event.metaKey, + addKeys = {}, + shouldAdd, shouldRemove, shouldSelect, shouldEditLastTag; + + if (isModifier || hotkeys.indexOf(key) === -1) { + return; + } + + addKeys[KEYS.enter] = options.addOnEnter; + addKeys[KEYS.comma] = options.addOnComma; + addKeys[KEYS.space] = options.addOnSpace; + + shouldAdd = !options.addFromAutocompleteOnly && addKeys[key]; + shouldRemove = (key === KEYS.backspace || key === KEYS.delete) && tagList.selected; + shouldEditLastTag = key === KEYS.backspace && scope.newTag.text.length === 0 && options.enableEditingLastTag; + shouldSelect = (key === KEYS.backspace || key === KEYS.left || key === KEYS.right) && scope.newTag.text.length === 0 && !options.enableEditingLastTag; + + if (shouldAdd) { + tagList.addText(scope.newTag.text); + } + else if (shouldEditLastTag) { + var tag; + + tagList.selectPrior(); + tag = tagList.removeSelected(); + + if (tag) { + scope.newTag.setText(tag[options.displayProperty]); + } + } + else if (shouldRemove) { + tagList.removeSelected(); + } + else if (shouldSelect) { + if (key === KEYS.left || key === KEYS.backspace) { + tagList.selectPrior(); + } + else if (key === KEYS.right) { + tagList.selectNext(); + } + } + + if (shouldAdd || shouldSelect || shouldRemove || shouldEditLastTag) { + event.preventDefault(); + } + }) + .on('input-paste', function(event) { + if (options.addOnPaste) { + var data = event.getTextData(); + var tags = data.split(options.pasteSplitPattern); + + if (tags.length > 1) { + tags.forEach(function(tag) { + tagList.addText(tag); + }); + event.preventDefault(); + } + } + }); + } + }; + }]); + + + /** + * @ngdoc directive + * @name tiTagItem + * @module ngTagsInput + * + * @description + * Represents a tag item. Used internally by the tagsInput directive. + */ + tagsInput.directive('tiTagItem', ["tiUtil", function(tiUtil) { + return { + restrict: 'E', + require: '^tagsInput', + template: '<ng-include src="$$template"></ng-include>', + scope: { data: '=' }, + link: function(scope, element, attrs, tagsInputCtrl) { + var tagsInput = tagsInputCtrl.registerTagItem(), + options = tagsInput.getOptions(); + + scope.$$template = options.template; + scope.$$removeTagSymbol = options.removeTagSymbol; + + scope.$getDisplayText = function() { + var label = tiUtil.safeToString(scope.data[options.displayProperty]); + label = label.replace("Name:","").replace("Type:","").replace("Tag:",""); + return label; + }; + + scope.getDisplayLabel = function () { + var label = tiUtil.safeToString(scope.data[options.displayProperty]); + if(label.indexOf("Name:") !== -1){ + label = "Name:"; + }else if(label.indexOf("Type:") !== -1){ + label = "Type:"; + }else if(label.indexOf("Tag:") !== -1){ + label = "Tag:"; + }else{ + label = ""; + } + return label; + }; + + scope.$removeTag = function() { + tagsInput.removeTag(scope.$index); + }; + + scope.$watch('$parent.$index', function(value) { + scope.$index = value; + }); + } + }; + }]); + + + /** + * @ngdoc directive + * @name autoComplete + * @module ngTagsInput + * + * @description + * Provides autocomplete support for the tagsInput directive. + * + * @param {expression} source Expression to evaluate upon changing the input content. The input value is available as + * $query. The result of the expression must be a promise that eventually resolves to an + * array of strings. + * @param {string=} [displayProperty=text] Property to be rendered as the autocomplete label. + * @param {number=} [debounceDelay=100] Amount of time, in milliseconds, to wait before evaluating the expression in + * the source option after the last keystroke. + * @param {number=} [minLength=3] Minimum number of characters that must be entered before evaluating the expression + * in the source option. + * @param {boolean=} [highlightMatchedText=true] Flag indicating that the matched text will be highlighted in the + * suggestions list. + * @param {number=} [maxResultsToShow=10] Maximum number of results to be displayed at a time. + * @param {boolean=} [loadOnDownArrow=false] Flag indicating that the source option will be evaluated when the down arrow + * key is pressed and the suggestion list is closed. The current input value + * is available as $query. + * @param {boolean=} {loadOnEmpty=false} Flag indicating that the source option will be evaluated when the input content + * becomes empty. The $query variable will be passed to the expression as an empty string. + * @param {boolean=} {loadOnFocus=false} Flag indicating that the source option will be evaluated when the input element + * gains focus. The current input value is available as $query. + * @param {boolean=} [selectFirstMatch=true] Flag indicating that the first match will be automatically selected once + * the suggestion list is shown. + * @param {string=} [template=] URL or id of a custom template for rendering each element of the autocomplete list. + */ + tagsInput.directive('autoComplete', ["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil", function($document, $timeout, $sce, $q, tagsInputConfig, tiUtil) { + function SuggestionList(loadFn, options, events) { + var self = {}, getDifference, lastPromise, getTagId; + + getTagId = function() { + return options.tagsInput.keyProperty || options.tagsInput.displayProperty; + }; + + getDifference = function(array1, array2) { + return array1.filter(function(item) { + return !tiUtil.findInObjectArray(array2, item, getTagId(), function(a, b) { + if (options.tagsInput.replaceSpacesWithDashes) { + a = tiUtil.replaceSpacesWithDashes(a); + b = tiUtil.replaceSpacesWithDashes(b); + } + return tiUtil.defaultComparer(a, b); + }); + }); + }; + + self.reset = function() { + lastPromise = null; + + self.items = []; + self.visible = false; + self.index = -1; + self.selected = null; + self.query = null; + }; + self.show = function() { + if (options.selectFirstMatch) { + self.select(0); + } + else { + self.selected = null; + } + self.visible = true; + }; + self.load = tiUtil.debounce(function(query, tags) { + self.query = query; + + var promise = $q.when(loadFn({ $query: query })); + lastPromise = promise; + + promise.then(function(items) { + if (promise !== lastPromise) { + return; + } + + items = tiUtil.makeObjectArray(items.data || items, getTagId()); + items = getDifference(items, tags); + self.items = items.slice(0, options.maxResultsToShow); + + if (self.items.length > 0) { + self.show(); + } + else { + self.reset(); + } + }); + }, options.debounceDelay); + + self.selectNext = function() { + self.select(++self.index); + }; + self.selectPrior = function() { + self.select(--self.index); + }; + self.select = function(index) { + if (index < 0) { + index = self.items.length - 1; + } + else if (index >= self.items.length) { + index = 0; + } + self.index = index; + self.selected = self.items[index]; + events.trigger('suggestion-selected', index); + }; + + self.reset(); + + return self; + } + + function scrollToElement(root, index) { + var element = root.find('li').eq(index), + parent = element.parent(), + elementTop = element.prop('offsetTop'), + elementHeight = element.prop('offsetHeight'), + parentHeight = parent.prop('clientHeight'), + parentScrollTop = parent.prop('scrollTop'); + + if (elementTop < parentScrollTop) { + parent.prop('scrollTop', elementTop); + } + else if (elementTop + elementHeight > parentHeight + parentScrollTop) { + parent.prop('scrollTop', elementTop + elementHeight - parentHeight); + } + } + + return { + restrict: 'E', + require: '^tagsInput', + scope: { source: '&' }, + templateUrl: 'ngTagsInput/auto-complete.html', + controller: ["$scope","$element","$attrs", function($scope, $element, $attrs) { + $scope.events = tiUtil.simplePubSub(); + + tagsInputConfig.load('autoComplete', $scope, $attrs, { + template: [String, 'ngTagsInput/auto-complete-match.html'], + debounceDelay: [Number, 100], + minLength: [Number, 3], + highlightMatchedText: [Boolean, true], + maxResultsToShow: [Number, 10], + loadOnDownArrow: [Boolean, false], + loadOnEmpty: [Boolean, false], + loadOnFocus: [Boolean, false], + selectFirstMatch: [Boolean, true], + displayProperty: [String, ''] + }); + + $scope.suggestionList = new SuggestionList($scope.source, $scope.options, $scope.events); + + this.registerAutocompleteMatch = function() { + return { + getOptions: function() { + return $scope.options; + }, + getQuery: function() { + return $scope.suggestionList.query; + } + }; + }; + }], + link: function(scope, element, attrs, tagsInputCtrl) { + var hotkeys = [KEYS.enter, KEYS.tab, KEYS.escape, KEYS.up, KEYS.down], + suggestionList = scope.suggestionList, + tagsInput = tagsInputCtrl.registerAutocomplete(), + options = scope.options, + events = scope.events, + shouldLoadSuggestions; + + options.tagsInput = tagsInput.getOptions(); + + shouldLoadSuggestions = function(value) { + return value && value.length >= options.minLength || !value && options.loadOnEmpty; + }; + + scope.addSuggestionByIndex = function(index) { + suggestionList.select(index); + scope.addSuggestion(); + }; + + scope.addSuggestion = function() { + var added = false; + + if (suggestionList.selected) { + tagsInput.addTag(angular.copy(suggestionList.selected)); + suggestionList.reset(); + tagsInput.focusInput(); + + added = true; + } + return added; + }; + + scope.track = function(item) { + return item[options.tagsInput.keyProperty || options.tagsInput.displayProperty]; + }; + + tagsInput + .on('tag-added invalid-tag input-blur', function() { + suggestionList.reset(); + }) + .on('input-change', function(value) { + if (shouldLoadSuggestions(value)) { + suggestionList.load(value, tagsInput.getTags()); + } + else { + suggestionList.reset(); + } + }) + .on('input-focus', function() { + var value = tagsInput.getCurrentTagText(); + if (options.loadOnFocus && shouldLoadSuggestions(value)) { + suggestionList.load(value, tagsInput.getTags()); + } + }) + .on('input-keydown', function(event) { + var key = event.keyCode, + handled = false; + + if (hotkeys.indexOf(key) === -1) { + return; + } + + if (suggestionList.visible) { + + if (key === KEYS.down) { + suggestionList.selectNext(); + handled = true; + } + else if (key === KEYS.up) { + suggestionList.selectPrior(); + handled = true; + } + else if (key === KEYS.escape) { + suggestionList.reset(); + handled = true; + } + else if (key === KEYS.enter || key === KEYS.tab) { + handled = scope.addSuggestion(); + } + } + else { + if (key === KEYS.down && scope.options.loadOnDownArrow) { + suggestionList.load(tagsInput.getCurrentTagText(), tagsInput.getTags()); + handled = true; + } + } + + if (handled) { + event.preventDefault(); + event.stopImmediatePropagation(); + return false; + } + }); + + events.on('suggestion-selected', function(index) { + scrollToElement(element, index); + }); + } + }; + }]); + + + /** + * @ngdoc directive + * @name tiAutocompleteMatch + * @module ngTagsInput + * + * @description + * Represents an autocomplete match. Used internally by the autoComplete directive. + */ + tagsInput.directive('tiAutocompleteMatch', ["$sce","tiUtil", function($sce, tiUtil) { + return { + restrict: 'E', + require: '^autoComplete', + template: '<ng-include src="$$template"></ng-include>', + scope: { data: '=' }, + link: function(scope, element, attrs, autoCompleteCtrl) { + var autoComplete = autoCompleteCtrl.registerAutocompleteMatch(), + options = autoComplete.getOptions(); + + scope.$$template = options.template; + scope.$index = scope.$parent.$index; + + scope.$highlight = function(text) { + if (options.highlightMatchedText) { + text = tiUtil.safeHighlight(text, autoComplete.getQuery()); + } + return $sce.trustAsHtml(text); + }; + scope.$getDisplayText = function() { + return tiUtil.safeToString(scope.data[options.displayProperty || options.tagsInput.displayProperty]); + }; + } + }; + }]); + + + /** + * @ngdoc directive + * @name tiTranscludeAppend + * @module ngTagsInput + * + * @description + * Re-creates the old behavior of ng-transclude. Used internally by tagsInput directive. + */ + tagsInput.directive('tiTranscludeAppend', function() { + return function(scope, element, attrs, ctrl, transcludeFn) { + transcludeFn(function(clone) { + element.append(clone); + }); + }; + }); + + /** + * @ngdoc directive + * @name tiAutosize + * @module ngTagsInput + * + * @description + * Automatically sets the input's width so its content is always visible. Used internally by tagsInput directive. + */ + tagsInput.directive('tiAutosize', ["tagsInputConfig", function(tagsInputConfig) { + return { + restrict: 'A', + require: 'ngModel', + link: function(scope, element, attrs, ctrl) { + var threshold = tagsInputConfig.getTextAutosizeThreshold(), + span, resize; + + span = angular.element('<span class="input"></span>'); + span.css('display', 'none') + .css('visibility', 'hidden') + .css('width', 'auto') + .css('white-space', 'pre'); + + element.parent().append(span); + + resize = function(originalValue) { + var value = originalValue, width; + + if (angular.isString(value) && value.length === 0) { + value = attrs.placeholder; + } + + if (value) { + span.text(value); + span.css('display', ''); + width = span.prop('offsetWidth'); + span.css('display', 'none'); + } + + element.css('width', width ? width + threshold + 'px' : ''); + + return originalValue; + }; + + ctrl.$parsers.unshift(resize); + ctrl.$formatters.unshift(resize); + + attrs.$observe('placeholder', function(value) { + if (!ctrl.$modelValue) { + resize(value); + } + }); + } + }; + }]); + + /** + * @ngdoc directive + * @name tiBindAttrs + * @module ngTagsInput + * + * @description + * Binds attributes to expressions. Used internally by tagsInput directive. + */ + tagsInput.directive('tiBindAttrs', function() { + return function(scope, element, attrs) { + scope.$watch(attrs.tiBindAttrs, function(value) { + angular.forEach(value, function(value, key) { + attrs.$set(key, value); + }); + }, true); + }; + }); + + /** + * @ngdoc service + * @name tagsInputConfig + * @module ngTagsInput + * + * @description + * Sets global configuration settings for both tagsInput and autoComplete directives. It's also used internally to parse and + * initialize options from HTML attributes. + */ + tagsInput.provider('tagsInputConfig', function() { + var globalDefaults = {}, + interpolationStatus = {}, + autosizeThreshold = 3; + + /** + * @ngdoc method + * @name setDefaults + * @description Sets the default configuration option for a directive. + * @methodOf tagsInputConfig + * + * @param {string} directive Name of the directive to be configured. Must be either 'tagsInput' or 'autoComplete'. + * @param {object} defaults Object containing options and their values. + * + * @returns {object} The service itself for chaining purposes. + */ + this.setDefaults = function(directive, defaults) { + globalDefaults[directive] = defaults; + return this; + }; + + /*** + * @ngdoc method + * @name setActiveInterpolation + * @description Sets active interpolation for a set of options. + * @methodOf tagsInputConfig + * + * @param {string} directive Name of the directive to be configured. Must be either 'tagsInput' or 'autoComplete'. + * @param {object} options Object containing which options should have interpolation turned on at all times. + * + * @returns {object} The service itself for chaining purposes. + */ + this.setActiveInterpolation = function(directive, options) { + interpolationStatus[directive] = options; + return this; + }; + + /*** + * @ngdoc method + * @name setTextAutosizeThreshold + * @description Sets the threshold used by the tagsInput directive to re-size the inner input field element based on its contents. + * @methodOf tagsInputConfig + * + * @param {number} threshold Threshold value, in pixels. + * + * @returns {object} The service itself for chaining purposes. + */ + this.setTextAutosizeThreshold = function(threshold) { + autosizeThreshold = threshold; + return this; + }; + + this.$get = ["$interpolate", function($interpolate) { + var converters = {}; + converters[String] = function(value) { return value; }; + converters[Number] = function(value) { return parseInt(value, 10); }; + converters[Boolean] = function(value) { return value.toLowerCase() === 'true'; }; + converters[RegExp] = function(value) { return new RegExp(value); }; + + return { + load: function(directive, scope, attrs, options) { + var defaultValidator = function() { return true; }; + + scope.options = {}; + + angular.forEach(options, function(value, key) { + var type, localDefault, validator, converter, getDefault, updateValue; + + type = value[0]; + localDefault = value[1]; + validator = value[2] || defaultValidator; + converter = converters[type]; + + getDefault = function() { + var globalValue = globalDefaults[directive] && globalDefaults[directive][key]; + return angular.isDefined(globalValue) ? globalValue : localDefault; + }; + + updateValue = function(value) { + scope.options[key] = value && validator(value) ? converter(value) : getDefault(); + }; + + if (interpolationStatus[directive] && interpolationStatus[directive][key]) { + attrs.$observe(key, function(value) { + updateValue(value); + scope.events.trigger('option-change', { name: key, newValue: value }); + }); + } + else { + updateValue(attrs[key] && $interpolate(attrs[key])(scope.$parent)); + } + }); + }, + getTextAutosizeThreshold: function() { + return autosizeThreshold; + } + }; + }]; + }); + + + /*** + * @ngdoc factory + * @name tiUtil + * @module ngTagsInput + * + * @description + * Helper methods used internally by the directive. Should not be called directly from user code. + */ + tagsInput.factory('tiUtil', ["$timeout", function($timeout) { + var self = {}; + + self.debounce = function(fn, delay) { + var timeoutId; + return function() { + var args = arguments; + $timeout.cancel(timeoutId); + timeoutId = $timeout(function() { fn.apply(null, args); }, delay); + }; + }; + + self.makeObjectArray = function(array, key) { + array = array || []; + if (array.length > 0 && !angular.isObject(array[0])) { + array.forEach(function(item, index) { + array[index] = {}; + array[index][key] = item; + }); + } + return array; + }; + + self.findInObjectArray = function(array, obj, key, comparer) { + var item = null; + comparer = comparer || self.defaultComparer; + + array.some(function(element) { + if (comparer(element[key], obj[key])) { + item = element; + return true; + } + }); + + return item; + }; + + self.defaultComparer = function(a, b) { + // I'm aware of the internationalization issues regarding toLowerCase() + // but I couldn't come up with a better solution right now + return self.safeToString(a).toLowerCase() === self.safeToString(b).toLowerCase(); + }; + + self.safeHighlight = function(str, value) { + if (!value) { + return str; + } + + function escapeRegexChars(str) { + return str.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + str = self.encodeHTML(str); + value = self.encodeHTML(value); + + var expression = new RegExp('&[^;]+;|' + escapeRegexChars(value), 'gi'); + return str.replace(expression, function(match) { + return match.toLowerCase() === value.toLowerCase() ? '<em>' + match + '</em>' : match; + }); + }; + + self.safeToString = function(value) { + return angular.isUndefined(value) || value == null ? '' : value.toString().trim(); + }; + + self.encodeHTML = function(value) { + return self.safeToString(value) + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); + }; + + self.handleUndefinedResult = function(fn, valueIfUndefined) { + return function() { + var result = fn.apply(null, arguments); + return angular.isUndefined(result) ? valueIfUndefined : result; + }; + }; + + self.replaceSpacesWithDashes = function(str) { + return self.safeToString(str).replace(/\s/g, '-'); + }; + + self.simplePubSub = function() { + var events = {}; + return { + on: function(names, handler) { + names.split(' ').forEach(function(name) { + if (!events[name]) { + events[name] = []; + } + events[name].push(handler); + }); + return this; + }, + trigger: function(name, args) { + var handlers = events[name] || []; + handlers.every(function(handler) { + return self.handleUndefinedResult(handler, true)(args); + }); + return this; + } + }; + }; + + return self; + }]); + + /* HTML templates */ + tagsInput.run(["$templateCache", function($templateCache) { + $templateCache.put('ngTagsInput/tags-input.html', + "<div class=\"host\" tabindex=\"-1\" ng-click=\"eventHandlers.host.click()\" ti-transclude-append=\"\">" + + "<div class=\"tags\" ng-class=\"{focused: hasFocus}\"><ul class=\"tag-list\">" + + "<li class=\"tag-item\" ng-repeat=\"tag in tagList.items track by track(tag)\" ng-class=\"{ selected: tag == tagList.selected }\">" + + "<ti-tag-item data=\"tag\"></ti-tag-item></li></ul>" + + "<input autofocus class=\"input\" autocomplete=\"off\" ng-model=\"newTag.text\" ng-change=\"eventHandlers.input.change(newTag.text)\" ng-keydown=\"eventHandlers.input.keydown($event)\" ng-focus=\"eventHandlers.input.focus($event)\" ng-blur=\"eventHandlers.input.blur($event)\" ng-paste=\"eventHandlers.input.paste($event)\" ng-trim=\"false\" ng-class=\"{'invalid-tag': newTag.invalid}\" ng-disabled=\"disabled\" ti-bind-attrs=\"{type: options.type, placeholder: options.placeholder, tabindex: options.tabindex, spellcheck: options.spellcheck}\" ti-autosize=\"\"></div></div>" + ); + + $templateCache.put('ngTagsInput/tag-item.html', + "<strong ng-bind=\"getDisplayLabel()\"></strong> " + + "<span ng-bind=\"$getDisplayText()\"></span> " + + "<a class=\"remove-button\" ng-click=\"$removeTag()\" ng-bind=\"$$removeTagSymbol\"></a>" + ); + + $templateCache.put('ngTagsInput/auto-complete.html', + "<div class=\"autocomplete\" ng-if=\"suggestionList.visible\"><ul class=\"suggestion-list\"><li class=\"suggestion-item\" ng-repeat=\"item in suggestionList.items track by track(item)\" ng-class=\"{selected: item == suggestionList.selected}\" ng-click=\"addSuggestionByIndex($index)\" ng-mouseenter=\"suggestionList.select($index)\"><ti-autocomplete-match data=\"item\"></ti-autocomplete-match></li></ul></div>" + ); + + $templateCache.put('ngTagsInput/auto-complete-match.html', + "<span ng-bind-html=\"$highlight($getDisplayText())\"></span>" + ); + }]); + +}()); \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/falcon-ui/app/js/directives/server-messages.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/server-messages.js b/falcon-ui/app/js/directives/server-messages.js index e5aa58a..6bd6ea9 100644 --- a/falcon-ui/app/js/directives/server-messages.js +++ b/falcon-ui/app/js/directives/server-messages.js @@ -24,7 +24,12 @@ return { replace:false, restrict: 'E', - templateUrl: 'html/directives/serverMessagesDv.html' + templateUrl: 'html/directives/serverMessagesDv.html', + link: function (scope, element) { + + //scope.allMessages + + } }; }); http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/falcon-ui/app/js/directives/tooltip.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/tooltip.js b/falcon-ui/app/js/directives/tooltip.js new file mode 100644 index 0000000..31bb684 --- /dev/null +++ b/falcon-ui/app/js/directives/tooltip.js @@ -0,0 +1,37 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function () { + 'use strict'; + + var module = angular.module('tooltip', []); + + module.directive('toogle', function () { + return { + restrict: 'A', + link: function(scope, element, attrs){ + if (attrs.toggle=="tooltip"){ + $(element).tooltip(); + } + if (attrs.toggle=="popover"){ + $(element).popover(); + } + } + }; + }); + +})(); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/falcon-ui/app/js/directives/validation-message.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/directives/validation-message.js b/falcon-ui/app/js/directives/validation-message.js index 5bd575b..7530244 100644 --- a/falcon-ui/app/js/directives/validation-message.js +++ b/falcon-ui/app/js/directives/validation-message.js @@ -50,14 +50,11 @@ element.parent().append( '<label ng-show="messageSwitcher.show" class="custom-danger validationMessageGral"></label>' ); - //var t0 = performance.now(); angular.forEach(element.parent().children(), function () { lastOne = lastOne + 1; }); lastOne = lastOne - 1; stringLabel = $(element).parent().children()[lastOne]; - //var t1 = performance.now(); - //console.log("Call to doSomething took " + (t1 - t0) + " milliseconds."); } function checkNameInList() { @@ -144,4 +141,109 @@ }; }]); + directivesModule.directive('validationOptionalMessage', [function () { + return { + replace: false, + scope: { + validationOptionalMessage: "@", + required: "@" + }, + restrict: 'A', + link: function (scope, element, attrs) { + + var lastOne = 0, + valLength = element[0].value.length, + required = attrs.required, + stringLabel, + valid, + invalidPattern, + messageObject = angular.fromJson(scope.validationOptionalMessage); + + messageObject.patternInvalid = messageObject.patternInvalid || messageObject.empty; + + function getLabelElement() { + lastOne = 0; + element.parent().append( + '<label ng-show="messageSwitcher.show" class="custom-danger validationMessageGral"></label>' + ); + angular.forEach(element.parent().children(), function () { + lastOne = lastOne + 1; + }); + lastOne = lastOne - 1; + stringLabel = $(element).parent().children()[lastOne]; + } + + function prepare() { + + valLength = element[0].value.length; + required = attrs.required; + valid = element.hasClass('ng-valid'); + invalidPattern = element.hasClass('ng-invalid-pattern'); + + if (valLength === 0 && required) { + element.addClass('empty'); + angular.element(stringLabel).html(messageObject.empty).addClass('hidden'); + element.parent().removeClass("showMessage showValidationStyle validationMessageParent"); + + } else if (valLength === 0 && !required) { + element.addClass('empty'); + element.parent().removeClass("showMessage showValidationStyle validationMessageParent"); + angular.element(stringLabel).addClass('hidden'); + + } else if (invalidPattern && valLength > 0) { + element.removeClass('empty'); + angular.element(stringLabel).html(messageObject.patternInvalid).removeClass('hidden'); + element.parent().addClass("showMessage showValidationStyle validationMessageParent"); + + } else if (valid && valLength > 0) { + element.removeClass('empty'); + angular.element(stringLabel).addClass('hidden'); + element.parent().removeClass("showMessage showValidationStyle validationMessageParent"); + + } else { + console.log("else"); + } + } + function addListeners() { + + element.bind('keyup', prepare); + element.bind('blur', function () { + if (valLength === 0 && required) { + element.removeClass('empty'); + angular.element(stringLabel).html(messageObject.empty).removeClass('hidden'); + element.parent().addClass("showMessage showValidationStyle validationMessageParent"); + } + }); + } + function normalize() { + prepare(); + setTimeout(function () { + if (valLength === 0 && required) { + angular.element(stringLabel).removeClass('hidden'); + element.removeClass('empty'); + } + }, 100); + } + function init() { + getLabelElement(); + addListeners(); + prepare(); + } + init(); + + scope.$watch(function () { + return scope.required; + }, normalize); + + scope.$watch(function () { + return element[0].value.length; + }, function () { + if (element[0].value.length === 0) { + element.addClass('empty'); + } + }); + } + }; + }]); + }()); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/falcon/blob/86180d93/falcon-ui/app/js/lib/bootstrap.notify.js ---------------------------------------------------------------------- diff --git a/falcon-ui/app/js/lib/bootstrap.notify.js b/falcon-ui/app/js/lib/bootstrap.notify.js new file mode 100644 index 0000000..3a2fc5d --- /dev/null +++ b/falcon-ui/app/js/lib/bootstrap.notify.js @@ -0,0 +1,347 @@ +/* + * Project: Bootstrap Notify = v3.0.2 + * Description: Turns standard Bootstrap alerts into "Growl-like" notifications. + * Author: Mouse0270 aka Robert McIntosh + * License: MIT License + * Website: https://github.com/mouse0270/bootstrap-growl + */ +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS + factory(require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + // Create the defaults once + var defaults = { + element: 'body', + position: null, + type: "info", + allow_dismiss: true, + newest_on_top: false, + showProgressbar: false, + placement: { + from: "top", + align: "right" + }, + offset: 20, + spacing: 10, + z_index: 1031, + delay: 5000, + timer: 1000, + url_target: '_blank', + mouse_over: null, + animate: { + enter: 'animated fadeInDown', + exit: 'animated fadeOutUp' + }, + onShow: null, + onShown: null, + onClose: null, + onClosed: null, + icon_type: 'class', + template: '<div data-notify="container" class="col-xs-11 col-sm-4 alert alert-{0}" role="alert"><button type="button" aria-hidden="true" class="close" data-notify="dismiss">×</button><span data-notify="icon"></span> <span data-notify="title">{1}</span> <span data-notify="message">{2}</span><div class="progress" data-notify="progressbar"><div class="progress-bar progress-bar-{0}" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div></div><a href="{3}" target="{4}" data-notify="url"></a></div>' + }; + + String.format = function() { + var str = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + str = str.replace(RegExp("\\{" + (i - 1) + "\\}", "gm"), arguments[i]); + } + return str; + }; + + function Notify ( element, content, options ) { + // Setup Content of Notify + var content = { + content: { + message: typeof content == 'object' ? content.message : content, + title: content.title ? content.title : '', + icon: content.icon ? content.icon : '', + url: content.url ? content.url : '#', + target: content.target ? content.target : '-' + } + }; + + options = $.extend(true, {}, content, options); + this.settings = $.extend(true, {}, defaults, options); + this._defaults = defaults; + if (this.settings.content.target == "-") { + this.settings.content.target = this.settings.url_target; + } + this.animations = { + start: 'webkitAnimationStart oanimationstart MSAnimationStart animationstart', + end: 'webkitAnimationEnd oanimationend MSAnimationEnd animationend' + } + + if (typeof this.settings.offset == 'number') { + this.settings.offset = { + x: this.settings.offset, + y: this.settings.offset + }; + } + + this.init(); + }; + + $.extend(Notify.prototype, { + init: function () { + var self = this; + + this.buildNotify(); + if (this.settings.content.icon) { + this.setIcon(); + } + if (this.settings.content.url != "#") { + this.styleURL(); + } + this.placement(); + this.bind(); + + this.notify = { + $ele: this.$ele, + update: function(command, update) { + var commands = {}; + if (typeof command == "string") { + commands[command] = update; + }else{ + commands = command; + } + for (var command in commands) { + switch (command) { + case "type": + this.$ele.removeClass('alert-' + self.settings.type); + this.$ele.find('[data-notify="progressbar"] > .progress-bar').removeClass('progress-bar-' + self.settings.type); + self.settings.type = commands[command]; + this.$ele.addClass('alert-' + commands[command]).find('[data-notify="progressbar"] > .progress-bar').addClass('progress-bar-' + commands[command]); + break; + case "icon": + var $icon = this.$ele.find('[data-notify="icon"]'); + if (self.settings.icon_type.toLowerCase() == 'class') { + $icon.removeClass(self.settings.content.icon).addClass(commands[command]); + }else{ + if (!$icon.is('img')) { + $icon.find('img'); + } + $icon.attr('src', commands[command]); + } + break; + case "progress": + var newDelay = self.settings.delay - (self.settings.delay * (commands[command] / 100)); + this.$ele.data('notify-delay', newDelay); + this.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', commands[command]).css('width', commands[command] + '%'); + break; + case "url": + this.$ele.find('[data-notify="url"]').attr('href', commands[command]); + break; + case "target": + this.$ele.find('[data-notify="url"]').attr('target', commands[command]); + break; + default: + this.$ele.find('[data-notify="' + command +'"]').html(commands[command]); + }; + } + var posX = this.$ele.outerHeight() + parseInt(self.settings.spacing) + parseInt(self.settings.offset.y); + self.reposition(posX); + }, + close: function() { + self.close(); + } + }; + }, + buildNotify: function () { + var content = this.settings.content; + this.$ele = $(String.format(this.settings.template, this.settings.type, content.title, content.message, content.url, content.target)); + this.$ele.attr('data-notify-position', this.settings.placement.from + '-' + this.settings.placement.align); + if (!this.settings.allow_dismiss) { + this.$ele.find('[data-notify="dismiss"]').css('display', 'none'); + } + if ((this.settings.delay <= 0 && !this.settings.showProgressbar) || !this.settings.showProgressbar) { + this.$ele.find('[data-notify="progressbar"]').remove(); + } + }, + setIcon: function() { + if (this.settings.icon_type.toLowerCase() == 'class') { + this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon); + }else{ + if (this.$ele.find('[data-notify="icon"]').is('img')) { + this.$ele.find('[data-notify="icon"]').attr('src', this.settings.content.icon); + }else{ + this.$ele.find('[data-notify="icon"]').append('<img src="'+this.settings.content.icon+'" alt="Notify Icon" />'); + } + } + }, + styleURL: function() { + this.$ele.find('[data-notify="url"]').css({ + backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)', + height: '100%', + left: '0px', + position: 'absolute', + top: '0px', + width: '100%', + zIndex: this.settings.z_index + 1 + }); + this.$ele.find('[data-notify="dismiss"]').css({ + position: 'absolute', + right: '10px', + top: '5px', + zIndex: this.settings.z_index + 2 + }); + }, + placement: function() { + var self = this, + offsetAmt = this.settings.offset.y, + css = { + display: 'inline-block', + margin: '0px auto', + position: this.settings.position ? this.settings.position : (this.settings.element === 'body' ? 'fixed' : 'absolute'), + transition: 'all .5s ease-in-out', + zIndex: this.settings.z_index + }, + hasAnimation = false, + settings = this.settings; + + $('[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])').each(function() { + return offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + parseInt($(this).outerHeight()) + parseInt(settings.spacing)); + }); + if (this.settings.newest_on_top == true) { + offsetAmt = this.settings.offset.y; + } + css[this.settings.placement.from] = offsetAmt+'px'; + + switch (this.settings.placement.align) { + case "left": + case "right": + css[this.settings.placement.align] = this.settings.offset.x+'px'; + break; + case "center": + css.left = 0; + css.right = 0; + break; + } + this.$ele.css(css).addClass(this.settings.animate.enter); + + $(this.settings.element).append(this.$ele); + + if (this.settings.newest_on_top == true) { + offsetAmt = (parseInt(offsetAmt)+parseInt(this.settings.spacing)) + this.$ele.outerHeight(); + this.reposition(offsetAmt); + } + + if ($.isFunction(self.settings.onShow)) { + self.settings.onShow.call(this.$ele); + } + + this.$ele.one(this.animations.start, function(event) { + hasAnimation = true; + }).one(this.animations.end, function(event) { + if ($.isFunction(self.settings.onShown)) { + self.settings.onShown.call(this); + } + }); + + setTimeout(function() { + if (!hasAnimation) { + if ($.isFunction(self.settings.onShown)) { + self.settings.onShown.call(this); + } + } + }, 600); + }, + bind: function() { + var self = this; + + this.$ele.find('[data-notify="dismiss"]').on('click', function() { + self.close(); + }) + + this.$ele.mouseover(function(e) { + $(this).data('data-hover', "true"); + }).mouseout(function(e) { + $(this).data('data-hover', "false"); + }); + this.$ele.data('data-hover', "false"); + + if (this.settings.delay > 0) { + self.$ele.data('notify-delay', self.settings.delay); + var timer = setInterval(function() { + var delay = parseInt(self.$ele.data('notify-delay')) - self.settings.timer; + if ((self.$ele.data('data-hover') === 'false' && self.settings.mouse_over == "pause") || self.settings.mouse_over != "pause") { + var percent = ((self.settings.delay - delay) / self.settings.delay) * 100; + self.$ele.data('notify-delay', delay); + self.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', percent).css('width', percent + '%'); + } + if (delay <= -(self.settings.timer)) { + clearInterval(timer); + self.close(); + } + }, self.settings.timer); + } + }, + close: function() { + var self = this, + $successors = null, + posX = parseInt(this.$ele.css(this.settings.placement.from)), + hasAnimation = false; + + this.$ele.data('closing', 'true').addClass(this.settings.animate.exit); + self.reposition(posX); + + if ($.isFunction(self.settings.onClose)) { + self.settings.onClose.call(this.$ele); + } + + this.$ele.one(this.animations.start, function(event) { + hasAnimation = true; + }).one(this.animations.end, function(event) { + $(this).remove(); + if ($.isFunction(self.settings.onClosed)) { + self.settings.onClosed.call(this); + } + }); + + setTimeout(function() { + if (!hasAnimation) { + self.$ele.remove(); + if (self.settings.onClosed) { + self.settings.onClosed(self.$ele); + } + } + }, 600); + }, + reposition: function(posX) { + var self = this, + notifies = '[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])', + $elements = this.$ele.nextAll(notifies); + if (this.settings.newest_on_top == true) { + $elements = this.$ele.prevAll(notifies); + } + $elements.each(function() { + $(this).css(self.settings.placement.from, posX); + posX = (parseInt(posX)+parseInt(self.settings.spacing)) + $(this).outerHeight(); + }); + } + }); + + $.notify = function ( content, options ) { + var plugin = new Notify( this, content, options ); + return plugin.notify; + }; + $.notifyDefaults = function( options ) { + defaults = $.extend(true, {}, defaults, options); + return defaults; + }; + $.notifyClose = function( command ) { + if (typeof command === "undefined" || command == "all") { + $('[data-notify]').find('[data-notify="dismiss"]').trigger('click'); + }else{ + $('[data-notify-position="'+command+'"]').find('[data-notify="dismiss"]').trigger('click'); + } + }; + +})); \ No newline at end of file
