<commit_before>/*
 * Tooltip/Popover component mixin
 * Common props
 */
import { isArray } from '../utils/array'
import { isElement, getById } from '../utils/dom'
import { HTMLElement } from '../utils/ssr'
import observeDom from '../utils/observe-dom'

const PLACEMENTS = {
  top: 'top',
  topleft: 'topleft',
  topright: 'topright',
  right: 'right',
  righttop: 'righttop',
  rightbottom: 'rightbottom',
  bottom: 'bottom',
  bottomleft: 'bottomleft',
  bottomright: 'bottomright',
  left: 'left',
  lefttop: 'lefttop',
  leftbottom: 'leftbottom',
  auto: 'auto'
}

const OBSERVER_CONFIG = {
  subtree: true,
  childList: true,
  characterData: true,
  attributes: true,
  attributeFilter: ['class', 'style']
}

// @vue/component
export default {
  props: {
    target: {
      // String ID of element, or element/component reference
      type: [String, Object, HTMLElement, Function]
      // default: undefined
    },
    delay: {
      type: [Number, Object, String],
      default: 0
    },
    offset: {
      type: [Number, String],
      default: 0
    },
    noFade: {
      type: Boolean,
      default: false
    },
    container: {
      // String ID of container, if null body is used (default)
      type: String,
      default: null
    },
    boundary: {
      // String: scrollParent, window, or viewport
      // Element: element reference
      type: [String, HTMLElement],
      default: 'scrollParent'
    },
    boundaryPadding: {
      type: Number,
      default: 5
    },
    show: {
      type: Boolean,
      default: false
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    baseConfig() {
      const cont = this.container
      let delay = typeof this.delay === 'object' ? this.delay : parseInt(this.delay, 10) || 0
      return {
        // Title prop
        title: (this.title || '').trim() || '',
        // Contnt prop (if popover)
        content: (this.content || '').trim() || '',
        // Tooltip/Popover placement
        placement: PLACEMENTS[this.placement] || 'auto',
        // Container curently needs to be an ID with '#' prepended, if null then body is used
        container: cont ? (/^#/.test(cont) ? cont : `#${cont}`) : false,
        // boundariesElement passed to popper
        boundary: this.boundary,
        // boundariesElement padding passed to popper
        boundaryPadding: this.boundaryPadding,
        // Show/Hide delay
        delay: delay || 0,
        // Offset can be css distance. if no units, pixels are assumed
        offset: this.offset || 0,
        // Disable fade Animation?
        animation: !this.noFade,
        // Open/Close Trigger(s)
        trigger: isArray(this.triggers) ? this.triggers.join(' ') : this.triggers,
        // Callbacks so we can trigger events on component
        callbacks: {
          show: this.onShow,
          shown: this.onShown,
          hide: this.onHide,
          hidden: this.onHidden,
          enabled: this.onEnabled,
          disabled: this.onDisabled
        }
      }
    }
  },
  watch: {
    show(show, old) {
      if (show === old) {
        /* istanbul ignore next */
        return
      }
      show ? this.onOpen() : this.onClose()
    },
    disabled(disabled, old) {
      if (disabled === old) {
        /* istanbul ignore next */
        return
      }
      disabled ? this.onDisable() : this.onEnable()
    }
  },
  created() {
    // Create non-reactive property
    this._toolpop = null
    this._obs_title = null
    this._obs_content = null
  },
  mounted() {
    // We do this in a next tick to ensure DOM has rendered first
    this.$nextTick(() => {
      // Instantiate ToolTip/PopOver on target
      // The createToolpop method must exist in main component
      if (this.createToolpop()) {
        if (this.disabled) {
          // Initially disabled
          this.onDisable()
        }
        // Listen to open signals from others
        this.$on('open', this.onOpen)
        // Listen to close signals from others
        this.$on('close', this.onClose)
        // Listen to disable signals from others
        this.$on('disable', this.onDisable)
        // Listen to disable signals from others
        this.$on('enable', this.onEnable)
        // Observe content Child changes so we can notify popper of possible size change
        this.setObservers(true)
        // Set intially open state
        if (this.show) {
          this.onOpen()
        }
      }
    })
  },
  updated() {
    // If content/props changes, etc
    /* istanbul ignore next: can't test in JSDOM */
    if (this._toolpop) {
      this._toolpop.updateConfig(this.getConfig())
    }
  },
  activated() {
    // Called when component is inside a <keep-alive> and component brought offline
    /* istanbul ignore next: can't test in JSDOM */
    this.setObservers(true)
  },
  deactivated() {
    // Called when component is inside a <keep-alive> and component taken offline
    /* istanbul ignore next: can't test in JSDOM */
    if (this._toolpop) {
      this.setObservers(false)
      this._toolpop.hide()
    }
  },
  beforeDestroy() /* istanbul ignore next: not easy to test */ {
    // Shutdown our local event listeners
    this.$off('open', this.onOpen)
    this.$off('close', this.onClose)
    this.$off('disable', this.onDisable)
    this.$off('enable', this.onEnable)
    this.setObservers(false)
    // bring our content back if needed
    this.bringItBack()
    if (this._toolpop) {
      this._toolpop.destroy()
      this._toolpop = null
    }
  },
  methods: {
    getConfig() {
      const cfg = { ...this.baseConfig }
      if (this.$refs.title && this.$refs.title.innerHTML.trim()) {
        // If slot has content, it overrides 'title' prop
        // We use the DOM node as content to allow components!
        cfg.title = this.$refs.title
        cfg.html = true
      }
      if (this.$refs.content && this.$refs.content.innerHTML.trim()) {
        // If slot has content, it overrides 'content' prop
        // We use the DOM node as content to allow components!
        cfg.content = this.$refs.content
        cfg.html = true
      }
      return cfg
    },
    onOpen() {
      if (this._toolpop) {
        this._toolpop.show()
      }
    },
    onClose(callback) {
      if (this._toolpop) {
        this._toolpop.hide(callback)
      } else if (typeof callback === 'function') {
        callback()
      }
    },
    onDisable() {
      /* istanbul ignore next: can't test in JSDOM */
      if (this._toolpop) {
        this._toolpop.disable()
      }
    },
    onEnable() {
      /* istanbul ignore next: can't test in JSDOM */
      if (this._toolpop) {
        this._toolpop.enable()
      }
    },
    updatePosition() {
      /* istanbul ignore next: can't test in JSDOM */
      if (this._toolpop) {
        // Instruct popper to reposition popover if necessary
        this._toolpop.update()
      }
    },
    getTarget() {
      let target = this.target
      if (typeof target === 'function') {
        target = target()
      }
      if (typeof target === 'string') {
        // Assume ID of element
        return getById(target)
      } else if (typeof target === 'object' && isElement(target.$el)) {
        // Component reference
        return target.$el
      } else if (typeof target === 'object' && isElement(target)) {
        // Element reference
        return target
      }
      return null
    },
    onShow(evt) {
      this.$emit('show', evt)
    },
    onShown(evt) {
      this.setObservers(true)
      this.$emit('update:show', true)
      this.$emit('shown', evt)
    },
    onHide(evt) {
      this.$emit('hide', evt)
    },
    onHidden(evt) {
      this.setObservers(false)
      // bring our content back if needed to keep Vue happy
      // Tooltip class will move it back to tip when shown again
      this.bringItBack()
      this.$emit('update:show', false)
      this.$emit('hidden', evt)
    },
    onEnabled(evt) {
      if (!evt || evt.type !== 'enabled') {
        // Prevent possible endless loop if user mistakienly fires enabled instead of enable
        return
      }
      this.$emit('update:disabled', false)
      this.$emit('disabled')
    },
    onDisabled(evt) {
      if (!evt || evt.type !== 'disabled') {
        // Prevent possible endless loop if user mistakienly fires disabled instead of disable
        return
      }
      this.$emit('update:disabled', true)
      this.$emit('enabled')
    },
    bringItBack() {
      // bring our content back if needed to keep Vue happy
      if (this.$el && this.$refs.title) {
        this.$el.appendChild(this.$refs.title)
      }
      if (this.$el && this.$refs.content) {
        this.$el.appendChild(this.$refs.content)
      }
    },
    setObservers(on) /* istanbul ignore next: can't test in JSDOM */ {
      if (on) {
        if (this.$refs.title) {
          this._obs_title = observeDom(
            this.$refs.title,
            this.updatePosition.bind(this),
            OBSERVER_CONFIG
          )
        }
        if (this.$refs.content) {
          this._obs_content = observeDom(
            this.$refs.content,
            this.updatePosition.bind(this),
            OBSERVER_CONFIG
          )
        }
      } else {
        if (this._obs_title) {
          this._obs_title.disconnect()
          this._obs_title = null
        }
        if (this._obs_content) {
          this._obs_content.disconnect()
          this._obs_content = null
        }
      }
    }
  }
}
<commit_msg>chore(mixins/toolpop): update coverage<commit_after>/*
 * Tooltip/Popover component mixin
 * Common props
 */
import { isArray } from '../utils/array'
import { isElement, getById } from '../utils/dom'
import { HTMLElement } from '../utils/ssr'
import observeDom from '../utils/observe-dom'

const PLACEMENTS = {
  top: 'top',
  topleft: 'topleft',
  topright: 'topright',
  right: 'right',
  righttop: 'righttop',
  rightbottom: 'rightbottom',
  bottom: 'bottom',
  bottomleft: 'bottomleft',
  bottomright: 'bottomright',
  left: 'left',
  lefttop: 'lefttop',
  leftbottom: 'leftbottom',
  auto: 'auto'
}

const OBSERVER_CONFIG = {
  subtree: true,
  childList: true,
  characterData: true,
  attributes: true,
  attributeFilter: ['class', 'style']
}

// @vue/component
export default {
  props: {
    target: {
      // String ID of element, or element/component reference
      type: [String, Object, HTMLElement, Function]
      // default: undefined
    },
    delay: {
      type: [Number, Object, String],
      default: 0
    },
    offset: {
      type: [Number, String],
      default: 0
    },
    noFade: {
      type: Boolean,
      default: false
    },
    container: {
      // String ID of container, if null body is used (default)
      type: String,
      default: null
    },
    boundary: {
      // String: scrollParent, window, or viewport
      // Element: element reference
      type: [String, HTMLElement],
      default: 'scrollParent'
    },
    boundaryPadding: {
      type: Number,
      default: 5
    },
    show: {
      type: Boolean,
      default: false
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    baseConfig() {
      const cont = this.container
      let delay = typeof this.delay === 'object' ? this.delay : parseInt(this.delay, 10) || 0
      return {
        // Title prop
        title: (this.title || '').trim() || '',
        // Contnt prop (if popover)
        content: (this.content || '').trim() || '',
        // Tooltip/Popover placement
        placement: PLACEMENTS[this.placement] || 'auto',
        // Container curently needs to be an ID with '#' prepended, if null then body is used
        container: cont ? (/^#/.test(cont) ? cont : `#${cont}`) : false,
        // boundariesElement passed to popper
        boundary: this.boundary,
        // boundariesElement padding passed to popper
        boundaryPadding: this.boundaryPadding,
        // Show/Hide delay
        delay: delay || 0,
        // Offset can be css distance. if no units, pixels are assumed
        offset: this.offset || 0,
        // Disable fade Animation?
        animation: !this.noFade,
        // Open/Close Trigger(s)
        trigger: isArray(this.triggers) ? this.triggers.join(' ') : this.triggers,
        // Callbacks so we can trigger events on component
        callbacks: {
          show: this.onShow,
          shown: this.onShown,
          hide: this.onHide,
          hidden: this.onHidden,
          enabled: this.onEnabled,
          disabled: this.onDisabled
        }
      }
    }
  },
  watch: {
    show(show, old) {
      /* istanbul ignore if */
      if (show === old) {
        /* istanbul ignore next */
        return
      }
      show ? this.onOpen() : this.onClose()
    },
    disabled(disabled, old) {
      /* istanbul ignore if */
      if (disabled === old) {
        /* istanbul ignore next */
        return
      }
      disabled ? this.onDisable() : this.onEnable()
    }
  },
  created() {
    // Create non-reactive property
    this._toolpop = null
    this._obs_title = null
    this._obs_content = null
  },
  mounted() {
    // We do this in a next tick to ensure DOM has rendered first
    this.$nextTick(() => {
      // Instantiate ToolTip/PopOver on target
      // The createToolpop method must exist in main component
      if (this.createToolpop()) {
        if (this.disabled) {
          // Initially disabled
          this.onDisable()
        }
        // Listen to open signals from others
        this.$on('open', this.onOpen)
        // Listen to close signals from others
        this.$on('close', this.onClose)
        // Listen to disable signals from others
        this.$on('disable', this.onDisable)
        // Listen to disable signals from others
        this.$on('enable', this.onEnable)
        // Observe content Child changes so we can notify popper of possible size change
        this.setObservers(true)
        // Set intially open state
        if (this.show) {
          this.onOpen()
        }
      }
    })
  },
  updated() {
    // If content/props changes, etc
    /* istanbul ignore next: can't test in JSDOM */
    if (this._toolpop) {
      this._toolpop.updateConfig(this.getConfig())
    }
  },
  activated() {
    // Called when component is inside a <keep-alive> and component brought offline
    /* istanbul ignore next: can't test in JSDOM */
    this.setObservers(true)
  },
  deactivated() {
    // Called when component is inside a <keep-alive> and component taken offline
    /* istanbul ignore next: can't test in JSDOM */
    if (this._toolpop) {
      this.setObservers(false)
      this._toolpop.hide()
    }
  },
  beforeDestroy() /* istanbul ignore next: not easy to test */ {
    // Shutdown our local event listeners
    this.$off('open', this.onOpen)
    this.$off('close', this.onClose)
    this.$off('disable', this.onDisable)
    this.$off('enable', this.onEnable)
    this.setObservers(false)
    // bring our content back if needed
    this.bringItBack()
    if (this._toolpop) {
      this._toolpop.destroy()
      this._toolpop = null
    }
  },
  methods: {
    getConfig() {
      const cfg = { ...this.baseConfig }
      if (this.$refs.title && this.$refs.title.innerHTML.trim()) {
        // If slot has content, it overrides 'title' prop
        // We use the DOM node as content to allow components!
        cfg.title = this.$refs.title
        cfg.html = true
      }
      if (this.$refs.content && this.$refs.content.innerHTML.trim()) {
        // If slot has content, it overrides 'content' prop
        // We use the DOM node as content to allow components!
        cfg.content = this.$refs.content
        cfg.html = true
      }
      return cfg
    },
    onOpen() {
      if (this._toolpop) {
        this._toolpop.show()
      }
    },
    onClose(callback) {
      if (this._toolpop) {
        this._toolpop.hide(callback)
      } else if (typeof callback === 'function') {
        callback()
      }
    },
    onDisable() {
      /* istanbul ignore next: can't test in JSDOM */
      if (this._toolpop) {
        this._toolpop.disable()
      }
    },
    onEnable() {
      /* istanbul ignore next: can't test in JSDOM */
      if (this._toolpop) {
        this._toolpop.enable()
      }
    },
    updatePosition() {
      /* istanbul ignore next: can't test in JSDOM */
      if (this._toolpop) {
        // Instruct popper to reposition popover if necessary
        this._toolpop.update()
      }
    },
    getTarget() {
      let target = this.target
      if (typeof target === 'function') {
        target = target()
      }
      if (typeof target === 'string') {
        // Assume ID of element
        return getById(target)
      } else if (typeof target === 'object' && isElement(target.$el)) {
        // Component reference
        return target.$el
      } else if (typeof target === 'object' && isElement(target)) {
        // Element reference
        return target
      }
      return null
    },
    onShow(evt) {
      this.$emit('show', evt)
    },
    onShown(evt) {
      this.setObservers(true)
      this.$emit('update:show', true)
      this.$emit('shown', evt)
    },
    onHide(evt) {
      this.$emit('hide', evt)
    },
    onHidden(evt) {
      this.setObservers(false)
      // bring our content back if needed to keep Vue happy
      // Tooltip class will move it back to tip when shown again
      this.bringItBack()
      this.$emit('update:show', false)
      this.$emit('hidden', evt)
    },
    onEnabled(evt) {
      if (!evt || evt.type !== 'enabled') {
        // Prevent possible endless loop if user mistakienly fires enabled instead of enable
        return
      }
      this.$emit('update:disabled', false)
      this.$emit('disabled')
    },
    onDisabled(evt) {
      if (!evt || evt.type !== 'disabled') {
        // Prevent possible endless loop if user mistakienly fires disabled instead of disable
        return
      }
      this.$emit('update:disabled', true)
      this.$emit('enabled')
    },
    bringItBack() {
      // bring our content back if needed to keep Vue happy
      if (this.$el && this.$refs.title) {
        this.$el.appendChild(this.$refs.title)
      }
      if (this.$el && this.$refs.content) {
        this.$el.appendChild(this.$refs.content)
      }
    },
    setObservers(on) /* istanbul ignore next: can't test in JSDOM */ {
      if (on) {
        if (this.$refs.title) {
          this._obs_title = observeDom(
            this.$refs.title,
            this.updatePosition.bind(this),
            OBSERVER_CONFIG
          )
        }
        if (this.$refs.content) {
          this._obs_content = observeDom(
            this.$refs.content,
            this.updatePosition.bind(this),
            OBSERVER_CONFIG
          )
        }
      } else {
        if (this._obs_title) {
          this._obs_title.disconnect()
          this._obs_title = null
        }
        if (this._obs_content) {
          this._obs_content.disconnect()
          this._obs_content = null
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>/**
 * @ngdoc service
 * @name ngCordovaMocks.cordovaKeychain
 *
 * @description
 * A service for testing Keychain features
 * in an app built with ngCordova.
**/
ngCordovaMocks.factory('$cordovaKeychain', ['$q', function($q) {
	var keychains = {};

	return {
	/**
		* @ngdoc property
		* @name keychains
		* @propertyOf ngCordovaMocks.cordovaKeychain
		*
		* @description
		* The collection of 'keychains' that have been saved.
		* This property should only be used in automated tests.
		**/
		keychains: keychains,

		getForKey: function (key, serviceName) {
			var defer = $q.defer();

			if (this.keychains[serviceName]) {
				defer.resolve(this.keychains[serviceName][key]);
			} else {
				defer.reject();
			}

			return defer.promise;
		},

		setForKey: function (key, serviceName, value) {
			var defer = $q.defer();

			if (!this.keychains[serviceName]) {
				this.keychains[serviceName] = {};
			}

			this.keychains[serviceName][key] = value;

			defer.resolve();

			return defer.promise;
		},

		removeForKey: function (key, serviceName) {
			var defer = $q.defer();

			if (this.keychains[serviceName]) {
				this.keychains[serviceName][key] = undefined;
			}

			defer.resolve();

			return defer.promise;
		}
	};
}]);
<commit_msg>Proper indenting + properly delete object property<commit_after>/**
 * @ngdoc service
 * @name ngCordovaMocks.cordovaKeychain
 *
 * @description
 * A service for testing Keychain features
 * in an app built with ngCordova.
 **/
ngCordovaMocks.factory('$cordovaKeychain', ['$q', function($q) {
  var keychains = {};

  return {
    /**
     * @ngdoc property
     * @name keychains
     * @propertyOf ngCordovaMocks.cordovaKeychain
     *
     * @description
     * The collection of 'keychains' that have been saved.
     * This property should only be used in automated tests.
     **/
    keychains: keychains,

    getForKey: function (key, serviceName) {
      var defer = $q.defer();

      if (this.keychains[serviceName]) {
        defer.resolve(this.keychains[serviceName][key]);
      } else {
        defer.reject();
      }

      return defer.promise;
    },

    setForKey: function (key, serviceName, value) {
      var defer = $q.defer();

      if (!this.keychains[serviceName]) {
        this.keychains[serviceName] = {};
      }

      this.keychains[serviceName][key] = value;

      defer.resolve();

      return defer.promise;
    },

    removeForKey: function (key, serviceName) {
      var defer = $q.defer();

      if (this.keychains[serviceName]) {
        delete this.keychains[serviceName][key];
      }

      defer.resolve();

      return defer.promise;
    }
  };
}]);
<|endoftext|>


<commit_before>angular.module('pascalprecht.translate')
/**
 * @ngdoc directive
 * @name pascalprecht.translate.directive:translate
 * @requires $compile
 * @requires $filter
 * @requires $interpolate
 * @restrict A
 *
 * @description
 * Translates given translation id either through attribute or DOM content.
 * Internally it uses `translate` filter to translate translation id. It possible to
 * pass an optional `translate-values` object literal as string into translation id.
 *
 * @param {string=} translate Translation id which could be either string or interpolated string.
 * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
 * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
 *
 * @example
   <example module="ngView">
    <file name="index.html">

TRANSLATION_ID

{{translationId}}

WITH_VALUES

WITH_VALUES

    </file>
    <file name="script.js">
      angular.module('ngView', ['pascalprecht.translate'])

      .config(function ($translateProvider) {

        $translateProvider.translations('en',{
          'TRANSLATION_ID': 'Hello there!',
          'WITH_VALUES': 'The following value is dynamic: {{value}}'
        }).preferredLanguage('en');

      });

      angular.module('ngView').controller('TranslateCtrl', function ($scope) {
        $scope.translationId = 'TRANSLATION_ID';

        $scope.values = {
          value: 78
        };
      });
    </file>
    <file name="scenario.js">
      it('should translate', function () {
        inject(function ($rootScope, $compile) {
          $rootScope.translationId = 'TRANSLATION_ID';

          element = $compile('

')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('

')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('
TRANSLATION_ID
')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('
{{translationId}}
')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('

')($rootScope);
          $rootScope.$digest();
          expect(element.attr('title')).toBe('Hello there!');
        });
      });
    </file>
   </example>
 */
.directive('translate', ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope', function ($translate, $q, $interpolate, $compile, $parse, $rootScope) {

  return {
    restrict: 'AE',
    scope: true,
    compile: function (tElement, tAttr) {

      var translateValuesExist = (tAttr.translateValues) ?
        tAttr.translateValues : undefined;

      var translateInterpolation = (tAttr.translateInterpolation) ?
        tAttr.translateInterpolation : undefined;

      var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);

      var interpolateRegExp = "^(.*)(" + $interpolate.startSymbol() + ".*" + $interpolate.endSymbol() + ")(.*)";

      return function linkFn(scope, iElement, iAttr) {

        scope.interpolateParams = {};
        scope.preText = "";
        scope.postText = "";
        var translationIds = {};

        // Ensures any change of the attribute "translate" containing the id will
        // be re-stored to the scope's "translationId".
        // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
        var observeElementTranslation = function (translationId) {
          if (angular.equals(translationId , '') || !angular.isDefined(translationId)) {
            // Resolve translation id by inner html if required
            var interpolateMatches = iElement.text().match(interpolateRegExp);
            // Interpolate translation id if required
            if (angular.isArray(interpolateMatches)) {
              scope.preText = interpolateMatches[1];
              scope.postText = interpolateMatches[3];
              translationIds['translate'] = $interpolate(interpolateMatches[2])(scope.$parent);
            } else {
              translationIds['translate'] = iElement.text().replace(/^\s+|\s+$/g,'');
            }
          } else {
            translationIds['translate'] = translationId;
          }
          updateTranslations();
        };

        var observeAttributeTranslation = function (translateAttr) {
          iAttr.$observe(translateAttr, function (translationId) {
            translationIds[translateAttr] = translationId;
            updateTranslations();
          });
        };

        iAttr.$observe('translate', function (translationId) {
          observeElementTranslation(translationId);
        });

        for (var translateAttr in iAttr) {
          if(iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr') {
            observeAttributeTranslation(translateAttr);
          }
        }

        iAttr.$observe('translateDefault', function (value) {
          scope.defaultText = value;
        });

        if (translateValuesExist) {
          iAttr.$observe('translateValues', function (interpolateParams) {
            if (interpolateParams) {
              scope.$parent.$watch(function () {
                angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
              });
            }
          });
        }

        if (translateValueExist) {
          var observeValueAttribute = function (attrName) {
            iAttr.$observe(attrName, function (value) {
              var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
              scope.interpolateParams[attributeName] = value;
            });
          };
          for (var attr in iAttr) {
            if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
              observeValueAttribute(attr);
            }
          }
        }

        // Master update function
        var updateTranslations = function () {
          for (var key in translationIds) {
            if (translationIds.hasOwnProperty(key) && translationIds[key]) {
              updateTranslation(key, translationIds[key], scope, scope.interpolateParams);
            }
          }
        };

        // Put translation processing function outside loop
        var updateTranslation = function(translateAttr, translationId, scope, interpolateParams) {
          $translate(translationId, interpolateParams, translateInterpolation)
            .then(function (translation) {
              applyTranslation(translation, scope, true, translateAttr);
            }, function (translationId) {
              applyTranslation(translationId, scope, false, translateAttr);
            });
        };

        var applyTranslation = function (value, scope, successful, translateAttr) {
          if (translateAttr === 'translate') {
            // default translate into innerHTML
            if (!successful && typeof scope.defaultText !== 'undefined') {
              value = scope.defaultText;
            }
            iElement.html(scope.preText + value + scope.postText);
            var globallyEnabled = $translate.isPostCompilingEnabled();
            var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
            var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
            if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
              $compile(iElement.contents())(scope);
            }
          } else {
            // translate attribute
            if (!successful && typeof scope.defaultText !== 'undefined') {
              value = scope.defaultText;
            }
            var attributeName = iAttr.$attr[translateAttr].substr(15);
            iElement.attr(attributeName, value);
          }
        };

        scope.$watch('interpolateParams', updateTranslations, true);

        // Ensures the text will be refreshed after the current language was changed
        // w/ $translate.use(...)
        var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);

        updateTranslations();
        scope.$on('$destroy', unbind);
      };
    }
  };
}]);
<commit_msg>fix(directive): ensure directive's text will be parsed at least once<commit_after>angular.module('pascalprecht.translate')
/**
 * @ngdoc directive
 * @name pascalprecht.translate.directive:translate
 * @requires $compile
 * @requires $filter
 * @requires $interpolate
 * @restrict A
 *
 * @description
 * Translates given translation id either through attribute or DOM content.
 * Internally it uses `translate` filter to translate translation id. It possible to
 * pass an optional `translate-values` object literal as string into translation id.
 *
 * @param {string=} translate Translation id which could be either string or interpolated string.
 * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
 * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
 *
 * @example
   <example module="ngView">
    <file name="index.html">

TRANSLATION_ID

{{translationId}}

WITH_VALUES

WITH_VALUES

    </file>
    <file name="script.js">
      angular.module('ngView', ['pascalprecht.translate'])

      .config(function ($translateProvider) {

        $translateProvider.translations('en',{
          'TRANSLATION_ID': 'Hello there!',
          'WITH_VALUES': 'The following value is dynamic: {{value}}'
        }).preferredLanguage('en');

      });

      angular.module('ngView').controller('TranslateCtrl', function ($scope) {
        $scope.translationId = 'TRANSLATION_ID';

        $scope.values = {
          value: 78
        };
      });
    </file>
    <file name="scenario.js">
      it('should translate', function () {
        inject(function ($rootScope, $compile) {
          $rootScope.translationId = 'TRANSLATION_ID';

          element = $compile('

')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('

')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('
TRANSLATION_ID
')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('
{{translationId}}
')($rootScope);
          $rootScope.$digest();
          expect(element.text()).toBe('Hello there!');

          element = $compile('

')($rootScope);
          $rootScope.$digest();
          expect(element.attr('title')).toBe('Hello there!');
        });
      });
    </file>
   </example>
 */
.directive('translate', ['$translate', '$q', '$interpolate', '$compile', '$parse', '$rootScope', function ($translate, $q, $interpolate, $compile, $parse, $rootScope) {

  return {
    restrict: 'AE',
    scope: true,
    compile: function (tElement, tAttr) {

      var translateValuesExist = (tAttr.translateValues) ?
        tAttr.translateValues : undefined;

      var translateInterpolation = (tAttr.translateInterpolation) ?
        tAttr.translateInterpolation : undefined;

      var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);

      var interpolateRegExp = "^(.*)(" + $interpolate.startSymbol() + ".*" + $interpolate.endSymbol() + ")(.*)";

      return function linkFn(scope, iElement, iAttr) {

        scope.interpolateParams = {};
        scope.preText = "";
        scope.postText = "";
        var translationIds = {};

        // Ensures any change of the attribute "translate" containing the id will
        // be re-stored to the scope's "translationId".
        // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
        var observeElementTranslation = function (translationId) {
          if (angular.equals(translationId , '') || !angular.isDefined(translationId)) {
            // Resolve translation id by inner html if required
            var interpolateMatches = iElement.text().match(interpolateRegExp);
            // Interpolate translation id if required
            if (angular.isArray(interpolateMatches)) {
              scope.preText = interpolateMatches[1];
              scope.postText = interpolateMatches[3];
              translationIds['translate'] = $interpolate(interpolateMatches[2])(scope.$parent);
            } else {
              translationIds['translate'] = iElement.text().replace(/^\s+|\s+$/g,'');
            }
          } else {
            translationIds['translate'] = translationId;
          }
          updateTranslations();
        };

        var observeAttributeTranslation = function (translateAttr) {
          iAttr.$observe(translateAttr, function (translationId) {
            translationIds[translateAttr] = translationId;
            updateTranslations();
          });
        };

        iAttr.$observe('translate', function (translationId) {
          observeElementTranslation(translationId);
        });

        for (var translateAttr in iAttr) {
          if(iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr') {
            observeAttributeTranslation(translateAttr);
          }
        }

        iAttr.$observe('translateDefault', function (value) {
          scope.defaultText = value;
        });

        if (translateValuesExist) {
          iAttr.$observe('translateValues', function (interpolateParams) {
            if (interpolateParams) {
              scope.$parent.$watch(function () {
                angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
              });
            }
          });
        }

        if (translateValueExist) {
          var observeValueAttribute = function (attrName) {
            iAttr.$observe(attrName, function (value) {
              var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
              scope.interpolateParams[attributeName] = value;
            });
          };
          for (var attr in iAttr) {
            if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
              observeValueAttribute(attr);
            }
          }
        }

        // Master update function
        var updateTranslations = function () {
          for (var key in translationIds) {
            if (translationIds.hasOwnProperty(key) && translationIds[key]) {
              updateTranslation(key, translationIds[key], scope, scope.interpolateParams);
            }
          }
        };

        // Put translation processing function outside loop
        var updateTranslation = function(translateAttr, translationId, scope, interpolateParams) {
          $translate(translationId, interpolateParams, translateInterpolation)
            .then(function (translation) {
              applyTranslation(translation, scope, true, translateAttr);
            }, function (translationId) {
              applyTranslation(translationId, scope, false, translateAttr);
            });
        };

        var applyTranslation = function (value, scope, successful, translateAttr) {
          if (translateAttr === 'translate') {
            // default translate into innerHTML
            if (!successful && typeof scope.defaultText !== 'undefined') {
              value = scope.defaultText;
            }
            iElement.html(scope.preText + value + scope.postText);
            var globallyEnabled = $translate.isPostCompilingEnabled();
            var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
            var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
            if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
              $compile(iElement.contents())(scope);
            }
          } else {
            // translate attribute
            if (!successful && typeof scope.defaultText !== 'undefined') {
              value = scope.defaultText;
            }
            var attributeName = iAttr.$attr[translateAttr].substr(15);
            iElement.attr(attributeName, value);
          }
        };

        scope.$watch('interpolateParams', updateTranslations, true);

        // Ensures the text will be refreshed after the current language was changed
        // w/ $translate.use(...)
        var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);

        // ensure translation will be looked up at least one
        if (iElement.text().length) {
          observeElementTranslation('');
        }
        updateTranslations();
        scope.$on('$destroy', unbind);
      };
    }
  };
}]);
<|endoftext|>


