// This [jQuery](https://jquery.com/) plugin implements an `
" + this.textos["estado_" + estadoFinanciero + "b"] + "";
},
calculateTextosDiaADia: function () {
var resultsA = 0
var resultsB = 0
var resultsC = 0
for (i = 0; i < this.questions.length; i++) {
answer = this.filterAnswersByValue(this.questions[i].answers, this.results[i])
if (answer == undefined) {
continue;
}
resultsA += answer.areaA
resultsB += answer.areaB
resultsC += answer.areaC
}
var estadoFinanciero = 3;
var estadoDiaADia = resultsA / 2;
var estadoSiOcurriera = resultsB / 2;
var estadoPlanifique = resultsC / 2;
if ((estadoDiaADia >= 4) || (estadoSiOcurriera >= 4) || (estadoPlanifique >= 4)) {
estadoFinanciero = 3;
} else {
estadoFinanciero = Math.round((estadoDiaADia + estadoSiOcurriera + estadoPlanifique) / 3);
if (estadoFinanciero >= 4) {
estadoFinanciero = 3;
}
}
return this.textos["DAD_" + estadoFinanciero];
},
calculateTextosSiOcurriera: function () {
var resultsA = 0
var resultsB = 0
var resultsC = 0
for (i = 0; i < this.questions.length; i++) {
answer = this.filterAnswersByValue(this.questions[i].answers, this.results[i])
if (answer == undefined) {
continue;
}
resultsA += answer.areaA
resultsB += answer.areaB
resultsC += answer.areaC
}
var estadoFinanciero = 3;
var estadoDiaADia = resultsA / 2;
var estadoSiOcurriera = resultsB / 2;
var estadoPlanifique = resultsC / 2;
if ((estadoDiaADia >= 4) || (estadoSiOcurriera >= 4) || (estadoPlanifique >= 4)) {
estadoFinanciero = 3;
} else {
estadoFinanciero = Math.round((estadoDiaADia + estadoSiOcurriera + estadoPlanifique) / 3);
if (estadoFinanciero >= 4) {
estadoFinanciero = 3;
}
}
return this.textos["SOP_" + estadoFinanciero];
},
calculateTextosPlanificacion: function () {
var resultsA = 0
var resultsB = 0
var resultsC = 0
for (i = 0; i < this.questions.length; i++) {
answer = this.filterAnswersByValue(this.questions[i].answers, this.results[i])
if (answer == undefined) {
continue;
}
resultsA += answer.areaA
resultsB += answer.areaB
resultsC += answer.areaC
}
var estadoFinanciero = 3;
var estadoDiaADia = resultsA / 2;
var estadoSiOcurriera = resultsB / 2;
var estadoPlanifique = resultsC / 2;
if ((estadoDiaADia >= 4) || (estadoSiOcurriera >= 4) || (estadoPlanifique >= 4)) {
estadoFinanciero = 3;
} else {
estadoFinanciero = Math.round((estadoDiaADia + estadoSiOcurriera + estadoPlanifique) / 3);
if (estadoFinanciero >= 4) {
estadoFinanciero = 3;
}
}
return this.textos["FUT_" + estadoFinanciero];
},
}
});
function formatCurrencyNumber(number, fractionDigits) {
fractionDigits = fractionDigits || 2
return number.toLocaleString('de-DE', { maximumFractionDigits: fractionDigits }) + "€"
}
function formatNumber(number, fractionDigits) {
fractionDigits = fractionDigits || 2
return number.toLocaleString('de-DE', { maximumFractionDigits: fractionDigits })
}
;
// Function to show result of tooltipItem
function labelGoalsTooltip(tooltipItems) {
return formatCurrencyNumber(parseFloat(tooltipItems.raw));
}
// Function to show result of tooltipItem
function titleGoalsTooltip(tooltipItems) {
return tooltipItems[0].label;
}
function myGoalsConfig(data) {
var config = {
type: 'bar',
data: data,
options: {
plugins: {
title: {
display: false,
},
legend: {
display: false
},
tooltip: {
displayColors: false,
callbacks: {
title: titleGoalsTooltip,
label: labelGoalsTooltip,
},
backgroundColor: '#FFF',
titleColor: '#0066ff',
bodyColor: '#000',
footerColor: '#000'
},
},
responsive: true,
scales: {
x: {
min: 0
}
}
}
}
return config;
}
Vue.component('my-goals-calculator', {
props: [
'bgImage',
'footerInfo',
'title',
'summary'
],
data: function () {
return {
categories: [
{ title: 'Objetivo 1', total: '0' },
{ title: 'Objetivo 2', total: '0' },
{ title: 'Objetivo 3', total: '0' },
{ title: 'Objetivo 4', total: '0' },
{ title: 'Objetivo 5', total: '0' }
],
ctx: undefined,
data: undefined,
mc_aportacion: 0,
mc_capital: 1000,
mc_numAnyos: 5,
mc_impositivo: 19,
mc_interes: 2,
myChart: undefined,
ratio: undefined,
timestamp: new Date().getTime(),
total: 0
}
},
mounted: function () {
this.renderChart()
},
computed: {
app_id: function () {
return 'my-goals-calculator-' + this.timestamp
},
backgroundImage: function () {
return 'background: url(' + this.bgImage + ') 0 50% no-repeat;'
},
disableCalculateButton: function () {
return (this.mc_capital == '' || this.mc_numAnyos == '' || this.mc_impositivo == '' || this.mc_interes == '')
},
message: function () {
return "Tendrás que ahorrar " + formatCurrencyNumber(this.total) + " al mes para poder conseguir los " + formatCurrencyNumber(this.mc_aportacion) + " que te hacen falta para llevar a cabo todos tus objetivos en " + this.mc_numAnyos + " años años.
Recuerda que esta cantidad tiene en cuenta los intereses percibidos y los impuestos correspondientes."
},
messageTotal: function () {
return "Aportación semanal necesaria (€): " + formatCurrencyNumber(this.total / 4) + ""
},
messageLabel: function() {
return "Tendrás que ahorrar " + formatCurrencyNumber(this.total) + " al mes para poder conseguir los " + formatCurrencyNumber(this.mc_aportacion) + " que te hacen falta para llevar a cabo todos tus objetivos en " + this.mc_numAnyos + " años. Recuerda que esta cantidad tiene en cuenta los intereses percibidos y los impuestos correspondientes."
}
},
methods: {
calculate: function () {
this.mc_aportacion = 0
for (i = 0; i < this.categories.length; i++) {
this.mc_aportacion += parseFloat(this.categories[i].total)
}
interes = this.mc_interes / 100
impositivo = this.mc_impositivo / 100
interesMensual = (interes / 12) * (1 - (impositivo))
interesAnyos = Math.pow((1 + interesMensual), (this.mc_numAnyos * 12))
if (this.mc_capital >= this.mc_aportacion) {
alert("El capital disponible al inicio no debe superar al capital que se desea ahorrar. Capital inicial: " + this.mc_capital + " Ahorro: " + this.mc_aportacion)
this.total = 0
}
else {
this.total = (this.mc_aportacion - (this.mc_capital * interesAnyos)) / ((interesAnyos - 1) / interesMensual);
this.data = this.calculateAccumulated(interesMensual, this.mc_capital, this.total)
this.myChart.data = this.data
this.myChart.update()
}
},
calculateAccumulated: function (interesMensual, incrementoCapital, aportacion) {
this.data = {
labels: [],
datasets: [{
label: '',
data: [],
backgroundColor: [],
borderColor: [],
borderWidth: 1
}]
}
for (var i = 0; i < this.mc_numAnyos; i++) {
incrementoCapital = incrementoCapital * Math.pow(1 + interesMensual, 12) + aportacion * ((Math.pow(1 + interesMensual, 12) - 1) / (interesMensual));
this.data.labels[i] = "Año " + (i + 1)
this.data.datasets[0].data.push(incrementoCapital)
this.data.datasets[0].backgroundColor.push('rgba(128, 211, 49, 1)')
this.data.datasets[0].borderColor.push('rgba(128, 211, 49, 1)')
}
return this.data
},
checkCategoriesValues: function () {
for (var i = 0; i < this.categories.length; i++) {
if (this.categories[i].total != undefined && this.categories[i].total != '') {
continue;
}
this.categories[i].total = 0;
}
},
renderChart: function () {
this.ctx = document.getElementById(this.app_id);
this.myChart = new Chart(this.ctx, myGoalsConfig(this.data));
}
}
});
// Definición de componente para calculo de mis objetivos
// Se corresponde con la herramienta de FxT http://finanzasparatodos.es/es/secciones/herramientas/calculadoraahorrar_mes2.html
// Definición de componente para calculo de mis ahorros
// Se corresponde con la herramienta de FxT http://finanzasparatodos.es/es/secciones/herramientas/calculadoraahorrar_mes1.html
Vue.component('my-goals-child-calculator', {
props: [
'bgImage',
'footerInfo',
'title',
'summary'
],
data: function () {
return {
colors: ['u-color1', 'u-color2', 'u-color3', 'u-color4'],
goals: [],
incomings: [],
newGoal: '',
step: 1,
results: [],
variations: [0.7, 0.3, 0.1]
}
},
mounted: function () {
this.resetValues()
},
computed: {
app_id: function () {
return 'my-goals-child-calculator-' + this.timestamp
},
backgroundImage: function () {
return 'background: url(' + this.bgImage + ') 0 50% no-repeat;'
},
incomingsTotal: function () {
var total = 0
for (var i = 0; i < this.incomings.length; i++) {
total += parseFloat(this.incomings[i].value)
}
return total
}
},
methods: {
addGoal: function () {
if (this.newGoal == '') {
return;
}
this.goals.push({ title: this.newGoal, value: 0 })
this.newGoal = ''
},
calculate: function () {
this.step += 1
var goalsTotal = 0
for (var i = 0; i < this.goals.length; i++) {
goalsTotal += parseFloat(this.goals[i].value)
}
this.results = this.generateResults(this.incomingsTotal, goalsTotal)
},
calculateDuration: function (total, income) {
var duration = total / income;
if (duration < 1) {
return 1;
}
return Math.ceil(duration);
},
checkGoalsValues: function () {
for (var i = 0; i < this.goals.length; i++) {
if (this.goals[i].value != undefined && this.goals[i].value != '') {
continue;
}
this.goals[i].value = 0;
}
},
checkIncomingsValues: function () {
for (var i = 0; i < this.incomings.length; i++) {
if (this.incomings[i].value != undefined && this.incomings[i].value != '') {
continue;
}
this.incomings[i].value = 0;
}
},
formatDuration: function (income, duration) {
var numMonths = Math.floor(duration / 4.3)
var numWeeks = duration - Math.floor(numMonths * 4.3);
var text = ''
if (numMonths > 1) {
text += numMonths + " meses"
} else if (numMonths == 1) {
text += "1 mes"
}
if (text != "" && numWeeks > 1) {
text += " y " + numWeeks + " semanas"
} else if (text != "" && numWeeks == 1) {
text += " y 1 semana"
} else if (numWeeks > 1) {
text += numWeeks + " semanas"
} else {
text += "1 semana"
}
return text
},
generateResults: function (incomingsTotal, goalsTotal) {
results = []
for (i = 0; i < this.variations.length; i++) {
var income = incomingsTotal * this.variations[i]
if (income < 1) {
income = 1
}
var duration = this.calculateDuration(goalsTotal, income)
var durationText = this.formatDuration(income, duration)
results.push({ income: formatNumber(income), duration: duration, durationText: durationText })
}
return results
},
goToStep: function (step) {
this.newGoal = ''
this.step = step
},
removeGoal: function (index) {
this.goals.splice(index, 1)
},
resetValues: function () {
this.goals = [{ title: '', value: 0 }]
this.incomings = [{ title: 'Paga', value: 0 }, { title: 'Regalos', value: 0 }, { title: 'Trabajo', value: 0 }, { title: 'Otros', value: 0 }]
},
}
});
// Function to show result of tooltipItem
function labelMyNetWorth(tooltipItems) {
return formatCurrencyNumber(parseFloat(tooltipItems.raw));
}
// Function to show result of tooltipItem
function titleMyNetWorth(tooltipItems) {
return tooltipItems[0].label;
}
// Function to configure budget
// @param [Array] with data to show in canvas
function myNetWorthConfig(data) {
var config = {
type: 'doughnut',
data: data,
options: {
plugins: {
legend: {
display: false
},
tooltip: {
displayColors: false,
callbacks: {
title: titleMyNetWorth,
label: labelMyNetWorth,
},
backgroundColor: '#FFF',
titleColor: '#000',
bodyColor: '#000',
footerColor: '#000'
}
}
}
}
return config;
}
// Define a new component called button-counter
Vue.component('my-net-worth-calculator', {
props: [
'bgImage',
'footerInfo',
'title',
'summary'
],
data: function () {
return {
ctx: undefined,
data: {
labels: [],
datasets: [{
label: '',
data: [],
backgroundColor: [],
borderColor: [],
borderWidth: 1
}]
},
openBudgetSelect: false,
fixedExpenses: {
color: 'rgb(255, 130, 61)',
categories: [
{
color_tag: 'u-herramienta-orange',
icon_tag: '',
title: 'Pasivo Circulante (deudas a corto plazo)',
total: 0,
collapsed: false,
expenses: [{ title: 'Tarjetas de crédito y otras facturas pendientes de pago', icon_tag: '', total: 0, time: 1 },
{ title: 'Créditos y préstamos a corto plazo', icon_tag: '', total: 0, time: 1 },
{ title: 'Impuestos', icon_tag: '', total: 0, time: 1 }]
}, {
color_tag: 'u-herramienta-green',
icon_tag: '',
title: 'Obligaciones a largo plazo',
total: 0,
collapsed: true,
expenses: [{ title: 'Hipotecas de bienes personales', icon_tag: '', total: 0, time: 1 },
{ title: 'Hipotecas de inversiones inmobiliarias', icon_tag: '', total: 0, time: 1 },
{ title: 'Préstamos', icon_tag: '', total: 0, time: 1 },
{ title: 'Otros', icon_tag: '', total: 0, time: 1 }]
},
],
total: 0,
title: 'Pasivo'
},
saving: {
color: 'rgb(128, 211, 49)',
categories: [
{
color_tag: 'u-herramienta-orange',
icon_tag: '',
title: 'Activos líquidos',
total: 0,
collapsed: false,
expenses: [{ title: 'Activos líquidos', icon_tag: '', total: 0, time: 1 },
{ title: 'Efectivo y C/C', icon_tag: '', total: 0, time: 1 },
{ title: 'Cuentas de ahorro a la vista', icon_tag: '', total: 0, time: 1 },
{ title: 'FIAMMs', icon_tag: '', total: 0, time: 1 },
{ title: 'Letras del tesoro', icon_tag: '', total: 0, time: 1 }]
}, {
color_tag: 'u-herramienta-green',
icon_tag: '',
title: 'Inversiones en activos negociables',
total: 0,
collapsed: true,
expenses: [{ title: 'Acciones', icon_tag: '', total: 0, time: 1 },
{ title: 'Fondos de inversión', icon_tag: '', total: 0, time: 1 },
{ title: 'Renta fija privada', icon_tag: '', total: 0, time: 1 },
{ title: 'Deuda pública', icon_tag: '', total: 0, time: 1 },
{ title: 'Otros', icon_tag: '', total: 0, time: 1 }]
}, {
color_tag: 'u-herramienta-purple',
icon_tag: '',
title: 'Inversiones en activos no negociables',
total: 0,
collapsed: true,
expenses: [{ title: 'Participaciones en negocios', icon_tag: '', total: 0, time: 1 },
{ title: 'Inversiones inmobiliarias', icon_tag: '', total: 0, time: 1 },
{ title: 'Seguros de jubilación', icon_tag: '', total: 0, time: 1 },
{ title: 'Depósitos fijos', icon_tag: '', total: 0, time: 1 },
{ title: 'Planes de Pensiones', icon_tag: '', total: 0, time: 1 },
{ title: 'Otros', icon_tag: '', total: 0, time: 1 }]
}, {
color_tag: 'u-herramienta-pink',
icon_tag: '',
title: 'Activos inmobiliarios personales',
total: 0,
collapsed: true,
expenses: [{ title: 'Vivienda habitual', icon_tag: '', total: 0, time: 1 },
{ title: 'Otras viviendas', icon_tag: '', total: 0, time: 1 }]
}, {
color_tag: 'u-herramienta-blue',
icon_tag: '',
title: 'Otros activos personales',
total: 0,
collapsed: true,
expenses: [{ title: 'Automóviles', icon_tag: '', total: 0, time: 1 },
{ title: 'Bancos', icon_tag: '', total: 0, time: 1 },
{ title: 'Pieles / Joyas / Arte', icon_tag: '', total: 0, time: 1 },
{ title: 'Colecciones', icon_tag: '', total: 0, time: 1 },
{ title: 'Mobiliario y accesorios', icon_tag: '', total: 0, time: 1 },
{ title: 'Otros', icon_tag: '', total: 0, time: 1 }]
}
],
total: 0,
title: 'Activo'
},
key: '',
period: 1,
selectedTab: 'savings',
myChart: undefined,
timestamp: new Date().getTime(),
total: 0
}
},
mounted: function () {
this.renderChart()
},
computed: {
app_id: function () {
return 'my-net-worth-calculator-' + this.timestamp
},
backgroundImage: function () {
return 'background: url(' + this.bgImage + ') 0 50% no-repeat;'
},
selectedTabTitle: function () {
switch (this.selectedTab) {
case 'savings':
return this.saving.title
case 'fixedExpenses':
return this.fixedExpenses.title
}
},
totalBills: function () {
return this.fixedExpenses.total;
},
totalDifference: function () {
return (this.totalSaving - this.totalBills);
},
totalSaving: function () {
return this.saving.total;
}
},
methods: {
addCategoryToData: function (category) {
this.data.labels.push(category.title)
this.data.datasets[0].data.push(category.total)
this.data.datasets[0].backgroundColor.push(category.color)
this.data.datasets[0].borderColor.push(category.color)
},
calculateCategory: function (category, parentCategory) {
_total = 0
category.expenses.map(function (expense) {
if (expense.total == '' || expense.total == undefined) {
expense.total = 0
} else {
expense.total = parseFloat(expense.total)
}
_total += (parseFloat(expense.time) * parseFloat(expense.total))
})
category.total = _total
this.calculateParentCategory(parentCategory);
this.calculate()
},
calculateParentCategory: function (parentCategory) {
_total = 0
parentCategory.categories.map(function (category) {
_total += parseFloat(category.total)
})
parentCategory.total = _total
},
calculate: function () {
this.total = this.fixedExpenses.total +
this.saving.total
this.data = {
labels: [],
datasets: [{
label: '',
data: [],
backgroundColor: [],
borderColor: [],
borderWidth: 1
}]
}
this.addCategoryToData(this.fixedExpenses)
this.addCategoryToData(this.saving)
this.myChart.data = this.data
this.myChart.update()
},
percentage: function (category) {
return +(parseFloat(category.total / this.total) * 100).toFixed(2)
},
setTab: function (categoryTab) {
this.selectedTab = categoryTab
this.openBudgetSelect = false
},
renderChart: function () {
this.ctx = document.getElementById(this.app_id)
this.myChart = new Chart(this.ctx, myNetWorthConfig(this.data))
}
}
});
// Define a new component called button-counter
Vue.component('my-prioritized-goals-calculator', {
props: [
'footerInfo',
'title',
'summary'
],
data: function () {
return {
colors: ['u-color1', 'u-color2', 'u-color3', 'u-color4'],
finalResults: [],
goals: [],
newGoal: '',
results: [],
step: 1,
timestamp: new Date().getTime(),
}
},
mounted: function () {
this.setInitialValues()
},
computed: {
app_id: function () {
return 'my-prioritized-goals-calculator-' + this.timestamp
}
},
methods: {
addGoal: function () {
if (this.newGoal == '') {
return;
}
this.goals.push({ title: this.newGoal, points: 0 })
this.newGoal = ''
},
calculate: function () {
this.step += 1
for (i = 0; i < this.results.length; i++) {
index = parseInt(this.results[i].value);
this.goals[index].points += 1
}
this.finalResults = this.goals.sort(function (a, b) {
return b.points - a.points
})
},
formatPercentage: function (value, total) {
return +(parseFloat((value * 100) / total)).toFixed(2)
},
goToInit: function () {
this.newGoal = ''
this.finalResults = []
this.step = 1
},
removeGoal: function (index) {
this.goals.splice(index, 1)
},
setInitialValues: function () {
this.step = 1
this.goals = [
{ title: 'Entrada para una nueva vivienda', points: 0 },
{ title: 'Crucero 1 semana', points: 0 },
{ title: 'Ortodoncia de los niños', points: 0 },
{ title: 'Saldar deudas tarjetas de crédito', points: 0 }
]
},
setPriorities: function () {
this.step += 1
this.finalResults = []
this.results = []
for (i = 0; i < this.goals.length; i++) {
for (j = i + 1; j < this.goals.length; j++) {
this.results.push({ indexA: i, indexB: j, optionA: this.goals[i].title, optionB: this.goals[j].title, value: i })
}
}
}
}
});
// Function to show result of tooltipItem
function labelMySavingsTooltip(tooltipItems) {
return formatCurrencyNumber(parseFloat(tooltipItems.raw));
}
// Function to show result of tooltipItem
function titleMySavingsTooltip(tooltipItems) {
return tooltipItems[0].label;
}
function mySavingsConfig(data) {
var config = {
type: 'bar',
data: this.data,
options: {
plugins: {
title: {
display: false,
},
legend: {
display: false
},
tooltip: {
displayColors: false,
callbacks: {
title: titleMySavingsTooltip,
label: labelMySavingsTooltip,
},
backgroundColor: '#FFF',
titleColor: '#0066ff',
bodyColor: '#000',
footerColor: '#000'
},
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
}
}
}
return config;
}
// Definición de componente para calculo de mis ahorros
// Se corresponde con la herramienta de FxT
Vue.component('my-savings-calculator', {
props: [
'bgImage',
'footerInfo',
'title',
'summary'
],
data: function () {
return {
ctx: undefined,
data: undefined,
mc_aportacion: 300,
mc_capital: 1000,
mc_numAnyos: 5,
mc_impositivo: 19,
mc_interes: 2,
myChart: undefined,
ratio: undefined,
timestamp: new Date().getTime(),
total: 0
}
},
mounted: function () {
this.renderChart()
},
computed: {
app_id: function () {
return 'my-savings-calculator-' + this.timestamp
},
backgroundImage: function () {
return 'background: url(' + this.bgImage + ') 0 50% no-repeat;'
},
message: function () {
return "Podrás conseguir " + formatCurrencyNumber(this.total) + " en " + this.mc_numAnyos + " años si ahorras " + formatCurrencyNumber(this.mc_aportacion) + " al mes recibiendo un interés del " +
this.mc_interes + "% y después de impuestos. La gráfica muestra la evolución del capital a lo largo de los años."
},
messageTotal: function () {
return "Capital acumulado hasta la fecha: " + formatCurrencyNumber(this.total) + ""
}
},
methods: {
calculate: function () {
interes = (this.mc_interes / 100)
impositivo = this.mc_impositivo / 100
interesMensual = (interes / 12) * (1 - impositivo)
this.data = this.calculateAccumulated(interesMensual, this.mc_capital, this.mc_aportacion)
this.total = this.mc_capital * Math.pow(1 + interesMensual, this.mc_numAnyos * 12) + (this.mc_aportacion * ((Math.pow(1 + interesMensual, this.mc_numAnyos * 12) - 1) / (interesMensual)))
this.myChart.data = this.data
this.myChart.update()
},
calculateAccumulated: function (interesMensual, incrementoCapital, aportacion) {
this.data = {
labels: [],
datasets: [{
label: '',
data: [],
backgroundColor: [],
borderColor: [],
borderWidth: 1
}]
}
for (var i = 0; i < this.mc_numAnyos; i++) {
incrementoCapital = incrementoCapital * Math.pow(1 + interesMensual, 12) + aportacion * ((Math.pow(1 + interesMensual, 12) - 1) / (interesMensual));
this.data.labels[i] = "Año " + (i + 1)
this.data.datasets[0].data.push(incrementoCapital)
this.data.datasets[0].backgroundColor.push('rgba(128, 211, 49, 1)')
this.data.datasets[0].borderColor.push('rgba(128, 211, 49, 1)')
}
return this.data
},
renderChart: function () {
this.ctx = document.getElementById(this.app_id);
this.myChart = new Chart(this.ctx, mySavingsConfig(this.data));
}
}
});
// Function to show result of tooltipItem
function labelMyWhimsTooltip(tooltipItems) {
return tooltipItems.dataset.label + ": " + formatCurrencyNumber(parseFloat(tooltipItems.formattedValue));
}
// Function to show result of tooltipItem
function titleMyWhimsTooltip(tooltipItems) {
return tooltipItems[0].label;
}
// Function to configure budget
// @param [Array] with data to show in canvas
function myWhimsConfig(data) {
var config = {
type: 'bar',
data: this.data,
options: {
plugins: {
title: {
display: false,
},
legend: {
display: false
},
tooltip: {
displayColors: false,
callbacks: {
title: titleMyWhimsTooltip,
label: labelMyWhimsTooltip,
},
backgroundColor: '#FFF',
titleColor: '#0066ff',
bodyColor: '#000',
footerColor: '#000'
},
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
}
}
}
return config;
}
// Definición de componente para calculo de mis ahorros
// Se corresponde con la herramienta de FxT
Vue.component('my-whims-calculator', {
props: [
'bgImage',
'footerInfo',
'title',
'summary'
],
data: function () {
return {
categories: [
{ title: 'Comer menos fuera de casa', total: '90', ahorroAnual: 0, ahorroAcumulado: 0, inversion: 0 },
{ title: 'Dejar de fumar', total: '100', ahorroAnual: 0, ahorroAcumulado: 0, inversion: 0 }
],
ctx: undefined,
data: undefined,
mc_interes: 2,
mc_numAnyos: 2,
mc_totalAmount: 0,
myChart: undefined,
showCanvas: true,
showData: false,
timestamp: new Date().getTime(),
total: 0
}
},
mounted: function () {
this.clearData()
this.renderChart()
},
watch: {
categories: {
// Controlar elementos de tipo objeto o array
deep: true,
handler: function (categories) {
for (var i = 0; i < categories.length; i++) {
if (categories[i].total == undefined || categories[i].total == '' || categories[i].total < 0) {
this.categories[i].total = 0
}
}
}
},
mc_interes: function () {
if (this.mc_interes < 0 || this.mc_interes == '') {
this.mc_interes = 0
}
},
mc_numAnyos: function () {
if (this.mc_numAnyos < 0 || this.mc_numAnyos == '') {
this.mc_numAnyos = 0
}
},
},
computed: {
app_id: function () {
return 'my-whims-calculator-' + this.timestamp
},
backgroundImage: function () {
return 'background: url(' + this.bgImage + ') 0 50% no-repeat;'
},
message: function () {
return "Si ahorra mensualmente " + formatCurrencyNumber(this.monthlyAmount) + ", esta cifra puede convertirse en " + formatCurrencyNumber(this.mc_totalAmount) + " al final de los " + this.mc_numAnyos + " años suponiendo que recibe un interés del " + this.mc_interes + "%. A continuación se presenta la gráfica con la evolución de sus ahorros a lo largo de los años."
},
monthlyAmount: function () {
var amount = 0;
for (var i = 0; i < this.categories.length; i++) {
total = parseFloat(this.categories[i].total);
if (total <= 0) {
continue;
}
amount += total;
}
return amount;
},
},
methods: {
addCategory: function () {
this.categories.push({ title: 'Introduzca título', total: 0, ahorroAnual: 0, ahorroAcumulado: 0, inversion: 0 })
},
calculateInteresByYear: function (intereses, year) {
interesesByYear = intereses.filter(function (item) {
return (item.year == year);
});
interesTotal = 0
interesesByYear.forEach(function (interes) {
interesTotal += parseFloat(interes.intereses)
});
return interesTotal.toFixed(2)
},
calculate: function () {
aAhorros = []
aAcumulados = []
aIntereses = []
aInversiones = []
ahorroTotalAcumulado = 0
interesesTotales = 0
ahorroTotalConseguido = 0
for (i = 0; i < this.categories.length; i++) {
if (this.categories[i].total == undefined || this.categories[i].total < 0) {
this.removeCategory(i);
}
ahorroMensual = parseFloat(this.categories[i].total)
inversion = 0
ahorroAnual = ahorroMensual * 12
ahorroAcumulado = ahorroAnual * this.mc_numAnyos
intereses = 0.0
for (idxMes = 1; idxMes <= (this.mc_numAnyos * 12); idxMes++) {
inversion = (inversion + (ahorroMensual)) * ((1 + ((this.mc_interes / 12)) / 100))
// Calculo los intereses anuales de cada una de las diferentes categorias
if (idxMes % 12 == 0) {
aIntereses.push({ year: (idxMes / 12), intereses: inversion - (ahorroMensual * idxMes) });
}
}
intereses = inversion - ahorroAcumulado
interesesTotales += intereses
ahorroTotalAcumulado += ahorroAcumulado
this.categories[i].ahorroAnual = ahorroAnual
this.categories[i].ahorroAcumulado = ahorroAcumulado
this.categories[i].inversion = inversion
aAhorros.push(ahorroAnual)
aAcumulados.push(ahorroAcumulado)
aInversiones.push(inversion)
}
this.mc_totalAmount = interesesTotales + ahorroTotalAcumulado
this.myChart.data = this.generateData(aIntereses)
this.myChart.update()
this.showData = true
},
clearData: function () {
this.data = {
labels: [],
datasets: []
}
},
generateData: function (intereses) {
this.clearData()
this.data.datasets.push({ label: 'Ahorro acumulado', backgroundColor: 'rgb(128, 211, 49)', fill: true, data: [] })
this.data.datasets.push({ label: 'Intereses', backgroundColor: 'rgb(255, 130, 61)', fill: true, data: [] })
var ahorroAnual = 0;
this.categories.forEach(function (category) {
ahorroAnual += parseFloat(category.total)
});
for (i = 0; i < this.mc_numAnyos; i++) {
this.data.labels.push("Año " + (i + 1));
this.data.datasets[0].data.push((ahorroAnual * 12 * (i + 1)).toFixed(2))
this.data.datasets[1].data.push(this.calculateInteresByYear(intereses, i + 1))
}
return this.data;
},
removeCategory: function (index) {
this.categories.splice(index, 1)
},
renderChart: function () {
this.ctx = document.getElementById(this.app_id);
this.myChart = new Chart(this.ctx, myWhimsConfig(this.data));
}
}
});
// Function to show result of tooltipItem
function labelRetirementTooltip(tooltipItems) {
return tooltipItems.formattedValue + '€';
}
// Function to show result of tooltipItem
function titleRetirementTooltip(tooltipItems) {
return tooltipItems[0].label;
}
function retirementConfig(data) {
var config = {
type: 'bar',
data: data,
options: {
plugins: {
title: {
display: false,
},
legend: {
display: false
},
tooltip: {
displayColors: false,
callbacks: {
title: titleRetirementTooltip,
label: labelRetirementTooltip,
},
backgroundColor: '#FFF',
titleColor: '#0066ff',
bodyColor: '#000',
footerColor: '#000'
},
},
responsive: true,
scales: {
x: {
min: 0
}
}
}
}
return config;
}
// Define a new component called button-counter
Vue.component('retirement-calculator', {
props: [
'bgImage',
'footerInfo',
'summary',
'title'
],
data: function () {
return {
amountTotal: undefined,
ctx: undefined,
data: undefined,
mc_anyosDevolver: undefined,
mc_aportacion: undefined,
mc_aportacionAnual: undefined,
mc_edad: undefined,
mc_edadRetiro: undefined,
mc_interesAntes: undefined,
mc_interesDespues: undefined,
myChart: undefined,
numAnyos: undefined,
timestamp: new Date().getTime(),
total: 0,
totalMes: undefined,
totalAnyo: undefined
}
},
mounted: function () {
this.defaultValues()
},
watch: {
mc_anyosDevolver: function () {
if (this.mc_anyosDevolver < 0 || this.mc_anyosDevolver == '') {
this.mc_anyosDevolver = 0
}
},
mc_aportacion: function () {
if (this.mc_aportacion < 0 || this.mc_aportacion == '') {
this.mc_aportacion = 0
}
},
mc_aportacionAnual: function () {
if (this.mc_aportacionAnual < 0 || this.mc_aportacionAnual == '') {
this.mc_aportacionAnual = 0
}
},
mc_edad: function () {
if (this.mc_edad < 0 || this.mc_edad == '') {
this.mc_edad = 0
}
},
mc_edadRetiro: function () {
if (this.mc_edadRetiro < 0 || this.mc_edadRetiro == '') {
this.mc_edadRetiro = 0
}
},
mc_interesAntes: function () {
if (this.mc_interesAntes < 0.1 || this.mc_interesAntes == '') {
this.mc_interesAntes = 0.1
}
},
mc_interesDespues: function () {
if (this.mc_interesDespues < 0.1 || this.mc_interesDespues == '') {
this.mc_interesDespues = 0.1
}
},
numAnyos: function () {
if (this.numAnyos < 0 || this.numAnyos == '') {
this.numAnyos = 0
}
},
},
computed: {
app_id: function () {
return 'retirement-calculator-' + this.timestamp
},
backgroundImage: function () {
return 'background: url(' + this.bgImage + ') 0 50% no-repeat;'
},
messageRetirement: function () {
return "
Llegada tu edad estimada de jubilación y gracias a tus aportaciones habrás acumulado " + formatCurrencyNumber(this.amountTotal) +
" y podrás disfrutar de " + formatCurrencyNumber(this.totalMes) + " al mes durante " + this.mc_anyosDevolver + " años.
" +
"Ahorrar para la jubilación es uno de los objetivos financieros más importantes, así que, cuanto antes empieces, mejor.
" +
"Recuerda que conviene ser prudente a la hora de fijar el tipo de interés que esperas obtener de tus ahorros.
"
}
},
methods: {
calculate: function () {
aportacion = parseFloat(this.mc_aportacion);
edad = parseInt(this.mc_edad);
edadRetiro = parseInt(this.mc_edadRetiro);
aportacionAnual = parseFloat(this.mc_aportacionAnual);
interesAntes = parseFloat(this.mc_interesAntes) / 100;
anyosDevolver = parseInt(this.mc_anyosDevolver);
interesDespues = parseFloat(this.mc_interesDespues) / 100;
tiempo = edadRetiro - edad;
this.numAnyos = numAnyos = anyosDevolver + tiempo;
capitalAnual = cantidadFinal = (aportacion * Math.pow(1 + interesAntes, tiempo)) + (aportacionAnual * ((Math.pow(interesAntes + 1, tiempo + 1) - (1 + interesAntes)) / (interesAntes)))
this.total = -(((0 - (cantidadFinal * (Math.pow(1 + interesDespues, anyosDevolver)))) * (interesDespues)) / (Math.pow(1 + interesDespues, anyosDevolver + 1) - (1 + interesDespues)))
this.totalMes = this.total / 12
this.totalAnyo = this.total
this.data = {
labels: [],
datasets: [{
label: '',
data: [],
backgroundColor: [],
borderColor: [],
borderWidth: 1
}]
}
for (var i = 0; i < numAnyos; i++) {
this.data.labels.push((edad + i) + ' años')
if ((i + edad) <= edadRetiro) {
this.amountTotal = capital = (aportacion * Math.pow(1 + interesAntes, i)) + (aportacionAnual * ((Math.pow(interesAntes + 1, i + 1) - (1 + interesAntes)) / (interesAntes)));
this.data.datasets[0].data.push(Number(capital).toFixed(2))
this.data.datasets[0].backgroundColor.push('rgb(128, 211, 49)')
this.data.datasets[0].borderColor.push('rgb(128, 211, 49)')
} else {
capitalAnual = (capitalAnual - this.total) * (1 + interesDespues);
this.data.datasets[0].data.push(Number(capitalAnual).toFixed(2))
this.data.datasets[0].backgroundColor.push('rgb(255, 130, 61)')
this.data.datasets[0].borderColor.push('rgb(255, 130, 61)')
}
}
if (this.ctx == undefined) {
this.renderChart()
} else {
this.myChart.data = this.data
this.myChart.update()
}
},
defaultValues: function () {
this.mc_anyosDevolver = 20
this.mc_aportacion = 1000
this.mc_aportacionAnual = 2000
this.mc_edad = 40
this.mc_edadRetiro = 67
this.mc_interesAntes = 2
this.mc_interesDespues = 2
},
renderChart: function () {
this.ctx = document.getElementById(this.app_id);
this.myChart = new Chart(this.ctx, retirementConfig(this.data));
}
}
});
// Define a new component called button-counter
Vue.component('savings-investments-calculator', {
props: {
'bgImage': String,
'footerInfo': String,
'title': String,
'summary': String
},
data: function() {
return {
questions: [
{
answers: [{ answer: 'Intento ahorrar un poco si me queda algo a fin de mes.', value: 0 }, { answer: 'No puedo ahorrar de momento porque mi salario es muy bajo y apenas cubre mis gastos.', value: 1 }, { answer: 'Ya empezaré más tarde a ahorrar, prefiero disfrutar de la vida ahora mientras sea joven.', value: 2 }, { answer: 'Siempre destino un porcentaje fijo de mi salario al ahorro y la inversión.', value: 3 }],
question: '¿Cuál de las siguientes afirmaciones describe mejor tu situación?',
},
],
percentage: 0,
results: [],
resultsDescription: [
{ title: '', content: 'Las intenciones son buenas, pero no siempre son suficientes. Deberías considerar convertir el ahorro en una obligación fija y no simplemente si te queda algo a fin de mes. ¡Págate a ti mismo!. De esta forma podrás planificar mejor cómo y en cuánto tiempo podrás conseguir tus objetivos financieros.
', value: 0 },
{ title: '', content: 'Puede que no te encuentres en la mejor situación económica pero es importante que intentes destinar alguna parte de tus recursos a ahorro, nunca se sabe para qué podrías necesitar un colchón y es importante que planifiques como alcanzar tus objetivos en el futuro.
Prueba a ajustar un poco algunos gastos.
', value: 1 },
{ title: '', content: 'Es cierto que sólo se es joven una vez. Pero por desgracia eso también se aplica al ahorro y la inversión. Debido a la magia del interés compuesto, el tiempo es el factor que más influye sobre las posibilidades de acumular un capital a largo plazo. Si quieres lograr tus objetivos financieros es importante que empieces a planificar cómo vas a conseguirlos y que te pongas a ello lo antes posible.
', value: 2 },
{ title: 'Enhorabuena.', content: 'Vas por buen camino. Destinar una cantidad fija de tu salario al ahorro y la inversión es la mejor forma para conseguir tus objetivos financieros.
', value: 3 },
],
step: 0,
showResults: false,
timestamp: new Date().getTime(),
total: 0
}
},
computed: {
app_id: function() {
return 'savings-investments-calculator-' + this.timestamp
},
backgroundImage: function () {
return 'background: url(' + this.bgImage + ') 0 50% no-repeat;'
},
showResultTitle: function() {
return this.resultsDescription[this.total].title
},
showResultMessage: function() {
return this.resultsDescription[this.total].content
}
},
mounted: function() {
for(i = 0; i < this.questions.length; i++) {
this.results.push(undefined)
}
},
methods: {
calculatePercentage: function() {
return Math.round((this.step / this.questions.length) * 100)
},
nextStep: function() {
this.step += 1
this.percentage = this.calculatePercentage()
if (this.step >= this.questions.length) {
this.step = this.questions.length
this.showResultsMessage()
}
},
previousStep: function() {
this.step -= 1
this.percentage = this.calculatePercentage()
},
refreshTest: function() {
this.percentage = 0
this.results = []
this.showResults = false
this.step = 0
this.total = 0
},
showResultsMessage: function() {
this.showResults = true
total = 0
this.results.map(function(result) {
if (result != undefined) {
total += result
}
})
this.total = total
}
}
});
/* =========================================================
* bootstrap-datepicker.js
* Repo: https://github.com/eternicode/bootstrap-datepicker/
* Demo: http://eternicode.github.io/bootstrap-datepicker/
* Docs: http://bootstrap-datepicker.readthedocs.org/
* Forked from http://www.eyecon.ro/bootstrap-datepicker
* =========================================================
* Started by Stefan Petre; improvements by Andrew Rowls + contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
(function($, undefined){
var $window = $(window);
function UTCDate(){
return new Date(Date.UTC.apply(Date, arguments));
}
function UTCToday(){
var today = new Date();
return UTCDate(today.getFullYear(), today.getMonth(), today.getDate());
}
function alias(method){
return function(){
return this[method].apply(this, arguments);
};
}
var DateArray = (function(){
var extras = {
get: function(i){
return this.slice(i)[0];
},
contains: function(d){
// Array.indexOf is not cross-browser;
// $.inArray doesn't work with Dates
var val = d && d.valueOf();
for (var i=0, l=this.length; i < l; i++)
if (this[i].valueOf() === val)
return i;
return -1;
},
remove: function(i){
this.splice(i,1);
},
replace: function(new_array){
if (!new_array)
return;
if (!$.isArray(new_array))
new_array = [new_array];
this.clear();
this.push.apply(this, new_array);
},
clear: function(){
this.splice(0);
},
copy: function(){
var a = new DateArray();
a.replace(this);
return a;
}
};
return function(){
var a = [];
a.push.apply(a, arguments);
$.extend(a, extras);
return a;
};
})();
// Picker object
var Datepicker = function(element, options){
this.dates = new DateArray();
this.viewDate = UTCToday();
this.focusDate = null;
this._process_options(options);
this.element = $(element);
this.isInline = false;
this.isInput = this.element.is('input');
this.component = this.element.is('.date') ? this.element.find('.add-on, .input-group-addon, .btn') : false;
this.hasInput = this.component && this.element.find('input').length;
if (this.component && this.component.length === 0)
this.component = false;
this.picker = $(DPGlobal.template);
this._buildEvents();
this._attachEvents();
if (this.isInline){
this.picker.addClass('datepicker-inline').appendTo(this.element);
}
else {
this.picker.addClass('datepicker-dropdown dropdown-menu');
}
if (this.o.rtl){
this.picker.addClass('datepicker-rtl');
}
this.viewMode = this.o.startView;
if (this.o.calendarWeeks)
this.picker.find('tfoot th.today')
.attr('colspan', function(i, val){
return parseInt(val) + 1;
});
this._allow_update = false;
this.setStartDate(this._o.startDate);
this.setEndDate(this._o.endDate);
this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
this.fillDow();
this.fillMonths();
this._allow_update = true;
this.update();
this.showMode();
if (this.isInline){
this.show();
}
};
Datepicker.prototype = {
constructor: Datepicker,
_process_options: function(opts){
// Store raw options for reference
this._o = $.extend({}, this._o, opts);
// Processed options
var o = this.o = $.extend({}, this._o);
// Check if "de-DE" style date is available, if not language should
// fallback to 2 letter code eg "de"
var lang = o.language;
if (!dates[lang]){
lang = lang.split('-')[0];
if (!dates[lang])
lang = defaults.language;
}
o.language = lang;
switch (o.startView){
case 2:
case 'decade':
o.startView = 2;
break;
case 1:
case 'year':
o.startView = 1;
break;
default:
o.startView = 0;
}
switch (o.minViewMode){
case 1:
case 'months':
o.minViewMode = 1;
break;
case 2:
case 'years':
o.minViewMode = 2;
break;
default:
o.minViewMode = 0;
}
o.startView = Math.max(o.startView, o.minViewMode);
// true, false, or Number > 0
if (o.multidate !== true){
o.multidate = Number(o.multidate) || false;
if (o.multidate !== false)
o.multidate = Math.max(0, o.multidate);
else
o.multidate = 1;
}
o.multidateSeparator = String(o.multidateSeparator);
o.weekStart %= 7;
o.weekEnd = ((o.weekStart + 6) % 7);
var format = DPGlobal.parseFormat(o.format);
if (o.startDate !== -Infinity){
if (!!o.startDate){
if (o.startDate instanceof Date)
o.startDate = this._local_to_utc(this._zero_time(o.startDate));
else
o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
}
else {
o.startDate = -Infinity;
}
}
if (o.endDate !== Infinity){
if (!!o.endDate){
if (o.endDate instanceof Date)
o.endDate = this._local_to_utc(this._zero_time(o.endDate));
else
o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
}
else {
o.endDate = Infinity;
}
}
o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
if (!$.isArray(o.daysOfWeekDisabled))
o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){
return parseInt(d, 10);
});
var plc = String(o.orientation).toLowerCase().split(/\s+/g),
_plc = o.orientation.toLowerCase();
plc = $.grep(plc, function(word){
return (/^auto|left|right|top|bottom$/).test(word);
});
o.orientation = {x: 'auto', y: 'auto'};
if (!_plc || _plc === 'auto')
; // no action
else if (plc.length === 1){
switch (plc[0]){
case 'top':
case 'bottom':
o.orientation.y = plc[0];
break;
case 'left':
case 'right':
o.orientation.x = plc[0];
break;
}
}
else {
_plc = $.grep(plc, function(word){
return (/^left|right$/).test(word);
});
o.orientation.x = _plc[0] || 'auto';
_plc = $.grep(plc, function(word){
return (/^top|bottom$/).test(word);
});
o.orientation.y = _plc[0] || 'auto';
}
},
_events: [],
_secondaryEvents: [],
_applyEvents: function(evs){
for (var i=0, el, ch, ev; i < evs.length; i++){
el = evs[i][0];
if (evs[i].length === 2){
ch = undefined;
ev = evs[i][1];
}
else if (evs[i].length === 3){
ch = evs[i][1];
ev = evs[i][2];
}
el.on(ev, ch);
}
},
_unapplyEvents: function(evs){
for (var i=0, el, ev, ch; i < evs.length; i++){
el = evs[i][0];
if (evs[i].length === 2){
ch = undefined;
ev = evs[i][1];
}
else if (evs[i].length === 3){
ch = evs[i][1];
ev = evs[i][2];
}
el.off(ev, ch);
}
},
_buildEvents: function(){
if (this.isInput){ // single input
this._events = [
[this.element, {
focus: $.proxy(this.show, this),
keyup: $.proxy(function(e){
if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1)
this.update();
}, this),
keydown: $.proxy(this.keydown, this)
}]
];
}
else if (this.component && this.hasInput){ // component: input + button
this._events = [
// For components that are not readonly, allow keyboard nav
[this.element.find('input'), {
focus: $.proxy(this.show, this),
keyup: $.proxy(function(e){
if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1)
this.update();
}, this),
keydown: $.proxy(this.keydown, this)
}],
[this.component, {
click: $.proxy(this.show, this)
}]
];
}
else if (this.element.is('div')){ // inline datepicker
this.isInline = true;
}
else {
this._events = [
[this.element, {
click: $.proxy(this.show, this)
}]
];
}
this._events.push(
// Component: listen for blur on element descendants
[this.element, '*', {
blur: $.proxy(function(e){
this._focused_from = e.target;
}, this)
}],
// Input: listen for blur on element
[this.element, {
blur: $.proxy(function(e){
this._focused_from = e.target;
}, this)
}]
);
this._secondaryEvents = [
[this.picker, {
click: $.proxy(this.click, this)
}],
[$(window), {
resize: $.proxy(this.place, this)
}],
[$(document), {
'mousedown touchstart': $.proxy(function(e){
// Clicked outside the datepicker, hide it
if (!(
this.element.is(e.target) ||
this.element.find(e.target).length ||
this.picker.is(e.target) ||
this.picker.find(e.target).length
)){
this.hide();
}
}, this)
}]
];
},
_attachEvents: function(){
this._detachEvents();
this._applyEvents(this._events);
},
_detachEvents: function(){
this._unapplyEvents(this._events);
},
_attachSecondaryEvents: function(){
this._detachSecondaryEvents();
this._applyEvents(this._secondaryEvents);
},
_detachSecondaryEvents: function(){
this._unapplyEvents(this._secondaryEvents);
},
_trigger: function(event, altdate){
var date = altdate || this.dates.get(-1),
local_date = this._utc_to_local(date);
this.element.trigger({
type: event,
date: local_date,
dates: $.map(this.dates, this._utc_to_local),
format: $.proxy(function(ix, format){
if (arguments.length === 0){
ix = this.dates.length - 1;
format = this.o.format;
}
else if (typeof ix === 'string'){
format = ix;
ix = this.dates.length - 1;
}
format = format || this.o.format;
var date = this.dates.get(ix);
return DPGlobal.formatDate(date, format, this.o.language);
}, this)
});
},
show: function(){
if (!this.isInline)
this.picker.appendTo('body');
this.picker.show();
this.place();
this._attachSecondaryEvents();
this._trigger('show');
},
hide: function(){
if (this.isInline)
return;
if (!this.picker.is(':visible'))
return;
this.focusDate = null;
this.picker.hide().detach();
this._detachSecondaryEvents();
this.viewMode = this.o.startView;
this.showMode();
if (
this.o.forceParse &&
(
this.isInput && this.element.val() ||
this.hasInput && this.element.find('input').val()
)
)
this.setValue();
this._trigger('hide');
},
remove: function(){
this.hide();
this._detachEvents();
this._detachSecondaryEvents();
this.picker.remove();
delete this.element.data().datepicker;
if (!this.isInput){
delete this.element.data().date;
}
},
_utc_to_local: function(utc){
return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*60000));
},
_local_to_utc: function(local){
return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000));
},
_zero_time: function(local){
return local && new Date(local.getFullYear(), local.getMonth(), local.getDate());
},
_zero_utc_time: function(utc){
return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate()));
},
getDates: function(){
return $.map(this.dates, this._utc_to_local);
},
getUTCDates: function(){
return $.map(this.dates, function(d){
return new Date(d);
});
},
getDate: function(){
return this._utc_to_local(this.getUTCDate());
},
getUTCDate: function(){
return new Date(this.dates.get(-1));
},
setDates: function(){
var args = $.isArray(arguments[0]) ? arguments[0] : arguments;
this.update.apply(this, args);
this._trigger('changeDate');
this.setValue();
},
setUTCDates: function(){
var args = $.isArray(arguments[0]) ? arguments[0] : arguments;
this.update.apply(this, $.map(args, this._utc_to_local));
this._trigger('changeDate');
this.setValue();
},
setDate: alias('setDates'),
setUTCDate: alias('setUTCDates'),
setValue: function(){
var formatted = this.getFormattedDate();
if (!this.isInput){
if (this.component){
this.element.find('input').val(formatted).change();
}
}
else {
this.element.val(formatted).change();
}
},
getFormattedDate: function(format){
if (format === undefined)
format = this.o.format;
var lang = this.o.language;
return $.map(this.dates, function(d){
return DPGlobal.formatDate(d, format, lang);
}).join(this.o.multidateSeparator);
},
setStartDate: function(startDate){
this._process_options({startDate: startDate});
this.update();
this.updateNavArrows();
},
setEndDate: function(endDate){
this._process_options({endDate: endDate});
this.update();
this.updateNavArrows();
},
setDaysOfWeekDisabled: function(daysOfWeekDisabled){
this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
this.update();
this.updateNavArrows();
},
place: function(){
if (this.isInline)
return;
var calendarWidth = this.picker.outerWidth(),
calendarHeight = this.picker.outerHeight(),
visualPadding = 10,
windowWidth = $window.width(),
windowHeight = $window.height(),
scrollTop = $window.scrollTop();
var zIndex = parseInt(this.element.parents().filter(function(){
return $(this).css('z-index') !== 'auto';
}).first().css('z-index'))+10;
var offset = this.component ? this.component.parent().offset() : this.element.offset();
var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false);
var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false);
var left = offset.left,
top = offset.top;
this.picker.removeClass(
'datepicker-orient-top datepicker-orient-bottom '+
'datepicker-orient-right datepicker-orient-left'
);
if (this.o.orientation.x !== 'auto'){
this.picker.addClass('datepicker-orient-' + this.o.orientation.x);
if (this.o.orientation.x === 'right')
left -= calendarWidth - width;
}
// auto x orientation is best-placement: if it crosses a window
// edge, fudge it sideways
else {
// Default to left
this.picker.addClass('datepicker-orient-left');
if (offset.left < 0)
left -= offset.left - visualPadding;
else if (offset.left + calendarWidth > windowWidth)
left = windowWidth - calendarWidth - visualPadding;
}
// auto y orientation is best-situation: top or bottom, no fudging,
// decision based on which shows more of the calendar
var yorient = this.o.orientation.y,
top_overflow, bottom_overflow;
if (yorient === 'auto'){
top_overflow = -scrollTop + offset.top - calendarHeight;
bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight);
if (Math.max(top_overflow, bottom_overflow) === bottom_overflow)
yorient = 'top';
else
yorient = 'bottom';
}
this.picker.addClass('datepicker-orient-' + yorient);
if (yorient === 'top')
top += height;
else
top -= calendarHeight + parseInt(this.picker.css('padding-top'));
this.picker.css({
top: top,
left: left,
zIndex: zIndex
});
},
_allow_update: true,
update: function(){
if (!this._allow_update)
return;
var oldDates = this.dates.copy(),
dates = [],
fromArgs = false;
if (arguments.length){
$.each(arguments, $.proxy(function(i, date){
if (date instanceof Date)
date = this._local_to_utc(date);
dates.push(date);
}, this));
fromArgs = true;
}
else {
dates = this.isInput
? this.element.val()
: this.element.data('date') || this.element.find('input').val();
if (dates && this.o.multidate)
dates = dates.split(this.o.multidateSeparator);
else
dates = [dates];
delete this.element.data().date;
}
dates = $.map(dates, $.proxy(function(date){
return DPGlobal.parseDate(date, this.o.format, this.o.language);
}, this));
dates = $.grep(dates, $.proxy(function(date){
return (
date < this.o.startDate ||
date > this.o.endDate ||
!date
);
}, this), true);
this.dates.replace(dates);
if (this.dates.length)
this.viewDate = new Date(this.dates.get(-1));
else if (this.viewDate < this.o.startDate)
this.viewDate = new Date(this.o.startDate);
else if (this.viewDate > this.o.endDate)
this.viewDate = new Date(this.o.endDate);
if (fromArgs){
// setting date by clicking
this.setValue();
}
else if (dates.length){
// setting date by typing
if (String(oldDates) !== String(this.dates))
this._trigger('changeDate');
}
if (!this.dates.length && oldDates.length)
this._trigger('clearDate');
this.fill();
},
fillDow: function(){
var dowCnt = this.o.weekStart,
html = '';
if (this.o.calendarWeeks){
var cell = ' | ';
html += cell;
this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
}
while (dowCnt < this.o.weekStart + 7){
html += ''+dates[this.o.language].daysMin[(dowCnt++)%7]+' | ';
}
html += '
';
this.picker.find('.datepicker-days thead').append(html);
},
fillMonths: function(){
var html = '',
i = 0;
while (i < 12){
html += ''+dates[this.o.language].monthsShort[i++]+'';
}
this.picker.find('.datepicker-months td').html(html);
},
setRange: function(range){
if (!range || !range.length)
delete this.range;
else
this.range = $.map(range, function(d){
return d.valueOf();
});
this.fill();
},
getClassNames: function(date){
var cls = [],
year = this.viewDate.getUTCFullYear(),
month = this.viewDate.getUTCMonth(),
today = new Date();
if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){
cls.push('old');
}
else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){
cls.push('new');
}
if (this.focusDate && date.valueOf() === this.focusDate.valueOf())
cls.push('focused');
// Compare internal UTC date with local today, not UTC today
if (this.o.todayHighlight &&
date.getUTCFullYear() === today.getFullYear() &&
date.getUTCMonth() === today.getMonth() &&
date.getUTCDate() === today.getDate()){
cls.push('today');
}
if (this.dates.contains(date) !== -1)
cls.push('active');
if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
$.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1){
cls.push('disabled');
}
if (this.range){
if (date > this.range[0] && date < this.range[this.range.length-1]){
cls.push('range');
}
if ($.inArray(date.valueOf(), this.range) !== -1){
cls.push('selected');
}
}
return cls;
},
fill: function(){
var d = new Date(this.viewDate),
year = d.getUTCFullYear(),
month = d.getUTCMonth(),
startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
todaytxt = dates[this.o.language].today || dates['en'].today || '',
cleartxt = dates[this.o.language].clear || dates['en'].clear || '',
tooltip;
this.picker.find('.datepicker-days thead th.datepicker-switch')
.text(dates[this.o.language].months[month]+' '+year);
this.picker.find('tfoot th.today')
.text(todaytxt)
.toggle(this.o.todayBtn !== false);
this.picker.find('tfoot th.clear')
.text(cleartxt)
.toggle(this.o.clearBtn !== false);
this.updateNavArrows();
this.fillMonths();
var prevMonth = UTCDate(year, month-1, 28),
day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
prevMonth.setUTCDate(day);
prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
var nextMonth = new Date(prevMonth);
nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
nextMonth = nextMonth.valueOf();
var html = [];
var clsName;
while (prevMonth.valueOf() < nextMonth){
if (prevMonth.getUTCDay() === this.o.weekStart){
html.push('');
if (this.o.calendarWeeks){
// ISO 8601: First week contains first thursday.
// ISO also states week starts on Monday, but we can be more abstract here.
var
// Start of current week: based on weekstart/current date
ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
// Thursday of this week
th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
// First Thursday of year, year from thursday
yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
// Calendar week: ms between thursdays, div ms per day, div 7 days
calWeek = (th - yth) / 864e5 / 7 + 1;
html.push(''+ calWeek +' | ');
}
}
clsName = this.getClassNames(prevMonth);
clsName.push('day');
if (this.o.beforeShowDay !== $.noop){
var before = this.o.beforeShowDay(this._utc_to_local(prevMonth));
if (before === undefined)
before = {};
else if (typeof(before) === 'boolean')
before = {enabled: before};
else if (typeof(before) === 'string')
before = {classes: before};
if (before.enabled === false)
clsName.push('disabled');
if (before.classes)
clsName = clsName.concat(before.classes.split(/\s+/));
if (before.tooltip)
tooltip = before.tooltip;
}
clsName = $.unique(clsName);
html.push(''+prevMonth.getUTCDate() + ' | ');
if (prevMonth.getUTCDay() === this.o.weekEnd){
html.push('
');
}
prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
}
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
var months = this.picker.find('.datepicker-months')
.find('th:eq(1)')
.text(year)
.end()
.find('span').removeClass('active');
$.each(this.dates, function(i, d){
if (d.getUTCFullYear() === year)
months.eq(d.getUTCMonth()).addClass('active');
});
if (year < startYear || year > endYear){
months.addClass('disabled');
}
if (year === startYear){
months.slice(0, startMonth).addClass('disabled');
}
if (year === endYear){
months.slice(endMonth+1).addClass('disabled');
}
html = '';
year = parseInt(year/10, 10) * 10;
var yearCont = this.picker.find('.datepicker-years')
.find('th:eq(1)')
.text(year + '-' + (year + 9))
.end()
.find('td');
year -= 1;
var years = $.map(this.dates, function(d){
return d.getUTCFullYear();
}),
classes;
for (var i = -1; i < 11; i++){
classes = ['year'];
if (i === -1)
classes.push('old');
else if (i === 10)
classes.push('new');
if ($.inArray(year, years) !== -1)
classes.push('active');
if (year < startYear || year > endYear)
classes.push('disabled');
html += ''+year+'';
year += 1;
}
yearCont.html(html);
},
updateNavArrows: function(){
if (!this._allow_update)
return;
var d = new Date(this.viewDate),
year = d.getUTCFullYear(),
month = d.getUTCMonth();
switch (this.viewMode){
case 0:
if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()){
this.picker.find('.prev').css({visibility: 'hidden'});
}
else {
this.picker.find('.prev').css({visibility: 'visible'});
}
if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()){
this.picker.find('.next').css({visibility: 'hidden'});
}
else {
this.picker.find('.next').css({visibility: 'visible'});
}
break;
case 1:
case 2:
if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()){
this.picker.find('.prev').css({visibility: 'hidden'});
}
else {
this.picker.find('.prev').css({visibility: 'visible'});
}
if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()){
this.picker.find('.next').css({visibility: 'hidden'});
}
else {
this.picker.find('.next').css({visibility: 'visible'});
}
break;
}
},
click: function(e){
e.preventDefault();
var target = $(e.target).closest('span, td, th'),
year, month, day;
if (target.length === 1){
switch (target[0].nodeName.toLowerCase()){
case 'th':
switch (target[0].className){
case 'datepicker-switch':
this.showMode(1);
break;
case 'prev':
case 'next':
var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1);
switch (this.viewMode){
case 0:
this.viewDate = this.moveMonth(this.viewDate, dir);
this._trigger('changeMonth', this.viewDate);
break;
case 1:
case 2:
this.viewDate = this.moveYear(this.viewDate, dir);
if (this.viewMode === 1)
this._trigger('changeYear', this.viewDate);
break;
}
this.fill();
break;
case 'today':
var date = new Date();
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
this.showMode(-2);
var which = this.o.todayBtn === 'linked' ? null : 'view';
this._setDate(date, which);
break;
case 'clear':
var element;
if (this.isInput)
element = this.element;
else if (this.component)
element = this.element.find('input');
if (element)
element.val("").change();
this.update();
this._trigger('changeDate');
if (this.o.autoclose)
this.hide();
break;
}
break;
case 'span':
if (!target.is('.disabled')){
this.viewDate.setUTCDate(1);
if (target.is('.month')){
day = 1;
month = target.parent().find('span').index(target);
year = this.viewDate.getUTCFullYear();
this.viewDate.setUTCMonth(month);
this._trigger('changeMonth', this.viewDate);
if (this.o.minViewMode === 1){
this._setDate(UTCDate(year, month, day));
}
}
else {
day = 1;
month = 0;
year = parseInt(target.text(), 10)||0;
this.viewDate.setUTCFullYear(year);
this._trigger('changeYear', this.viewDate);
if (this.o.minViewMode === 2){
this._setDate(UTCDate(year, month, day));
}
}
this.showMode(-1);
this.fill();
}
break;
case 'td':
if (target.is('.day') && !target.is('.disabled')){
day = parseInt(target.text(), 10)||1;
year = this.viewDate.getUTCFullYear();
month = this.viewDate.getUTCMonth();
if (target.is('.old')){
if (month === 0){
month = 11;
year -= 1;
}
else {
month -= 1;
}
}
else if (target.is('.new')){
if (month === 11){
month = 0;
year += 1;
}
else {
month += 1;
}
}
this._setDate(UTCDate(year, month, day));
}
break;
}
}
if (this.picker.is(':visible') && this._focused_from){
$(this._focused_from).focus();
}
delete this._focused_from;
},
_toggle_multidate: function(date){
var ix = this.dates.contains(date);
if (!date){
this.dates.clear();
}
else if (ix !== -1){
this.dates.remove(ix);
}
else {
this.dates.push(date);
}
if (typeof this.o.multidate === 'number')
while (this.dates.length > this.o.multidate)
this.dates.remove(0);
},
_setDate: function(date, which){
if (!which || which === 'date')
this._toggle_multidate(date && new Date(date));
if (!which || which === 'view')
this.viewDate = date && new Date(date);
this.fill();
this.setValue();
this._trigger('changeDate');
var element;
if (this.isInput){
element = this.element;
}
else if (this.component){
element = this.element.find('input');
}
if (element){
element.change();
}
if (this.o.autoclose && (!which || which === 'date')){
this.hide();
}
},
moveMonth: function(date, dir){
if (!date)
return undefined;
if (!dir)
return date;
var new_date = new Date(date.valueOf()),
day = new_date.getUTCDate(),
month = new_date.getUTCMonth(),
mag = Math.abs(dir),
new_month, test;
dir = dir > 0 ? 1 : -1;
if (mag === 1){
test = dir === -1
// If going back one month, make sure month is not current month
// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
? function(){
return new_date.getUTCMonth() === month;
}
// If going forward one month, make sure month is as expected
// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
: function(){
return new_date.getUTCMonth() !== new_month;
};
new_month = month + dir;
new_date.setUTCMonth(new_month);
// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
if (new_month < 0 || new_month > 11)
new_month = (new_month + 12) % 12;
}
else {
// For magnitudes >1, move one month at a time...
for (var i=0; i < mag; i++)
// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
new_date = this.moveMonth(new_date, dir);
// ...then reset the day, keeping it in the new month
new_month = new_date.getUTCMonth();
new_date.setUTCDate(day);
test = function(){
return new_month !== new_date.getUTCMonth();
};
}
// Common date-resetting loop -- if date is beyond end of month, make it
// end of month
while (test()){
new_date.setUTCDate(--day);
new_date.setUTCMonth(new_month);
}
return new_date;
},
moveYear: function(date, dir){
return this.moveMonth(date, dir*12);
},
dateWithinRange: function(date){
return date >= this.o.startDate && date <= this.o.endDate;
},
keydown: function(e){
if (this.picker.is(':not(:visible)')){
if (e.keyCode === 27) // allow escape to hide and re-show picker
this.show();
return;
}
var dateChanged = false,
dir, newDate, newViewDate,
focusDate = this.focusDate || this.viewDate;
switch (e.keyCode){
case 27: // escape
if (this.focusDate){
this.focusDate = null;
this.viewDate = this.dates.get(-1) || this.viewDate;
this.fill();
}
else
this.hide();
e.preventDefault();
break;
case 37: // left
case 39: // right
if (!this.o.keyboardNavigation)
break;
dir = e.keyCode === 37 ? -1 : 1;
if (e.ctrlKey){
newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir);
newViewDate = this.moveYear(focusDate, dir);
this._trigger('changeYear', this.viewDate);
}
else if (e.shiftKey){
newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir);
newViewDate = this.moveMonth(focusDate, dir);
this._trigger('changeMonth', this.viewDate);
}
else {
newDate = new Date(this.dates.get(-1) || UTCToday());
newDate.setUTCDate(newDate.getUTCDate() + dir);
newViewDate = new Date(focusDate);
newViewDate.setUTCDate(focusDate.getUTCDate() + dir);
}
if (this.dateWithinRange(newDate)){
this.focusDate = this.viewDate = newViewDate;
this.setValue();
this.fill();
e.preventDefault();
}
break;
case 38: // up
case 40: // down
if (!this.o.keyboardNavigation)
break;
dir = e.keyCode === 38 ? -1 : 1;
if (e.ctrlKey){
newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir);
newViewDate = this.moveYear(focusDate, dir);
this._trigger('changeYear', this.viewDate);
}
else if (e.shiftKey){
newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir);
newViewDate = this.moveMonth(focusDate, dir);
this._trigger('changeMonth', this.viewDate);
}
else {
newDate = new Date(this.dates.get(-1) || UTCToday());
newDate.setUTCDate(newDate.getUTCDate() + dir * 7);
newViewDate = new Date(focusDate);
newViewDate.setUTCDate(focusDate.getUTCDate() + dir * 7);
}
if (this.dateWithinRange(newDate)){
this.focusDate = this.viewDate = newViewDate;
this.setValue();
this.fill();
e.preventDefault();
}
break;
case 32: // spacebar
// Spacebar is used in manually typing dates in some formats.
// As such, its behavior should not be hijacked.
break;
case 13: // enter
focusDate = this.focusDate || this.dates.get(-1) || this.viewDate;
this._toggle_multidate(focusDate);
dateChanged = true;
this.focusDate = null;
this.viewDate = this.dates.get(-1) || this.viewDate;
this.setValue();
this.fill();
if (this.picker.is(':visible')){
e.preventDefault();
if (this.o.autoclose)
this.hide();
}
break;
case 9: // tab
this.focusDate = null;
this.viewDate = this.dates.get(-1) || this.viewDate;
this.fill();
this.hide();
break;
}
if (dateChanged){
if (this.dates.length)
this._trigger('changeDate');
else
this._trigger('clearDate');
var element;
if (this.isInput){
element = this.element;
}
else if (this.component){
element = this.element.find('input');
}
if (element){
element.change();
}
}
},
showMode: function(dir){
if (dir){
this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
}
this.picker
.find('>div')
.hide()
.filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName)
.css('display', 'block');
this.updateNavArrows();
}
};
var DateRangePicker = function(element, options){
this.element = $(element);
this.inputs = $.map(options.inputs, function(i){
return i.jquery ? i[0] : i;
});
delete options.inputs;
$(this.inputs)
.datepicker(options)
.bind('changeDate', $.proxy(this.dateUpdated, this));
this.pickers = $.map(this.inputs, function(i){
return $(i).data('datepicker');
});
this.updateDates();
};
DateRangePicker.prototype = {
updateDates: function(){
this.dates = $.map(this.pickers, function(i){
return i.getUTCDate();
});
this.updateRanges();
},
updateRanges: function(){
var range = $.map(this.dates, function(d){
return d.valueOf();
});
$.each(this.pickers, function(i, p){
p.setRange(range);
});
},
dateUpdated: function(e){
// `this.updating` is a workaround for preventing infinite recursion
// between `changeDate` triggering and `setUTCDate` calling. Until
// there is a better mechanism.
if (this.updating)
return;
this.updating = true;
var dp = $(e.target).data('datepicker'),
new_date = dp.getUTCDate(),
i = $.inArray(e.target, this.inputs),
l = this.inputs.length;
if (i === -1)
return;
$.each(this.pickers, function(i, p){
if (!p.getUTCDate())
p.setUTCDate(new_date);
});
if (new_date < this.dates[i]){
// Date being moved earlier/left
while (i >= 0 && new_date < this.dates[i]){
this.pickers[i--].setUTCDate(new_date);
}
}
else if (new_date > this.dates[i]){
// Date being moved later/right
while (i < l && new_date > this.dates[i]){
this.pickers[i++].setUTCDate(new_date);
}
}
this.updateDates();
delete this.updating;
},
remove: function(){
$.map(this.pickers, function(p){ p.remove(); });
delete this.element.data().datepicker;
}
};
function opts_from_el(el, prefix){
// Derive options from element data-attrs
var data = $(el).data(),
out = {}, inkey,
replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])');
prefix = new RegExp('^' + prefix.toLowerCase());
function re_lower(_,a){
return a.toLowerCase();
}
for (var key in data)
if (prefix.test(key)){
inkey = key.replace(replace, re_lower);
out[inkey] = data[key];
}
return out;
}
function opts_from_locale(lang){
// Derive options from locale plugins
var out = {};
// Check if "de-DE" style date is available, if not language should
// fallback to 2 letter code eg "de"
if (!dates[lang]){
lang = lang.split('-')[0];
if (!dates[lang])
return;
}
var d = dates[lang];
$.each(locale_opts, function(i,k){
if (k in d)
out[k] = d[k];
});
return out;
}
var old = $.fn.datepicker;
$.fn.datepicker = function(option){
var args = Array.apply(null, arguments);
args.shift();
var internal_return;
this.each(function(){
var $this = $(this),
data = $this.data('datepicker'),
options = typeof option === 'object' && option;
if (!data){
var elopts = opts_from_el(this, 'date'),
// Preliminary otions
xopts = $.extend({}, defaults, elopts, options),
locopts = opts_from_locale(xopts.language),
// Options priority: js args, data-attrs, locales, defaults
opts = $.extend({}, defaults, locopts, elopts, options);
if ($this.is('.input-daterange') || opts.inputs){
var ropts = {
inputs: opts.inputs || $this.find('input').toArray()
};
$this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
}
else {
$this.data('datepicker', (data = new Datepicker(this, opts)));
}
}
if (typeof option === 'string' && typeof data[option] === 'function'){
internal_return = data[option].apply(data, args);
if (internal_return !== undefined)
return false;
}
});
if (internal_return !== undefined)
return internal_return;
else
return this;
};
var defaults = $.fn.datepicker.defaults = {
autoclose: false,
beforeShowDay: $.noop,
calendarWeeks: false,
clearBtn: false,
daysOfWeekDisabled: [],
endDate: Infinity,
forceParse: true,
format: 'mm/dd/yyyy',
keyboardNavigation: true,
language: 'en',
minViewMode: 0,
multidate: false,
multidateSeparator: ',',
orientation: "auto",
rtl: false,
startDate: -Infinity,
startView: 0,
todayBtn: false,
todayHighlight: false,
weekStart: 0
};
var locale_opts = $.fn.datepicker.locale_opts = [
'format',
'rtl',
'weekStart'
];
$.fn.datepicker.Constructor = Datepicker;
var dates = $.fn.datepicker.dates = {
en: {
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
today: "Today",
clear: "Clear"
}
};
var DPGlobal = {
modes: [
{
clsName: 'days',
navFnc: 'Month',
navStep: 1
},
{
clsName: 'months',
navFnc: 'FullYear',
navStep: 1
},
{
clsName: 'years',
navFnc: 'FullYear',
navStep: 10
}],
isLeapYear: function(year){
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
},
getDaysInMonth: function(year, month){
return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
},
validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
parseFormat: function(format){
// IE treats \0 as a string end in inputs (truncating the value),
// so it's a bad format delimiter, anyway
var separators = format.replace(this.validParts, '\0').split('\0'),
parts = format.match(this.validParts);
if (!separators || !separators.length || !parts || parts.length === 0){
throw new Error("Invalid date format.");
}
return {separators: separators, parts: parts};
},
parseDate: function(date, format, language){
if (!date)
return undefined;
if (date instanceof Date)
return date;
if (typeof format === 'string')
format = DPGlobal.parseFormat(format);
var part_re = /([\-+]\d+)([dmwy])/,
parts = date.match(/([\-+]\d+)([dmwy])/g),
part, dir, i;
if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){
date = new Date();
for (i=0; i < parts.length; i++){
part = part_re.exec(parts[i]);
dir = parseInt(part[1]);
switch (part[2]){
case 'd':
date.setUTCDate(date.getUTCDate() + dir);
break;
case 'm':
date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
break;
case 'w':
date.setUTCDate(date.getUTCDate() + dir * 7);
break;
case 'y':
date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
break;
}
}
return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
}
parts = date && date.match(this.nonpunctuation) || [];
date = new Date();
var parsed = {},
setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
setters_map = {
yyyy: function(d,v){
return d.setUTCFullYear(v);
},
yy: function(d,v){
return d.setUTCFullYear(2000+v);
},
m: function(d,v){
if (isNaN(d))
return d;
v -= 1;
while (v < 0) v += 12;
v %= 12;
d.setUTCMonth(v);
while (d.getUTCMonth() !== v)
d.setUTCDate(d.getUTCDate()-1);
return d;
},
d: function(d,v){
return d.setUTCDate(v);
}
},
val, filtered;
setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
setters_map['dd'] = setters_map['d'];
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
var fparts = format.parts.slice();
// Remove noop parts
if (parts.length !== fparts.length){
fparts = $(fparts).filter(function(i,p){
return $.inArray(p, setters_order) !== -1;
}).toArray();
}
// Process remainder
function match_part(){
var m = this.slice(0, parts[i].length),
p = parts[i].slice(0, m.length);
return m === p;
}
if (parts.length === fparts.length){
var cnt;
for (i=0, cnt = fparts.length; i < cnt; i++){
val = parseInt(parts[i], 10);
part = fparts[i];
if (isNaN(val)){
switch (part){
case 'MM':
filtered = $(dates[language].months).filter(match_part);
val = $.inArray(filtered[0], dates[language].months) + 1;
break;
case 'M':
filtered = $(dates[language].monthsShort).filter(match_part);
val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
break;
}
}
parsed[part] = val;
}
var _date, s;
for (i=0; i < setters_order.length; i++){
s = setters_order[i];
if (s in parsed && !isNaN(parsed[s])){
_date = new Date(date);
setters_map[s](_date, parsed[s]);
if (!isNaN(_date))
date = _date;
}
}
}
return date;
},
formatDate: function(date, format, language){
if (!date)
return '';
if (typeof format === 'string')
format = DPGlobal.parseFormat(format);
var val = {
d: date.getUTCDate(),
D: dates[language].daysShort[date.getUTCDay()],
DD: dates[language].days[date.getUTCDay()],
m: date.getUTCMonth() + 1,
M: dates[language].monthsShort[date.getUTCMonth()],
MM: dates[language].months[date.getUTCMonth()],
yy: date.getUTCFullYear().toString().substring(2),
yyyy: date.getUTCFullYear()
};
val.dd = (val.d < 10 ? '0' : '') + val.d;
val.mm = (val.m < 10 ? '0' : '') + val.m;
date = [];
var seps = $.extend([], format.separators);
for (var i=0, cnt = format.parts.length; i <= cnt; i++){
if (seps.length)
date.push(seps.shift());
date.push(val[format.parts[i]]);
}
return date.join('');
},
headTemplate: ''+
''+
'« | '+
' | '+
'» | '+
'
'+
'',
contTemplate: ' |
',
footTemplate: ''+
''+
' | '+
'
'+
''+
' | '+
'
'+
''
};
DPGlobal.template = ''+
'
'+
'
'+
DPGlobal.headTemplate+
''+
DPGlobal.footTemplate+
'
'+
'
'+
'
'+
'
'+
DPGlobal.headTemplate+
DPGlobal.contTemplate+
DPGlobal.footTemplate+
'
'+
'
'+
'
'+
'
'+
DPGlobal.headTemplate+
DPGlobal.contTemplate+
DPGlobal.footTemplate+
'
'+
'
'+
'
';
$.fn.datepicker.DPGlobal = DPGlobal;
/* DATEPICKER NO CONFLICT
* =================== */
$.fn.datepicker.noConflict = function(){
$.fn.datepicker = old;
return this;
};
/* DATEPICKER DATA-API
* ================== */
$(document).on(
'focus.datepicker.data-api click.datepicker.data-api',
'[data-provide="datepicker"]',
function(e){
var $this = $(this);
if ($this.data('datepicker'))
return;
e.preventDefault();
// component click requires us to explicitly show it
$this.datepicker('show');
}
);
$(function(){
$('[data-provide="datepicker-inline"]').datepicker();
});
}(window.jQuery));
/**
* Spanish translation for bootstrap-datepicker
* Bruno Bonamin
*/
;(function($){
$.fn.datepicker.dates['es'] = {
days: ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"],
daysShort: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"],
daysMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sa"],
months: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
monthsShort: ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"],
today: "Hoy",
monthsTitle: "Meses",
clear: "Borrar",
weekStart: 1,
format: "dd/mm/yyyy"
};
}(jQuery));
/*!
* Infinite Scroll PACKAGED v3.0.6
* Automatically add next page
*
* Licensed GPLv3 for open source use
* or Infinite Scroll Commercial License for commercial use
*
* https://infinite-scroll.com
* Copyright 2018 Metafizzy
*/
/**
* Bridget makes jQuery widgets
* v2.0.1
* MIT license
*/
/* jshint browser: true, strict: true, undef: true, unused: true */
( function( window, factory ) {
// universal module definition
/*jshint strict: false */ /* globals define, module, require */
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'jquery-bridget/jquery-bridget',[ 'jquery' ], function( jQuery ) {
return factory( window, jQuery );
});
} else if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('jquery')
);
} else {
// browser global
window.jQueryBridget = factory(
window,
window.jQuery
);
}
}( window, function factory( window, jQuery ) {
'use strict';
// ----- utils ----- //
var arraySlice = Array.prototype.slice;
// helper function for logging errors
// $.error breaks jQuery chaining
var console = window.console;
var logError = typeof console == 'undefined' ? function() {} :
function( message ) {
console.error( message );
};
// ----- jQueryBridget ----- //
function jQueryBridget( namespace, PluginClass, $ ) {
$ = $ || jQuery || window.jQuery;
if ( !$ ) {
return;
}
// add option method -> $().plugin('option', {...})
if ( !PluginClass.prototype.option ) {
// option setter
PluginClass.prototype.option = function( opts ) {
// bail out if not an object
if ( !$.isPlainObject( opts ) ){
return;
}
this.options = $.extend( true, this.options, opts );
};
}
// make jQuery plugin
$.fn[ namespace ] = function( arg0 /*, arg1 */ ) {
if ( typeof arg0 == 'string' ) {
// method call $().plugin( 'methodName', { options } )
// shift arguments by 1
var args = arraySlice.call( arguments, 1 );
return methodCall( this, arg0, args );
}
// just $().plugin({ options })
plainCall( this, arg0 );
return this;
};
// $().plugin('methodName')
function methodCall( $elems, methodName, args ) {
var returnValue;
var pluginMethodStr = '$().' + namespace + '("' + methodName + '")';
$elems.each( function( i, elem ) {
// get instance
var instance = $.data( elem, namespace );
if ( !instance ) {
logError( namespace + ' not initialized. Cannot call methods, i.e. ' +
pluginMethodStr );
return;
}
var method = instance[ methodName ];
if ( !method || methodName.charAt(0) == '_' ) {
logError( pluginMethodStr + ' is not a valid method' );
return;
}
// apply method, get return value
var value = method.apply( instance, args );
// set return value if value is returned, use only first value
returnValue = returnValue === undefined ? value : returnValue;
});
return returnValue !== undefined ? returnValue : $elems;
}
function plainCall( $elems, options ) {
$elems.each( function( i, elem ) {
var instance = $.data( elem, namespace );
if ( instance ) {
// set options & init
instance.option( options );
instance._init();
} else {
// initialize new instance
instance = new PluginClass( elem, options );
$.data( elem, namespace, instance );
}
});
}
updateJQuery( $ );
}
// ----- updateJQuery ----- //
// set $.bridget for v1 backwards compatibility
function updateJQuery( $ ) {
if ( !$ || ( $ && $.bridget ) ) {
return;
}
$.bridget = jQueryBridget;
}
updateJQuery( jQuery || window.jQuery );
// ----- ----- //
return jQueryBridget;
}));
/**
* EvEmitter v1.1.0
* Lil' event emitter
* MIT License
*/
/* jshint unused: true, undef: true, strict: true */
( function( global, factory ) {
// universal module definition
/* jshint strict: false */ /* globals define, module, window */
if ( typeof define == 'function' && define.amd ) {
// AMD - RequireJS
define( 'ev-emitter/ev-emitter',factory );
} else if ( typeof module == 'object' && module.exports ) {
// CommonJS - Browserify, Webpack
module.exports = factory();
} else {
// Browser globals
global.EvEmitter = factory();
}
}( typeof window != 'undefined' ? window : this, function() {
function EvEmitter() {}
var proto = EvEmitter.prototype;
proto.on = function( eventName, listener ) {
if ( !eventName || !listener ) {
return;
}
// set events hash
var events = this._events = this._events || {};
// set listeners array
var listeners = events[ eventName ] = events[ eventName ] || [];
// only add once
if ( listeners.indexOf( listener ) == -1 ) {
listeners.push( listener );
}
return this;
};
proto.once = function( eventName, listener ) {
if ( !eventName || !listener ) {
return;
}
// add event
this.on( eventName, listener );
// set once flag
// set onceEvents hash
var onceEvents = this._onceEvents = this._onceEvents || {};
// set onceListeners object
var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
// set flag
onceListeners[ listener ] = true;
return this;
};
proto.off = function( eventName, listener ) {
var listeners = this._events && this._events[ eventName ];
if ( !listeners || !listeners.length ) {
return;
}
var index = listeners.indexOf( listener );
if ( index != -1 ) {
listeners.splice( index, 1 );
}
return this;
};
proto.emitEvent = function( eventName, args ) {
var listeners = this._events && this._events[ eventName ];
if ( !listeners || !listeners.length ) {
return;
}
// copy over to avoid interference if .off() in listener
listeners = listeners.slice(0);
args = args || [];
// once stuff
var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
for ( var i=0; i < listeners.length; i++ ) {
var listener = listeners[i]
var isOnce = onceListeners && onceListeners[ listener ];
if ( isOnce ) {
// remove listener
// remove before trigger to prevent recursion
this.off( eventName, listener );
// unset once flag
delete onceListeners[ listener ];
}
// trigger listener
listener.apply( this, args );
}
return this;
};
proto.allOff = function() {
delete this._events;
delete this._onceEvents;
};
return EvEmitter;
}));
/**
* matchesSelector v2.0.2
* matchesSelector( element, '.selector' )
* MIT license
*/
/*jshint browser: true, strict: true, undef: true, unused: true */
( function( window, factory ) {
/*global define: false, module: false */
'use strict';
// universal module definition
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'desandro-matches-selector/matches-selector',factory );
} else if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
window.matchesSelector = factory();
}
}( window, function factory() {
'use strict';
var matchesMethod = ( function() {
var ElemProto = window.Element.prototype;
// check for the standard method name first
if ( ElemProto.matches ) {
return 'matches';
}
// check un-prefixed
if ( ElemProto.matchesSelector ) {
return 'matchesSelector';
}
// check vendor prefixes
var prefixes = [ 'webkit', 'moz', 'ms', 'o' ];
for ( var i=0; i < prefixes.length; i++ ) {
var prefix = prefixes[i];
var method = prefix + 'MatchesSelector';
if ( ElemProto[ method ] ) {
return method;
}
}
})();
return function matchesSelector( elem, selector ) {
return elem[ matchesMethod ]( selector );
};
}));
/**
* Fizzy UI utils v2.0.7
* MIT license
*/
/*jshint browser: true, undef: true, unused: true, strict: true */
( function( window, factory ) {
// universal module definition
/*jshint strict: false */ /*globals define, module, require */
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'fizzy-ui-utils/utils',[
'desandro-matches-selector/matches-selector'
], function( matchesSelector ) {
return factory( window, matchesSelector );
});
} else if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('desandro-matches-selector')
);
} else {
// browser global
window.fizzyUIUtils = factory(
window,
window.matchesSelector
);
}
}( window, function factory( window, matchesSelector ) {
var utils = {};
// ----- extend ----- //
// extends objects
utils.extend = function( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
};
// ----- modulo ----- //
utils.modulo = function( num, div ) {
return ( ( num % div ) + div ) % div;
};
// ----- makeArray ----- //
var arraySlice = Array.prototype.slice;
// turn element or nodeList into an array
utils.makeArray = function( obj ) {
if ( Array.isArray( obj ) ) {
// use object if already an array
return obj;
}
// return empty array if undefined or null. #6
if ( obj === null || obj === undefined ) {
return [];
}
var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
if ( isArrayLike ) {
// convert nodeList to array
return arraySlice.call( obj );
}
// array of single index
return [ obj ];
};
// ----- removeFrom ----- //
utils.removeFrom = function( ary, obj ) {
var index = ary.indexOf( obj );
if ( index != -1 ) {
ary.splice( index, 1 );
}
};
// ----- getParent ----- //
utils.getParent = function( elem, selector ) {
while ( elem.parentNode && elem != document.body ) {
elem = elem.parentNode;
if ( matchesSelector( elem, selector ) ) {
return elem;
}
}
};
// ----- getQueryElement ----- //
// use element as selector string
utils.getQueryElement = function( elem ) {
if ( typeof elem == 'string' ) {
return document.querySelector( elem );
}
return elem;
};
// ----- handleEvent ----- //
// enable .ontype to trigger from .addEventListener( elem, 'type' )
utils.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
// ----- filterFindElements ----- //
utils.filterFindElements = function( elems, selector ) {
// make array of elems
elems = utils.makeArray( elems );
var ffElems = [];
elems.forEach( function( elem ) {
// check that elem is an actual element
if ( !( elem instanceof HTMLElement ) ) {
return;
}
// add elem if no selector
if ( !selector ) {
ffElems.push( elem );
return;
}
// filter & find items if we have a selector
// filter
if ( matchesSelector( elem, selector ) ) {
ffElems.push( elem );
}
// find children
var childElems = elem.querySelectorAll( selector );
// concat childElems to filterFound array
for ( var i=0; i < childElems.length; i++ ) {
ffElems.push( childElems[i] );
}
});
return ffElems;
};
// ----- debounceMethod ----- //
utils.debounceMethod = function( _class, methodName, threshold ) {
threshold = threshold || 100;
// original method
var method = _class.prototype[ methodName ];
var timeoutName = methodName + 'Timeout';
_class.prototype[ methodName ] = function() {
var timeout = this[ timeoutName ];
clearTimeout( timeout );
var args = arguments;
var _this = this;
this[ timeoutName ] = setTimeout( function() {
method.apply( _this, args );
delete _this[ timeoutName ];
}, threshold );
};
};
// ----- docReady ----- //
utils.docReady = function( callback ) {
var readyState = document.readyState;
if ( readyState == 'complete' || readyState == 'interactive' ) {
// do async to allow for other scripts to run. metafizzy/flickity#441
setTimeout( callback );
} else {
document.addEventListener( 'DOMContentLoaded', callback );
}
};
// ----- htmlInit ----- //
// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
utils.toDashed = function( str ) {
return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
return $1 + '-' + $2;
}).toLowerCase();
};
var console = window.console;
/**
* allow user to initialize classes via [data-namespace] or .js-namespace class
* htmlInit( Widget, 'widgetName' )
* options are parsed from data-namespace-options
*/
utils.htmlInit = function( WidgetClass, namespace ) {
utils.docReady( function() {
var dashedNamespace = utils.toDashed( namespace );
var dataAttr = 'data-' + dashedNamespace;
var dataAttrElems = document.querySelectorAll( '[' + dataAttr + ']' );
var jsDashElems = document.querySelectorAll( '.js-' + dashedNamespace );
var elems = utils.makeArray( dataAttrElems )
.concat( utils.makeArray( jsDashElems ) );
var dataOptionsAttr = dataAttr + '-options';
var jQuery = window.jQuery;
elems.forEach( function( elem ) {
var attr = elem.getAttribute( dataAttr ) ||
elem.getAttribute( dataOptionsAttr );
var options;
try {
options = attr && JSON.parse( attr );
} catch ( error ) {
// log error, do not initialize
if ( console ) {
console.error( 'Error parsing ' + dataAttr + ' on ' + elem.className +
': ' + error );
}
return;
}
// initialize
var instance = new WidgetClass( elem, options );
// make available via $().data('namespace')
if ( jQuery ) {
jQuery.data( elem, namespace, instance );
}
});
});
};
// ----- ----- //
return utils;
}));
// core
( function( window, factory ) {
// universal module definition
/* globals define, module, require */
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'infinite-scroll/js/core',[
'ev-emitter/ev-emitter',
'fizzy-ui-utils/utils',
], function( EvEmitter, utils) {
return factory( window, EvEmitter, utils );
});
} else if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('ev-emitter'),
require('fizzy-ui-utils')
);
} else {
// browser global
window.InfiniteScroll = factory(
window,
window.EvEmitter,
window.fizzyUIUtils
);
}
}( window, function factory( window, EvEmitter, utils ) {
var jQuery = window.jQuery;
// internal store of all InfiniteScroll intances
var instances = {};
function InfiniteScroll( element, options ) {
var queryElem = utils.getQueryElement( element );
if ( !queryElem ) {
console.error( 'Bad element for InfiniteScroll: ' + ( queryElem || element ) );
return;
}
element = queryElem;
// do not initialize twice on same element
if ( element.infiniteScrollGUID ) {
var instance = instances[ element.infiniteScrollGUID ];
instance.option( options );
return instance;
}
this.element = element;
// options
this.options = utils.extend( {}, InfiniteScroll.defaults );
this.option( options );
// add jQuery
if ( jQuery ) {
this.$element = jQuery( this.element );
}
this.create();
}
// defaults
InfiniteScroll.defaults = {
// path: null,
// hideNav: null,
// debug: false,
};
// create & destroy methods
InfiniteScroll.create = {};
InfiniteScroll.destroy = {};
var proto = InfiniteScroll.prototype;
// inherit EvEmitter
utils.extend( proto, EvEmitter.prototype );
// -------------------------- -------------------------- //
// globally unique identifiers
var GUID = 0;
proto.create = function() {
// create core
// add id for InfiniteScroll.data
var id = this.guid = ++GUID;
this.element.infiniteScrollGUID = id; // expando
instances[ id ] = this; // associate via id
// properties
this.pageIndex = 1; // default to first page
this.loadCount = 0;
this.updateGetPath();
// bail if getPath not set, or returns falsey #776
var hasPath = this.getPath && this.getPath();
if ( !hasPath ) {
console.error('Disabling InfiniteScroll');
return;
}
this.updateGetAbsolutePath();
this.log( 'initialized', [ this.element.className ] );
this.callOnInit();
// create features
for ( var method in InfiniteScroll.create ) {
InfiniteScroll.create[ method ].call( this );
}
};
proto.option = function( opts ) {
utils.extend( this.options, opts );
};
// call onInit option, used for binding events on init
proto.callOnInit = function() {
var onInit = this.options.onInit;
if ( onInit ) {
onInit.call( this, this );
}
};
// ----- events ----- //
proto.dispatchEvent = function( type, event, args ) {
this.log( type, args );
var emitArgs = event ? [ event ].concat( args ) : args;
this.emitEvent( type, emitArgs );
// trigger jQuery event
if ( !jQuery || !this.$element ) {
return;
}
// namespace jQuery event
type += '.infiniteScroll';
var $event = type;
if ( event ) {
// create jQuery event
var jQEvent = jQuery.Event( event );
jQEvent.type = type;
$event = jQEvent;
}
this.$element.trigger( $event, args );
};
var loggers = {
initialized: function( className ) {
return 'on ' + className;
},
request: function( path ) {
return 'URL: ' + path;
},
load: function( response, path ) {
return ( response.title || '' ) + '. URL: ' + path;
},
error: function( error, path ) {
return error + '. URL: ' + path;
},
append: function( response, path, items ) {
return items.length + ' items. URL: ' + path;
},
last: function( response, path ) {
return 'URL: ' + path;
},
history: function( title, path ) {
return 'URL: ' + path;
},
pageIndex: function( index, origin ) {
return 'current page determined to be: ' + index + ' from ' + origin;
},
};
// log events
proto.log = function( type, args ) {
if ( !this.options.debug ) {
return;
}
var message = '[InfiniteScroll] ' + type;
var logger = loggers[ type ];
if ( logger ) {
message += '. ' + logger.apply( this, args );
}
console.log( message );
};
// -------------------------- methods used amoung features -------------------------- //
proto.updateMeasurements = function() {
this.windowHeight = window.innerHeight;
var rect = this.element.getBoundingClientRect();
this.top = rect.top + window.pageYOffset;
};
proto.updateScroller = function() {
var elementScroll = this.options.elementScroll;
if ( !elementScroll ) {
// default, use window
this.scroller = window;
return;
}
// if true, set to element, otherwise use option
this.scroller = elementScroll === true ? this.element :
utils.getQueryElement( elementScroll );
if ( !this.scroller ) {
throw 'Unable to find elementScroll: ' + elementScroll;
}
};
// -------------------------- page path -------------------------- //
proto.updateGetPath = function() {
var optPath = this.options.path;
if ( !optPath ) {
console.error( 'InfiniteScroll path option required. Set as: ' + optPath );
return;
}
// function
var type = typeof optPath;
if ( type == 'function' ) {
this.getPath = optPath;
return;
}
// template string: '/pages/{{#}}.html'
var templateMatch = type == 'string' && optPath.match('{{#}}');
if ( templateMatch ) {
this.updateGetPathTemplate( optPath );
return;
}
// selector: '.next-page-selector'
this.updateGetPathSelector( optPath );
};
proto.updateGetPathTemplate = function( optPath ) {
// set getPath with template string
this.getPath = function() {
var nextIndex = this.pageIndex + 1;
return optPath.replace( '{{#}}', nextIndex );
}.bind( this );
// get pageIndex from location
// convert path option into regex to look for pattern in location
// escape query (?) in url, allows for parsing GET parameters
var regexString = optPath
.replace( /(\\\?|\?)/, '\\?' )
.replace( '{{#}}', '(\\d\\d?\\d?)' );
var templateRe = new RegExp( regexString );
var match = location.href.match( templateRe );
if ( match ) {
this.pageIndex = parseInt( match[1], 10 );
this.log( 'pageIndex', [ this.pageIndex, 'template string' ] );
}
};
var pathRegexes = [
// WordPress & Tumblr - example.com/page/2
// Jekyll - example.com/page2
/^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/,
// Drupal - example.com/?page=1
/^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/,
// catch all, last occurence of a number
/(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/,
];
proto.updateGetPathSelector = function( optPath ) {
// parse href of link: '.next-page-link'
var hrefElem = document.querySelector( optPath );
if ( !hrefElem ) {
console.error( 'Bad InfiniteScroll path option. Next link not found: ' +
optPath );
return;
}
var href = hrefElem.getAttribute('href');
// try matching href to pathRegexes patterns
var pathParts, regex;
for ( var i=0; href && i < pathRegexes.length; i++ ) {
regex = pathRegexes[i];
var match = href.match( regex );
if ( match ) {
pathParts = match.slice(1); // remove first part
break;
}
}
if ( !pathParts ) {
console.error( 'InfiniteScroll unable to parse next link href: ' + href );
return;
}
this.isPathSelector = true; // flag for checkLastPage()
this.getPath = function() {
var nextIndex = this.pageIndex + 1;
return pathParts[0] + nextIndex + pathParts[2];
}.bind( this );
// get pageIndex from href
this.pageIndex = parseInt( pathParts[1], 10 ) - 1;
this.log( 'pageIndex', [ this.pageIndex, 'next link' ] );
};
proto.updateGetAbsolutePath = function() {
var path = this.getPath();
// path doesn't start with http or /
var isAbsolute = path.match( /^http/ ) || path.match( /^\// );
if ( isAbsolute ) {
this.getAbsolutePath = this.getPath;
return;
}
var pathname = location.pathname;
// query parameter #829. example.com/?pg=2
var isQuery = path.match( /^\?/ );
if ( isQuery ) {
this.getAbsolutePath = function() {
return pathname + this.getPath();
};
return;
}
// /foo/bar/index.html => /foo/bar
var directory = pathname.substring( 0, pathname.lastIndexOf('/') );
this.getAbsolutePath = function() {
return directory + '/' + this.getPath();
};
};
// -------------------------- nav -------------------------- //
// hide navigation
InfiniteScroll.create.hideNav = function() {
var nav = utils.getQueryElement( this.options.hideNav );
if ( !nav ) {
return;
}
nav.style.display = 'none';
this.nav = nav;
};
InfiniteScroll.destroy.hideNav = function() {
if ( this.nav ) {
this.nav.style.display = '';
}
};
// -------------------------- destroy -------------------------- //
proto.destroy = function() {
this.allOff(); // remove all event listeners
// call destroy methods
for ( var method in InfiniteScroll.destroy ) {
InfiniteScroll.destroy[ method ].call( this );
}
delete this.element.infiniteScrollGUID;
delete instances[ this.guid ];
// remove jQuery data. #807
if ( jQuery && this.$element ) {
jQuery.removeData( this.element, 'infiniteScroll' );
}
};
// -------------------------- utilities -------------------------- //
// https://remysharp.com/2010/07/21/throttling-function-calls
InfiniteScroll.throttle = function( fn, threshold ) {
threshold = threshold || 200;
var last, timeout;
return function() {
var now = +new Date();
var args = arguments;
var trigger = function() {
last = now;
fn.apply( this, args );
}.bind( this );
if ( last && now < last + threshold ) {
// hold on to it
clearTimeout( timeout );
timeout = setTimeout( trigger, threshold );
} else {
trigger();
}
};
};
InfiniteScroll.data = function( elem ) {
elem = utils.getQueryElement( elem );
var id = elem && elem.infiniteScrollGUID;
return id && instances[ id ];
};
// set internal jQuery, for Webpack + jQuery v3
InfiniteScroll.setJQuery = function( $ ) {
jQuery = $;
};
// -------------------------- setup -------------------------- //
utils.htmlInit( InfiniteScroll, 'infinite-scroll' );
// add noop _init method for jQuery Bridget. #768
proto._init = function() {};
if ( jQuery && jQuery.bridget ) {
jQuery.bridget( 'infiniteScroll', InfiniteScroll );
}
// -------------------------- -------------------------- //
return InfiniteScroll;
}));
// page-load
( function( window, factory ) {
// universal module definition
/* globals define, module, require */
if ( typeof define == 'function' && define.amd ) {
// AMD
define( 'infinite-scroll/js/page-load',[
'./core',
], function( InfiniteScroll ) {
return factory( window, InfiniteScroll );
});
} else if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
window,
require('./core')
);
} else {
// browser global
factory(
window,
window.InfiniteScroll
);
}
}( window, function factory( window, InfiniteScroll ) {
var proto = InfiniteScroll.prototype;
// InfiniteScroll.defaults.append = false;
InfiniteScroll.defaults.loadOnScroll = true;
InfiniteScroll.defaults.checkLastPage = true;
InfiniteScroll.defaults.responseType = 'document';
// InfiniteScroll.defaults.prefill = false;
// InfiniteScroll.defaults.outlayer = null;
InfiniteScroll.create.pageLoad = function() {
this.canLoad = true;
this.on( 'scrollThreshold', this.onScrollThresholdLoad );
this.on( 'load', this.checkLastPage );
if ( this.options.outlayer ) {
this.on( 'append', this.onAppendOutlayer );
}
};
proto.onScrollThresholdLoad = function() {
if ( this.options.loadOnScroll ) {
this.loadNextPage();
}
};
proto.loadNextPage = function() {
if ( this.isLoading || !this.canLoad ) {
return;
}
var path = this.getAbsolutePath();
this.isLoading = true;
var onLoad = function( response ) {
this.onPageLoad( response, path );
}.bind( this );
var onError = function( error ) {
this.onPageError( error, path );
}.bind( this );
var onLast = function( response ) {
this.lastPageReached( response, path );
}.bind( this );
request( path, this.options.responseType, onLoad, onError, onLast );
this.dispatchEvent( 'request', null, [ path ] );
};
proto.onPageLoad = function( response, path ) {
// done loading if not appending
if ( !this.options.append ) {
this.isLoading = false;
}
this.pageIndex++;
this.loadCount++;
this.dispatchEvent( 'load', null, [ response, path ] );
this.appendNextPage( response, path );
return response;
};
proto.appendNextPage = function( response, path ) {
var optAppend = this.options.append;
// do not append json
var isDocument = this.options.responseType == 'document';
if ( !isDocument || !optAppend ) {
return;
}
var items = response.querySelectorAll( optAppend );
var fragment = getItemsFragment( items );
var appendReady = function () {
this.appendItems( items, fragment );
this.isLoading = false;
this.dispatchEvent( 'append', null, [ response, path, items ] );
}.bind( this );
// TODO add hook for option to trigger appendReady
if ( this.options.outlayer ) {
this.appendOutlayerItems( fragment, appendReady );
} else {
appendReady();
}
};
proto.appendItems = function( items, fragment ) {
if ( !items || !items.length ) {
return;
}
// get fragment if not provided
fragment = fragment || getItemsFragment( items );
refreshScripts( fragment );
this.element.appendChild( fragment );
};
function getItemsFragment( items ) {
// add items to fragment
var fragment = document.createDocumentFragment();
for ( var i=0; items && i < items.length; i++ ) {
fragment.appendChild( items[i] );
}
return fragment;
}
// replace