').html(html).text();
}
function getClientSearchPostUrl() {
return WHMCS.adminUtils.getAdminRouteUrl('/search/client');
}
$( document ).ready(function() {
$('#predefinedFilters a').click(function(e) {
e.preventDefault();
$('#predefinedFilters a').removeClass('active');
$(this).addClass('active');
$('#inputPredefinedFilter').val($(this).data('filter'));
})
});
$( document ).ready(function() {
$("#inputWatch").bootstrapSwitch({
size: 'small',
onColor: 'success',
labelText: lang.watching
}).on('switchChange.bootstrapSwitch', function(event, state) {
if (state) {
ProjectManager.call('watch', '');
} else {
ProjectManager.call('unwatch', '');
}
});
$(document).on('click', '.task-status-indicator', function() {
var taskId = $(this).parent('td').parent('tr').attr('id');
ProjectManager.call('taskstatustoggle', 'taskid=' + taskId.substr(5));
});
$(document).on('click', '.task-edit', function() {
var taskId = $(this).parent('div').parent('td').parent('tr').attr('id');
ProjectManager.call('gettaskinfo', 'taskid=' + taskId.substr(5));
$('#modalTaskEdit').modal('show');
});
$(document).on('click', '.task-delete', function() {
var taskId = $(this).parent('div').parent('td').parent('tr').attr('id');
ProjectManager.confirm('deletetask', 'taskid=' + taskId.substr(5));
});
$(document).on('click', '.task-delete-button', function() {
var taskId = $('#inputTaskId').val();
$('#modalTaskEdit').modal('hide');
ProjectManager.confirm('deletetask', 'taskid=' + taskId);
});
$(document).on('click', '.timer-edit', function() {
var timerId = $(this).parent('td').parent('tr').attr('id');
ProjectManager.call('gettimerinfo', 'timerid=' + timerId.substr(6));
$('#modalEditTimer').modal('show');
});
$(document).on('click', '.timer-delete', function() {
var timerId = $(this).data('timer-id');
ProjectManager.confirm('deleteTimer', 'timerId=' + timerId);
});
$(document).on('click', '.ticket-result', function(e) {
e.preventDefault();
var ticketTid = $(this).attr('data-ticket-tid');
ProjectManager.call('addticket', 'ticketmask=' + ticketTid);
});
$(document).on('click', '.invoice-result', function(e) {
e.preventDefault();
var invoiceId = $(this).attr('data-invoice-id');
ProjectManager.call('addInvoice', 'invoice=' + invoiceId);
});
$(document).on('click', '.unlink-ticket', function() {
ProjectManager.call('unlinkTicket', 'ticketmask=' + $(this).data('ticket-tid'));
});
$(document).on('click', '.view-ticket', function() {
var child = window.open();
child.opener = null;
child.location = 'supporttickets.php?action=view&id=' + $(this).data('ticket-id');
});
$(document).on('click', '.unlink-invoice', function() {
ProjectManager.call('unlinkInvoice', 'invoice=' + $(this).data('invoice-id'));
});
$(document).on('click', '.view-invoice', function() {
var child = window.open();
child.opener = null;
child.location = 'invoices.php?action=edit&id=' + $(this).data('invoice-id');
});
$('#btnStartTimer').click(function() {
$(this).prop('disabled', true);
$(this).find('i').toggleClass('fa-clock fa-spinner fa-spin');
ProjectManager.call('starttimer', '');
});
$('#btnEndTimer').click(function() {
$(this).prop('disabled', true);
$(this).find('i').toggleClass('fa-clock fa-spinner fa-spin');
ProjectManager.call('endtimer', 'timerid=' + $(this).data('timerid'));
});
if (typeof Sortable !== "undefined" && $('#adminList').length) {
Sortable.create(
adminList,
{
group: {
name: 'adminSort',
pull: 'clone',
put: false
},
sort: false,
ghostClass: 'ghost'
}
);
}
jQuery('span[id^="assigned-admin-task-"]').each(function(index, group) {
Sortable.create(
group,
{
group: {
name: 'admin' + index,
pull: false,
put: ['adminSort']
},
sort: false,
ghostClass: 'ghost',
onAdd: function(evt) {
jQuery(evt.target).html(evt.item);
ProjectManager.call(
'assigntask',
'taskid=' + jQuery(evt.target).data('id')
+ '&admin=' + jQuery(evt.item).data('id')
);
}
}
);
});
if (typeof Sortable !== "undefined" && jQuery('#tableTasksBody').length) {
Sortable.create(tableTasksBody, {
group: 'tasks',
dataIdAttr: 'data-task-id',
draggable: 'tr.task-line-item',
store: {
get: function (sortable) {
// Do nothing upon initialization.
return [];
},
set: function (sortable) {
var requestVars = '',
taskOrder = sortable.toArray();
for (var i = 0; i < taskOrder.length; i++) {
requestVars += '&task[' + i + ']=' + taskOrder[i];
}
ProjectManager.call(
'tasksort',
requestVars.substr(1)
);
}
}
});
}
if (typeof Sortable !== "undefined" && $('#dueDatePicker').length) {
Sortable.create(
dueDatePicker,
{
group: {
name: 'dueDateSelect',
pull: 'clone',
put: false
},
sort: false,
ghostClass: 'ghost',
handle: '.handle',
onEnd: function() {
initDateRangePicker();
}
}
);
}
jQuery('span[id^="task-due-date-"]').each(function(index, group) {
Sortable.create(
group,
{
group: {
name: 'due-date-' + index,
pull: false,
put: ['dueDateSelect']
},
sort: false,
ghostClass: 'ghost',
onAdd: function(evt) {
jQuery(evt.target).html('
' + lang.updating + '...');
ProjectManager.call(
'taskduedate',
'taskid=' + jQuery(evt.target).data('id')
+ '&duedate=' + jQuery(evt.item).find('input').val()
);
}
}
);
});
jQuery('.icheck-button').each(function() {
var self = jQuery(this);
self.iCheck({
checkboxClass: 'icheckbox_flat-blue',
radioClass: 'iradio_flat-blue'
});
});
var toggleCompleteHide = jQuery('#toggleCompleteHide');
toggleCompleteHide.on('ifChecked', function(event){
jQuery('.task-line-item-completed').each(function() {
jQuery(this).children().toggle('highlight');
});
});
toggleCompleteHide.on('ifUnchecked', function(event){
jQuery('.task-line-item-completed').each(function() {
jQuery(this).children().toggle('highlight');
});
});
Dropzone.options.myProjectUploads = {
maxFilesize: maximumFileSize, // MB
addRemoveLinks: true,
init: function() {
this.on("sending", function(file, xhr, formData) {
// Will send the filesize along with the file as POST data.
formData.append("filesize", file.size);
});
this.on('success', function(file, response) {
if (response.status != "1") {
jQuery.growl.error(
{
title: lang.error,
message: response.error
}
);
} else {
jQuery('#fileCount').html(response.fileCount);
var className;
if (response.isImage) {
className = 'lightbox';
}
if (response.browserViewable) {
className = ''
}
appendFile(response.key, response);
this.removeFile(file);
jQuery.growl.notice(
{
title: lang.success,
message: lang.fileUploadSuccess
}
);
}
});
this.on('error', function(file, error, xhr) {
jQuery.growl.error(
{
title: lang.error,
message: error
}
);
});
}
};
var counter = 0;
if (jQuery("#newTicketMessage").length > 0) {
newProjectTicket = jQuery("#newTicketMessage").markdown(
{
footer: '',
autofocus: false,
savable: false,
resize: 'vertical',
iconlibrary: 'glyph',
onShow: function(e){
var content = '',
save_enabled = false;
if(typeof(Storage) !== "undefined") {
// Code for localStorage/sessionStorage.
content = localStorage.getItem("project-" + jQuery('#projectId').val() + "-new-ticket");
save_enabled = true;
if (content && typeof(content) !== "undefined") {
e.setContent(content);
}
}
jQuery("#newTicketMessage-footer").html(parseMdeFooter(content, save_enabled, lang.saved));
},
onChange: function(e){
var content = e.getContent(),
save_enabled = false;
if(typeof(Storage) !== "undefined") {
counter = 3;
save_enabled = true;
localStorage.setItem("project-" + jQuery('#projectId').val() + "-new-ticket", content);
doCountdown();
}
jQuery("#newTicketMessage-footer").html(parseMdeFooter(content, save_enabled));
},
onPreview: function(e){
var originalContent = e.getContent(),
parsedContent;
jQuery.ajax({
url: window.location.pathname + window.location.search,
async: false,
data: {
token: jQuery('#csrfToken').val(),
action: 'parseMarkdown',
content: originalContent,
ajax: 1,
projectId: jQuery('#projectId').val()
},
dataType: 'json',
success: function (data) {
parsedContent = data;
}
});
return parsedContent.body ? parsedContent.body : '';
},
additionalButtons: [
[{
name: "groupCustom",
data: [{
name: "cmdHelp",
title: lang.help,
hotkey: "Ctrl+F1",
btnClass: "btn open-modal",
href: "supporttickets.php?action=markdown",
icon: {
glyph: 'fas fa-question-circle',
fa: 'fas fa-question-circle',
'fa-3': 'icon-question-sign'
},
callback: function(e) {
e.$editor.removeClass("md-fullscreen-mode");
},
additionalAttr: [
{
name: 'data-modal-title',
value: lang.markdownGuide
},
{
name: 'data-modal-size',
value: 'modal-lg'
}
]
}]
}]
]
}
);
}
/**
* Parse the content to populate the markdown editor footer.
*
* @param {string} content
* @param {bool} auto_save
* @param {string} [saveText]
* @returns {string}
*/
function parseMdeFooter(content, auto_save, saveText)
{
saveText = saveText || lang.saving;
var pattern = /[^\s]+/g,
m = [],
word_count = 0,
line_count = 0;
if (content) {
m = content.match(pattern);
line_count = content.split(/\\r\\n|\\r|\\n/).length;
}
if (m) {
for (var i = 0; i < m.length; i++) {
if (m[i].charCodeAt(0) >= 0x4E00) {
word_count += m[i].length;
} else {
word_count += 1;
}
}
}
return '
' + lang.lines + ': ' + line_count
+ ' ' + lang.words + ': ' + word_count + ''
+ (auto_save ? ' ' + saveText + '' : '')
+ '
';
}
/**
* Countdown the save timeout. When zero, the span will update to show saved.
*/
function doCountdown()
{
if (counter >= 0) {
if (counter == 0) {
jQuery("span.markdown-save").html('saved');
}
counter--;
setTimeout(doCountdown, 1000);
}
}
jQuery('#associatedTicketSearch').on('keydown', function(e) {
if (e.keyCode == 13) {
e.preventDefault();
if (jQuery(this).val().length < 3) {
return;
}
jQuery('#associatedTicketResults').find('.ticket').hide().remove();
jQuery('#resultInfo').fadeIn('fast');
ProjectManager.call(
'searchTickets',
'search=' + jQuery(this).val()
);
}
});
jQuery('#modalAssociateInvoice').on('show.bs.modal', function() {
jQuery('#associatedInvoiceResults').find('.invoice').hide().remove();
jQuery('#invoiceResultInfo').fadeIn('fast');
ProjectManager.call(
'searchInvoices',
'search=0'
);
});
jQuery('#associatedInvoiceSearch').on('keydown', function(e) {
if (e.keyCode == 13) {
e.preventDefault();
if (jQuery(this).val().length < 1) {
return;
}
jQuery('#associatedInvoiceResults').find('.invoice').hide().remove();
jQuery('#invoiceResultInfo').fadeIn('fast');
ProjectManager.call(
'searchInvoices',
'search=' + jQuery(this).val()
);
}
});
jQuery('#btnMainUploadFile').on('click', function() {
jQuery('#myProjectUploads').click();
});
jQuery('#addMessage').on('submit', function(e) {
e.preventDefault();
var file = jQuery('#addMessageFiles'),
message = jQuery('#inputAddMessage'),
form = jQuery(this);
form.find('.btn').attr('disabled', 'disabled');
if (!message.val()) {
jQuery(this).find('.error-feedback')
.html(lang.messageRequired)
.hide()
.removeClass('hidden')
.fadeIn()
.end();
form.find('.btn').removeAttr('disabled');
return false;
}
if (file.val()) {
file.off('filebatchuploaderror');
file.off('filebatchuploadsuccess');
file.on('filebatchuploaderror', function(event, data, message) {
data.form.find('.error-feedback').html(message).hide().removeClass('hidden').fadeIn();
data.form.find('.btn').removeAttr('disabled');
});
file.on('filebatchuploadsuccess', sendAddMessagePost);
file.fileinput('upload');
} else {
sendAddMessagePost();
}
});
function sendAddMessagePost(event, data)
{
var form = jQuery('#addMessage'),
postData = form.serialize(),
action = form.data('action');
if (typeof data !== 'undefined' && data.response.newFiles.length) {
jQuery.each(data.response.newFiles, function (index, value) {
postData += '&fileId[]=' + value;
});
}
WHMCS.http.jqClient.post("addonmodules.php?module=project_management", postData + '&' + '&ajax=1&action=' + action, function(data) {
if (data.status === 0) {
form.find('.error-feedback').html(data.error).hide().removeClass('hidden').fadeIn();
} else {
window["ProjectManagerHandlers"][action](data);
}
form.find('.btn').removeAttr('disabled');
}, "json");
}
jQuery('#btnInvoiceSelected').on('click', function() {
var timers = jQuery('table.timers').find('input:checked[name="timerId[]"]');
if (timers.length === 0) {
jQuery.growl.error(
{
title: lang.error,
message: lang.selectTimers
}
);
return false;
}
jQuery(this)
.prop('disabled', true)
.find('i')
.toggleClass('fas fa-spinner fa-spin');
var requestVars = '';
timers.each(function () {
requestVars += 'timerId[]=' + jQuery(this).val() + '&';
});
var timersToInvoice = jQuery('#timersToInvoice'),
modalId = jQuery('#modalInvoiceItems');
timersToInvoice.html('');
WHMCS.http.jqClient.post(
'addonmodules.php?module=project_management',
requestVars + 'ajax=1&action=prepareInvoiceTimers&projectid=' + jQuery('#iIProjectId').val() + '&token=' + jQuery('#csrfToken').val() + '&rate=' + jQuery('#defaultRate').val(),
function(data) {
if (data.status === 0) {
modalId.find('.error-feedback')
.html(data.error)
.hide()
.removeClass('hidden')
.fadeIn();
} else {
modalId.find('.error-feedback')
.html('')
.hide();
jQuery.each(data.times, function(key, time) {
var newTimer = jQuery('#timersToInvoiceSample').clone();
newTimer.find('.description')
.attr('name', 'description[' + key + ']')
.val(time.description)
.end()
.find('.hours')
.attr('name', 'hours[' + key + ']')
.attr('id', 'invoiceItemsHours' + key)
.val((time.seconds/3600).toFixed(2))
.end()
.find('.displayHours')
.attr('name', 'displayHours[' + key + ']')
.attr('data-key', key)
.attr('id', 'invoiceItemsDisplayHours' + key)
.val(time.hours)
.end()
.find('.itemRate')
.attr('name', 'rate[' + key + ']')
.attr('data-key', key)
.attr('id', 'invoiceItemsRate' + key)
.val(time.rate)
.end()
.find('.invoiceAmount')
.attr('name', 'amount[' + key + ']')
.attr('data-key', key)
.attr('id', 'invoiceItemsAmount' + key)
.val(time.amount)
.end()
.find('span.currency')
.html(data.currency.prefix)
.end()
.find('span.currency-suffix')
.html(data.currency.suffix)
.end();
timersToInvoice.append(newTimer.children('div'));
});
}
jQuery('#formInvoiceItems').data('requestvars', requestVars);
modalId.modal('show').find('.btn-success').focus();
},
'json'
);
});
jQuery('#modalInvoiceItems').on('hidden.bs.modal', function() {
jQuery('#btnInvoiceSelected')
.prop('disabled', false)
.find('i')
.toggleClass('fas fa-spinner fa-spin');
});
jQuery('#modalSaveProject').on('show.bs.modal', function() {
jQuery(this).find('input[name="title"]').focus();
});
jQuery('#modalTaskEdit,#modalEditTimer').on('hidden.bs.modal', function() {
jQuery(this).find('.modal-body').find('div.loading,div.body-content').toggleClass('hidden');
});
jQuery(document).on('keyup', '.displayHours', function(){
var hms = jQuery(this).val().split(":"),
hours = Number(hms[0]) + Number(hms[1] / 60) + Number(hms[2] / 3600),
id = jQuery(this).data('key'),
hoursFloat = jQuery("#invoiceItemsHours" + id);
hoursFloat.val(hours);
var amount = hoursFloat.val() * jQuery("#invoiceItemsRate" + id).val();
jQuery("#invoiceItemsAmount" + id).val(amount.toFixed(2));
}).on('keyup', '.itemRate', function() {
var id = jQuery(this).data('key'),
hours = jQuery("#invoiceItemsHours" + id).val(),
rate = jQuery(this).val();
jQuery("#invoiceItemsAmount" + id).val(parseFloat(hours * rate).toFixed(2));
}).on('click', '.message-file', function(e) {
e.preventDefault();
var key = jQuery(this).data('key');
document.getElementById('fileView' + key).click();
});
if (jQuery('#tableLog').length > 0) {
jQuery('#tableLog').DataTable({
'dom': '<"listtable"fit>pl',
'info': false,
'filter': true,
'responsive': true,
'oLanguage': {
'sEmptyTable': lang.noRecordsFound,
'sInfo': lang.tableShowingXOfY,
'sInfoEmpty': lang.tableShowingEmpty,
'sInfoFiltered': lang.tableFiltered,
'sInfoPostFix': "",
'sInfoThousands': ',',
'sLengthMenu': lang.tableMenuLength,
'sLoadingRecords': lang.tableLoading,
'sProcessing': lang.tableProcessing,
'sSearch': "",
'sZeroRecords': lang.noRecordsFound,
'oPaginate': {
'sFirst': lang.tableFirst,
'sLast': lang.tableLast,
'sNext': lang.tableNext,
'sPrevious': lang.tablePrevious
}
},
'pageLength': 10,
'order': [[0, 'desc']],
'lengthMenu': [
[10, 25, 50, -1],
[10, 25, 50, lang.all]
],
'aoColumnDefs': [
{
'bSortable': false,
'aTargets': [1, 2]
}
],
"columns": [
{ "width": "15%" },
{ "width": "65%" },
null
],
'stateSave': true
});
jQuery('.dataTables_filter input').attr('placeholder', lang.enterSearch);
}
jQuery("#addMessageFiles").fileinput({
maxFileSize: maximumFileSize * 1024,
showUpload: false,
allowedPreviewTypes: ['image', 'html', 'text', 'pdf'],
showCaption: true,
initialCaption: lang.multipleFiles,
overwriteInitial: false,
uploadUrl: 'addonmodules.php?module=project_management&action=uploadFileForMessage&ajax=1&projectid=' + jQuery('#projectId').val() + '&token=' + jQuery('#csrfToken').val(),
uploadAsync: false,
dropZoneEnabled: false
});
jQuery('#btnAddComment').on('click', function() {
jQuery('#tabMessages').find('a').click();
jQuery('#inputAddMessage').focus();
});
jQuery('#modalImportTasks').on('hidden.bs.modal', function(e) {
var importTaskResults = jQuery('#importTaskResults');
importTaskResults.find('.ticket').remove();
importTaskResults.find('[name="reference"]').remove();
importTaskResults.find('#tasksResultInfo').removeClass('hidden').show();
jQuery('#btnImportTasks').addClass('disabled').prop('disabled', true);
});
jQuery('#addTimerEndDate').on('apply.daterangepicker', function(ev, picker) {
var addTimerStartDateInput = jQuery('#addTimerStartDate'),
addTimerStartDate = parseInt(addTimerStartDateInput.data('daterangepicker').startDate.format('X')),
addTimerStartDateFormatted = addTimerStartDateInput.data('daterangepicker').startDate
.format(adminJsVars.dateTimeRangeFormat),
thisValue = parseInt(picker.startDate.format('X'));
if (thisValue < addTimerStartDate) {
jQuery(this).data('daterangepicker').setStartDate(
addTimerStartDateFormatted
);
jQuery(this).val(
addTimerStartDateFormatted
);
}
});
jQuery('#editTimerEndDate').on('apply.daterangepicker', function(ev, picker) {
var editTimerStartDateInput = jQuery('#editTimerStartDate'),
editTimerStartDate = parseInt(editTimerStartDateInput.data('daterangepicker').startDate.format('X')),
editTimerStartDateFormatted = editTimerStartDateInput.data('daterangepicker').startDate
.format(adminJsVars.dateTimeRangeFormat),
thisValue = parseInt(picker.startDate.format('X'));
if (thisValue < editTimerStartDate) {
jQuery(this).data('daterangepicker').setStartDate(
editTimerStartDateFormatted
);
jQuery(this).val(
editTimerStartDateFormatted
);
}
});
});
function appendFile(key, attachment)
{
var projectId = jQuery('#projectId').val();
jQuery('#fileList').append(
'
\
\
' + attachment.filename + '' + attachment.extension + '\
' + lang.by + ' ' + attachment.admin + ' x ' + lang.justNow + ' X ' + attachment.filesize + '\
'
);
}
function appendTicket(ticket)
{
jQuery('#noTickets').hide();
jQuery('#tickets').find('.tickets').append(
'
' +
'
' +
' ' +
'' +
'
' +
'
#' + ticket.tid + '' +
'
' + ticket.title +
' ' + ticket.status + '' +
'
' + lang.ticketUser + ':' + ticket.userDetails + '
' +
lang.department + ': ' + ticket.departmentName + '
' +
lang.lastReplyBy + ' ' + ticket.lastReplyUser +'' +
(ticket.isAdminReply ? ' (' + lang.staffMember + ') - ' : ' - ') + ticket.lastreply + '' +
'
'
);
}
function appendInvoice(invoice)
{
jQuery('#noInvoices').hide();
jQuery('#invoices').append(
'
' +
'' + lang.invoiceNum + (invoice.invoiceNumber ? invoice.invoiceNumber : invoice.id) + ' | ' +
'' + invoice.dateCreated + ' | ' +
'' + invoice.dateDue + ' | ' +
'' + invoice.total + ' | ' +
'' + invoice.balance + ' | ' +
'' + invoice.status + ' | ' +
'' +
' ' +
'' +
' |
'
);
}
var newProjectTicket;
var ProjectManagerHandlers = {
addmessage: function (data) {
var newMessage = data.newMessage[0],
attached = '',
projectId = jQuery('#projectId').val(),
deleteMessagePerm = data.deletePermission,
deleteButton = '';
if (newMessage.attachment) {
jQuery.each(newMessage.attachment, function (index, attachment) {
attached = attached + '
' + attachment.displayFilename + '';
appendFile(index, attachment);
});
}
if (deleteMessagePerm) {
deleteButton = '
';
}
jQuery('.messages').prepend('
' + newMessage.number + '.
' + newMessage.name + '' + newMessage.date + '' + deleteButton + '' + newMessage.message + '' + attached + '
');
jQuery('#message-' + newMessage.id).hide().removeClass('hidden').slideDown();
jQuery('#addMessage').find('.fileinput-remove').click();
jQuery('#inputAddMessage').val('').focus();
jQuery('#tabMessages').find('span').html(data.messageCount);
jQuery('#fileCount').html(data.fileCount);
},
addtask: function (data) {
var newTask = data.newTask[0],
assigned = '
' + newTask.assigned + '',
dueDate = '
' + newTask.duedate + ' ',
editTasksPermission = data.editPermission,
deleteTasksPermission = data.deletePermission,
actionButtons = '';
if (editTasksPermission) {
actionButtons = '
';
}
if (deleteTasksPermission) {
if (actionButtons !== '') {
actionButtons = actionButtons + ' ';
}
actionButtons = actionButtons + '
';
}
jQuery('#noTasks').hide();
jQuery('.tasks').append('
' + newTask.task + ' ' + assigned + dueDate + ' ' + actionButtons + ' 00:00:00 ' + newTask.notes + ' |
');
Sortable.create(
document.getElementById('assigned-admin-task-' + newTask.id),
{
group: {
name: 'admin' + newTask.id,
pull: false,
put: ['adminSort']
},
sort: false,
ghostClass: 'ghost',
onAdd: function(evt) {
jQuery(evt.target).html(evt.item);
ProjectManager.call(
'assigntask',
'taskid=' + jQuery(evt.target).data('id')
+ '&admin=' + jQuery(evt.item).data('id')
);
}
}
);
Sortable.create(
document.getElementById('task-due-date-' + newTask.id),
{
group: {
name: 'due-date-' + newTask.id,
pull: false,
put: ['dueDateSelect']
},
sort: false,
ghostClass: 'ghost',
onAdd: function(evt) {
jQuery(evt.target).html('
' + lang.updating + '...');
ProjectManager.call(
'taskduedate',
'taskid=' + jQuery(evt.target).data('id')
+ '&duedate=' + jQuery(evt.item).find('input').val()
);
}
}
);
jQuery('#task-' + newTask.id).hide().removeClass('hidden').slideDown();
jQuery('#inputAddTask, #inputAddDueDate').val('');
jQuery('#inputAssignId').val(0);
jQuery('#totalTasks').html(data.summary.total);
jQuery('#completedTasks').html(data.summary.completed);
jQuery('#inputTaskAssignment').append(jQuery('
', {
value: newTask.id,
text: newTask.task
}));
jQuery('#editTimerTaskId').append(jQuery('
', {
value: newTask.id,
text: newTask.task
}));
},
deletetask: function (data) {
jQuery('#task-' + data.deletedTaskId).fadeOut('fast', function() {
if (data.summary.total === "0") {
jQuery('#noTasks').hide().removeClass('hidden').show();
}
});
jQuery('#totalTasks').html(data.summary.total);
jQuery('#completedTasks').html(data.summary.completed);
jQuery('#inputTaskAssignment').find("option[value='" + data.deletedTaskId + "']").remove();
jQuery('#editTimerTaskId').find("option[value='" + data.deletedTaskId + "']").remove();
},
taskstatustoggle: function (data) {
var taskLine = $('#task-' + data.taskId);
if (data.isCompleted == 1) {
taskLine.addClass('task-line-item-completed');
if ($('#toggleCompleteHide').is(':checked')) {
taskLine.addClass('hidden');
}
} else {
taskLine.removeClass('task-line-item-completed');
}
jQuery('#totalTasks').html(data.summary.total);
jQuery('#completedTasks').html(data.summary.completed);
},
taskedit: function (data) {
var task = data.task[0],
taskRow = $('#task-' + task.id).find('td');
taskRow.find('.description').text(decodeHtml(task.task));
taskRow.find('.assigned-admin').html(task.assigned);
taskRow.find('.task-due-date').html(task.duedate);
taskRow.find('.task-notes').text(decodeHtml(task.notes));
},
gettaskinfo: function (data) {
var task = data.task[0];
$('#inputTaskId').val(task.id);
$('#inputTaskTitle').val(decodeHtml(task.task));
$('#inputTaskNotes').val(decodeHtml(task.notes));
$('#inputTaskEditAdminAssignment').val(task.adminId);
$('#inputTaskDue').val(task.rawDueDate);
jQuery('#frmModalEditTask').find('div.loading,div.body-content').toggleClass('hidden');
},
deletemsg: function (data) {
jQuery('#message-' + data.deletedMessageId).fadeOut();
jQuery('#tabMessages').find('span').html(data.messageCount);
jQuery.each(data.deletedFiles, function(index, value) {
jQuery('#file-' + value).fadeOut();
});
jQuery('#fileCount').html(data.fileCount);
},
deletefile: function (data) {
jQuery('#file-' + data.deletedFileNumber).fadeOut();
jQuery('a.message-file[data-key="' + data.deletedFileNumber + '"]').remove();
jQuery('#fileCount').html(data.fileCount);
},
addticket: function (data) {
if (data.status == "0") {
jQuery('#frmAddTicket').find('.error-feedback').html(data.error).hide().removeClass('hidden').fadeIn();
} else {
appendTicket(data.ticket);
jQuery('#ticketCount').text(data.ticketCount);
jQuery.growl.notice(
{
title: lang.success,
message: lang.ticketAssociated.replace(':tid', data.ticket.tid)
}
);
jQuery('#frmAddTicket').find('.modal').modal('hide');
jQuery('#associatedTicketResults').find('.ticket').remove();
jQuery('#resultInfo').fadeIn('fast');
}
},
openticket: function (data) {
if (data.status == "0") {
jQuery.growl.error(
{
title: lang.error,
message: data.error
}
);
} else {
appendTicket(data.ticket);
jQuery('#modalOpenTicket').find('input').not('#newTicketClientRO').each(function() {
jQuery(this).val('');
});
jQuery('#newTicketContact').val('0');
if(typeof(Storage) !== "undefined") {
// Code for localStorage/sessionStorage.
localStorage.removeItem("project-" + jQuery('#projectId').val() + "-new-ticket");
}
newProjectTicket[0].value = '';
jQuery('#ticketCount').text(data.ticketCount);
jQuery.growl.notice(
{
title: lang.success,
message: lang.ticketCreated.replace(':tid', data.ticket.tid)
}
);
}
},
starttimer: function (data) {
$('#btnStartTimer').hide().find('i').toggleClass('fa-clock fa-spinner fa-spin');
$('#btnEndTimer').removeProp('disabled').removeClass('hidden').show().data('timerid', data.newTimerId);
var timer = data.newTimer[0];
$('.timers').append('
| ' + timer.date + ' | ' + timer.adminName + ' | ' + timer.taskName + ' | ' + timer.startTime + ' | ' + timer.endTime + ' | | ' + timer.duration + ' | | |
');
$('#timer-' + timer.id).find('input[type="checkbox"]').prop('disabled', true).addClass('disabled');
jQuery('#noTimers').hide();
},
endtimer: function (data) {
$('#btnStartTimer').removeProp('disabled').removeClass('hidden').show();
$('#btnEndTimer').hide().find('i').toggleClass('fa-clock fa-spinner fa-spin');
var timerId = data.endedTimerId,
timerRow = $('#timer-' + timerId),
timer = data.timer[0],
taskSpan = $('#total-time-task-' + timer.taskId);
timerRow.find('.timer-end-time').html(timer.endTime);
timerRow.find('.timer-duration').html(timer.duration);
timerRow.find('input[type="checkbox"]').prop('disabled', false).removeClass('disabled');
if (timer.taskId && taskSpan.length) {
taskSpan.html(timer.totalTaskTime);
}
$('#time-logged').html(data.totalTime);
$('#time-billed').html(data.totalBilled);
},
deleteTimer: function(data) {
var timerId = data.deletedTimerId,
timerRow = $('#timer-' + timerId);
$(timerRow).fadeOut('fast', function() {
if (!data.totalCount) {
$('#noTimers').hide().removeClass('hidden').show();
}
if (!data.openTimerId) {
$('#btnStartTimer').removeProp('disabled').removeClass('hidden').show();
$('#btnEndTimer').hide();
}
});
$('#time-logged').html(data.totalTime);
$('#time-billed').html(data.totalBilled);
},
taskduedate: function(data) {
if (data.status == "0") {
jQuery.growl.error(
{
title: lang.error,
message: data.error
}
);
} else {
jQuery.growl.notice(
{
title: lang.success,
message: lang.dueDateUpdated
}
);
jQuery('#task-due-date-' + data.taskId).html(data.dueDate);
}
},
tasksort: function(data) {
if (data.status == "0") {
jQuery.growl.error(
{
title: lang.error,
message: data.error
}
);
} else {
jQuery.growl.notice(
{
title: lang.success,
message: data.successMsg
}
);
}
},
assigntask: function(data) {
if (data.status == "0") {
jQuery.growl.error(
{
title: lang.error,
message: data.error
}
);
} else {
jQuery.growl.notice(
{
title: lang.success,
message: lang.assignmentUpdated
}
);
}
},
searchTickets: function(data) {
if (data.status == "0") {
jQuery('#frmAddTicket').find('.error-feedback').html(data.error).hide().removeClass('hidden').fadeIn();
jQuery('#associatedTicketResults').find('.ticket').hide().remove();
jQuery('#resultInfo').fadeIn('fast');
} else {
jQuery('#resultInfo').fadeOut('fast', function(){
if (data.tickets.length == 0) {
jQuery('#associatedTicketResults').append(
'
' + lang.noTicketsFound + ''
);
} else {
data.tickets.forEach(
function (ticket)
{
jQuery('#associatedTicketResults').append(
'
' +
'' + lang.ticketNum + ticket.tid + ' ' +
'' + ticket.title + ' ' +
'' + ticket.status + '' +
''
)
}
);
}
});
}
},
unlinkTicket: function(data) {
if (data.status == "0") {
jQuery.growl.error(
{
title: lang.error,
message: data.error
}
);
} else {
jQuery('#ticket-' + data.ticketId).toggle('highlight').remove();
jQuery('#ticketCount').text(data.ticketCount);
if (data.ticketCount === "0") {
jQuery('#noTickets').show();
}
jQuery.growl.notice(
{
title: lang.success,
message: lang.ticketRemoved
}
);
}
},
unlinkInvoice: function(data) {
if (data.status == "0") {
jQuery.growl.error(
{
title: lang.error,
message: data.error
}
);
} else {
jQuery('#invoice-' + data.invoiceId).toggle('highlight').remove();
jQuery('#associatedInvoiceCount').text(data.invoiceCount);
if (data.invoiceCount === "0") {
jQuery('#noInvoices').show();
}
jQuery.growl.notice(
{
title: lang.success,
message: lang.invoiceRemoved
}
);
}
},
searchInvoices: function(data) {
if (data.status == "0") {
jQuery('#frmAddInvoice').find('.error-feedback').html(data.error).hide().removeClass('hidden').fadeIn();
jQuery('#associatedInvoiceResults').find('.invoice').remove(function() {
jQuery('#invoiceResultInfo').fadeIn('fast');
});
} else {
jQuery('#invoiceResultInfo').fadeOut('fast', function(){
if (data.invoices.length == 0) {
jQuery('#associatedInvoiceResults').append(
'
' + lang.noInvoicesFound + ''
);
} else {
data.invoices.forEach(
function (invoice)
{
jQuery('#associatedInvoiceResults').append(
'
' +
'' + lang.invoiceNum + (invoice.invoiceNumber ? invoice.invoiceNumber : invoice.id) + ' ' +
' - ' + lang.total + ': ' + invoice.total + '' +
' - ' + lang.due + ': ' + invoice.dateDue + '' +
'' + invoice.status + '' +
''
)
}
);
}
});
}
},
addInvoice: function (data) {
if (data.status == "0") {
jQuery('#frmAddInvoice').find('.error-feedback').html(data.error).hide().removeClass('hidden').fadeIn();
} else {
var invoice = data.invoice;
appendInvoice(invoice);
jQuery('#associatedInvoiceCount').text(data.invoiceCount);
jQuery.growl.notice(
{
title: lang.success,
message: lang.invoiceAssociated.replace(':num', invoice.id)
}
);
jQuery('#frmAddInvoice').find('.modal').modal('hide');
jQuery('#associatedInvoiceResults').find('.ticket').remove();
jQuery('#invoiceResultInfo').fadeIn('fast');
}
},
createInvoice: function (data) {
if (data.status == "0") {
jQuery('#frmCreateInvoice').find('.error-feedback').html(data.error).hide().removeClass('hidden').fadeIn();
} else {
var invoice = data.invoice;
appendInvoice(invoice);
jQuery('#associatedInvoiceCount').text(data.invoiceCount);
jQuery.growl.notice(
{
title: lang.success,
message: lang.invoiceCreated.replace(':num', invoice.id)
}
);
jQuery('#frmCreateInvoice').find('.modal').modal('hide');
jQuery('#createInvoiceApplyTax').removeAttr('checked');
jQuery('#createInvoiceSendEmail').removeAttr('checked');
jQuery('#createInvoiceCreated').val('');
jQuery('#createInvoiceDue').val('');
jQuery('#createInvoiceAmount').val('');
jQuery('#createInvoiceDescription').val('');
}
},
selectTaskList: function (data) {
if (data.status == "0") {
jQuery('#formImportTasks').find('.error-feedback').html(data.error).hide().removeClass('hidden').fadeIn();
} else {
var tasks = data.tasks;
var taskResults = jQuery('#importTaskResults');
jQuery('#tasksResultInfo').fadeOut('fast', function(){
jQuery(this).addClass('hidden').show();
taskResults.find('.ticket').fadeOut('fast', function() {
jQuery(this).remove();
});
taskResults.find('[name="reference"]').remove();
if (typeof tasks == "undefined") {
taskResults.append(
'
' + lang.noTasksFound + ''
);
} else {
tasks.forEach(
function (task)
{
taskResults.append(
'
' +
'
' +
'' +
'
' +
'
' +
'' + task.name + '' +
'
' +
'
'
)
}
);
taskResults.append('
');
}
}).promise().done(function () {
addedItems = jQuery('#importTaskResults').find('.list-group-item');
var importButton = jQuery('#btnImportTasks');
if (addedItems.length > 0) {
importButton.prop('disabled', false).removeClass('disabled');
} else {
importButton.prop('disabled', true).addClass('disabled');
}
});
}
},
importTasks: function (data) {
var editTasksPermission = data.editPermission,
deleteTasksPermission = data.deletePermission,
actionButtons = '';
if (editTasksPermission) {
actionButtons = '
';
}
if (deleteTasksPermission) {
if (actionButtons !== '') {
actionButtons = actionButtons + ' ';
}
actionButtons = actionButtons + '
';
}
data.tasks.forEach(
function (newTask) {
$('.tasks').append('
' + newTask.task + ' ' + newTask.assigned + newTask.duedate + ' ' + actionButtons + ' ' + newTask.notes + ' |
');
$('#task-' + newTask.id).hide().removeClass('hidden').slideDown();
}
);
jQuery('#totalTasks').html(data.summary.total);
jQuery('#completedTasks').html(data.summary.completed);
},
watch: function (data) {
//do nothing
},
unwatch: function (data) {
//do nothing
},
saveTaskList: function (data) {
jQuery('#formSaveTaskList').find('.modal').modal('hide').end()
.find('input[name="name"]').val('');
jQuery.growl.notice(
{
title: lang.success,
message: lang.taskListCreated + ': ' + data.taskListName
}
);
},
gettimerinfo: function (data) {
var editTimers = jQuery('#editTimers'),
timer = data.timer[0],
editTimerTaskId = jQuery('#editTimerTaskId'),
editTimerAdminId = jQuery('#editTimerAdminId'),
editTimerStartDate = jQuery('#editTimerStartDate'),
editTimerEndDate = jQuery('#editTimerEndDate');
jQuery('#frmTimeTrackingTimerId').val(timer.id);
editTimerTaskId.val(timer.taskId);
editTimerAdminId.val(timer.adminId);
editTimerEndDate.val(timer.endDateTime);
editTimerEndDate.data('daterangepicker').setStartDate(timer.endDateTime);
editTimerEndDate.data('daterangepicker').setEndDate(timer.endDateTime);
editTimerStartDate.val(timer.dateTime);
editTimerStartDate.data('daterangepicker').setStartDate(timer.dateTime);
editTimerStartDate.data('daterangepicker').setEndDate(timer.dateTime);
jQuery('#frmTimeTracking').find('div.loading,div.body-content').toggleClass('hidden').end().find('.btn-primary').focus();
},
updateTimer: function (data) {
var timer = data.timer[0],
billed = '
',
taskSpan = jQuery('#total-time-task-' + timer.taskId);
if (timer.billed === 1) {
billed = '
';
}
jQuery('#timer-' + timer.id).html(
'
| ' + timer.date + ' | ' + timer.adminName + ' | ' + timer.taskName + ' | ' + timer.startTime + ' | ' + timer.endTime + ' | ' + billed + ' | ' + timer.duration + ' | | | '
);
if (timer.endTime === '-') {
jQuery('#timer-' + timerId).find('input[type="checkbox"]').prop('disabled', true).addClass('disabled');
}
if (timer.taskId && taskSpan.length) {
taskSpan.html(timer.totalTaskTime);
}
jQuery.growl.notice(
{
title: lang.success,
message: lang.timerEdited
}
);
},
taskTimeAdd: function (data) {
if (data.status === "0") {
jQuery.growl.error(
{
title: lang.error,
message: data.error
}
);
} else {
var timer = data.timer[0],
billed = '
',
taskSpan = jQuery('#total-time-task-' + timer.taskId);
if (timer.billed === 1) {
billed = '
';
}
jQuery('#noTimers').hide();
jQuery('#timersTable').append('
| ' + timer.date + ' | ' + timer.adminName + ' | ' + timer.taskName + ' | ' + timer.startTime + ' | ' + timer.endTime + ' | ' + billed + ' | ' + timer.duration + ' | |
');
if (timer.taskId && taskSpan.length) {
taskSpan.html(timer.totalTaskTime);
}
$('#time-logged').html(data.totalTime);
$('#time-billed').html(data.totalBilled);
jQuery.growl.notice(
{
title: lang.success,
message: lang.timeAdded
}
);
}
},
invoiceItems: function (data) {
var timersTable = jQuery('#timersTable'),
timers = data.timers,
invoice = data.invoice;
timersTable.find('tbody').html('');
jQuery.each(timers, function(key, timer) {
var disabled = '',
thisClass = '';
if (timer.endTime === '-' || timer.billed) {
disabled = ' disabled="disabled"';
thisClass = ' class="disabled"';
}
timersTable.find('tbody').append(
'
' +
' | ' +
'' + timer.date + ' | ' +
'' + timer.adminName + ' | ' +
'' + timer.taskName + ' | ' +
'' + timer.startTime + ' | ' +
'' + timer.endTime + ' | ' +
'' + (timer.billed ? '' : '') + ' | ' +
'' + timer.duration + ' | ' +
' | ' +
'
'
);
});
jQuery('#time-logged').html(data.totalTime);
jQuery('#time-billed').html(data.totalBilled);
jQuery('#associatedInvoiceCount').text(data.invoiceCount);
appendInvoice(invoice);
jQuery.growl.notice(
{
title: lang.success,
message: lang.invoiceGenerated + ': ' + data.invoiceId
}
);
},
saveProject: function (data) {
var dueDate = jQuery('#detailsDue'),
assigned = jQuery('#detailsAssigned'),
client = jQuery('#detailsClient'),
status = jQuery('#detailsStatus'),
updated = jQuery('#detailsUpdated'),
createInvoice = jQuery('#createInvoice'),
btnInvoiceSelected = jQuery('#btnInvoiceSelected'),
openTicketClient = jQuery('#openTicketClient'),
openTicketNoClient = jQuery('#openTicketNoClient'),
newTicketClientRO = jQuery('#newTicketClientRO'),
btnSendEmail = jQuery('#btnSendEmail'),
projectTitle = jQuery('#projectTitle'),
detailsClientLi = jQuery('#detailsClientLi');
projectTitle.html(data.title);
dueDate.html(data.due);
assigned.html(data.admin);
client.html(data.client);
status.html(data.status);
updated.html(data.modified);
createInvoice.addClass('disabled').prop('disabled', true);
btnInvoiceSelected.addClass('disabled').prop('disabled', true);
openTicketClient.addClass('hidden');
openTicketNoClient.removeClass('hidden');
btnSendEmail.addClass('hidden');
detailsClientLi.attr('data-toggle', 'modal');
if (data.clientId) {
detailsClientLi.removeAttr('data-toggle');
createInvoice.removeClass('disabled').prop('disabled', false);
btnInvoiceSelected.removeClass('disabled').prop('disabled', false);
openTicketClient.removeClass('hidden');
openTicketNoClient.addClass('hidden');
newTicketClientRO.val(data.clientName);
btnSendEmail.removeClass('hidden');
}
},
sendEmail: function(data) {
if (data.status === "0") {
jQuery('#formSendEmail').find('.error-feedback').html(data.error).hide().removeClass('hidden').fadeIn();
} else {
jQuery.growl.notice(
{
title: lang.success,
message: lang.emailSent
}
);
}
},
duplicateProject: function(data) {
if (data.status === "0") {
jQuery('#formSendEmail').find('.error-feedback').html(data.error).hide().removeClass('hidden').fadeIn();
} else {
window.location.href = 'addonmodules.php?module=project_management&m=view&projectid=' + data.newProjectId;
}
}
};
/**
* selectize.js (v0.12.4)
* Copyright (c) 2013–2015 Brian Reavis & 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.
*
* @author Brian Reavis
*/
/*jshint curly:false */
/*jshint browser:true */
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery','sifter','microplugin'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
} else {
root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
}
}(this, function($, Sifter, MicroPlugin) {
'use strict';
var highlight = function($element, pattern) {
if (typeof pattern === 'string' && !pattern.length) return;
var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
var highlight = function(node) {
var skip = 0;
if (node.nodeType === 3) {
var pos = node.data.search(regex);
if (pos >= 0 && node.data.length > 0) {
var match = node.data.match(regex);
var spannode = document.createElement('span');
spannode.className = 'highlight';
var middlebit = node.splitText(pos);
var endbit = middlebit.splitText(match[0].length);
var middleclone = middlebit.cloneNode(true);
spannode.appendChild(middleclone);
middlebit.parentNode.replaceChild(spannode, middlebit);
skip = 1;
}
} else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
for (var i = 0; i < node.childNodes.length; ++i) {
i += highlight(node.childNodes[i]);
}
}
return skip;
};
return $element.each(function() {
highlight(this);
});
};
/**
* removeHighlight fn copied from highlight v5 and
* edited to remove with() and pass js strict mode
*/
$.fn.removeHighlight = function() {
return this.find("span.highlight").each(function() {
this.parentNode.firstChild.nodeName;
var parent = this.parentNode;
parent.replaceChild(this.firstChild, this);
parent.normalize();
}).end();
};
var MicroEvent = function() {};
MicroEvent.prototype = {
on: function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
off: function(event, fct){
var n = arguments.length;
if (n === 0) return delete this._events;
if (n === 1) return delete this._events[event];
this._events = this._events || {};
if (event in this._events === false) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger: function(event /* , args... */){
this._events = this._events || {};
if (event in this._events === false) return;
for (var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
/**
* Mixin will delegate all MicroEvent.js function in the destination object.
*
* - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
*
* @param {object} the object which will support MicroEvent
*/
MicroEvent.mixin = function(destObject){
var props = ['on', 'off', 'trigger'];
for (var i = 0; i < props.length; i++){
destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
}
};
var IS_MAC = /Mac/.test(navigator.userAgent);
var KEY_A = 65;
var KEY_COMMA = 188;
var KEY_RETURN = 13;
var KEY_ESC = 27;
var KEY_LEFT = 37;
var KEY_UP = 38;
var KEY_P = 80;
var KEY_RIGHT = 39;
var KEY_DOWN = 40;
var KEY_N = 78;
var KEY_BACKSPACE = 8;
var KEY_DELETE = 46;
var KEY_SHIFT = 16;
var KEY_CMD = IS_MAC ? 91 : 17;
var KEY_CTRL = IS_MAC ? 18 : 17;
var KEY_TAB = 9;
var TAG_SELECT = 1;
var TAG_INPUT = 2;
// for now, android support in general is too spotty to support validity
var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('input').validity;
var isset = function(object) {
return typeof object !== 'undefined';
};
/**
* Converts a scalar to its best string representation
* for hash keys and HTML attribute values.
*
* Transformations:
* 'str' -> 'str'
* null -> ''
* undefined -> ''
* true -> '1'
* false -> '0'
* 0 -> '0'
* 1 -> '1'
*
* @param {string} value
* @returns {string|null}
*/
var hash_key = function(value) {
if (typeof value === 'undefined' || value === null) return null;
if (typeof value === 'boolean') return value ? '1' : '0';
return value + '';
};
/**
* Escapes a string for use within HTML.
*
* @param {string} str
* @returns {string}
*/
var escape_html = function(str) {
return (str + '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
};
/**
* Escapes "$" characters in replacement strings.
*
* @param {string} str
* @returns {string}
*/
var escape_replace = function(str) {
return (str + '').replace(/\$/g, '$$$$');
};
var hook = {};
/**
* Wraps `method` on `self` so that `fn`
* is invoked before the original method.
*
* @param {object} self
* @param {string} method
* @param {function} fn
*/
hook.before = function(self, method, fn) {
var original = self[method];
self[method] = function() {
fn.apply(self, arguments);
return original.apply(self, arguments);
};
};
/**
* Wraps `method` on `self` so that `fn`
* is invoked after the original method.
*
* @param {object} self
* @param {string} method
* @param {function} fn
*/
hook.after = function(self, method, fn) {
var original = self[method];
self[method] = function() {
var result = original.apply(self, arguments);
fn.apply(self, arguments);
return result;
};
};
/**
* Wraps `fn` so that it can only be invoked once.
*
* @param {function} fn
* @returns {function}
*/
var once = function(fn) {
var called = false;
return function() {
if (called) return;
called = true;
fn.apply(this, arguments);
};
};
/**
* Wraps `fn` so that it can only be called once
* every `delay` milliseconds (invoked on the falling edge).
*
* @param {function} fn
* @param {int} delay
* @returns {function}
*/
var debounce = function(fn, delay) {
var timeout;
return function() {
var self = this;
var args = arguments;
window.clearTimeout(timeout);
timeout = window.setTimeout(function() {
fn.apply(self, args);
}, delay);
};
};
/**
* Debounce all fired events types listed in `types`
* while executing the provided `fn`.
*
* @param {object} self
* @param {array} types
* @param {function} fn
*/
var debounce_events = function(self, types, fn) {
var type;
var trigger = self.trigger;
var event_args = {};
// override trigger method
self.trigger = function() {
var type = arguments[0];
if (types.indexOf(type) !== -1) {
event_args[type] = arguments;
} else {
return trigger.apply(self, arguments);
}
};
// invoke provided function
fn.apply(self, []);
self.trigger = trigger;
// trigger queued events
for (type in event_args) {
if (event_args.hasOwnProperty(type)) {
trigger.apply(self, event_args[type]);
}
}
};
/**
* A workaround for http://bugs.jquery.com/ticket/6696
*
* @param {object} $parent - Parent element to listen on.
* @param {string} event - Event name.
* @param {string} selector - Descendant selector to filter by.
* @param {function} fn - Event handler.
*/
var watchChildEvent = function($parent, event, selector, fn) {
$parent.on(event, selector, function(e) {
var child = e.target;
while (child && child.parentNode !== $parent[0]) {
child = child.parentNode;
}
e.currentTarget = child;
return fn.apply(this, [e]);
});
};
/**
* Determines the current selection within a text input control.
* Returns an object containing:
* - start
* - length
*
* @param {object} input
* @returns {object}
*/
var getSelection = function(input) {
var result = {};
if ('selectionStart' in input) {
result.start = input.selectionStart;
result.length = input.selectionEnd - result.start;
} else if (document.selection) {
input.focus();
var sel = document.selection.createRange();
var selLen = document.selection.createRange().text.length;
sel.moveStart('character', -input.value.length);
result.start = sel.text.length - selLen;
result.length = selLen;
}
return result;
};
/**
* Copies CSS properties from one element to another.
*
* @param {object} $from
* @param {object} $to
* @param {array} properties
*/
var transferStyles = function($from, $to, properties) {
var i, n, styles = {};
if (properties) {
for (i = 0, n = properties.length; i < n; i++) {
styles[properties[i]] = $from.css(properties[i]);
}
} else {
styles = $from.css();
}
$to.css(styles);
};
/**
* Measures the width of a string within a
* parent element (in pixels).
*
* @param {string} str
* @param {object} $parent
* @returns {int}
*/
var measureString = function(str, $parent) {
if (!str) {
return 0;
}
var $test = $('').css({
position: 'absolute',
top: -99999,
left: -99999,
width: 'auto',
padding: 0,
whiteSpace: 'pre'
}).text(str).appendTo('body');
transferStyles($parent, $test, [
'letterSpacing',
'fontSize',
'fontFamily',
'fontWeight',
'textTransform'
]);
var width = $test.width();
$test.remove();
return width;
};
/**
* Sets up an input to grow horizontally as the user
* types. If the value is changed manually, you can
* trigger the "update" handler to resize:
*
* $input.trigger('update');
*
* @param {object} $input
*/
var autoGrow = function($input) {
var currentWidth = null;
var update = function(e, options) {
var value, keyCode, printable, placeholder, width;
var shift, character, selection;
e = e || window.event || {};
options = options || {};
if (e.metaKey || e.altKey) return;
if (!options.force && $input.data('grow') === false) return;
value = $input.val();
if (e.type && e.type.toLowerCase() === 'keydown') {
keyCode = e.keyCode;
printable = (
(keyCode >= 97 && keyCode <= 122) || // a-z
(keyCode >= 65 && keyCode <= 90) || // A-Z
(keyCode >= 48 && keyCode <= 57) || // 0-9
keyCode === 32 // space
);
if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
selection = getSelection($input[0]);
if (selection.length) {
value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
} else if (keyCode === KEY_BACKSPACE && selection.start) {
value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
} else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
value = value.substring(0, selection.start) + value.substring(selection.start + 1);
}
} else if (printable) {
shift = e.shiftKey;
character = String.fromCharCode(e.keyCode);
if (shift) character = character.toUpperCase();
else character = character.toLowerCase();
value += character;
}
}
placeholder = $input.attr('placeholder');
if (!value && placeholder) {
value = placeholder;
}
width = measureString(value, $input) + 4;
if (width !== currentWidth) {
currentWidth = width;
$input.width(width);
$input.triggerHandler('resize');
}
};
$input.on('keydown keyup update blur', update);
update();
};
var domToString = function(d) {
var tmp = document.createElement('div');
tmp.appendChild(d.cloneNode(true));
return tmp.innerHTML;
};
var logError = function(message, options){
if(!options) options = {};
var component = "Selectize";
console.error(component + ": " + message)
if(options.explanation){
// console.group is undefined in ').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
$control = $('').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
$control_input = $('
').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
$dropdown_parent = $(settings.dropdownParent || $wrapper);
$dropdown = $('
').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
$dropdown_content = $('
').addClass(settings.dropdownContentClass).appendTo($dropdown);
if(inputId = $input.attr('id')) {
$control_input.attr('id', inputId + '-selectized');
$("label[for='"+inputId+"']").attr('for', inputId + '-selectized');
}
if(self.settings.copyClassesToDropdown) {
$dropdown.addClass(classes);
}
$wrapper.css({
width: $input[0].style.width
});
if (self.plugins.names.length) {
classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
$wrapper.addClass(classes_plugins);
$dropdown.addClass(classes_plugins);
}
if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
$input.attr('multiple', 'multiple');
}
if (self.settings.placeholder) {
$control_input.attr('placeholder', settings.placeholder);
}
// if splitOn was not passed in, construct it from the delimiter to allow pasting universally
if (!self.settings.splitOn && self.settings.delimiter) {
var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
}
if ($input.attr('autocorrect')) {
$control_input.attr('autocorrect', $input.attr('autocorrect'));
}
if ($input.attr('autocapitalize')) {
$control_input.attr('autocapitalize', $input.attr('autocapitalize'));
}
self.$wrapper = $wrapper;
self.$control = $control;
self.$control_input = $control_input;
self.$dropdown = $dropdown;
self.$dropdown_content = $dropdown_content;
$dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
$dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
autoGrow($control_input);
$control.on({
mousedown : function() { return self.onMouseDown.apply(self, arguments); },
click : function() { return self.onClick.apply(self, arguments); }
});
$control_input.on({
mousedown : function(e) { e.stopPropagation(); },
keydown : function() { return self.onKeyDown.apply(self, arguments); },
keyup : function() { return self.onKeyUp.apply(self, arguments); },
keypress : function() { return self.onKeyPress.apply(self, arguments); },
resize : function() { self.positionDropdown.apply(self, []); },
blur : function() { return self.onBlur.apply(self, arguments); },
focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
paste : function() { return self.onPaste.apply(self, arguments); }
});
$document.on('keydown' + eventNS, function(e) {
self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
self.isShiftDown = e.shiftKey;
});
$document.on('keyup' + eventNS, function(e) {
if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
if (e.keyCode === KEY_CMD) self.isCmdDown = false;
});
$document.on('mousedown' + eventNS, function(e) {
if (self.isFocused) {
// prevent events on the dropdown scrollbar from causing the control to blur
if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
return false;
}
// blur on click outside
if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
self.blur(e.target);
}
}
});
$window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
if (self.isOpen) {
self.positionDropdown.apply(self, arguments);
}
});
$window.on('mousemove' + eventNS, function() {
self.ignoreHover = false;
});
// store original children and tab index so that they can be
// restored when the destroy() method is called.
this.revertSettings = {
$children : $input.children().detach(),
tabindex : $input.attr('tabindex')
};
$input.attr('tabindex', -1).hide().after(self.$wrapper);
if ($.isArray(settings.items)) {
self.setValue(settings.items);
delete settings.items;
}
// feature detect for the validation API
if (SUPPORTS_VALIDITY_API) {
$input.on('invalid' + eventNS, function(e) {
e.preventDefault();
self.isInvalid = true;
self.refreshState();
});
}
self.updateOriginalInput();
self.refreshItems();
self.refreshState();
self.updatePlaceholder();
self.isSetup = true;
if ($input.is(':disabled')) {
self.disable();
}
self.on('change', this.onChange);
$input.data('selectize', self);
$input.addClass('selectized');
self.trigger('initialize');
// preload options
if (settings.preload === true) {
self.onSearchChange('');
}
},
/**
* Sets up default rendering functions.
*/
setupTemplates: function() {
var self = this;
var field_label = self.settings.labelField;
var field_optgroup = self.settings.optgroupLabelField;
var templates = {
'optgroup': function(data) {
return '
' + data.html + '
';
},
'optgroup_header': function(data, escape) {
return '';
},
'option': function(data, escape) {
return '
' + escape(data[field_label]) + '
';
},
'item': function(data, escape) {
return '
' + escape(data[field_label]) + '
';
},
'option_create': function(data, escape) {
return '
Add ' + escape(data.input) + '…
';
}
};
self.settings.render = $.extend({}, templates, self.settings.render);
},
/**
* Maps fired events to callbacks provided
* in the settings used when creating the control.
*/
setupCallbacks: function() {
var key, fn, callbacks = {
'initialize' : 'onInitialize',
'change' : 'onChange',
'item_add' : 'onItemAdd',
'item_remove' : 'onItemRemove',
'clear' : 'onClear',
'option_add' : 'onOptionAdd',
'option_remove' : 'onOptionRemove',
'option_clear' : 'onOptionClear',
'optgroup_add' : 'onOptionGroupAdd',
'optgroup_remove' : 'onOptionGroupRemove',
'optgroup_clear' : 'onOptionGroupClear',
'dropdown_open' : 'onDropdownOpen',
'dropdown_close' : 'onDropdownClose',
'type' : 'onType',
'load' : 'onLoad',
'focus' : 'onFocus',
'blur' : 'onBlur'
};
for (key in callbacks) {
if (callbacks.hasOwnProperty(key)) {
fn = this.settings[callbacks[key]];
if (fn) this.on(key, fn);
}
}
},
/**
* Triggered when the main control element
* has a click event.
*
* @param {object} e
* @return {boolean}
*/
onClick: function(e) {
var self = this;
// necessary for mobile webkit devices (manual focus triggering
// is ignored unless invoked within a click event)
if (!self.isFocused) {
self.focus();
e.preventDefault();
}
},
/**
* Triggered when the main control element
* has a mouse down event.
*
* @param {object} e
* @return {boolean}
*/
onMouseDown: function(e) {
var self = this;
var defaultPrevented = e.isDefaultPrevented();
var $target = $(e.target);
if (self.isFocused) {
// retain focus by preventing native handling. if the
// event target is the input it should not be modified.
// otherwise, text selection within the input won't work.
if (e.target !== self.$control_input[0]) {
if (self.settings.mode === 'single') {
// toggle dropdown
self.isOpen ? self.close() : self.open();
} else if (!defaultPrevented) {
self.setActiveItem(null);
}
return false;
}
} else {
// give control focus
if (!defaultPrevented) {
window.setTimeout(function() {
self.focus();
}, 0);
}
}
},
/**
* Triggered when the value of the control has been changed.
* This should propagate the event to the original DOM
* input / select element.
*/
onChange: function() {
this.$input.trigger('change');
},
/**
* Triggered on
paste.
*
* @param {object} e
* @returns {boolean}
*/
onPaste: function(e) {
var self = this;
if (self.isFull() || self.isInputHidden || self.isLocked) {
e.preventDefault();
return;
}
// If a regex or string is included, this will split the pasted
// input and create Items for each separate value
if (self.settings.splitOn) {
// Wait for pasted text to be recognized in value
setTimeout(function() {
var pastedText = self.$control_input.val();
if(!pastedText.match(self.settings.splitOn)){ return }
var splitInput = $.trim(pastedText).split(self.settings.splitOn);
for (var i = 0, n = splitInput.length; i < n; i++) {
self.createItem(splitInput[i]);
}
}, 0);
}
},
/**
* Triggered on
keypress.
*
* @param {object} e
* @returns {boolean}
*/
onKeyPress: function(e) {
if (this.isLocked) return e && e.preventDefault();
var character = String.fromCharCode(e.keyCode || e.which);
if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
this.createItem();
e.preventDefault();
return false;
}
},
/**
* Triggered on
keydown.
*
* @param {object} e
* @returns {boolean}
*/
onKeyDown: function(e) {
var isInput = e.target === this.$control_input[0];
var self = this;
if (self.isLocked) {
if (e.keyCode !== KEY_TAB) {
e.preventDefault();
}
return;
}
switch (e.keyCode) {
case KEY_A:
if (self.isCmdDown) {
self.selectAll();
return;
}
break;
case KEY_ESC:
if (self.isOpen) {
e.preventDefault();
e.stopPropagation();
self.close();
}
return;
case KEY_N:
if (!e.ctrlKey || e.altKey) break;
case KEY_DOWN:
if (!self.isOpen && self.hasOptions) {
self.open();
} else if (self.$activeOption) {
self.ignoreHover = true;
var $next = self.getAdjacentOption(self.$activeOption, 1);
if ($next.length) self.setActiveOption($next, true, true);
}
e.preventDefault();
return;
case KEY_P:
if (!e.ctrlKey || e.altKey) break;
case KEY_UP:
if (self.$activeOption) {
self.ignoreHover = true;
var $prev = self.getAdjacentOption(self.$activeOption, -1);
if ($prev.length) self.setActiveOption($prev, true, true);
}
e.preventDefault();
return;
case KEY_RETURN:
if (self.isOpen && self.$activeOption) {
self.onOptionSelect({currentTarget: self.$activeOption});
e.preventDefault();
}
return;
case KEY_LEFT:
self.advanceSelection(-1, e);
return;
case KEY_RIGHT:
self.advanceSelection(1, e);
return;
case KEY_TAB:
if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
self.onOptionSelect({currentTarget: self.$activeOption});
// Default behaviour is to jump to the next field, we only want this
// if the current field doesn't accept any more entries
if (!self.isFull()) {
e.preventDefault();
}
}
if (self.settings.create && self.createItem()) {
e.preventDefault();
}
return;
case KEY_BACKSPACE:
case KEY_DELETE:
self.deleteSelection(e);
return;
}
if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
e.preventDefault();
return;
}
},
/**
* Triggered on
keyup.
*
* @param {object} e
* @returns {boolean}
*/
onKeyUp: function(e) {
var self = this;
if (self.isLocked) return e && e.preventDefault();
var value = self.$control_input.val() || '';
if (self.lastValue !== value) {
self.lastValue = value;
self.onSearchChange(value);
self.refreshOptions();
self.trigger('type', value);
}
},
/**
* Invokes the user-provide option provider / loader.
*
* Note: this function is debounced in the Selectize
* constructor (by `settings.loadThrottle` milliseconds)
*
* @param {string} value
*/
onSearchChange: function(value) {
var self = this;
var fn = self.settings.load;
if (!fn) return;
if (self.loadedSearches.hasOwnProperty(value)) return;
self.loadedSearches[value] = true;
self.load(function(callback) {
fn.apply(self, [value, callback]);
});
},
/**
* Triggered on
focus.
*
* @param {object} e (optional)
* @returns {boolean}
*/
onFocus: function(e) {
var self = this;
var wasFocused = self.isFocused;
if (self.isDisabled) {
self.blur();
e && e.preventDefault();
return false;
}
if (self.ignoreFocus) return;
self.isFocused = true;
if (self.settings.preload === 'focus') self.onSearchChange('');
if (!wasFocused) self.trigger('focus');
if (!self.$activeItems.length) {
self.showInput();
self.setActiveItem(null);
self.refreshOptions(!!self.settings.openOnFocus);
}
self.refreshState();
},
/**
* Triggered on
blur.
*
* @param {object} e
* @param {Element} dest
*/
onBlur: function(e, dest) {
var self = this;
if (!self.isFocused) return;
self.isFocused = false;
if (self.ignoreFocus) {
return;
} else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
// necessary to prevent IE closing the dropdown when the scrollbar is clicked
self.ignoreBlur = true;
self.onFocus(e);
return;
}
var deactivate = function() {
self.close();
self.setTextboxValue('');
self.setActiveItem(null);
self.setActiveOption(null);
self.setCaret(self.items.length);
self.refreshState();
// IE11 bug: element still marked as active
dest && dest.focus && dest.focus();
self.ignoreFocus = false;
self.trigger('blur');
};
self.ignoreFocus = true;
if (self.settings.create && self.settings.createOnBlur) {
self.createItem(null, false, deactivate);
} else {
deactivate();
}
},
/**
* Triggered when the user rolls over
* an option in the autocomplete dropdown menu.
*
* @param {object} e
* @returns {boolean}
*/
onOptionHover: function(e) {
if (this.ignoreHover) return;
this.setActiveOption(e.currentTarget, false);
},
/**
* Triggered when the user clicks on an option
* in the autocomplete dropdown menu.
*
* @param {object} e
* @returns {boolean}
*/
onOptionSelect: function(e) {
var value, $target, $option, self = this;
if (e.preventDefault) {
e.preventDefault();
e.stopPropagation();
}
$target = $(e.currentTarget);
if ($target.hasClass('create')) {
self.createItem(null, function() {
if (self.settings.closeAfterSelect) {
self.close();
}
});
} else {
value = $target.attr('data-value');
if (typeof value !== 'undefined') {
self.lastQuery = null;
self.setTextboxValue('');
self.addItem(value);
if (self.settings.closeAfterSelect) {
self.close();
} else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
self.setActiveOption(self.getOption(value));
}
}
}
},
/**
* Triggered when the user clicks on an item
* that has been selected.
*
* @param {object} e
* @returns {boolean}
*/
onItemSelect: function(e) {
var self = this;
if (self.isLocked) return;
if (self.settings.mode === 'multi') {
e.preventDefault();
self.setActiveItem(e.currentTarget, e);
}
},
/**
* Invokes the provided method that provides
* results to a callback---which are then added
* as options to the control.
*
* @param {function} fn
*/
load: function(fn) {
var self = this;
var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
self.loading++;
fn.apply(self, [function(results) {
self.loading = Math.max(self.loading - 1, 0);
if (results && results.length) {
self.addOption(results);
self.refreshOptions(self.isFocused && !self.isInputHidden);
}
if (!self.loading) {
$wrapper.removeClass(self.settings.loadingClass);
}
self.trigger('load', results);
}]);
},
/**
* Sets the input field of the control to the specified value.
*
* @param {string} value
*/
setTextboxValue: function(value) {
var $input = this.$control_input;
var changed = $input.val() !== value;
if (changed) {
$input.val(value).triggerHandler('update');
this.lastValue = value;
}
},
/**
* Returns the value of the control. If multiple items
* can be selected (e.g.