<commit_before>goog.provide('ngeo_btngroup_directive');

goog.require('ngeo');

/**
 * This directive allows creating "toggle" groups. It works with the `ngeo-btn`
 * directive.
 *
 * Example:
 *
 * <div ngeo-btn-group>
 *   <button ngeo-btn class="btn" ng-model="ctrl.drawPoint.active"></button>
 *   <button ngeo-btn class="btn" ng-model="ctrl.drawLine.active"></button>
 * </div>
 *
 * In that example the `ngeo-btn` are combined together in a "toggle group",
 * where activating a button will deactivate the others.
 *
 * See the `ngeo-btn` directive for more information.
 */
ngeoModule.directive('ngeoBtnGroup', [
  /**
   * @return {angular.Directive} The directive specs.
   */
  function() {
    return {
      restrict: 'A',
      controller: 'ngeoBtnGroupController'
    };
  }]);

ngeoModule.controller('ngeoBtnGroupController', ['$scope',
  /**
   * @param {angular.Scope} $scope Scope.
   */
  function($scope) {
    /** @type {Array.<function(!angular.Scope, boolean)>} */
    var buttons = [];

    /**
     * @param {number} index Index of the button in buttons array.
     */
    this.activate = function(index) {
      goog.array.forEach(buttons,
          function(s, i) {
            if (i != index) {
              s($scope, false);
            }
          });
    };

    /**
     * @param {function((!angular.Scope), *)} ngModelSet Setter.
     * @return {number} Index of the pushed setter.
     */
    this.addButton = function(ngModelSet) {
      buttons.push(ngModelSet);
      return buttons.length - 1;
    };
  }]);

/**
 * This directive allows creating toggle buttons working with `ng-model`.
 * To be typically used with Bootstrap buttons (`btn`).
 *
 * Example:
 *
 * <button ngeo-btn class="btn" ng-model="ctrl.interaction.active"></button>
 *
 * This example is about creating a Bootstrap button that can pressed/depressed
 * to activate/deactivate an OpenLayers 3 interaction.
 *
 * The `ngeo-btn` directive can be used alone, as in the above example, or
 * together with the `ngeo-btn-group` directive.
 *
 * Example:
 *
 * <div ngeo-btn-group>
 *   <button ngeo-btn class="btn" ng-model="ctrl.drawPoint.active"></button>
 *   <button ngeo-btn class="btn" ng-model="ctrl.drawLine.active"></button>
 * </div>
 *
 * In that example the `ngeo-btn` are combined together in a "toggle group",
 * where activating a button will deactivate the others.
 */
ngeoModule.directive('ngeoBtn', ['$parse',
  /**
   * @return {angular.Directive} The directive specs.
   */
  function($parse) {
    return {
      require: ['?^ngeoBtnGroup', 'ngModel'],
      restrict: 'A',
      link:
          /**
           * @param {angular.Scope} scope Scope.
           * @param {angular.JQLite} element Element.
           * @param {angular.Attributes} attrs Attributes.
           * @param {!Array.<!Object>} ctrls Controllers.
           */
          function(scope, element, attrs, ctrls) {
            var buttonsCtrl = ctrls[0];
            var ngModelCtrl = ctrls[1];
            var indexInGroup = -1;

            var ngModelGet = $parse(attrs['ngModel']);
            var ngModelSet = ngModelGet.assign;

            // Set ng-model value to false if undefined
            if (!goog.isDef(ngModelGet(scope))) {
              ngModelSet(scope, false);
            }
            if (!goog.isNull(buttonsCtrl)) {
              indexInGroup = buttonsCtrl.addButton(ngModelSet);
            }

            // UI -> model
            element.bind('click', function() {
              scope.$apply(function() {
                ngModelCtrl.$setViewValue(!ngModelCtrl.$viewValue);
                ngModelCtrl.$render();
              });
            });

            // model -> UI
            ngModelCtrl.$render = function() {
              if (ngModelCtrl.$viewValue && !goog.isNull(buttonsCtrl)) {
                buttonsCtrl.activate(indexInGroup);
              }
              element.toggleClass('active', ngModelCtrl.$viewValue);
            };
          }
    };
  }]);
