Skip to content

Instantly share code, notes, and snippets.

@alanhamlett
Created September 29, 2015 23:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alanhamlett/55e08ec7a599944ae35b to your computer and use it in GitHub Desktop.
Save alanhamlett/55e08ec7a599944ae35b to your computer and use it in GitHub Desktop.
Utility Function to Map JSON errors from wtforms-json onto an HTML form
(function() {
var utils = {};
utils.clear_form_errors = function($el) {
$el.find('.text-danger').each(function() {
$(this).empty();
});
return $el;
};
utils.set_form_errors = function($el, json) {
function flash_errors(obj, namespace) {
if (!namespace)
namespace = '';
if (_.isArray(obj)) {
if (_.isObject(obj[0]))
obj[0] = _.values(obj[0])[0];
var add_namespace = false;
var $div = $el.find('.flash-'+namespace+',#flash-'+namespace);
if (!$div.length) {
var dotns = namespace.replace('-', '.');
if (namespace) {
var $inp = $el.find('input[name="'+dotns+'"],select[name="'+dotns+'"],textarea[name="'+dotns+'"],#'+dotns);
if ($inp.length) {
$div = $('<div class="text-danger" />');
if ($inp.parent().hasClass('input-group')) {
$inp.parent().before($div);
} else {
$inp.parent().append($div);
}
}
}
if (!$div.length) {
$div = $el.find('.flash-error,#flash-error');
if (!$div.length) {
$div = $('<div class="text-danger" />');
$el.prepend($div);
add_namespace = !!namespace;
}
}
}
if (add_namespace)
$div.text(namespace+': '+obj[0]);
else
$div.text(obj[0]);
if (obj[0] === 'User account can not view time before one week ago.') {
$div.append('<a href="/change_plan/premium" class="btn btn-primary m-left-xs-10">Upgrade to Premium</a> <span style="color:#000;">or</span> <a href="/referrals" class="btn btn-primary">Refer a Friend</a>');
window.location = '/referrals';
}
} else if(_.isObject(obj)) {
for (var name in obj) {
var newnamespace = namespace ? namespace+'-'+name : name;
flash_errors(obj[name], newnamespace);
}
} else {
var add_namespace = false;
var $div = $el.find('.flash-'+namespace+',#flash-'+namespace);
if (!$div.length) {
var dotns = namespace.replace('-', '.');
if (namespace) {
var $inp = $el.find('input[name="'+dotns+'"],select[name="'+dotns+'"],textarea[name="'+dotns+'"],#'+dotns);
if ($inp.length) {
$div = $('<div class="text-danger" />');
if ($inp.parent().hasClass('input-group')) {
$inp.parent().before($div);
} else {
$inp.parent().append($div);
}
}
}
if (!$div.length) {
$div = $el.find('.flash-error,#flash-error');
if (!$div.length) {
$div = $('<div class="text-danger" />');
$el.prepend($div);
add_namespace = !!namespace;
}
}
}
if (add_namespace)
$div.text(namespace+': '+obj);
else
$div.text(obj);
if (obj === 'User account can not view time before one week ago.') {
$div.append('<a href="/change_plan/premium" class="btn btn-primary m-left-xs-10">Upgrade to Premium</a> <span style="color:#000;">or</span> <a href="/referrals" class="btn btn-primary">Refer a Friend</a>');
window.location = '/referrals';
}
}
}
utils.clear_form_errors($el);
if (!_.isObject(json))
json = utils.parse_errors(json);
if (json.error) {
if (_.isObject(json.error))
json.error = json.error.message || 'An error occurred';
var $div = $el.find('.flash-error,#flash-error');
if (!$div.length) {
$div = $('<div class="text-danger" />');
$el.prepend($div);
}
$div.text(json.error);
} else if (json.errors) {
flash_errors(json.errors);
} else {
flash_errors(json);
}
};
utils.parse_errors = function(text) {
var json = false;
try {
json = JSON.parse(text);
} catch (e) { }
if (!json)
json = {};
if (!json.error && !json.errors)
json['error'] = 'Oops, something went wrong.';
if (_.isObject(json.errors) && !_.isArray(json.errors)) {
for (var key in json.errors) {
if (_.isArray(json.errors[key]))
json.errors[key] = _.first(json.errors[key]);
}
}
return json;
};
var explode = function(key, value, obj) {
if (!_.isObject(obj)) obj = {};
var key = key.split('.');
if (key.length > 1) {
var firstKey = key.shift()
obj[firstKey] = explode(key.join('.'), value, obj[firstKey]);
} else {
obj[key[0]] = value;
}
return obj;
};
var extract = function(obj, key) {
if (!_.isObject(obj)) obj = {};
var key = key.split('.');
if (key.length > 1) {
var firstKey = key.shift()
return extract(obj[firstKey], key.join('.'));
}
return obj[key[0]];
};
utils.get_form_inputs = function($el, expand) {
if (_.isUndefined(expand)) expand = true;
var data = {};
$el.find('input,select,textarea').each(function() {
var $el = $(this);
var value = undefined;
if ($el.attr('type') === 'checkbox') {
value = $el.is(':checked');
} else if ($el.attr('type') === 'radio') {
if ($el.is(':checked')) value = $el.val();
} else {
value = $el.val();
}
var name = $el.attr('name');
var type = $el.attr('data-type') || 'string';
if (type == 'integer' || type == 'int') var value = $el.find('input').val();
if (type == 'float') value = parseFloat(value);
if (type == 'bool' || type == 'boolean') value = !!value;
if (expand) {
data = explode(name, value, data);
} else {
data[name] = value;
}
});
return data;
};
utils.confirmBeforeSubmit = function(e) {
var msg = $(this).attr('data-confirm') || 'Are you sure?';
if (!window.confirm(msg)) {
e.preventDefault();
e.stopPropagation();
}
};
$('form.confirm').each(function() {
$(this).on('submit', $.proxy(utils.confirmBeforeSubmit, this));
$(this).removeClass('confirm');
});
jQuery.fn.highlight = function(color) {
if (_.isUndefined(color)) color = '#53C774';
$(this).each(function() {
var el = $(this);
el.before("<div/>")
el.prev()
.width(el.width())
.height(el.height())
.css({
'position': 'absolute',
'background-color': color,
'opacity': '.9',
'margin': el.css('margin'),
'padding': el.css('padding'),
})
.fadeOut(900);
});
}
utils.browserSpecificRender = function() {
var mozilla = !!navigator.userAgent.match(/firefox/i);
if (mozilla) {
$('.hide-if-mozilla').hide();
$('.show-unless-mozilla').hide();
$('.hide-unless-mozilla').removeClass('hide-unless-mozilla');
$('.show-if-mozilla').removeClass('show-if-mozilla');
}
};
utils.browserSpecificRender();
utils.clickToShow = function() {
$('.click-to-show').parent().click(function(e) {
var $el = $(e.target);
if ($el.hasClass('click-to-show')) {
var name = $el[0].nodeName.toLowerCase();
var value = $el.attr('data-click-value');
if (name == 'span')
$el.text(value);
else
$el.val(value);
$el.removeClass('click-to-show');
}
});
};
utils.clickToShow();
utils.clickToCopy = function(selector) {
var $el = $(selector);
var clipboard = new Clipboard(selector);
clipboard.on('success', function(e) {
$el.attr('data-original-title', 'Copied!');
$el.tooltip({
trigger: 'manual',
});
$el.tooltip('show');
e.clearSelection();
});
clipboard.on('error', function(e) {
$el.attr('data-original-title', 'Press Ctrl+C to copy');
$el.tooltip({
trigger: 'manual',
});
$el.tooltip('show');
});
return clipboard;
};
function saveEditable($el, method) {
if (!method) method = 'PATCH';
$.ajax({
type: method,
url: $el.attr('data-url')+'?show='+encodeURIComponent($el.find('input').attr('name')),
contentType: 'application/json',
data: JSON.stringify(utils.get_form_inputs($el)),
}).fail(function(response) {
utils.set_form_errors($el, response.responseText);
}).done(function(data) {
$el.find('span').text(extract(data.data, $el.find('input').attr('name')));
$el.editing = false;
});
}
$('.editable').each(function() {
var $el = $(this);
var value = $el.text();
var $edit = $('<a href="#"><i class="fa fa-edit"></i></a>');
$el.html('<span></span>').append($edit).find('span').text(value);
var space = 5;
$edit.css({
'padding-left': space+'px',
'display': 'inline-block',
'font-size': '18px',
'width': '18px',
});
$el.css('padding-right', ($edit.width()+space)+'px');
$edit.css('display', 'none');
$el.hover(function() {
if (!$el.editing) {
$el.css('padding-right', '0px');
$edit.css('display', 'inline-block');
}
}, function() {
$el.css('padding-right', ($edit.width()+space)+'px');
$edit.css('display', 'none');
});
$edit.on('click', function(e) {
e && e.preventDefault();
$el.editing = true;
$el.css('padding-right', $edit.width()+'px');
$edit.css('display', 'none');
$el.attr('data-value', $el.find('span').text());
var $inp = $('<input type="text" />');
if ($el.attr('data-name')) $inp.attr('name', $el.attr('data-name'));
if ($el.attr('data-type')) $inp.attr('data-type', $el.attr('data-type'));
$el.find('span').html($inp);
$inp.focus();
$inp.val($el.attr('data-value'));
$inp.blur(function() {
saveEditable($el);
});
$inp.keyup(function(e) {
if ((e.keyCode ? e.keyCode : e.which) == 13)
$inp.blur();
});
});
});
utils.formatSecondsForDisplay = function(num, without_commas) {
var units = 'Seconds';
if (!num) {
return {
value: 0,
units: units,
};
}
if (num > 60) {
units = 'Minutes';
num = Math.floor(num / 60.0);
}
if (num > 60) {
units = 'Hours';
num = Math.floor(num / 60.0);
}
if (!without_commas) num = utils.numberWithCommas(num);
return {
value: num,
units: units,
};
};
utils.numberWithCommas = function(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
utils.format_seconds = function(total_seconds, show_seconds, hide_minutes) {
var text = '';
if (total_seconds == 0 || (!show_seconds && total_seconds < 60)) {
return '0 minutes';
}
var hours = Math.floor(total_seconds / 3600.0);
var minutes = Math.floor(total_seconds / 60.0) % 60;
var seconds = total_seconds % 60;
if (hours > 0) {
text += hours + ' hour';
if (hours !== 1) {
text += 's';
}
}
if (minutes > 0 && !hide_minutes) {
text += ' ' + minutes + ' minute';
if (minutes !== 1) {
text += 's';
}
}
if (seconds > 0 && show_seconds) {
text += ' ' + seconds + ' second';
if (seconds !== 1) {
text += 's';
}
}
return $.trim(text);
};
utils.commonPath = function(paths, sep) {
if (paths.length < 2)
return '';
if (!sep)
sep = '/';
var hash = {};
_.each(paths, function(path) {
if (path !== undefined) {
if (path.substring(0, 1) == sep)
path = path.substring(1);
var tok = path.split(sep);
_.each(tok, function(t, index) {
if (!(tok.slice(0,index).join(sep) in hash))
hash[tok.slice(0,index).join(sep)] = 0;
hash[tok.slice(0,index).join(sep)] += 1;
});
}
});
var max = _.max(_.values(hash), function(v) { return v;});
if (max != paths.length)
return '';
var res = _.filter(_.keys(hash), function(h) {
return hash[h] == max;
});
var common = _.reduce(res, function(a, b) {
return a.length > b.length ? a : b;
});
if (common.length > 0)
common = sep+common;
return common;
};
utils.toMoment = function(dateString, timezone) {
if (_.isString(dateString)) {
dateString = dateString.replace('/', '-').replace('/', '-');
if (timezone) {
if (dateString.match(/\d\d\d\d-\d\d-\d\d/)) return moment.tz(dateString, 'YYYY-MM-DD', timezone);
if (dateString.match(/\d\d-\d\d-\d\d\d\d/)) return moment.tz(dateString, 'MM-DD-YYYY', timezone);
} else {
if (dateString.match(/\d\d\d\d-\d\d-\d\d/)) return moment(dateString, 'YYYY-MM-DD');
if (dateString.match(/\d\d-\d\d-\d\d\d\d/)) return moment(dateString, 'MM-DD-YYYY');
}
} else if (_.isNumber(dateString)) {
dateString = moment.unix(dateString);
if (timezone) dateString = dateString.tz(timezone);
return dateString;
} else if (moment.isMoment(dateString)) {
return dateString.clone();
}
return dateString;
};
utils.naturalDate = function(date, timezone) {
date = utils.toMoment(date, timezone);
var today = moment();
if (timezone) today = today.tz(timezone);
if (today.isSame(date, 'day')) {
return 'Today';
}
if (today.clone().subtract(1, 'days').isSame(date, 'day')) {
return 'Yesterday';
}
return date.format('ddd MMM Do');
};
utils.naturalDateRange = function(start, end, timezone) {
start = utils.toMoment(start, timezone);
end = utils.toMoment(end, timezone);
var today = moment();
if (timezone) today = today.tz(timezone);
var range = Math.abs(end.diff(start.clone().subtract(1, 'days'), 'days'));
var range_as_text = end.from(start.clone().subtract(1, 'days'), true);
if (start.isSame(end, 'day')) {
return {text:utils.naturalDate(start), range: range, range_as_text: range_as_text};
}
if (today.isSame(end, 'day') && today.clone().subtract(6, 'days').isSame(start, 'day')) {
return {text:'Last 7 Days', article:'in the', range: range, range_as_text: range_as_text};
}
if (today.clone().subtract(1, 'days').isSame(end, 'day') && today.clone().subtract(7, 'days').isSame(start, 'day')) {
return {text:'Last 7 Days from Yesterday', article:'in the', range: range, range_as_text: range_as_text};
}
if (today.isSame(end, 'day') && today.clone().subtract(29, 'days').isSame(start, 'day')) {
return {text:'Last 30 Days', article:'in the', range: range, range_as_text: range_as_text};
}
if (today.clone().subtract(1, 'days').isSame(end, 'day') && today.clone().subtract(30, 'days').isSame(start, 'day')) {
return {text:'Last 30 Days from Yesterday', article:'in the', range: range, range_as_text: range_as_text};
}
if (today.startOf('week').isSame(start, 'day') && today.clone().endOf('week').isSame(end, 'day')) {
return {text:'This Week', range: range, range_as_text: range_as_text};
}
if (today.clone().subtract(1, 'week').startOf('week').isSame(start, 'day') && today.clone().subtract(1, 'week').endOf('week').isSame(end, 'day')) {
return {text:'Last Week', range: range, range_as_text: range_as_text};
}
if (today.startOf('month').isSame(start, 'day') && today.isSame(end, 'day')) {
return {text:'This Month', range: range, range_as_text: range_as_text};
}
if (today.clone().subtract(1, 'month').startOf('month').isSame(start, 'day') && today.clone().subtract(1, 'month').endOf('month').isSame(end, 'day')) {
return {text:'Last Month', range: range, range_as_text: range_as_text};
}
return {text: utils.naturalDate(start) + ' until ' + utils.naturalDate(end), article:'from', range: range, range_as_text: range_as_text};
};
utils.hideModalWhenEscapePressed = function() {
$('.modal').modal('show').on('hidden.bs.modal', function (e) {
$(document).unbind('keyup');
});
$(document).keyup(function(e) {
if (e.keyCode == 27) $('.modal').modal('hide');
});
};
utils.initProgressBar = function(startValue) {
var progress = progressJs();
progress.setOptions({
overlayMode: false,
theme: 'blueOverlay',
});
progress.start();
if (startValue) progress.set(startValue);
progress.autoIncrease(4, 100);
return progress;
};
utils.updateTimeZone = function(timezone, options) {
if (timezone) {
var params = {
type: 'PATCH',
url: '/api/v1/users/current',
contentType: 'application/json',
data: JSON.stringify({
timezone: timezone,
}),
};
params = _.defaults(options || {}, params);
$.ajax(params);
}
};
utils.getTimeRanges = function(timezone) {
if (moment !== undefined && timezone) {
return {
'Today': [moment().tz(timezone), moment().tz(timezone)],
'Yesterday': [moment().tz(timezone).subtract(1, 'days'), moment().tz(timezone).subtract(1, 'days')],
'Last 7 Days': [moment().tz(timezone).subtract(6, 'days'), moment().tz(timezone)],
'Last 30 Days': [moment().tz(timezone).subtract(29, 'days'), moment().tz(timezone)],
'This Week': [moment().tz(timezone).startOf('week'), moment().tz(timezone).endOf('week')],
'Last Week': [moment().tz(timezone).subtract(1, 'week').startOf('week'), moment().tz(timezone).subtract(1, 'week').endOf('week')],
'This Month': [moment().tz(timezone).startOf('month'), moment().tz(timezone)],
'Last Month': [moment().tz(timezone).subtract(1, 'month').startOf('month'), moment().tz(timezone).subtract(1, 'month').endOf('month')],
};
} else {
return [
'Today',
'Yesterday',
'Last 7 Days',
'Last 30 Days',
'This Week',
'Last Week',
'This Month',
'Last Month',
];
}
};
utils.getUrlParams = function() {
var params = {};
_.each(window.location.search.replace('?', '').split('&'), function(param) {
param = param.split('=', 2);
if (param.length == 2) {
params[decodeURIComponent(param[0])] = decodeURIComponent(param[1]);
}
});
return params;
};
utils.updateUrl = function(params, callback) {
// Overwrite existing url args with new params.
params = _.defaults(params || {}, utils.getUrlParams());
// Turn params into browser query string.
var search = '?' + _.map(params, function(val, key) { return [encodeURIComponent(key), encodeURIComponent(val)].join('='); }).join('&');
// Check if the browser supports HTML5 push state.
if (window.history) {
var url = window.location.pathname + search;
window.history.pushState({}, document.title, url);
if (callback) callback();
} else {
// If browser does not support push state, we update the url
// with new params causing the browser to make a new request.
window.location.search = search;
}
};
utils.niceScroll = function(padding) {
if (!padding) padding = 0;
$('.scroll').each(function() {
var $el = $(this);
var url = $el.attr('href');
$el.click(function(e) {
var $anchor = $('a[name="'+url.substring(1)+'"]');
if (url.indexOf('#') == 0 && $anchor.length) {
e && e.preventDefault();
$('html,body').animate({
scrollTop: $anchor.next().offset().top + $anchor.next().scrollTop() - padding,
}, 1000);
}
});
});
};
utils.niceScroll();
utils.shortenLoggedTimeText = function(text) {
if (!_.isString(text)) return text;
return text.replace('hours', 'h').replace('hour', 'h').replace('minutes', 'm').replace('minute', 'm');
};
utils.renderTips = function() {
$('.tip').tooltip();
$('[data-toggle="popover"]').popover();
$('[data-toggle="popover"]').on('shown.bs.popover', function(e) {
var $content = $('#'+$(e.target).attr('aria-describedby'));
$content.length && $content.attr('data-forceclose', 'false');
});
$('[data-toggle="popover"]').on('hide.bs.popover', function(e) {
var $el = $(e.target);
if (s.include($el.attr('data-trigger'), 'hover')) {
var $content = $('#'+$(e.target).attr('aria-describedby'));
if ($content.length && $content.attr('data-forceclose') != 'true') {
e && e.preventDefault();
$content.on('mouseleave', function(e2) {
$content.attr('data-forceclose', 'true');
$el.popover('hide');
});
var timeout = setTimeout(function() {
$content.attr('data-forceclose', 'true');
$el.popover('hide');
}, 500);
$content.on('mouseenter', function(e2) {
if (timeout) clearTimeout(timeout);
});
}
}
});
};
utils.hideSumo = function() {
var hide = function() {
if ($('a[title="SumoMe"]').length) {
$('a[title="SumoMe"]').attr('data-style', $('a[title="SumoMe"]').attr('style'));
$('a[title="SumoMe"]').attr('style', 'display:none;');
} else {
setTimeout(hide, 20);
}
};
hide();
};
utils.showSumo = function() {
if ($('a[title="SumoMe"]').length) {
$('a[title="SumoMe"]').attr('style', $('a[title="SumoMe"]').attr('data-style'));
}
};
// mark notifications as read after viewed
$('#announcements-icon').on('click', function(e) {
if ($('#announcements-icon #announcements-icon-dropdown i.fa-circle').hasClass('has-announcements')) {
$.ajax({
type: 'POST',
url: '/api/v1/users/current/announcements/dismiss',
}).fail(function(response) {
$('#announcements-icon #announcements-icon-dropdown i.fa-circle').removeClass('has-announcements');
$('#announcements-icon #announcements-icon-dropdown i.fa span').remove();
$('#announcements-icon #announcements-icon-dropdown i.fa-bell').removeClass('hidden');
}).done(function(modal_html) {
$('#announcements-icon #announcements-icon-dropdown i.fa-circle').removeClass('has-announcements');
$('#announcements-icon #announcements-icon-dropdown i.fa span').remove();
$('#announcements-icon #announcements-icon-dropdown i.fa-bell').removeClass('hidden');
});
}
});
// prevent clicking on notifications from closing the dropdown
$('#announcements-icon ul.dropdown-menu').on('click', function(e) {
e && e.stopPropagation();
});
window.utils = utils;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment