Newer
Older
/*********************************************************************
ticket.js
Ticket utility loaded on ticket view!
Useful for UI config settings, ticket locking ...etc
Peter Rotich <peter@osticket.com>
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
var autoLock = {
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
addEvent: function(elm, evType, fn, useCapture) {
if(elm.addEventListener) {
elm.addEventListener(evType, fn, useCapture);
return true;
}else if(elm.attachEvent) {
return elm.attachEvent('on' + evType, fn);
}else{
elm['on' + evType] = fn;
}
},
removeEvent: function(elm, evType, fn, useCapture) {
if(elm.removeEventListener) {
elm.removeEventListener(evType, fn, useCapture);
return true;
}else if(elm.detachEvent) {
return elm.detachEvent('on' + evType, fn);
}else {
elm['on' + evType] = null;
}
},
//Incoming event...
handleEvent: function(e) {
if(!autoLock.lockId) {
var now = new Date().getTime();
//Retry every 5 seconds??
if(autoLock.retry && (!autoLock.lastattemptTime || (now-autoLock.lastattemptTime)>5000)) {
autoLock.acquireLock(e,autoLock.warn);
autoLock.lastattemptTime=new Date().getTime();
}
}else{
autoLock.renewLock(e);
}
if(!autoLock.lasteventTime) { //I hate nav away warnings..but
$(window).bind('beforeunload', function(e) {
return "Any changes or info you've entered will be discarded!";
});
}
autoLock.lasteventTime=new Date().getTime();
},
//Watch activity on individual form.
watchForm: function(fObj,fn) {
if(!fObj || !fObj.length)
return;
//Watch onSubmit event on the form.
autoLock.addEvent(fObj,'submit',autoLock.onSubmit,true);
//Watch activity on text + textareas + select fields.
for (var i=0; i<fObj.length; i++) {
switch(fObj[i].type) {
case 'textarea':
case 'text':
autoLock.addEvent(fObj[i],'keyup',autoLock.handleEvent,true);
break;
case 'select-one':
case 'select-multiple':
if(fObj.name!='reply') //Bug on double ajax call since select make it's own ajax call. TODO: fix it
autoLock.addEvent(fObj[i],'change',autoLock.handleEvent,true);
break;
default:
}
}
},
//Watch all the forms on the document.
watchDocument: function() {
//Watch forms of interest only.
for (var i=0; i<document.forms.length; i++) {
if(!document.forms[i].id.value || parseInt(document.forms[i].id.value)!=autoLock.tid)
continue;
autoLock.watchForm(document.forms[i],autoLock.checkLock);
}
},
Init: function(config) {
Peter Rotich
committed
//make sure we are on ticket view page & locking is enabled!
var fObj=$('form#note');
if(!fObj
|| !$(':input[name=id]',fObj).length
Peter Rotich
committed
|| !$(':input[name=locktime]',fObj).length
|| $(':input[name=locktime]',fObj).val()==0) {
return;
Peter Rotich
committed
void(autoLock.tid=parseInt($(':input[name=id]',fObj).val()));
void(autoLock.lockTime=parseInt($(':input[name=locktime]',fObj).val()));
autoLock.lockId=0;
autoLock.timerId=0;
autoLock.lasteventTime=0;
autoLock.lastattemptTime=0;
autoLock.acquireTime=0;
autoLock.renewTime=0;
autoLock.renewFreq=0; //renewal frequency in seconds...based on returned lock time.
autoLock.time=0;
autoLock.lockAttempts=0; //Consecutive lock attempt errors
autoLock.maxattempts=2; //Maximum failed lock attempts before giving up.
autoLock.warn=true;
autoLock.retry=true;
autoLock.watchDocument();
autoLock.resetTimer();
autoLock.addEvent(window,'unload',autoLock.releaseLock,true); //Release lock regardless of any activity.
},
onSubmit: function(e) {
if(e.type=='submit') { //Submit. double check!
//remove nav away warning if any.
$(window).unbind('beforeunload');
//Only warn if we had a failed lock attempt.
if(autoLock.warn && !autoLock.lockId && autoLock.lasteventTime) {
var answer=confirm('Unable to acquire a lock on the ticket. Someone else could be working on the same ticket. \
Please confirm if you wish to continue anyways.');
if(!answer) {
e.returnValue=false;
e.cancelBubble=true;
if(e.preventDefault) {
e.preventDefault();
}
return false;
}
}
}
return true;
},
if(!autoLock.tid) { return false; }
var warn = warn || false;
if(autoLock.lockId) {
autoLock.renewLock(e);
} else {
$.ajax({
url: 'ajax.php/tickets/'+autoLock.tid+'/lock',
dataType: 'json',
cache: false,
success: function(lock){
autoLock.setLock(lock,'acquire',warn);
}
})
.done(function() { })
.fail(function() { });
}
//Renewal only happens on form activity..
if(!autoLock.lastcheckTime || (now-autoLock.lastcheckTime)>=(autoLock.renewFreq*1000)){
$.ajax({
type: 'POST',
url: 'ajax.php/tickets/'+autoLock.tid+'/lock/'+autoLock.lockId+'/renew',
dataType: 'json',
cache: false,
success: function(lock){
autoLock.setLock(lock,'renew',autoLock.warn);
}
})
.done(function() { })
.fail(function() { });
}
releaseLock: function(e) {
if(!autoLock.tid) { return false; }
$.ajax({
type: 'POST',
url: 'ajax.php/tickets/'+autoLock.tid+'/lock/'+autoLock.lockId+'/release',
}
})
.done(function() { })
.fail(function() { });
},
setLock: function(lock, action, warn) {
var warn = warn || false;
if(!lock) return false;
if(lock.id) {
autoLock.renewFreq=lock.time?(lock.time/2):30;
autoLock.lastcheckTime=new Date().getTime();
}
autoLock.lockId=lock.id; //override the lockid.
switch(action){
case 'renew':
if(!lock.id && lock.retry) {
autoLock.lockAttempts=1; //reset retries.
autoLock.acquireLock(e,false); //We lost the lock?? ..try to re acquire now.
}
break;
case 'acquire':
if(!lock.id) {
autoLock.lockAttempts++;
if(warn && (!lock.retry || autoLock.lockAttempts>=autoLock.maxattempts)) {
autoLock.retry=false;
alert('Unable to lock the ticket. Someone else could be working on the same ticket.');
discardWarning: function(e) {
e.returnValue="Any changes or info you've entered will be discarded!";
},
//TODO: Monitor events and elapsed time and warn user when the lock is about to expire.
monitorEvents: function() {
// warn user when lock is about to expire??;
//autoLock.resetTimer();
},
clearTimer: function() {
clearTimeout(autoLock.timerId);
},
resetTimer: function() {
clearTimeout(autoLock.timerId);
autoLock.timerId=setTimeout(function () { autoLock.monitorEvents() },30000);
}
};
$.autoLock = autoLock;
/*
UI & form events
*/
jQuery(function($) {
$('#response_options form').hide();
$('#ticket_notes').hide();
if(location.hash != "" && $('#response_options '+location.hash).length) {
$('#response_options '+location.hash+'_tab').addClass('active');
$('#response_options '+location.hash).show();
} else if(location.hash == "#notes" && $('#ticket_notes').length) {
$('#response_options #note_tab').addClass('active');
$('#response_options form').hide();
$('#response_options #note').show();
$('#ticket_thread').hide();
$('#ticket_notes').show();
$('#toggle_ticket_thread').removeClass('active');
$('#toggle_notes').addClass('active');
} else {
$('#response_options ul.tabs li:first a').addClass('active');
$('#response_options '+$('#response_options ul.tabs li:first a').attr('href')).show();
});
$('#note_tab').click(function() {
if($('#response').val() != '') {
$('#reply_tab').addClass('tell');
}
});
$('#response_options ul.tabs li a').click(function(e) {
$('#response_options ul.tabs li a').removeClass('active');
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
$(this).addClass('active');
$('#response_options form').hide();
//window.location.hash = this.hash;
$('#response_options '+$(this).attr('href')).show();
$("#msg_error, #msg_notice, #msg_warning").fadeOut();
});
$('#toggle_ticket_thread, #toggle_notes, .show_notes').click(function(e) {
e.preventDefault();
$('#threads a').removeClass('active');
if($(this).attr('id') == 'toggle_ticket_thread') {
$('#ticket_notes').hide();
$('#ticket_thread').show();
$('#toggle_ticket_thread').addClass('active');
$('#reply_tab').removeClass('tell').click();
} else {
$('#ticket_thread').hide();
$('#ticket_notes').show();
$('#toggle_notes').addClass('active');
$('#note_tab').click();
if($('#response').val() != '') {
$('#reply_tab').addClass('tell');
}
}
});
//Start watching the form for activity.
autoLock.Init();
/*** Ticket Actions **/
//print options
$('a#ticket-print').click(function(e) {
e.preventDefault();
$('#overlay').show();
return false;
});
//ticket status (close & reopen)
$('a#ticket-close, a#ticket-reopen').click(function(e) {
e.preventDefault();
$('#overlay').show();
$('.dialog#ticket-status').show();
return false;
});
$('a#ticket-delete, a#ticket-claim, #action-dropdown-more li a:not(.change-user)').click(function(e) {
e.preventDefault();
if($('.dialog#confirm-action '+$(this).attr('href')+'-confirm').length) {
var action = $(this).attr('href').substr(1, $(this).attr('href').length);
$('.dialog#confirm-action #action').val(action);
$('#overlay').show();
$('.dialog#confirm-action .confirm-action').hide();
$('.dialog#confirm-action p'+$(this).attr('href')+'-confirm')
.show()
.parent('div').show().trigger('click');
} else {
alert('Unknown action '+$(this).attr('href')+'- get technical help.');
}
return false;
});
$(document).on('change', 'form#reply select#emailreply', function(e) {
var $cc = $('form#reply tbody#cc_sec');
if($(this).val() == 0)
$cc.hide();
else
$cc.show();
});
var showNonLocalImage = function(div) {
var $div = $(div),
$img = $div.append($('<img>')
.attr('src', $div.data('src'))
.attr('alt', $div.attr('alt'))
.attr('title', $div.attr('title'))
.attr('style', $div.data('style'))
);
if ($div.attr('width'))
$img.width($div.attr('width'));
if ($div.attr('height'))
$img.height($div.attr('height'));
};
// Optionally show external images
$('.thread-entry').each(function(i, te) {
var extra = $(te).find('.textra'),
imgs = $(te).find('.non-local-image[data-src]');
if (!extra) return;
if (!imgs.length) return;
extra.append($('<a>')
.addClass("action-button show-images")
.css({'font-weight':'normal'})
.text(' Show Images')
.click(function(ev) {
imgs.each(function(i, img) {
showNonLocalImage(img);
$(img).removeClass('non-local-image')
// Remove placeholder sizing
.width('auto')
.height('auto')
.removeAttr('width')
.removeAttr('height');
extra.find('.show-images').hide();
});
})
.prepend($('<i>')
.addClass('icon-picture')
)
);
imgs.each(function(i, img) {
var $img = $(img);
// Save a copy of the original styling
$img.data('style', $img.attr('style'));
// If the image has a 'height' attribute, use it, otherwise, use
// 40px
$img.height(($img.attr('height') || '40') + 'px');
// Ensure the image placeholder is visible width-wise
if (!$img.width())
$img.width(($img.attr('width') || '80') + 'px');
// TODO: Add a hover-button to show just one image
});
});
showImagesInline = function(urls, thread_id) {
var selector = (thread_id == undefined)
? '.thread-body img[data-cid]'
: '.thread-body#thread-id-'+thread_id+' img[data-cid]';
$(selector).each(function(i, el) {
var cid = $(el).data('cid').toLowerCase(),
info = urls[cid],
e = $(el);
if (info) {
// Add a hover effect with the filename
var timeout, caption = $('<div class="image-hover">')
function() {
var self = this;
timeout = setTimeout(
function() { $(self).find('.caption').slideDown(250); },
500);
},
function() {
clearTimeout(timeout);
$(this).find('.caption').slideUp(250);
}
).append($('<div class="caption">')
.append('<span class="filename">'+info.filename+'</span>')
.append('<a href="'+info.download_url+'" class="action-button"><i class="icon-download-alt"></i> Download</a>')