<commit_msg>New style guide for btn directive<commit_after>goog.provide('ngeo.btnDirective');
goog.provide('ngeo.btngroupDirective');

goog.require('ngeo');

/**
 * This directive allows creating "toggle" groups. It works with the `ngeo-btn`
 * directive.
 *
 * Example:
 *
 * <div ngeo-btn-group>
 *   <button ngeo-btn class="btn" ng-model="ctrl.drawPoint.active"></button>
 *   <button ngeo-btn class="btn" ng-model="ctrl.drawLine.active"></button>
 * </div>
 *
 * In that example the `ngeo-btn` are combined together in a "toggle group",
 * where activating a button will deactivate the others.
 *
 * See the `ngeo-btn` directive for more information.
 *
 * @return {angular.Directive} The directive specs.
 * @ngInject
 */
ngeo.btngroupDirective = function() {
  return {
    restrict: 'A',
    controller: 'ngeoBtnGroupController'
  };
};

ngeoModule.directive('ngeoBtnGroup', ngeo.btngroupDirective);

/**
 * @param {angular.Scope} $scope Scope.
 * @constructor
 * @ngInject
 */
ngeo.BtnGroupController = function($scope) {
  /**
   * @type {Array.<function(!angular.Scope, boolean)>}
   * @private
   */
  this.buttons_ = [];

  /**
   * @type {angular.Scope}
   * @private
   */
  this.scope_ = $scope;
};

