408 lines
12 KiB
JavaScript
408 lines
12 KiB
JavaScript
// License: LGPL-3.0-or-later
|
|
var confirmation = require('./confirmation')
|
|
var notification = require('./notification')
|
|
var request = require("superagent")
|
|
var moment = require('moment-timezone')
|
|
var client = require('./client')
|
|
var appl = require('view-script')
|
|
const on_ios11 = require('./on-ios11')
|
|
function prepareForIOS11(id)
|
|
{
|
|
let bad_elements = $("#" + id)
|
|
for(var i = 0; i < bad_elements.length; i++)
|
|
{
|
|
bad_elements[i].classList.add('ios-force-absolute-positioning')
|
|
}
|
|
|
|
|
|
document.body.scrollTop = 0 // so incredibly hacky
|
|
}
|
|
|
|
module.exports = appl
|
|
|
|
// A couple short convenience functions for disabling/enabling the global
|
|
// loading state
|
|
appl.is_loading = function() {appl.def('loading', true)}
|
|
appl.not_loading = function() {appl.def('loading', false)}
|
|
appl.not_loading()
|
|
|
|
// Define the current payment plan tier for a signed-in nonprofit
|
|
appl.def('current_plan_tier', app.current_plan_tier)
|
|
|
|
appl.def("is_at_least_plan", function(tier) {
|
|
return app.current_plan_tier >= tier
|
|
})
|
|
|
|
// Open a modal given by its modal id (uses the modal div's 'id' attribute)
|
|
appl.def('open_modal', function(modalId) {
|
|
$('.modal').removeClass('inView')
|
|
|
|
//$('body').scrollTop(0)
|
|
$('#' + modalId).addClass('inView')
|
|
|
|
$('body').addClass('is-showingModal')
|
|
if (on_ios11()){
|
|
prepareForIOS11(modalId)
|
|
}
|
|
return appl
|
|
})
|
|
|
|
// Close any and all open modals
|
|
appl.def('close_modal', function() {
|
|
$('.modal').removeClass('inView')
|
|
$('body').removeClass('is-showingModal')
|
|
return appl
|
|
})
|
|
|
|
// Open a given modal id only when the User's Account is confirmed via email
|
|
// If the user's account is not confirmed, then show an informational modal
|
|
// about confirming their account
|
|
appl.def('open_modal_if_confirmed', function(modalId){
|
|
if (app.user && app.user.confirmed)
|
|
appl.open_modal(modalId)
|
|
else if (app.user && !app.user.confirmed)
|
|
appl.open_modal('emailConfirmationModal')
|
|
else
|
|
appl.open_modal('signUpModal')
|
|
return appl
|
|
})
|
|
|
|
|
|
// Open a confirmation modal for the user to click 'yes' or 'no'
|
|
// Optionally pass in a string message as the first arg (default is 'Are you sure?')
|
|
// The last argument is the function to execute when 'yes' is clicked
|
|
// Clicking 'no' simply closes the modal
|
|
appl.def_lazy('confirm', function() {
|
|
var msg, expr, node, self = this
|
|
if(arguments.length === 2) {
|
|
msg = 'Are you sure?'
|
|
expr = arguments[0]
|
|
node = arguments[1]
|
|
} else {
|
|
msg = appl.vs(arguments[0])
|
|
expr = arguments[1]
|
|
node = arguments[2]
|
|
}
|
|
|
|
var result = confirmation(msg)
|
|
result.confirmed = function() { appl.vs(expr, node) }
|
|
return self
|
|
})
|
|
|
|
|
|
// Display a temporary notification message at the bottom of the window
|
|
appl.def('notify', function(msg) {
|
|
notification(msg)
|
|
return appl
|
|
})
|
|
|
|
// Convert cents to dollars
|
|
appl.def('cents_to_dollars', function(cents) {
|
|
return utils.cents_to_dollars(cents)
|
|
})
|
|
|
|
|
|
const momentTz = date =>
|
|
moment.tz(date, "YYYY-MM-DD HH:mm:ss", 'UTC').tz(ENV.nonprofitTimezone || 'UTC')
|
|
|
|
// Return a date in the format MM/DD/YY for a given date string or moment obj
|
|
appl.def('readable_date', function(date) {
|
|
if(!date) return
|
|
return momentTz(date).format("MM/DD/YY")
|
|
})
|
|
|
|
// Given a created_at string (eg. Charge.last.created_at.to_s), convert it to a readable date-time string
|
|
appl.def('readable_date_time', function(date) {
|
|
if(!date) return
|
|
return momentTz(date).format("MM/DD/YY H:mma z")
|
|
})
|
|
|
|
// converts the return value of readable_date_time to it's ISO equivalent
|
|
appl.def('readable_date_time_to_iso', date => {
|
|
if(!date) return
|
|
return moment.tz(date, 'MM/DD/YY H:mma z', ENV.nonprofitTimezone || 'UTC')
|
|
.tz('UTC')
|
|
.toISOString()
|
|
})
|
|
|
|
// Get the month number (eg 01,02...) for the given date string (or moment obj)
|
|
appl.def('get_month', function(date) {
|
|
var monthNum = moment(date).month()
|
|
return moment().month(monthNum).format('MMM')
|
|
})
|
|
|
|
// Get the year (eg 2017) for the given date string (or moment obj)
|
|
appl.def('get_year', function(date) {
|
|
return moment(date).year()
|
|
})
|
|
|
|
// Get the day (number in the month) for the given date string (or moment obj)
|
|
appl.def('get_day', function(date) {
|
|
return moment(date).date()
|
|
})
|
|
|
|
|
|
// Get the percentage of x over y
|
|
// eg: appl.percentage(34, 69) -> "49.28%"
|
|
appl.def('percentage', function(x, y) {
|
|
return String(x / y * 100) + '%'
|
|
})
|
|
|
|
|
|
// Given a quantity and a plural word describing that quantity,
|
|
// return the proper version of that word for that quantitiy
|
|
// eg: appl.pluralize(4, 'tomatoes') -> "4 tomatoes"
|
|
// appl.pluralize(1, 'donors') -> "1 donor"
|
|
appl.def('pluralize', function(quantity, plural_word) {
|
|
var str = String(quantity) + ' '
|
|
if(quantity !== 1) return str+plural_word
|
|
else return str + appl.to_singular(plural_word)
|
|
})
|
|
|
|
|
|
// Convert (most) words from their plural to their singular form
|
|
// Works with simple s-endings, ies-endings, and oes-endings
|
|
appl.def('to_singular', function(plural_word) {
|
|
return plural_word
|
|
.replace(/ies$/, 'y')
|
|
.replace(/oes$/, 'o')
|
|
.replace(/s$/, '')
|
|
})
|
|
|
|
|
|
// Truncate a text and add ellipsis to the end
|
|
appl.def('append_ellipsis', function(text, length) {
|
|
if(text.length <= length) return text
|
|
return text.slice(0,length).replace(/ [^ ]+$/, ' ...')
|
|
})
|
|
|
|
|
|
// General viewscript utilities
|
|
// All of these are to be added to the actual viewscript package in the future
|
|
|
|
// Push a given value into the arr given by the property name 'arr_key'
|
|
// Mutates the array stored at 'arr_key'
|
|
// appl.def('arr', [1,2,3])
|
|
// appl.push('arr', 4)
|
|
// appl.arr == [1,2,3,4]
|
|
appl.def('push', function(val, arr_key, node) {
|
|
var arr = appl.vs(arr_key, node)
|
|
if(!arr || !arr.length) arr = []
|
|
arr.push(val)
|
|
appl.def(arr_key, arr)
|
|
return appl
|
|
})
|
|
|
|
|
|
// Concatenate two arrays (this is mutating)
|
|
// The first array is given by its property name and will be mutated
|
|
// The second array is the array itself to concatenate
|
|
// appl.def('arr1', [1,2,3])
|
|
// appl.concat('arr1', [4,5,6])
|
|
// appl.arr1 == [1,2,3,4,5,6]
|
|
appl.def('concat', function(arr1_key, arr2, node) {
|
|
var arr1 = appl.vs(arr1_key, node)
|
|
appl.def(arr1_key, arr1.concat(arr2))
|
|
return appl
|
|
})
|
|
|
|
|
|
// Merge all key/vals from set_obj into all objects in the array given by the property 'arr_key'
|
|
// eg:
|
|
// appl.def('arr_of_objs', [{id: 1, name: 'Bob'}, {id: 2, name: 'Holga'}]
|
|
// appl.update_all('arr_of_objs', {name: 'Morty'})
|
|
// appl.arr_of_objs == [{id: 1, name: 'Morty'}, {id: 2, name: 'Morty'}]
|
|
appl.def('update_all', function(arr_key, set_obj, node) {
|
|
appl.def(arr_key, appl.vs(arr_key).map(function(obj) {
|
|
for(var key in set_obj) obj[key] = set_obj[key]
|
|
return obj
|
|
}))
|
|
})
|
|
|
|
|
|
// Given an array of objects in the view state (with property name 'arr_key'),
|
|
// and given an object to match on ('obj_matcher'),
|
|
// and given an object with values to set ('set_obj'),
|
|
// then set each object that matches key/vals in the obj_matcher to the key/vals in set_obj
|
|
//
|
|
// eg, if val at arr_key is: [{id: 1, name: 'Bob'}, {id: 2, name: 'Holga'}]
|
|
// and obj_matcher is: {id: 1}
|
|
// and set_obj is: {name: 'Gertrude'}
|
|
// then result will be: [{id: 1, name: 'Gertrude'}, {id: 2, name: 'Holga'}]
|
|
appl.def('find_and_set', function(arr_key, obj_matcher, set_obj, node) {
|
|
var arr = appl.vs(arr_key)
|
|
if(!arr) return appl
|
|
var result = arr.map(function(obj) {
|
|
for (var key in obj_matcher) {
|
|
if(obj_matcher[key] === obj[key]) {
|
|
return utils.merge(obj, set_obj)
|
|
}
|
|
}
|
|
return obj
|
|
})
|
|
appl.def(arr_key, result)
|
|
return appl
|
|
})
|
|
|
|
appl.def('find_and_remove', function(arr_key, obj_matcher, set_obj, node) {
|
|
var arr = appl.vs(arr_key)
|
|
if(!arr) return appl
|
|
var result = arr.reduce(function(new_arr, obj) {
|
|
for (var key in obj_matcher) {
|
|
if(obj_matcher[key] === obj[key]) {
|
|
return new_arr
|
|
} else {
|
|
new_arr.push(obj)
|
|
return new_arr
|
|
}
|
|
}
|
|
}, [])
|
|
appl.def(arr_key, result)
|
|
return appl
|
|
})
|
|
|
|
|
|
// Return a boolean whether the parent input is checked (must be a type checkbox)
|
|
appl.def('is_checked', function(node) {
|
|
return appl.prev_elem(node).checked
|
|
})
|
|
|
|
// Check a parent input node (must be type checkbox)
|
|
appl.def('check', function(node) {
|
|
appl.prev_elem(node).checked = true
|
|
})
|
|
|
|
// Uncheck a parent input node (must be type checkbox)
|
|
appl.def('uncheck', function(node) {
|
|
appl.prev_elem(node).checked = false
|
|
})
|
|
|
|
// Check the parent node if the predicate is true
|
|
appl.def('checked_if', function(pred, node) {
|
|
if(pred) appl.prev_elem(node).checked = true
|
|
else appl.prev_elem(node).checked = false
|
|
})
|
|
|
|
// Remove an attribute from the parent node
|
|
appl.def('remove_attr', function(attr, node) {
|
|
appl.prev_elem(node).removeAttribute(attr)
|
|
})
|
|
|
|
appl.def('remove_attr_if', function(pred, attr, node) {
|
|
if(!node) return
|
|
var n = appl.prev_elem(node)
|
|
if(pred) {
|
|
if(!n.hasAttribute('data-attr-' + attr)) n.setAttribute('data-attr-' + attr, n.getAttribute(attr)) // cache attr to add back in
|
|
n.removeAttribute(attr)
|
|
} else if(!n.hasAttribute(attr)) {
|
|
var val = n.getAttribute('data-attr-' + attr)
|
|
n.setAttribute(attr, val)
|
|
}
|
|
})
|
|
|
|
// Map over the given list and update it in the view
|
|
appl.transform = function(name, fn) {
|
|
var result = appl.vs(name).map(fn)
|
|
appl.def(name, result)
|
|
return result
|
|
}
|
|
|
|
// Return the current URL path
|
|
appl.def('pathname', function() { return window.location.pathname })
|
|
// Return the root url
|
|
appl.def('root_url', function() { return window.location.origin })
|
|
|
|
// Trigger a property to get updated in the view
|
|
appl.def('trigger_update', function(prop) {
|
|
return appl.def(prop, appl.vs(prop))
|
|
})
|
|
|
|
|
|
appl.def('snake_case', function(string) {
|
|
return string.replace(/ /g,'_')
|
|
})
|
|
|
|
appl.def('sort_arr_of_objs_by_key', function(arr_of_objs, key) {
|
|
return arr_of_objs.sort(function(a, b) {
|
|
return a[key].localeCompare(b[key]);
|
|
})
|
|
})
|
|
|
|
// Convert a positive integer into an ordinal (1st, 2nd, 3rd...)
|
|
appl.def('ordinalize', function(n) {
|
|
if(n <= 0) return n
|
|
// Deal with the preteen punks first
|
|
if([11,12,13].indexOf(n) !== -1) return String(n) + 'th'
|
|
var str = String(n)
|
|
var lst = str[str.length-1]
|
|
if(lst === '1') return String(n) + 'st'
|
|
else if(lst === '2') return String(n) + 'nd'
|
|
else if(lst === '3') return String(n) + 'rd'
|
|
else return String(n) + 'th'
|
|
})
|
|
|
|
appl.def('toggle_side_nav', function(){
|
|
if(appl.side_nav_is_open)
|
|
appl.def('side_nav_is_open', false)
|
|
else
|
|
appl.def('side_nav_is_open', true)
|
|
})
|
|
|
|
appl.def('head', function(arr) {
|
|
if(arr === undefined) return undefined
|
|
return arr[0]
|
|
})
|
|
|
|
appl.def('select_drop_down', function(node) {
|
|
var $li = $(node).parent()
|
|
var $dropDown = $li.parents('.dropDown')
|
|
$dropDown.find('li').removeClass('is-selected')
|
|
$dropDown.find('.dropDown-toggle').removeClass('is-droppedDown')
|
|
$li.addClass('is-selected')
|
|
})
|
|
|
|
appl.def('clear_drop_down', function(node){
|
|
var $dropDown = $(node).parents('.dropDown')
|
|
$dropDown.find('li').removeClass('is-selected')
|
|
$dropDown.find('.dropDown-toggle').removeClass('is-droppedDown')
|
|
})
|
|
|
|
appl.def('strip_tags', function(html){
|
|
if(!html) return
|
|
return html.replace(/(<([^>]+)>)/ig," ")
|
|
})
|
|
|
|
appl.def('replace', function(string, matcher, replacer) {
|
|
if(!string) return
|
|
// the new RegExp constructor takes a string
|
|
// and returns a regex: new RegExp("a|b", "i") becomes /a|b/i
|
|
return string.replace(new RegExp(matcher, 'g'), replacer)
|
|
})
|
|
|
|
appl.def('number_with_commas', function(n){
|
|
if(!n){return}
|
|
return utils.number_with_commas(n)
|
|
})
|
|
|
|
appl.def('remove_commas', function(s) {
|
|
return s.replace(/,/g, '')
|
|
})
|
|
|
|
appl.def('percentage', function(x, y, number_of_decimals){
|
|
if(!x || !y) return 0
|
|
number_of_decimals = number_of_decimals || 2
|
|
return Number((y/x * 100).toFixed(number_of_decimals))
|
|
})
|
|
|
|
appl.def('clean_url', function(string){
|
|
return string.replace(/.*?:\/\//g, "")
|
|
})
|
|
|
|
appl.def('address_with_commas', function(address, city, state){
|
|
return utils.address_with_commas(address, city, state)
|
|
})
|
|
|
|
appl.def('format_phone', function(st) {
|
|
return utils.pretty_phone(st)
|
|
})
|
|
|