@cevo/angular-util
Version:
Angular utility methods
1,505 lines (1,225 loc) • 84.5 kB
JavaScript
(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">—</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