/**
 * @param {number} index Index of the button in buttons array.
 */
ngeo.BtnGroupController.prototype.activate = function(index) {
  goog.array.forEach(this.buttons_,
      function(s, i) {
        if (i != index) {
          s(this.scope_, false);
        }
      }, this);
};

/**
 * @param {function((!angular.Scope), *)} ngModelSet Setter.
 * @return {number} Index of the pushed setter.
 */
ngeo.BtnGroupController.prototype.addButton = function(ngModelSet) {
  this.buttons_.push(ngModelSet);
  return this.buttons_.length - 1;
};

ngeoModule.controller('ngeoBtnGroupController', ngeo.BtnGroupController);

/**
 * This directive allows creating toggle buttons working with `ng-model`.
 * To be typically used with Bootstrap buttons (`btn`).
 *
 * Example:
 *
 * <button ngeo-btn class="btn" ng-model="ctrl.interaction.active"></button>
 *
 * This example is about creating a Bootstrap button that can pressed/depressed
 * to activate/deactivate an OpenLayers 3 interaction.
 *
 * The `ngeo-btn` directive can be used alone, as in the above example, or
 * together with the `ngeo-btn-group` directive.
 *
 * Example:
 *
 * <div ngeo-btn-group>
 *   <button ngeo-btn class="btn" ng-model="ctrl.drawPoint.active"></button>
 *   <button ngeo-btn class="btn" ng-model="ctrl.drawLine.active"></button>
 * </div>
 *
 * In that example the `ngeo-btn` are combined together in a "toggle group",
 * where activating a button will deactivate the others.
 *
 * @param {angular.$parse} $parse Angular parse service.
 * @return {angular.Directive} The directive specs.
 * @ngInject
 */
ngeo.btnDirective = function($parse) {
  return {
    require: ['?^ngeoBtnGroup', 'ngModel'],
    restrict: 'A',
    link:
        /**
         * @param {angular.Scope} scope Scope.
         * @param {angular.JQLite} element Element.
         * @param {angular.Attributes} attrs Attributes.
         * @param {!Array.<!Object>} ctrls Controllers.
         */
        function(scope, element, attrs, ctrls) {
          var buttonsCtrl = ctrls[0];
          var ngModelCtrl = ctrls[1];
          var indexInGroup = -1;

          var ngModelGet = $parse(attrs['ngModel']);
          var ngModelSet = ngModelGet.assign;

          // Set ng-model value to false if undefined
          if (!goog.isDef(ngModelGet(scope))) {
            ngModelSet(scope, false);
          }
          if (!goog.isNull(buttonsCtrl)) {
            indexInGroup = buttonsCtrl.addButton(ngModelSet);
          }

          // UI -> model
          element.bind('click', function() {
            scope.$apply(function() {
              ngModelCtrl.$setViewValue(!ngModelCtrl.$viewValue);
              ngModelCtrl.$render();
            });
          });

          // model -> UI
          ngModelCtrl.$render = function() {
            if (ngModelCtrl.$viewValue && !goog.isNull(buttonsCtrl)) {
              buttonsCtrl.activate(indexInGroup);
            }
            element.toggleClass('active', ngModelCtrl.$viewValue);
          };
        }
  };
};

ngeoModule.directive('ngeoBtn', ngeo.btnDirective);
<|endoftext|>


<commit_before>/**
 * Directive that handles the model arrays
 */
angular.module('schemaForm').directive('sfNewArray', ['sfSelect', 'sfPath', 'schemaForm',
function(sel, sfPath, schemaForm) {
  return {
    scope: false,
    link: function(scope, element, attrs) {
      scope.min = 0;

      scope.modelArray = scope.$eval(attrs.sfNewArray);

      // We need to have a ngModel to hook into validation. It doesn't really play well with
      // arrays though so we both need to trigger validation and onChange.
      // So we watch the value as well. But watching an array can be tricky. We wan't to know
      // when it changes so we can validate,
      var watchFn =  function() {
        //scope.modelArray = modelArray;
        scope.modelArray = scope.$eval(attrs.sfNewArray);
        // validateField method is exported by schema-validate
        if (scope.validateField) {
          scope.validateField();
        }
      };

      var onChangeFn =  function() {
        if (scope.form && scope.form.onChange) {
          if (angular.isFunction(scope.form.onChange)) {
            scope.form.onChange(scope.modelArray, scope.form);
          } else {
            scope.evalExpr(scope.form.onChange, {'modelValue': scope.modelArray, form: scope.form});
          }
        }
      };

      // We need the form definition to make a decision on how we should listen.
      var once = scope.$watch('form', function(form) {
        if (!form) {
          return;
        }

        // Always start with one empty form unless configured otherwise.
        // Special case: don't do it if form has a titleMap
        if (!form.titleMap && form.startEmpty !== true && (!scope.modelArray || scope.modelArray.length === 0)) {
          scope.appendToArray();
        }

        // If we have "uniqueItems" set to true, we must deep watch for changes.
        if (scope.form && scope.form.schema && scope.form.schema.uniqueItems === true) {
          scope.$watch(attrs.sfNewArray, watchFn, true);

          // We still need to trigger onChange though.
          scope.$watch([attrs.sfNewArray, attrs.sfNewArray + '.length'], onChangeFn);

        } else {
          // Otherwise we like to check if the instance of the array has changed, or if something
          // has been added/removed.
          if (scope.$watchGroup) {
            scope.$watchGroup([attrs.sfNewArray, attrs.sfNewArray + '.length'], function() {
              watchFn();
              onChangeFn();
            });
          } else {
            // Angular 1.2 support
            scope.$watch(attrs.sfNewArray, function() {
              watchFn();
              onChangeFn();
            });
            scope.$watch(attrs.sfNewArray + '.length', function() {
              watchFn();
              onChangeFn();
            });
          }
        }

        // Title Map handling
        // If form has a titleMap configured we'd like to enable looping over
        // titleMap instead of modelArray, this is used for intance in
        // checkboxes. So instead of variable number of things we like to create
        // a array value from a subset of values in the titleMap.
        // The problem here is that ng-model on a checkbox doesn't really map to
        // a list of values. This is here to fix that.
        if (form.titleMap && form.titleMap.length > 0) {
          scope.titleMapValues = [];

          // We watch the model for changes and the titleMapValues to reflect
          // the modelArray
          var updateTitleMapValues = function(arr) {
            scope.titleMapValues = [];
            arr = arr || [];

            form.titleMap.forEach(function(item) {
              scope.titleMapValues.push(arr.indexOf(item.value) !== -1);
            });
          };
          //Catch default values
          updateTitleMapValues(scope.modelArray);

          // TODO: Refactor and see if we can get rid of this watch by piggy backing on the
          // validation watch.
          scope.$watchCollection('modelArray', updateTitleMapValues);

          //To get two way binding we also watch our titleMapValues
          scope.$watchCollection('titleMapValues', function(vals, old) {
            if (vals && vals !== old) {
              var arr = scope.modelArray;

              // Apparently the fastest way to clear an array, readable too.
              // http://jsperf.com/array-destroy/32
              while (arr.length > 0) {
                arr.pop();
              }
              form.titleMap.forEach(function(item, index) {
                if (vals[index]) {
                  arr.push(item.value);
                }
              });

              // Time to validate the rebuilt array.
              // validateField method is exported by schema-validate
              if (scope.validateField) {
                scope.validateField();
              }
            }
          });
        }

        once();
      });

      scope.appendToArray = function() {

        var empty;

        // Same old add empty things to the array hack :(
        if (scope.form && scope.form.schema) {
          if (scope.form.schema.items) {
            if (scope.form.schema.items.type === 'object') {
              empty = {};
            } else if (scope.form.schema.items.type === 'array') {
              empty = [];
            }
          }
        }

        var model = scope.modelArray;
        if (!model) {
          // Create and set an array if needed.
          var selection = sfPath.parse(attrs.sfNewArray);
          model = [];
          sel(selection, scope, model);
          scope.modelArray = model;
        }
        model.push(empty);

        return model;
      };

      scope.deleteFromArray = function(index) {
        var model = scope.modelArray;
        if (model) {
          model.splice(index, 1);
        }
        return model;
      };

      // For backwards compatability, i.e. when a bootstrap-decorator tag is used
      // as child to the array.
      var setIndex = function(index) {
        return function(form) {
          if (form.key) {
            form.key[form.key.indexOf('')] = index;
          }
        };
      };
      var formDefCache = {};
      scope.copyWithIndex = function(index) {
        var form = scope.form;
        if (!formDefCache[index]) {

          // To be more compatible with JSON Form we support an array of items
          // in the form definition of "array" (the schema just a value).
          // for the subforms code to work this means we wrap everything in a
          // section. Unless there is just one.
          var subForm = form.items[0];
          if (form.items.length > 1) {
            subForm = {
              type: 'section',
              items: form.items.map(function(item) {
                item.ngModelOptions = form.ngModelOptions;
                if (angular.isUndefined(item.readonly)) {
                  item.readonly = form.readonly;
                }
                return item;
              })
            };
          }

          if (subForm) {
            var copy = angular.copy(subForm);
            copy.arrayIndex = index;
            schemaForm.traverseForm(copy, setIndex(index));
            formDefCache[index] = copy;
          }
        }
        return formDefCache[index];
      };

    }
  };
}]);
<commit_msg>New builder: defaults when adding to array.<commit_after>/**
 * Directive that handles the model arrays
 */
