You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@falcon.apache.org by so...@apache.org on 2015/10/13 02:04:00 UTC
[07/22] falcon git commit: FALCON-1315 Update falcon ui for HiveDR,
secure clusters and bug fixes. Contributed by Armando Reyna/Venkat
Ranganathan.
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