UNPKG

@cevo/angular-util

Version:

Angular utility methods

1,505 lines (1,225 loc) 84.5 kB
(function () { 'use strict'; 'format amd'; function __util(angular, inview) { angular.module('cevo.util', ['angular-inview', 'ui.router']).provider('cevo', function () { var util = { objectify: function objectify(arr, obj) { for (var i in arr) { arr[i] = new obj(arr[i]); }return arr; }, object: { size: function size(obj) { var size = 0, key; for (key in obj) { if (obj.hasOwnProperty(key)) size++; } return size; } }, getAttributes: function getAttributes(obj) { var attributes = {}; for (var i in obj) { if (typeof obj[i] !== 'function') attributes[i] = obj[i]; } return attributes; } }; var config = { notification: { defaultTimeout: 7000, defaultType: 'global' }, oauth: { clientId: '', activationUrl: '' }, env: 'production', endpoint: '', util: util }; return { setEnvironment: function setEnvironment(val) { config.env = val; }, setEndpoint: function setEndpoint(val) { config.endpoint = val; }, setOAuth: function setOAuth(val) { config.oauth = val; }, notification: { setDefaultTimeout: function setDefaultTimeout(val) { config.notification.defaultTimeout = val; }, setDefaultType: function setDefaultType(val) { config.notification.defaultType = val; } }, $get: function $get() { return config; } }; }); return 'cevo.util'; } module.exports = __util(require('angular'), require('angular-inview'), require('@uirouter/angularjs')); })(); angular.module('cevo.util').filter('encodeNewlines', ["$sce", function ($sce) { return function (msg) { msg = (msg + '').replace(/\\r\\n/g, "\r\n"); msg = (msg + '').replace(/\\t/g, "\t"); return $sce.trustAsHtml(msg); }; }]); angular.module('cevo.util').filter('nl2br', ["$sce", function ($sce) { return function (msg, is_xhtml) { var is_xhtml = is_xhtml || true; var breakTag = '<br />'; var msg = (msg + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag); return $sce.trustAsHtml(msg); }; }]); angular.module('cevo.util').filter('ordinal', ["$filter", function ($filter) { var suffixes = ["th", "st", "nd", "rd"]; return function (number) { var suffix = $filter('ordinalSuffix')(number); return number + suffix; }; }]); angular.module('cevo.util').filter('ordinalSuffix', ["$filter", function ($filter) { var suffixes = ["th", "st", "nd", "rd"]; return function (number) { var relevantDigits = number < 30 ? number % 20 : number % 30; var suffix = relevantDigits <= 3 ? suffixes[relevantDigits] : suffixes[0]; return suffix; }; }]); angular.module('cevo.util').filter('SplitArray', ["Logger", function (Logger) { return function (items, columns) { if (!items) return [items]; if (items.length <= columns) { return [items]; }; var rowsNum = Math.ceil(items.length / columns); var rowsArray = new Array(rowsNum); for (var i = 0; i < rowsNum; i++) { var columnsArray = new Array(columns); for (var j = 0; j < columns; j++) { var index = i * columns + j; if (index < items.length) { columnsArray[j] = items[index]; } else { break; } } rowsArray[i] = columnsArray; } Logger.debug('Finished splitting', rowsArray); return rowsArray; }; }]); angular.module('cevo.util').filter('stripTags', function () { return function (string) { return string ? String(string).replace(/<[^>]+>/gm, '') : ''; }; }); angular.module('cevo.util').filter('timezoneAbbreviation', ["Logger", function (Logger) { return function (string, concat) { if (typeof concat === 'undefined') concat = true; //Logger.debug('timezone', string, concat); var date = Date.now(); if (typeof Intl === 'undefined') return (concat ? string : '') + ' LT'; var idate = new Intl.DateTimeFormat('en-US', { 'timeZoneName': 'short' }).format(date); return (concat ? string : '') + ' ' + idate.split(' ').reverse()[0]; }; }]); angular.module('cevo.util').filter('ToArray', ["Logger", "cevo", function (Logger, cevo) { return function (items) { var array = []; //go through each filter //Logger.debug('To Array', cevo); if (cevo.util.object.size(items) <= 0) return array; for (var item in items) { item = items[item]; array.push(item); } return array; }; }]); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; angular.module('cevo.util').service('Form', ["Logger", function (Logger) { /* - In order to cleanly send files via ajax requests, we need to use the FormData object. - If we use FormData objects, values within FormData that are objects are not encoded, and thus must be stringified with angular.toJson. - If the form's data contain no files, we don't need to use multipart/form-data as the request type, and can instead use application/json and simply encode the data passed into this method. - Likewise, a FormData object can be passed directly into this method and it will be immediately returned. - Adding some code that allows us to pass objects with methods into formdata by stripping out anything that is a function */ this.errors = {}; this.clean = function (form) { var formData; var sendingFile = false; if (form instanceof FormData) formData = form;else { for (var i in form) { if (form[i] instanceof File) { sendingFile = true; break; } else if (angular.isFunction(form[i])) delete form[i]; } if (sendingFile) { formData = new FormData(); for (var i in form) { var data = form[i]; if (angular.isFunction(data)) continue; if (!(data instanceof File) && (typeof data === 'undefined' ? 'undefined' : _typeof(data)) === 'object') data = angular.toJson(data); formData.append(i, data); } } else formData = form; } return { 'data': formData, 'contentType': sendingFile ? undefined : 'application/json' //undefined will let the browser decide, but it's shitty at determining what is JSON }; }; this.setErrors = function (errors) { Logger.debug('Setting form errors', errors); this.errors = errors; }; }]); //https://medium.com/@skosno/server-error-handing-in-angularjs-forms-c5fd38ccf0fc angular.module('cevo.util').service('FormValidation', ["Logger", "Notification", function (Logger, Notification) { var _this = this; var formValidation = this; this.applyErrors = function (form, data) { Logger.debug('form validation', 'Applying errors... ', form, data); if (!data || !data.data || !data.data.errors) return Notification.error('There was an unknown error with the request.'); _this.reset(form); var errors = data.data.errors; Logger.debug('form validation', 'foreach ', errors); angular.forEach(errors, _this.applyError.bind(null, form)); for (var i in errors) { //var error = errors[i]; //Logger.debug('form', 'Looping errors, saw', i, type); //angular.forEach(type, this.applyError.bind(null, form)) } }; this.applyError = function (form, error) { var code = error.code, field = error.field || error.tag, message = error.message, api; Logger.debug('form validation', 'Applying error', form, error, code, field, message); if (!form.$api) _this.reset(form); api = form.$api; Logger.debug('form validation', 'got api', api); //field is not able to handle API errors if (!form[field] || !form[field].$registerError) { Logger.debug('form validation', 'did not see form field, cant register error', code, field, message, form[field]); if (!api.$errors) api.$errors = []; api.$errors.push(error); _this.setInvalid(form); } else { Logger.debug('form validation', 'Applied new error', form[field], form[field].$registerError, field, message); form[field].$registerError(error); } return true; }; this.reset = function (form) { form.$api = { $errors: [], $invalid: false, $valid: true }; form.$setPristine(); form.$setUntouched(); for (var i in form) { if (form[i] && form[i].$errors) { Logger.debug('form validations', 'resetting form object errors', form[i]); form[i].$errors = []; form[i].$valid = true; form[i].$invalid = false; } } }; this.setInvalid = function (form) { if (!form.$api) this.reset(form); form.$api.$invalid = true; form.$api.$valid = false; }; this.setValid = function (form) { if (!form.$api) this.reset(form); form.$api.$invalid = false; form.$api.$valid = true; }; }]); angular.module('cevo.util').service('Framework', function () { this.header = true; this.subnav = true; this.content = true; this.footer = true; this.bare = false; this.backgroundImage = ''; this.backgroundColor = ''; this.title = ''; }); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; angular.module('cevo.util').service('Listener', ["Logger", function (Logger) { var obj = this; this.id = 0; var listeners = {}; this.$registerListener = function (action) { listeners[action] = []; //Logger.debug('listener', 'registered', action, listeners); }; this.$on = function (actions, func, tag) { //Logger.debug('Registering new listener', actions, func); if ((typeof actions === 'undefined' ? 'undefined' : _typeof(actions)) !== 'object' && typeof actions != 'array') actions = [actions]; if (!tag) tag = Math.random(); for (var i in actions) { var action = actions[i]; if (!listeners[action]) return false; listeners[action].push({ tag: tag, func: func }); //Logger.debug('notifyer, Registered new listener', actions, listeners); } return tag; }; //return true if a listener was successfully unregistered, false if nothing was found this.$off = function (actions, tag) { var success = false; if (!angular.isObject(actions) && !angular.isArray(actions)) //typeof actions !== 'object' && typeof actions != 'array') actions = [actions]; for (var a in actions) { var action = actions[a]; for (var i in listeners[action]) { var listener = listeners[action][i]; if (listener.tag && listener.tag == tag) { //Logger.debug('Deregistering', action, tag); delete listeners[action][i]; success = true; break; } } } return success; }; this.$notify = function (action) { //Logger.debug('notifying ', listeners, action); if (!listeners[action]) return false; for (var i in listeners[action]) { //Logger.debug('Calling', listeners[action][i].tag, listeners[action][i].func); listeners[action][i].func.call(); } }; }]); angular.module('cevo.util').service('Logger', ["$log", "cevo", function ($log, cevo) { this.logs = []; this.log = function () { for (var i in arguments) { this.logs.push({ 'type': 'log', 'message': arguments[i], 'time': new Date().toUTCString() }); }$log.log.apply(this, arguments); }; this.debug = function () { for (var i in arguments) { this.logs.push({ 'type': 'debug', 'message': arguments[i], 'time': new Date().toUTCString() }); }if (cevo.env != 'production') $log.debug.apply(this, arguments); }; this.error = function () { for (var i in arguments) { this.logs.push({ 'type': 'error', 'message': arguments[i], 'time': new Date().toUTCString() }); }$log.error.apply(this, arguments); }; }]); angular.module('cevo.util').service('MetaTags', ["$http", "$window", "cevo", "Logger", "$sce", function ($http, $window, cevo, Logger, $sce) { var obj = this; obj.defaults = { title: '', description: '', og: {}, twitter: {}, height: '300', width: '400' }; obj.reset = function () { obj.title = obj.defaults.title; obj.description = obj.defaults.description; obj.og = obj.defaults.og; obj.twitter = obj.defaults.twitter; obj.height = obj.defaults.height; obj.width = obj.defaults.width; }; obj.getOrigin = function () { return $window.location.origin; }; obj.reset(); return obj; }]); angular.module('cevo.util').service('MIDIHelper', ["Logger", "$q", function (Logger, $q) { this.logs = []; this.MIDI = window.MIDI; this.MIDIAccess; this.outputs = []; var MIDIHelper = this; if (!this.MIDI) Logger.error('WARNING: MIDI Unavailable!'); this.requestAccess = function (message) { Logger.debug('Requesting access to MIDI devices'); if (navigator.requestMIDIAccess) { return navigator.requestMIDIAccess({ sysex: false }).then(function (MIDIAccess) { Logger.debug('triggering onrequest success'); return MIDIHelper.onRequestSuccess(MIDIAccess); }, function () { return MIDIHelper.onRequestFailure(); }); } else { Logger.error('ERROR: MIDI Access Request Failed!'); return $q.reject(); } }; this.onRequestSuccess = function (MIDIAccess) { Logger.debug('MIDI Access Request Successful', MIDIAccess); MIDIHelper.MIDIAccess = MIDIAccess; MIDIHelper.storeOutputDevices(); Logger.debug('resolving promise'); return $q.resolve(); }; this.onRequestFailure = function () { Logger.error('ERROR: MIDI Access Request Failed'); return $q.reject(); }; this.storeOutputDevices = function () { this.outputs = this.MIDIAccess.outputs; }; this.sendNote = function (note, length) { this.outputs.forEach(function (output, port) { Logger.debug('output: ' + port, output); output.send([0x90, note, 0x7f]); //noteOn, velocity(?), note byte (G9) output.send([0x80, note, 0x7f]); //noteOff, velocity(?), note byte (G9) }); }; }]); angular.module('cevo.util').service('Nav', ["$state", function ($state) { this.nav = {}; this.current = { state: $state.current, controller: '' }; this.setCurrentState = function (state) { this.current.state = state; this.setCurrentController(); }; this.setCurrentController = function () { var parts = this.current.state.name.split('.'); var site = parts[0]; this.current.controller = parts[1] || site; this.current.page = parts[2] || ''; }; this.getCurrentNav = function () { return this.nav[this.current.controller] || []; }; this.seed = function () { var states = $state.get(); for (var i in states) { var state = states[i]; if (state.visibility && state.visibility == 'hidden') continue; var parts = state.name.split('.'); var site = parts[0]; if (!site) continue; var controller = parts[1] || site; var page = parts[2] || ''; var testing; if (!this.nav[controller]) { this.nav[controller] = { 'nav': [], //use an array here so we can iterate cleanly 'name': state.name, 'controller': state.controller, 'url': state.url, 'title': controller, 'class': '' }; } if (page) { this.append(controller, { 'nav': [], //allow us to add a subnav, use an array again to easily iterate over it in the view 'url': this.nav[controller].url + state.url, 'title': page, 'name': state.name, 'class': 'capitalize' }); } } }; this.prepend = function (controller, nav, position) { if (!controller || !this.nav[controller] || !nav.title && !nav.class || this.exists(controller, nav.tag || nav.title || '')) return false; if (position) return this.add(controller, nav, position);else return this.nav[controller].nav.unshift(nav); }; this.append = function (controller, nav, position) { if (!controller || !this.nav[controller] || !nav.title && !nav.class || this.exists(controller, nav.tag || nav.title || '')) return false; if (position) return this.add(controller, nav, position);else return this.nav[controller].nav.push(nav); }; this.add = function (controller, nav, position, replace) { if (!controller || !this.nav[controller] || !nav.title && !nav.class) return false; if (typeof replace === 'undefined') //replace by default replace = true; if (replace) { var index = this.exists(controller, nav.tag || nav.title || ''); if (index) return this.nav[controller].nav[index] = nav; } return this.nav[controller].nav.splice(position, 0, nav); }; //returns the index of the existing nav item this.exists = function (controller, tag) { if (!controller || !this.nav[controller]) return false; for (var i in this.nav[controller].nav) { var item = this.nav[controller].nav[i]; if ((item.tag || item.title || '') == tag) return i; } return false; }; this.unset = function (controller, tag) { if (!controller || !this.nav[controller]) return false; for (var i in this.nav[controller].nav) { var item = this.nav[controller].nav[i]; if ((item.tag || item.title || '') == tag) return this.nav[controller].nav.splice(i, 1); } return false; }; }]); angular.module('cevo.util').service('Notification', ["$timeout", "cevo", "Logger", function ($timeout, cevo, Logger) { this.notifications = []; this.listeners = []; this.alert = function (message, style, type, duration) { if (angular.isObject(message)) { for (var i in message) { var notification = message[i]; this.alert(notification.message, style, type, duration); } return; } if (!type) type = cevo.notification.defaultType; if (!duration) duration = cevo.notification.defaultTimeout; this.notifications.push({ 'type': type, 'style': style, 'message': message, 'time': new Date().toUTCString() }); this.notify(); var e = this.notifications; $timeout(function () { e.shift(); }, duration); }; this.error = function (message, type, duration) { Logger.debug('Alerting new error message'); this.alert(message, 'error', type, duration); }; this.success = function (message, type, duration) { Logger.debug('Alerting new success message'); this.alert(message, 'success', type, duration); }; this.listen = function (callback) { Logger.debug('Attaching new listener'); this.listeners.push(callback); }; this.notify = function () { Logger.debug('Notifying listeners', this.listeners); for (var i in this.listeners) { this.listeners[i].call(); } }; }]); angular.module('cevo.util').factory('Rotator', ["$timeout", "Logger", function ($timeout, Logger) { function Rotator() { var delay = 5000; var rotateTimeout; var obj = { currentItem: {}, currentIndex: -1, length: 0, items: [], config: function config(i, d, run) { if (!i || i.length <= 0) return false; run = run !== false; obj.items = i; obj.length = obj.items.length; if (d) delay = d; //Logger.debug('rotator', 'finished config', i, d, run); if (run) obj.run(); }, run: function run() { obj.next(-1); }, stop: function stop() { $timeout.cancel(rotateTimeout); }, next: function next(index) { if (typeof index === 'undefined') index = obj.currentIndex; //Logger.debug('rotator next', index, obj.items.length, obj.items) if (++index >= obj.items.length) index = 0; obj.selectItem(index); }, previous: function previous(index) { if (typeof index === 'undefined') index = obj.currentIndex; //Logger.debug('rotator previous', index, obj.items.length, obj.items) if (--index < 0) index = obj.items.length - 1; obj.selectItem(index); }, selectItem: function selectItem(index) { if (rotateTimeout) $timeout.cancel(rotateTimeout); //swLogger.debug('rotator', 'selecting item', index, obj.items, obj.items[index]); obj.currentItem = obj.items[index]; obj.currentIndex = index; rotateTimeout = $timeout(function () { obj.next(index); }, delay); } }; return obj; } return Rotator; }]); angular.module('cevo.util').factory('Socket', ["Logger", "$rootScope", function (Logger, $rootScope) { function Socket() { var socket; var name; return { connect: function connect(host, transports, opts) { //Logger.log('connecting to '+host); if (typeof io === 'undefined') { return; } name = host; var opts = opts || {}; if (!transports) opts.transports = ['websocket']; socket = io.connect(host, opts); //Logger.log(name+' connected: '+socket.json.connected); socket.on('disconnect', function () { if (socket.json) Logger.debug(name + ' connected: ' + socket.json.connected);else Logger.debug(name + ' connected: ' + socket.socket.connected); }); socket.on('connect', function () { if (socket.json) Logger.debug(name + ' connected: ' + socket.json.connected);else Logger.debug(name + ' connected: ' + socket.socket.connected); }); }, on: function on(eventName, callback) { Logger.debug(name + ': Setting listener for ' + eventName); if (socket) { socket.on(eventName, function () { var args = arguments; $rootScope.$apply(function () { callback.apply(socket, args); }); }); } }, once: function once(eventName, callback) { Logger.debug(name + ': Setting listener once for ' + eventName); if (socket) { socket.once(eventName, function () { var args = arguments; $rootScope.$apply(function () { callback.apply(socket, args); }); }); } }, off: function off(eventName) { Logger.debug(name, ': Removing listener for ', eventName, socket); if (socket && socket.removeAllListeners) socket.removeAllListeners(eventName); }, emit: function emit(eventName, data, callback) { //console.log('sending '+data+' to '+name) if (socket && (socket.json && socket.json.connected || socket.socket && socket.socket.connected)) { //$log.log('emitting ...'+eventName); socket.emit(eventName, data, function () { //$log.log('done emitting...'); var args = arguments; $rootScope.$apply(function () { if (callback) { callback.apply(socket, args); } }); }); } }, disconnect: function disconnect() { if (socket.json) Logger.debug(name + ' connected: ' + socket.json.connected);else Logger.debug(name + ' connected: ' + socket.socket.connected); if (socket.json.connected) socket.disconnect(); if (socket.json) Logger.debug(name + ' connected: ' + socket.json.connected);else Logger.debug(name + ' connected: ' + socket.socket.connected); } }; } return Socket; }]); angular.module('cevo.util').directive('cActivateOnScroll', ["$q", "$window", "$timeout", "Logger", function ($q, $window, $timeout, Logger) { return { restrict: 'A', scope: { 'activeClass': '@cActivateOnScroll', 'id': '@cActivateId' }, link: function link(scope, elem, attrs) { var id = scope.id || (elem[0].href.indexOf('#') > -1 ? elem[0].href.split('#')[1] : ''); var activeClass = scope.activeClass || 'c-activate-on-scroll--active'; Logger.debug('c-activate-on-scroll', 'linked', activeClass, id); if (!id) return; var onScroll = function onScroll() { var destination = document.getElementById(id); if (!destination) { return; } var offsetTop = destination.offsetTop, offsetHeight = destination.offsetHeight, offsetBottom = offsetTop + offsetHeight; var el = document.scrollingElement || document.documentElement; var scrollTop = el.scrollTop; var windowHeight = window.innerHeight; var scrollBottom = scrollTop + windowHeight; var halfOfWindowVisible = scrollBottom - windowHeight * .5; Logger.debug('activate-on-scroll info', id, destination, offsetTop, offsetBottom, scrollTop, halfOfWindowVisible); if (halfOfWindowVisible > offsetTop && halfOfWindowVisible < offsetBottom) { elem.addClass(activeClass); } else { elem.removeClass(activeClass); } }; angular.element($window).bind('scroll', onScroll); angular.element($window).bind('load', onScroll); } }; }]); angular.module('cevo.util').directive('cAnimateLoad', function () { return { restrict: 'A', link: function link(scope, elem, attrs) { elem.addClass('c-animate-load'); elem.addClass('ng-hide'); elem.on('load', function () { elem.addClass('ng-enter').removeClass('ng-hide'); }); scope.changeSrc = function () { elem.addClass('ng-hide').removeClass('ng-enter'); }; attrs.$observe('src', function () { scope.changeSrc(); }); } }; }); angular.module('cevo.util').directive('cAnimateView', ["$compile", function ($compile) { return { restrict: 'A', terminal: true, priority: 1000, link: function link(scope, element, attrs) { var animation = attrs.cAnimateView || 'fadeIn'; element.attr('in-view', 'defineInViewAnimation($inview, $inviewInfo, "' + animation + '");'); element.removeAttr('c-animate-view'); element.addClass('c-animate-view animate-' + animation); scope.defineInViewAnimation = function ($inview, $inviewInfo, animation) { if ($inview) $inviewInfo.element.addClass('ng-enter').removeClass('ng-hide'); }; $compile(element)(scope); } }; }]); angular.module('cevo.util').directive('compileHtml', ["$compile", function ($compile) { return { restrict: 'A', link: function link(scope, element, attrs) { scope.$watch(function () { return scope.$eval(attrs.compileHtml); }, function (value) { element.html(value); $compile(element.contents())(scope); }); } }; }]); angular.module('cevo.util').directive('cCreditCard', ["$rootScope", "Notification", "$timeout", "$filter", "Logger", function ($rootScope, Notification, $timeout, $filter, Logger) { return { restrict: 'E', template: '\n\t\t\t<div class="c-credit-card">\n\t\t\t\t<div class="credit-card-wrapper flex flex-center grow-between">\n\t\t\t\t\t<span class="credit-card-numbers flex">\n\t\t\t\t\t\t<input type="text" ng-model="parts[0]" maxlength=4 tabindex="{{tabindex ? firstTab : \'\'}}" />\n\t\t\t\t\t\t<input type="text" ng-model="parts[1]" maxlength=4 tabindex="{{tabindex ? secondTab : \'\'}}" />\n\t\t\t\t\t\t<input type="text" ng-model="parts[2]" maxlength=4 tabindex="{{tabindex ? thirdTab : \'\'}}" />\n\t\t\t\t\t\t<input type="text" ng-model="parts[3]" maxlength=4 tabindex="{{tabindex ? fourthTab : \'\'}}" />\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class="credit-card-image">\n\t\t\t\t\t\t<i ng-if="type == \'discover\'" class="payment-card-image discover"></i>\n\t\t\t\t\t\t<i ng-if="type == \'amex\'" class="payment-card-image amex"></i>\n\t\t\t\t\t\t<i ng-if="type == \'visa\'" class="payment-card-image visa"></i>\n\t\t\t\t\t\t<i ng-if="type == \'mastercard\'" class="payment-card-image mastercard"></i>\n\t\t\t\t\t\t<span ng-if="type == \'\'" class="dim">&#8212;</span>\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t', replace: true, scope: { 'number': '=', 'type': '=', 'tabindex': '@starttabindex' }, link: function link(scope, elem, attrs) { scope.firstTab = scope.tabindex; scope.secondTab = parseInt(scope.firstTab) + 1; scope.thirdTab = parseInt(scope.firstTab) + 2; scope.fourthTab = parseInt(scope.firstTab) + 4; $(elem).on('click', function () { $(elem).find('input').each(function (i, e) { //$log.log('saw e with value', e.value, e.value.length); if (e.value.length == 0) { e.focus(); return false; } }); //elem.find('input:nth-child(1)').focus(); }); $(elem).find('input').on('keydown', function (ev) { var char = ev.which || ev.charCode; //$log.log('heard input keypress for credit card ', char, ev, this); if (this.value.length == 4 && char != 8) { Logger.debug('value is full, will focus next input'); $(this).next().focus(); } if (char != 8) //backspace return; if (this.value.length <= 0) { Logger.debug('value is empty, will focus previous input'); $(this).prev().focus(); } }); $(elem).find('input').on('click', function () { if (this.value.length > 0) return false; }); scope.parts = []; scope.$watch('parts', function () { scope.number = scope.parts.join(''); var one = parseInt(scope.number.substring(0, 1)); var two = parseInt(scope.number.substring(0, 2)); var three = parseInt(scope.number.substring(0, 3)); var four = parseInt(scope.number.substring(0, 4)); var six = parseInt(scope.number.substring(0, 6)); if (one == 4) scope.type = 'visa';else if (two == 34 || two == 37) scope.type = 'amex';else if (two >= 51 && two <= 55) scope.type = 'mastercard';else if (six >= 622126 && six <= 622925 || two == 65 || four == 6011 || three >= 644 && three <= 649) scope.type = 'discover';else scope.type = ''; Logger.debug('creditcard saw', scope.type, scope.number, one, two, three, four, six); }, true); } }; }]); angular.module('cevo.util').directive('cDragDrop', ["$q", "Logger", function ($q, Logger) { return { restrict: 'A', scope: { 'file': '=cDragDrop', 'progress': '=?cDragDropProgress', 'type': '@cDragDropType' }, link: function link(scope, elem, attrs) { if (typeof $ === 'undefined') { Logger.error('DRAGDROP', "ERROR", 'jQuery must be installed to use drag and drop.'); return; } scope.progress = 0; $('body').on('dragover dragenter', function (e) { //Logger.debug('Heard dragover dragenter on body', e); $('body').addClass(e.type); }); $('body').on('dragleave', function (e) { $('body').removeClass('dragover dragenter'); }); elem.addClass('c-drop-zone'); $(elem[0]).on('dragover dragenter dragleave', function (e) { //Logger.debug('dragdrop', 'Saw event', e); if (e.type === 'dragover') elem.addClass(e.type).addClass('c-drag-hover');else if (e.type === 'dragleave') elem.removeClass('c-drag-hover dragover dragenter c-drag-enter');else if (e.type === 'dragenter') elem.addClass('c-drag-enter'); e.preventDefault(); e.stopPropagation(); return false; }); $(elem[0]).on('drop', function (e) { e.stopPropagation(); e.preventDefault(); elem.removeClass('c-drag-hover dragover dragenter c-drag-enter'); var files = e.originalEvent.dataTransfer.files; Logger.debug('dragdrop', 'Saw dropped files', files); if (files.length) { readFile(files[0]).then(function (value) { Logger.debug('dragdrop', 'Read file with value', value); scope.file.lastModified = files[0].lastModified; scope.file.lastModifiedDate = files[0].lastModifiedDate; scope.file.name = files[0].name; scope.file.size = files[0].size; scope.file.type = files[0].type; scope.file.src = value; }); } else { var url = e.originalEvent.dataTransfer.getData('url'); Logger.debug('dragdrop', 'Read file with url', url); scope.file.src = url; } scope.$apply(); return false; }); function readFile(file) { Logger.debug('dragdrop', 'Reading file', file); var deferred = $q.defer(); var reader = new FileReader(); reader.onload = function (e) { deferred.resolve(e.target.result); }; reader.onprogress = function (data) { Logger.debug('dragdrop', 'onprogress', data); scope.data = data; scope.progress = data.loaded / data.total; }; reader.onerror = function (e) { Logger.debug('dragdrop', 'Reader onerror', e); deferred.reject(e); }; if (scope.type == 'text') { Logger.debug('dragdrop', 'file reading as text ..'); reader.readAsText(file); } else if (scope.type == 'buffer') { reader.readAsArrayBuffer(file); } else if (scope.type == 'binary') { reader.readAsBinaryString(file); } else { reader.readAsDataURL(file); } Logger.debug('dragdrop', 'Reader data url, reader', reader); return deferred.promise; } } }; }]); angular.module('cevo.util').directive('ngEnter', function () { return function (scope, element, attrs) { element.bind('keypress', function (event) { if (event.which === 13) { if (typeof attrs.ngEnterClear !== 'undefined') this.value = ''; scope.$apply(function () { scope.$eval(attrs.ngEnter); }); event.preventDefault(); } }); }; }); angular.module('cevo.util').directive('cFocusOn', ["$timeout", "Logger", function ($timeout, Logger) { return { restrict: 'A', scope: { focusValue: "=cFocusOn", focusDelay: "@", blurDelay: "@" }, link: function link($scope, $element, attrs) { $scope.$watch("focusValue", function (currentValue, previousValue) { if (currentValue === true) $timeout(function () { // $element[0].focus(); var len = $element.val().length; $element[0].focus(); $element[0].setSelectionRange(len, len); Logger.debug('focuson', $element.val(), $scope.focusDelay, $element[0]); }, $scope.focusDelay ? parseInt($scope.focusDelay) : 0);else if (currentValue === false && previousValue) $timeout(function () { $element[0].blur(); }, $scope.blurDelay ? parseInt($scope.blurDelay) : 0); }); } }; }]); angular.module('cevo.util').directive('cForm', ["Logger", "Form", "$timeout", "FormValidation", function (Logger, Form, $timeout, FormValidation) { return { restrict: 'A', require: 'form', link: function link(scope, elem, attr, $form) { Logger.debug('Linking form', attr.name, attr.cForm); var watchPromise = attr.cForm || null; var formSubmitted = function formSubmitted($form, promise) { Logger.debug('form', 'heard formsubmitted from directive', promise); if (!promise || !promise.then) return; FormValidation.reset($form); promise.then(null, function (reason) { Logger.debug('form', 'heard promise error fire', reason); FormValidation.applyErrors($form, reason); }); }; if (watchPromise !== null) scope.$watch(watchPromise, formSubmitted.bind(null, $form)); } }; }]); angular.module('cevo.util').directive('cFormErrors', ["Logger", "Form", "$timeout", "FormValidation", function (Logger, Form, $timeout, FormValidation) { return { restrict: 'E', template: '\n\t\t\t<div class="c-form-errors">\n\t\t\t\t<div class="c-form-error" ng-repeat="error in element.$errors"><i class="ico ico-exclamation-circle"></i><span>{{error.message}}</span></div>\n\t\t\t</div>\n\t\t', replace: true, scope: { element: '=' } }; }]); angular.module('cevo.util').directive('cInfiniteScroll', ["$window", "$document", "$rootScope", "$timeout", "$compile", "Logger", function ($window, $document, $rootScope, $timeout, $compile, Logger) { return { scope: { infiniteScroll: '=cInfiniteScroll', //if a 'string' is passed, it will be emitted every infiniteScrollDistance (5px, 5%, etc...)... infiniteScrollDistance: '@cInfiniteScrollDistance', //how much distance (in px or %) scroll is from the bottom within itself, or if relative, to the window infiniteScrollRelative: '@cInfiniteScrollRelative', //whether or not infiniteScrollDistance is relative to the window's scroll position (true), or the element's scroll position (false) infiniteScrollLimit: '@cInfiniteScrollLimit', //how many infinite scroll calls should be allowed before a reset is needed infiniteScrollReverse: '@cInfiniteScrollReverse', //if set we will listen for scroll distance towards the top of the element / window, this is default to the bottom infiniteScrollDisabled: '=?cInfiniteScrollDisabled' }, link: function link(scope, elem, attrs) { var win = angular.element($window); var elementHeight = elem[0].offsetHeight; var lastVal; scope.calling = false; var style = $window.getComputedStyle(elem[0]); var overflow = style['overflow'] || style['overflowY']; var isOverflow = overflow == 'scroll' || overflow == 'auto'; Logger.debug('infinite scroll link overflow', elem[0].className, overflow, style.overflow, style.overflowY, isOverflow); var infiniteScrollReverse = typeof scope.infiniteScrollReverse !== 'undefined'; //make sure we set this to a var because the scope.infiniteScrollRelative will keep getting reset if (typeof scope.infiniteScrollRelative !== 'undefined' && scope.infiniteScrollRelative !== 'false') var infiniteScrollRelative = true;else var infiniteScrollRelative = false; var scrollIsFunction = angular.isFunction(scope.infiniteScroll); if (scrollIsFunction && typeof scope.infiniteScrollLimit !== 'undefined') scope.currentLimit = parseInt(scope.infiniteScrollLimit);else scope.currentLimit = 999999999; Logger.debug('infinite scroll link', elem[0].className, scrollIsFunction, isOverflow, scope.infiniteScrollRelative, infiniteScrollRelative, scope.infiniteScrollDistance, scope.currentLimit); if (!elem.hasClass('c-infinite-scroll')) elem.addClass('c-infinite-scroll'); var onScroll = function onScroll() { var element = { bind: elem[0].offsetHeight < elem[0].scrollHeight, scrollHeight: elem[0].scrollHeight, scrollTop: elem[0].scrollTop, scrollPct: elem[0].scrollTop / (elem[0].scrollHeight - elem[0].offsetHeight) * 100, scrollBot: elem[0].offsetHeight + elem[0].scrollTop, height: elem[0].offsetHeight, topPos: elem[0].offsetTop, bottomPos: elem[0].offsetTop + elem[0].offsetHeight }; element.scrollRemaining = element.scrollHeight - element.scrollBot; element.scrollPctRemaining = element.scrollRemaining / (element.scrollHeight - element.height) * 100; var wdw = { scrollPos: $window.scrollY, scrollHeight: $document[0].documentElement.offsetHeight, height: $document[0].documentElement.clientHeight, bottomPosition: $window.scrollY + $document[0].documentElement.clientHeight }; wdw.scrollRemaining = wdw.scrollHeight - (wdw.scrollPos + wdw.height); wdw.scrollPctRemaining = wdw.scrollRemaining / (wdw.scrollHeight - wdw.height) * 100; //window to element relative positioning wdw.toElementBottomPos = element.bottomPos - wdw.bottomPosition; wdw.toElementBottomPct = 100 - wdw.toElementBottomPos / element.height * 100; wdw.toElementBottomPctRemaining = wdw.toElementBottomPos / element.height * 100; //Logger.debug('infinite scroll', wdw, element); // return if limit is completed; if (!element.height) return; if (scrollIsFunction) { if (scope.calling || scope.currentLimit === 0 || scope.infiniteScrollDisabled) return; var shouldCall = false; if (scope.infiniteScrollDistance) { if (infiniteScrollReverse && !infiniteScrollRelative) { if (scope.infiniteScrollDistance.indexOf('%') > -1) { //%-based decision making. //if undefined, we check the element, if relative, check relative to the window positioning //Logger.debug('infinite scroll', 'checking % up') shouldCall = parseInt(scope.infiniteScrollDistance.replace('%', '')) >= element.scrollPct; } else { //px-based decision making shouldCall = parseInt(scope.infiniteScrollDistance.replace('%', '')) >= element.scrollTop; } } else { if (scope.infiniteScrollDistance.indexOf('%') > -1) { //%-based decision making. //if undefined, we check the element, if relative, check relative to the window positioning //Logger.debug('infinite scroll', 'checking %') shouldCall = parseInt(scope.infiniteScrollDistance.replace('%', '')) >= (!infiniteScrollRelative ? element.scrollPctRemaining : wdw.toElementBottomPctRemaining); } else { //px-based decision making //Logger.debug('infinite scroll', 'checking num', parseInt(scope.infiniteScrollDistance.replace('%', '')), ' >= ', (!infiniteScrollRelative ? 'ele '+element.scrollRemaining : 'wdw '+wdw.toElementBottomPos), infiniteScrollRelative, element.scrollRemaining, wdw.toElementBottomPos); shouldCall = parseInt(scope.infiniteScrollDistance.replace('%', '')) >= (!infiniteScrollRelative ? element.scrollRemaining : wdw.toElementBottomPos); } } } if (!shouldCall) return; Logger.debug('infinite scroll', elem[0].className, 'calling', parseInt(scope.infiniteScrollDistance.replace('%', '')), ' >= ', !infiniteScrollRelative ? 'ele ' + element.scrollRemaining : 'wdw ' + wdw.toElementBottomPos, infiniteScrollRelative, element.scrollRemaining, wdw.toElementBottomPos); scope.calling = true; var call = scope.infiniteScroll(); if (call && call.finally) { return call.finally(function () { scope.calling = false; if (scope.currentLimit > 0) scope.currentLimit--; }); } else { scope.calling = false; } } else if (scope.infiniteScroll) { if (typeof scope.infiniteScrollDistance === 'undefined') scope.infiniteScrollDistance = '3%'; //default 5% var shouldCall = false; if (scope.infiniteScrollDistance.indexOf('%') > -1) var curVal = infiniteScrollRelative ? wdw.scrollPctRemaining : element.scrollPctRemaining;else var curVal = infiniteScrollRelative ? wdw.scrollRemaining : element.scrollRemaining; var bounce = parseInt(scope.infiniteScrollDistance.replace('%', '').replace('px', '')); if (typeof lastVal === 'undefined' || lastVal < curVal) lastVal = curVal; if (lastVal - curVal >= bounce) shouldCall = true; //Logger.debug('infinite scroll', scope.infiniteScroll, infiniteScrollRelative, shouldCall, lastVal - curVal, bounce, scope.infiniteScrollDistance); if (shouldCall) { $rootScope.$emit(scope.infiniteScroll, { window: wdw, element: element }); lastVal = curVal; if (scope.currentLimit > 0) scope.currentLimit--; } } }; // set initial data and limit scope.initialLimit = function () { if (scope.currentLimit === 0) { scope.currentLimit = scope.infiniteScrollLimit; onScroll(); } }; elem.bind('scroll', onScroll); win.bind('scroll', onScroll); scope.$on('$destroy', function () { //Logger.debug('infinite scroll unbind', elem); elem.unbind('scroll', onScroll); win.bind('scroll', onScroll); }); // Check on next event loop to give the browser a moment to load more. //$timeout(onScroll); }, controller: ["$scope", function controller($scope) { //Logger.debug('infinite', 'controller', $scope); $scope.$watch("currentLimit", function (newValue, oldValue) { //Logger.debug('infinite', 'current limit', $scope.currentLimit); $scope.currentLimit = newValue; }); this.getScope = function () { return $scope; }; this.getLimit = function () { return $scope.currentLimit; }; this.resetLimit = function () { $scope.initialLimit(); }; }] }; }]); angular.module('cevo.util').directive('cInfiniteScrollLimitReset', ["$window", "$rootScope", "Logger", function ($window, $rootScope, Logger) { return { scope: true, require: "^cInfiniteScroll", link: function link(scope, elem, attrs, ctrlInfinite) { // set hide button to load more var parentScope = ctrlInfinite.getScope(); scope.loadButton = false; parentScope.$watch('currentLimit', function (newValue, oldValue) { if (newValue === 0) scope.loadButton = true; }); scope.resetLimit = function () { if (ctrlInfinite.getLimit() === 0) { scope.loadButton = false; ctrlInfinite.resetLimit(); } else { scope.loadButton = true; } }; } }; }]); angular.module('cevo.util').directive('cInput', ["Logger", function (Logger) { return { restrict: 'E', template: '\n\t\t\t<div class="c-input animated" ng-class="status == \'inactive\' ? \'dim\' : status" ng-style="{ \'width\' : (width && status == \'active\' ? width : \'auto\')}" ng-click="status = \'active\'">\n\t\t\t\t<span class="label">\n\t\t\t\t\t<i class="ico" ng-class="ico"></i>\n\t\t\t\t\t<span ng-bind-html="label">{{label}}</span>\n\t\t\t\t</span>\n\t\t\t\t<input placeholder="{{label}}" size="size" ng-model="model" ng-blur="blurInput()" />\n\t\t\t</div>', replace: true, scope: { 'ico': '@', 'label': '@', 'model': '=', 'width': '@', 'size': '@' }, link: function link(scope, elem, attrs) { scope.status = 'inactive'; var originalSize = elem[0].offsetWidth; scope.$watch('status', function () { if (scope.status == 'active') elem[0].getElementsByTagName('input')[0].focus();else resetWidth(); }); function resetWidth() { //we can't transition from auto=>defined width (css3 issue), so we need to set the width to a static value elem[0].style.width = originalSize; } scope.blurInput = function () { if (!scope.model) scope.status = 'inactive'; }; } }; }]); angular.module('cevo.util').directive('inputNumeric', function () { return { require: 'ngModel', link: function link(scope, element, attr, ngModelCtrl) { function fromUser(text) { var transformedInput = text.replace(/[^0-9]/g, ''); if (transformedInput !== text) { ngModelCtrl.$setViewValue(transformedInput); ngModelCtrl.$render(); } return transformedInput; // or return Number(transformedInput) } ngModelCtrl.$parsers.push(fromUser); } }; }); angular.module('cevo.util').directive('modal', ["$rootScope", "$timeout", "$interval", "$sce", "Logger", function ($rootScope, $timeout, $interval, $sce, Logger) { return { restrict: 'E', template: '\n\t\t\t<div class=\'ng-modal\' ng-show=\'show\'>\n\t\t\t\t<div class=\'ng-modal-overlay\' ng-if="show" ng-click=\'hideModal("overlay")\'></div>\n\t\t\t\t<div class=\'ng-modal-dialog\' ng-if="show">\n\t\t\t\t\t<a class=\'ng-modal-close ico ico-times\' href="" ng-if="!settings.hideCancel || !settings.hideCornerCancel" ng-click=\'hideModal(); cancelClick();\'></a>\n\t\t\t\t\t<p class="ng-modal-title" ng-show="title">\n\t\t\t\t\t\t<span ng-bind-html="title" class=""></span>\n\t\t\t\t\t</p>\n\t\t\t\t\t<div class=\'ng-modal-dialog-content\'>\n\t\t\t\t\t\t<div ng-transclude></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>', scope: { show: '=', hideCancel: '@hidecancel', cancelAction: '&?', hideCornerCancel: '@hidecornercancel', hideButtonCancel: '@hidebuttoncancel', cancelText: '@canceltext', cancelClick: '&cancelclick' }, replace: true, // Replace with the template below transclude: true, // we want to insert custom content inside the directive link: function link(scope, element, attrs) { scope.overlayCancel = attrs.overlayCancel === 'true'; Logger.debug('Linking modal with cancel settings', scope.hideCancel, scope.hideCornerCancel, scope.hideButtonCancel); scope.title = $sce.trustAsHtml(attrs.mtitle); Logger.debug('trusting title', scope.title, attrs.mtitle); scope.settings = { hideCancel: scope.hideCancel !== 'false', hideCornerCancel: scope.hideCornerCancel != 'false', hideButtonCancel: scope.hideButtonCancel != 'false' }; scope.$watch('hideCornerCancel', function () { scope.settings.hideCornerCancel = scope.hideCornerCancel != 'false'; }); scope.$watch('show', function () { if (scope.show) $rootScope.$emit('modal:active');else $rootScope.$emit('modal:inactive'); }); scope.hideModal = function (method) { if (method == 'overlay' && !scope.overlayCancel) return; scope.show = false; if (scope.cancelAction) { //keep this here, removes any delay and immediately updates the scope $timeout(function () { scope.cancelAction(); }); } }; } }; }]); angular.module('cevo.util').directive('cNotifications', ["Notification", "Logger", function (Notification, Logger) { return { restrict: 'E', template: '\n\t\t\t<div class="c-notifications notifications-wrapper"">\n\t\t\t\t<div class="c-notification" ng-class="notification.style" ng-repeat="notification in notifications | filter:{ type : \'global\' }" ng-click="notifications.splice($index, 1);">\n\t\t\t\t\t<span ng-if="notification.style == \'error\'" class="ico ico-exclamation-circle"></span>\n\t\t\t\t\t<span ng-if="notification.style == \'success\'" class="ico ico-check-circle"></span>\n\t\t\t\t\t<p>{{notification.message}}</p>\n\t\t\t\t\t<span class="ico ico-times"></span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t', replace: true, scope: {}, link: function link(scope, elem, attrs) { Logger.debug('Attaching listener to Notification from cNotifications'); Notification.listen(function () { Logger.debug('Heard notification listener trigger in Notifications', Notification.notifications); scope.notifications = Notification.notifications; }); } }; }]); angular.module('cevo.util').directive('cSearch', ["$timeout", "$filter", function ($timeout, $filter) { return { restrict: 'E', //chrome ignores autocompleted="off", so use "new-password" instead template: '\n\t\t\t<div class="c-search" ng-class="[active ? \'focus\' : \'\', curVal != \'\' ? \'ng-not-empty\' : \'\']">\n\t\t\t\t<div class="input-wrapper icon">\n\t\t\t\t\t<i class="ico ico-magnify"></i>\n\t\t\t\t\t<input type="text" autocomplete="new-password" ng-model-options="{ debounce: 200 }" ng-model="curVal" ng-focus="active=true" ng-blur="blur()" ng-enter="triggerEnter()" placeholder="{{placeholder}}" />\n\t\t\t\t</div>\n\t\t\t\t<div class="dropdown" ng-class="visible && results.length > 0 ? \'visible\' : \'\'">\n\t\t\t\t\t<p class="" ng-repeat="row in results | limitTo:limit track by $index" ng-click="selectValue((val == \'{}\' ? row : row[val]), row[label]);">\n\t\t\t\t\t\t<span class="">{{row[label]}}</span>\n\t\t\t\t\t\t<i ng-if="icon" class="ico" ng-class="[\'ico-\'+icon]"></i>\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>', replace: true, scope: { 'data': '=?', 'val': '@', 'label': '@', 'icon': '@', 'title': '@', 'model': '=?', 'onSelect': '&', 'onCh