angular.module('schemaForm').directive('sfNewArray', ['sfSelect', 'sfPath', 'schemaForm',
function(sel, sfPath, schemaForm) {
  return {
    scope: false,
    link: function(scope, element, attrs) {
      scope.min = 0;

      scope.modelArray = scope.$eval(attrs.sfNewArray);

      // We need to have a ngModel to hook into validation. It doesn't really play well with
      // arrays though so we both need to trigger validation and onChange.
      // So we watch the value as well. But watching an array can be tricky. We wan't to know
      // when it changes so we can validate,
      var watchFn =  function() {
        //scope.modelArray = modelArray;
        scope.modelArray = scope.$eval(attrs.sfNewArray);
        // validateField method is exported by schema-validate
        if (scope.validateField) {
          scope.validateField();
        }
      };

      var onChangeFn =  function() {
        if (scope.form && scope.form.onChange) {
          if (angular.isFunction(scope.form.onChange)) {
            scope.form.onChange(scope.modelArray, scope.form);
          } else {
            scope.evalExpr(scope.form.onChange, {'modelValue': scope.modelArray, form: scope.form});
          }
        }
      };

      // We need the form definition to make a decision on how we should listen.
      var once = scope.$watch('form', function(form) {
        if (!form) {
          return;
        }

        // Always start with one empty form unless configured otherwise.
        // Special case: don't do it if form has a titleMap
        if (!form.titleMap && form.startEmpty !== true && (!scope.modelArray || scope.modelArray.length === 0)) {
          scope.appendToArray();
        }

        // If we have "uniqueItems" set to true, we must deep watch for changes.
        if (scope.form && scope.form.schema && scope.form.schema.uniqueItems === true) {
          scope.$watch(attrs.sfNewArray, watchFn, true);

          // We still need to trigger onChange though.
          scope.$watch([attrs.sfNewArray, attrs.sfNewArray + '.length'], onChangeFn);

        } else {
          // Otherwise we like to check if the instance of the array has changed, or if something
          // has been added/removed.
          if (scope.$watchGroup) {
            scope.$watchGroup([attrs.sfNewArray, attrs.sfNewArray + '.length'], function() {
              watchFn();
              onChangeFn();
            });
          } else {
            // Angular 1.2 support
            scope.$watch(attrs.sfNewArray, function() {
              watchFn();
              onChangeFn();
            });
            scope.$watch(attrs.sfNewArray + '.length', function() {
              watchFn();
              onChangeFn();
            });
          }
        }

        // Title Map handling
        // If form has a titleMap configured we'd like to enable looping over
        // titleMap instead of modelArray, this is used for intance in
        // checkboxes. So instead of variable number of things we like to create
        // a array value from a subset of values in the titleMap.
        // The problem here is that ng-model on a checkbox doesn't really map to
        // a list of values. This is here to fix that.
        if (form.titleMap && form.titleMap.length > 0) {
          scope.titleMapValues = [];

          // We watch the model for changes and the titleMapValues to reflect
          // the modelArray
          var updateTitleMapValues = function(arr) {
            scope.titleMapValues = [];
            arr = arr || [];

            form.titleMap.forEach(function(item) {
              scope.titleMapValues.push(arr.indexOf(item.value) !== -1);
            });
          };
          //Catch default values
          updateTitleMapValues(scope.modelArray);

          // TODO: Refactor and see if we can get rid of this watch by piggy backing on the
          // validation watch.
          scope.$watchCollection('modelArray', updateTitleMapValues);

          //To get two way binding we also watch our titleMapValues
          scope.$watchCollection('titleMapValues', function(vals, old) {
            if (vals && vals !== old) {
              var arr = scope.modelArray;

              // Apparently the fastest way to clear an array, readable too.
              // http://jsperf.com/array-destroy/32
              while (arr.length > 0) {
                arr.pop();
              }
              form.titleMap.forEach(function(item, index) {
                if (vals[index]) {
                  arr.push(item.value);
                }
              });

              // Time to validate the rebuilt array.
              // validateField method is exported by schema-validate
              if (scope.validateField) {
                scope.validateField();
              }
            }
          });
        }

        once();
      });

      scope.appendToArray = function() {
        var empty;

        // Create and set an array if needed.
        var model = scope.modelArray;
        if (!model) {
          var selection = sfPath.parse(attrs.sfNewArray);
          model = [];
          sel(selection, scope, model);
          scope.modelArray = model;
        }

        // Same old add empty things to the array hack :(
        if (scope.form && scope.form.schema && scope.form.schema.items) {

          var items = scope.form.schema.items;
          if (items.type && items.type.indexOf('object') !== -1) {
            empty = {};

            // Check for possible defaults
            if (!scope.options || scope.options.setSchemaDefaults !== false) {
              empty = angular.isDefined(items['default']) ? items['default'] : empty;

              // Check for defaults further down in the schema.
              // If the default instance sets the new array item to something falsy, i.e. null
              // then there is no need to go further down.
              if (empty) {
                schemaForm.traverseSchema(items, function(prop, path) {
                  if (angular.isDefined(prop['default'])) {
                    sel(path, empty, prop['default']);
                  }
                });
              }
            }

          } else if (items.type && items.type.indexOf('array') !== -1) {
            empty = [];
            if (!scope.options || scope.options.setSchemaDefaults !== false) {
              empty = items['default'] || empty;
            }
          } else {
            // No type? could still have defaults.
            if (!scope.options || scope.options.setSchemaDefaults !== false) {
              empty = items['default'] || empty;
            }
          }
        }
        model.push(empty);

        return model;
      };

      scope.deleteFromArray = function(index) {
        var model = scope.modelArray;
        if (model) {
          model.splice(index, 1);
        }
        return model;
      };

      // For backwards compatability, i.e. when a bootstrap-decorator tag is used
      // as child to the array.
      var setIndex = function(index) {
        return function(form) {
          if (form.key) {
            form.key[form.key.indexOf('')] = index;
          }
        };
      };
      var formDefCache = {};
      scope.copyWithIndex = function(index) {
        var form = scope.form;
        if (!formDefCache[index]) {

          // To be more compatible with JSON Form we support an array of items
          // in the form definition of "array" (the schema just a value).
          // for the subforms code to work this means we wrap everything in a
          // section. Unless there is just one.
          var subForm = form.items[0];
          if (form.items.length > 1) {
            subForm = {
              type: 'section',
              items: form.items.map(function(item) {
                item.ngModelOptions = form.ngModelOptions;
                if (angular.isUndefined(item.readonly)) {
                  item.readonly = form.readonly;
                }
                return item;
              })
            };
          }

          if (subForm) {
            var copy = angular.copy(subForm);
            copy.arrayIndex = index;
            schemaForm.traverseForm(copy, setIndex(index));
            formDefCache[index] = copy;
          }
        }
        return formDefCache[index];
      };

    }
  };
}]);
<|endoftext|>


<commit_before>/**
 * Created by artem on 10/7/15.
 */
(function (angular, geoXML3) {
    'use strict';
    angular.module('LogicifyGMap')
        .directive('xmlOverlays', [
            '$timeout',
            '$log',
            '$q',
            '$compile',
            '$http',
            'SmartCollection',
            function ($timeout, $log, $q, $compile, $http, SmartCollection) {
                return {
                    restrict: 'E',
                    require: '^logicifyGmap',
                    link: function (scope, element, attrs, ctrl) {
                        var geoXml3Parser = null;
                        scope.kmlCollection = new SmartCollection(scope.$eval(attrs['kmlCollection']));
                        var currentCollectionPrefix = scope.kmlCollection._uid;
                        scope.events = scope.$eval(attrs['gmapEvents']) || {};
                        scope.parserOptions = scope.$eval(attrs['parserOptions']) || {};
                        scope.onProgress = scope.$eval(attrs['onProgress']);
                        scope.fitBoundsAfterAll = scope.$eval(attrs['fitAllLayers']); //true by default
                        var promises = [], PROMISE_STATUSES = {PENDING: 0, RESOLVED: 1, REJECTED: 2};
                        ;

                        function getParserOptions(map, wnd) {
                            var opts = {};
                            angular.extend(opts, scope.parserOptions);
                            //override options
                            opts.map = map;
                            opts.afterParse = afterParse;
                            opts.onAfterCreateGroundOverlay = scope.events.onAfterCreateGroundOverlay;
                            opts.onAfterCreatePolygon = scope.events.onAfterCreatePolygon;
                            opts.onAfterCreatePolyLine = scope.events.onAfterCreatePolyLine;
                            opts.failedParse = failedParse;
                            opts.infoWindow = wnd;
                            return opts;
                        }

                        /**
                         * get google map object from controller
                         */
                        ctrl.$mapReady(function (map) {
                            scope.infowindow = scope.$eval(attrs['infoWindow']);
                            scope.collectionsWatcher = attachCollectionWatcher();
                            scope.gMap = map;
                            if (scope.infowindow && typeof scope.infowindow.$ready === 'function') {
                                scope.infowindow.$ready(function (wnd) {
                                    geoXml3Parser = new geoXML3.parser(getParserOptions(map, wnd));
                                    initKmlCollection();
                                });
                            } else {
                                geoXml3Parser = new geoXML3.parser(getParserOptions(map));
                                initKmlCollection();
                            }
                        });
                        scope.$on('$destroy', function () {
                            if (typeof scope.collectionsWatcher === 'function') {
                                scope.collectionsWatcher();//cancel watcher
                            }
                        });

                        /**
                         *
                         * @return {function()|*} listener
                         */
                        function attachCollectionWatcher() {
                            return scope.$watch('kmlCollection._uid', function (newValue, oldValue) {
                                //watch for top level object reference change
                                if (newValue == null || newValue != currentCollectionPrefix) {
                                    if (!(scope.kmlCollection instanceof SmartCollection)) {
                                        scope.kmlCollection = new SmartCollection(scope.$eval(attrs['kmlCollection']));
                                    }
                                    currentCollectionPrefix = scope.kmlCollection._uid;
                                    if (scope['downLoadingStarted'] === true || scope['parserStarted'] === true) {
                                        promises.forEach(function (promise) {
                                            promise._abort();
                                        });
                                        promises.splice(0, promises.length);
                                        clearAll();
                                    }
                                    initKmlCollection().then(function () {
                                        promises.splice(0, promises.length);
                                    });
                                }
                            });
                        }

                        function onAddArrayItem(item) {
                            if (item != null) {
                                downLoadOverlayFile(item);
                            }
                        }

                        function onRemoveArrayItem(item) {
                            clearAll(item);
                        }

                        /**
                         * Fires when kml or kmz file has been parsed
                         * @param doc - Array that contains only one item: [0] = {Document}
                         */
                        function afterParse(doc, promise) {
                            doc[0].$uid = new Date().getTime() + '-index-' + Math.floor(Math.random() * ( -9));
                            if (typeof scope.events.onAfterParse === 'function') {
                                scope.events.onAfterParse(doc);
                            }
                            if (promise) {
                                promise.resolve(doc);
                            }
                        }

                        /**
                         * Fires when failed parse kmz or kml
                         */
                        function failedParse(doc, promise) {
                            if (promise) {
                                promise.reject(doc);
                            }
                            if (typeof scope.events.onAfterParseFailed === 'function') {
                                scope.events.onAfterParseFailed(doc);
                            }
                        }

                        function initGlobalBounds() {
                            scope.globalBounds = new google.maps.LatLngBounds();
                            if (scope.kmlCollection.length != 1 && scope.fitBoundsAfterAll !== false) {
                                scope.kmlCollection.forEach(function (item) {
                                    scope.globalBounds.extend(item.doc[0].bounds.getCenter());
                                });
                                $timeout(function () {
                                    scope.gMap.fitBounds(scope.globalBounds);
                                }, 10);
                            } else if (scope.kmlCollection.length > 0 && scope.fitBoundsAfterAll !== false) {
                                $timeout(function () {
                                    scope.gMap.fitBounds(scope.kmlCollection[0].doc[0].bounds);
                                }, 10);
                            }
                        }

                        /**
                         * Cleanup
                         */
                        function clearAll(item) {
                            if (item) {
                                geoXml3Parser.hideDocument(item.doc[0]);
                                var index = geoXml3Parser.docs.indexOf(item.doc[0]);
                                if (index > -1) {
                                    delete geoXml3Parser.docsByUrl[item.doc[0].baseUrl];
                                    geoXml3Parser.docs.splice(index, 1);
                                    initGlobalBounds();
                                }
                            } else {
                                angular.forEach(geoXml3Parser.docs, function (doc) {
                                    geoXml3Parser.hideDocument(doc);
                                });
                                geoXml3Parser.docs.splice(0, geoXml3Parser.docs.length);
                                geoXml3Parser.docsByUrl = {};
                                scope.globalBounds = null;
                            }
                        }

                        /**
                         * Download all files by asset
                         */
                        function initKmlCollection() {
                            if (scope.kmlCollection instanceof SmartCollection) {
                                scope['finished'] = false;
                                scope.kmlCollection.onAddItem(onAddArrayItem);
                                scope.kmlCollection.onRemoveItem(onRemoveArrayItem);
                                scope.kmlCollection.forEach(function (kmlFile) {
                                    promises.push(downLoadOverlayFile(kmlFile));
                                });
                                return $q.all(promises).then(function (results) {
                                    initGlobalBounds();
                                    //clear all promises;
                                    promises.splice(0, promises.length);
                                });
                            }
                        }

                        /**
                         * Each time when "downLoadingStarted" or "parserStarted" changes we are calling this callback
                         */
                        function progress() {
                            if (typeof scope.onProgress === 'function') {
                                scope.onProgress({
                                    total: promises.length,
                                    done: getCountOf(PROMISE_STATUSES.RESOLVED),
                                    errors: getCountOf(PROMISE_STATUSES.REJECTED)
                                });
                            }
                        }

                        function getCountOf(statusCode) {
                            var count = 0;
                            promises.forEach(function (promise) {
                                if (promise.$$state.status === statusCode) {
                                    count++;
                                }
                            });
                            return count;
                        }

                        /**
                         * FIred when we need to start downloading of new kml or kmz file
                         * @param kmlObject
                         * @return {boolean}
                         */
                        function downLoadOverlayFile(kmlObject) {
                            var deferred = $q.defer();
                            var httpCanceler = $q.defer();
                            deferred.promise._abort = function () {
                                deferred.reject();
                                httpCanceler.resolve();
                            };
                            if (kmlObject.url != null) {
                                $http.get(kmlObject.url, {timeout: httpCanceler.promise, responseType: "arraybuffer"})
                                    .then(function (response) {
                                        var data = new Blob([response.data], {type: response.headers()['content-type']});
                                        data.lastModifiedDate = new Date();
                                        data.name = 'example' + data.lastModifiedDate;
                                        onAfterDownload(data, null, deferred);
                                    });

                            } else if (typeof kmlObject.content === 'String') {
                                onAfterDownload(null, kmlObject.content, deferred);
                            } else {
                                if (kmlObject instanceof Blob) {
                                    onAfterDownload(kmlObject, null, deferred);
                                } else {
                                    $log.error('Incorrect file type. Should be an instance of a Blob or String (url).');
                                }
                            }
                            var promise = deferred.promise
                                .then(function (doc) {
                                    kmlObject.doc = doc;
                                })
                                .catch(function (doc) {
                                    //handle errors here
                                })
                                .finally(function () {
                                    progress();
                                });
                            return promise;
                        }

                        /**
                         * When downloading finished we need start parsing
                         * @param blob - if it's a file
                         * @param content - if it's a string
                         */
                        function onAfterDownload(blob, content, deferred) {
                            content == null ? geoXml3Parser.parse(blob, null, deferred) : geoXml3Parser.parseKmlString(content, null, deferred);
                        }
                    }
                }
            }
        ]);
})(angular, geoXML3);<commit_msg>clear all pending promises on scope destroy<commit_after>/**
 * Created by artem on 10/7/15.
 */
(function (angular, geoXML3) {
    'use strict';
    angular.module('LogicifyGMap')
        .directive('xmlOverlays', [
            '$timeout',
            '$log',
            '$q',
            '$compile',
            '$http',
            'SmartCollection',
            function ($timeout, $log, $q, $compile, $http, SmartCollection) {
                return {
                    restrict: 'E',
                    require: '^logicifyGmap',
                    link: function (scope, element, attrs, ctrl) {
                        var geoXml3Parser = null;
                        scope.kmlCollection = new SmartCollection(scope.$eval(attrs['kmlCollection']));
                        var currentCollectionPrefix = scope.kmlCollection._uid;
                        scope.events = scope.$eval(attrs['gmapEvents']) || {};
                        scope.parserOptions = scope.$eval(attrs['parserOptions']) || {};
                        scope.onProgress = scope.$eval(attrs['onProgress']);
                        scope.fitBoundsAfterAll = scope.$eval(attrs['fitAllLayers']); //true by default
                        var promises = [], PROMISE_STATUSES = {PENDING: 0, RESOLVED: 1, REJECTED: 2};
                        ;

                        function getParserOptions(map, wnd) {
                            var opts = {};
                            angular.extend(opts, scope.parserOptions);
                            //override options
                            opts.map = map;
                            opts.afterParse = afterParse;
                            opts.onAfterCreateGroundOverlay = scope.events.onAfterCreateGroundOverlay;
                            opts.onAfterCreatePolygon = scope.events.onAfterCreatePolygon;
                            opts.onAfterCreatePolyLine = scope.events.onAfterCreatePolyLine;
                            opts.failedParse = failedParse;
                            opts.infoWindow = wnd;
                            return opts;
                        }

                        /**
                         * get google map object from controller
                         */
                        ctrl.$mapReady(function (map) {
                            scope.infowindow = scope.$eval(attrs['infoWindow']);
                            scope.collectionsWatcher = attachCollectionWatcher();
                            scope.gMap = map;
                            if (scope.infowindow && typeof scope.infowindow.$ready === 'function') {
                                scope.infowindow.$ready(function (wnd) {
                                    geoXml3Parser = new geoXML3.parser(getParserOptions(map, wnd));
                                    initKmlCollection();
                                });
                            } else {
                                geoXml3Parser = new geoXML3.parser(getParserOptions(map));
                                initKmlCollection();
                            }
                        });
                        scope.$on('$destroy', function () {
                            if (typeof scope.collectionsWatcher === 'function') {
                                scope.collectionsWatcher();//cancel watcher
                            }
                            //clear all pending promises
                            promises.forEach(function (promise) {
                                promise._abort();
                            });
                        });

                        /**
                         *
                         * @return {function()|*} listener
                         */
                        function attachCollectionWatcher() {
                            return scope.$watch('kmlCollection._uid', function (newValue, oldValue) {
                                //watch for top level object reference change
                                if (newValue == null || newValue != currentCollectionPrefix) {
                                    if (!(scope.kmlCollection instanceof SmartCollection)) {
                                        scope.kmlCollection = new SmartCollection(scope.$eval(attrs['kmlCollection']));
                                    }
                                    currentCollectionPrefix = scope.kmlCollection._uid;
                                    if (scope['downLoadingStarted'] === true || scope['parserStarted'] === true) {
                                        promises.forEach(function (promise) {
                                            promise._abort();
                                        });
                                        promises.splice(0, promises.length);
                                        clearAll();
                                    }
                                    initKmlCollection().then(function () {
                                        promises.splice(0, promises.length);
                                    });
                                }
                            });
                        }

                        function onAddArrayItem(item) {
                            if (item != null) {
                                downLoadOverlayFile(item);
                            }
                        }

                        function onRemoveArrayItem(item) {
                            clearAll(item);
                        }

                        /**
                         * Fires when kml or kmz file has been parsed
                         * @param doc - Array that contains only one item: [0] = {Document}
                         */
                        function afterParse(doc, promise) {
                            doc[0].$uid = new Date().getTime() + '-index-' + Math.floor(Math.random() * ( -9));
                            if (typeof scope.events.onAfterParse === 'function') {
                                scope.events.onAfterParse(doc);
                            }
                            if (promise) {
                                promise.resolve(doc);
                            }
                        }

                        /**
                         * Fires when failed parse kmz or kml
                         */
                        function failedParse(doc, promise) {
                            if (promise) {
                                promise.reject(doc);
                            }
                            if (typeof scope.events.onAfterParseFailed === 'function') {
                                scope.events.onAfterParseFailed(doc);
                            }
                        }

                        function initGlobalBounds() {
                            scope.globalBounds = new google.maps.LatLngBounds();
                            if (scope.kmlCollection.length != 1 && scope.fitBoundsAfterAll !== false) {
                                scope.kmlCollection.forEach(function (item) {
                                    scope.globalBounds.extend(item.doc[0].bounds.getCenter());
                                });
                                $timeout(function () {
                                    scope.gMap.fitBounds(scope.globalBounds);
                                }, 10);
                            } else if (scope.kmlCollection.length > 0 && scope.fitBoundsAfterAll !== false) {
                                $timeout(function () {
                                    scope.gMap.fitBounds(scope.kmlCollection[0].doc[0].bounds);
                                }, 10);
                            }
                        }

                        /**
                         * Cleanup
                         */
                        function clearAll(item) {
                            if (item) {
                                geoXml3Parser.hideDocument(item.doc[0]);
                                var index = geoXml3Parser.docs.indexOf(item.doc[0]);
                                if (index > -1) {
                                    delete geoXml3Parser.docsByUrl[item.doc[0].baseUrl];
                                    geoXml3Parser.docs.splice(index, 1);
                                    initGlobalBounds();
                                }
                            } else {
                                angular.forEach(geoXml3Parser.docs, function (doc) {
                                    geoXml3Parser.hideDocument(doc);
                                });
                                geoXml3Parser.docs.splice(0, geoXml3Parser.docs.length);
                                geoXml3Parser.docsByUrl = {};
                                scope.globalBounds = null;
                            }
                        }

                        /**
                         * Download all files by asset
                         */
                        function initKmlCollection() {
                            if (scope.kmlCollection instanceof SmartCollection) {
                                scope['finished'] = false;
                                scope.kmlCollection.onAddItem(onAddArrayItem);
                                scope.kmlCollection.onRemoveItem(onRemoveArrayItem);
                                scope.kmlCollection.forEach(function (kmlFile) {
                                    promises.push(downLoadOverlayFile(kmlFile));
                                });
                                return $q.all(promises).then(function (results) {
                                    initGlobalBounds();
                                    //clear all promises;
                                    promises.splice(0, promises.length);
                                });
                            }
                        }

                        /**
                         * Each time when "downLoadingStarted" or "parserStarted" changes we are calling this callback
                         */
                        function progress() {
                            if (typeof scope.onProgress === 'function') {
                                scope.onProgress({
                                    total: promises.length,
                                    done: getCountOf(PROMISE_STATUSES.RESOLVED),
                                    errors: getCountOf(PROMISE_STATUSES.REJECTED)
                                });
                            }
                        }

                        function getCountOf(statusCode) {
                            var count = 0;
                            promises.forEach(function (promise) {
                                if (promise.$$state.status === statusCode) {
                                    count++;
                                }
                            });
                            return count;
                        }

                        /**
                         * FIred when we need to start downloading of new kml or kmz file
                         * @param kmlObject
                         * @return {boolean}
                         */
                        function downLoadOverlayFile(kmlObject) {
                            var deferred = $q.defer();
                            var httpCanceler = $q.defer();
                            deferred.promise._abort = function () {
                                deferred.reject();
                                httpCanceler.resolve();
                            };
                            if (kmlObject.url != null) {
                                $http.get(kmlObject.url, {timeout: httpCanceler.promise, responseType: "arraybuffer"})
                                    .then(function (response) {
                                        var data = new Blob([response.data], {type: response.headers()['content-type']});
                                        data.lastModifiedDate = new Date();
                                        data.name = 'example' + data.lastModifiedDate;
                                        onAfterDownload(data, null, deferred);
                                    });

                            } else if (typeof kmlObject.content === 'String') {
                                onAfterDownload(null, kmlObject.content, deferred);
                            } else {
                                if (kmlObject instanceof Blob) {
                                    onAfterDownload(kmlObject, null, deferred);
                                } else {
                                    $log.error('Incorrect file type. Should be an instance of a Blob or String (url).');
                                }
                            }
                            var promise = deferred.promise
                                .then(function (doc) {
                                    kmlObject.doc = doc;
                                })
                                .catch(function (doc) {
                                    //handle errors here
                                })
                                .finally(function () {
                                    progress();
                                });
                            return promise;
                        }

                        /**
                         * When downloading finished we need start parsing
                         * @param blob - if it's a file
                         * @param content - if it's a string
                         */
                        function onAfterDownload(blob, content, deferred) {
                            content == null ? geoXml3Parser.parse(blob, null, deferred) : geoXml3Parser.parseKmlString(content, null, deferred);
                        }
                    }
                }
            }
        ]);
})(angular, geoXML3);<|endoftext|>