Table of contents
No headers
/** Author: Blake Harms Version 2.9 See: http://developer.mindtouch.com/App_Catalog/Integrated_Bug_and_Issue_Tracker on 2.9 added performance tunning posted by Sego on this blog post: http://forums.developer.mindtouch.com/showthread.php?5882-Integrated-Bug-tracker-Support-Ticket-Library-with-JQuery&p=45759#post45759 */ // ~~ Parameters ~~ var parameters = $0 ?? $options ?? [ {'title': {label:'Title',type:'text'}}, {'type': {data:['Bug','Support'],label:'Type',show:'both'}}, {'urgency': {data:['Low','Medium','High'],label:'Urgency',show:'both'}}, {'assign': {data:[u.name foreach var u in site.users where !u.anonymous],label:'Assign',show:'both', type:'suggest'}}, {'status': {data:{'#FBB':'Open','#FBF':'Assigned','#CCF':'Fix Issued','#DDD':'Closed','#AFA':'Fixed'},label:'Status'}}, {'summary': {label:'Summary',type:'textarea', show:'ticket'}}, {'time': {data:date.now,type:'hidden',show:'table'}} ]; // -- Added by carles.coll 2010/12/27 var new_page_content = $1 ?? $new_page_content ?? '<pre class=\\'script\\'> Ticket() </pre>'; var params = []; foreach(var param in parameters){ let params ..= Map.keyValues(param); } if(! list.contains(list.collect(params,'key'),'title')){ <div style="font-weight:bold;color:red;">"Warning: A title field must be assigned."</div> } dekiapi(); jquery.ui("smoothness"); var options = "{'params':'" .. String.serialize(params) .. "'}"; var updateOptions = false; if(page.properties.options.text != options){ let updateOptions=true; let options = String.replace(String.replace(options,'"','\\"'),"'","\\'"); } <html><head> <script type="text/javascript" src="http://www.datatables.net/media/javascript/jquery.dataTables.min.js"></script> <script type="text/javascript" src="http://jqplugins.appspot.com/js/jquery.table2csv.js"></script> <script type="text/javascript" src="http://developer.mindtouch.com/@api/deki/files/4634/=jquery.autocomplete.pack.js"></script> <link rel="stylesheet" type="text/css" href="http://developer.mindtouch.com/@api/deki/files/4635/=jquery.autocomplete.css" /> <script type="text/javascript">" var oTable; Deki.$(document).ready(function(){ oTable = Deki.$('#"..@tickets.."').dataTable(); Deki.$('table.display tfoot select').change(filter); Deki.$('table.display tfoot input').keyup(filter); Deki.$('.datepicker').datepicker(); Deki.$('.resizable').resizable(); }); "</script> if(updateOptions){ <script type="text/javascript">" // Set page property 'options' with the options submitted to this template. var prop = 'urn:custom.mindtouch.com#' + 'options'; // url that retrieves the ticket options Deki.Api.ReadPageProperty(null, prop, function(result) { if(result.etag) { // page property exists, write over it. Deki.Api.UpdatePageProperty(result.href, '"..options.."', result.etag, function() { }, function(result) { // alert('An error occurred trying to update the store (status: ' + result.status + ' - ' + result.text + ')'); }); } else { // page property doesn't exist, create one. Deki.Api.CreatePageProperty(null, prop, '"..options.."', null,null); } }, function(result) { //alert('An error occurred trying to read the store (status: ' + result.status + ' - ' + result.text + ')'); }); "</script> } <script type="text/javascript">" var filter= function() { Deki.$('table.display tfoot select, table.display tfoot input').each(function() { oTable.fnFilter(Deki.$(this).val(),$('table.display tfoot th').index(Deki.$(this).parent())); }); } var saveTicket= function(page, properties){ // Hide the ability to change stuff. Deki.$('#saveButton').attr('value','Saving...').attr('disabled','disabled'); Deki.$('#" .. @form .. " tbody').find('input,select,textarea').each(function(){ $(this).attr('disabled','disabled'); }); saveProperty(page,properties); // recursive... } var saveProperty= function(page, properties){ // NOTE: This function is recursive. It will call itself for all of properties. var propertyname; var propertytext; var first = false; var theRest= new Array(); // the rest of the array. for(var key in properties){ if(! first){ first = true; propertyname = key; propertytext = properties[key]; } else { theRest[key] = properties[key]; theRest.length++; } } var pageapi = '/@api/deki/pages/=' + Deki.url.encode(Deki.url.encode(page)); // no need to check for update as we just created this page. Deki.Api.CreatePageProperty(pageapi, 'urn:custom.mindtouch.com#' + propertyname, propertytext, function() { if(theRest.length > 0){ saveProperty(page,theRest); // recurse } else{ location.href = '/' + page; } }, function(result) { // just ignore it... alert('An error occurred trying to create the store (status: ' + result.status + ' - ' + result.text + ')'); }); } "</script> <style type="text/css">" /* Ticket Table CSS */ .dataTables_wrapper { position: relative; min-height: 302px; _height: 302px; clear: both; } .dataTables_processing { position: absolute; top: 0px; left: 50%; width: 250px; margin-left: -125px; border: 1px solid #ddd; text-align: center; color: #999; font-size: 11px; padding: 2px 0; } .dataTables_length { width: 40%; float: left; } .dataTables_filter { width: 50%; float: right; text-align: right; } .dataTables_info { width: 60%; float: left; } .dataTables_paginate { float: right; text-align: right;padding:10px; } /* DataTables display */ table.display { margin: 0 auto; clear: both;width:100%; } table.display thead th { padding: 3px 10px; border-bottom: 1px solid black; font-weight: bold; cursor: pointer; _cursor: hand;text-align:center; } table.display tfoot th { padding: 3px 10px; border-top: 1px solid black; font-weight: bold;text-align:center; } table.display tr.heading2 td { border-bottom: 1px solid #aaa; } table.display td { padding: 3px 10px; text-align:center } table.display td.center { text-align: center; } /* sorting */ .sorting_asc { background: url('http://www.datatables.net/media/images/sort_asc.jpg') no-repeat center right; } .sorting_desc { background: url('http://www.datatables.net/media/images/sort_desc.jpg') no-repeat center right; } .sorting { background: url('http://www.datatables.net/media/images/sort_both.jpg') no-repeat center right; } /* paginate */ .paginate_disabled_previous, .paginate_enabled_previous, .paginate_disabled_next, .paginate_enabled_next { height: 19px; width: 19px; margin-left: 3px; float: left; } .paginate_disabled_previous { background-image: url('http://www.datatables.net/media/images/back_disabled.jpg'); } .paginate_enabled_previous { background-image: url('http://www.datatables.net/media/images/back_enabled.jpg'); } .paginate_disabled_next { background-image: url('http://www.datatables.net/media/images/forward_disabled.jpg'); } .paginate_enabled_next { background-image: url('http://www.datatables.net/media/images/forward_enabled.jpg'); } /* Pagination nested */ .paginate_button, .paginate_active { border:1px solid black; padding:0px 3px; text-align:center; -moz-border-radius:3px;margin:3px;font-size:12px; } .paginate_button { background-color: #F0F0F0; cursor:pointer; _cursor:hand; } .paginate_active { font-weight:bold;background-color: #DDD; } table.display tr.odd { background-color: #F0F0F0; } table.display tr.even { background-color: white; } "</style> if(list.contains(list.collect(params,'key'),'status')){ <style type="text/css">" /* Ticket Table CSS */ .new { background-color: #FFFFA7 !important; } "</style> var status = list.collect(params,'value')[list.indexof(list.collect(params,'key'),'status')]; // This is absolutely wicked. Is there a better way? <style type='text/css'> foreach(var s in Map.keyvalues(status.data)){ "."..String.replace(String.ToLower(s.value),' ','').." { background-color:"..s.key.." !important; }"; } </style> } </head></html> <form id=(@submit)> <table class="submitform" cellspacing="0" cellpadding="5" border="1" id=(@form)> <tbody> foreach(var param in params){ if(param.value.type=='hidden') continue; // skip the hidden values. <tr> <td> param.value.label ?? String.ToCamelCase(param.key)</td> // check for type variable. if non-existant, determine based on data. var type= param.value.type; if(! type){ if(param.value.data is list || param.value is map) let type = 'select'; if(!param.value.data || param.value.data is str) let type = 'text'; } <td> switch(type){ case 'suggest': if(param.value.data is list){ <input type="text" name=(String.toLower(String.replace(param.key,' ','_'))) style=(param.value.style.form) value=(param.value.data) ctor=( "$this.autocomplete("..JSON.emit(param.value.data)..");") /> } break; case 'text': <input type="text" name=(String.toLower(String.replace(param.key,' ','_'))) style=(param.value.style.form) value=(param.value.data) /> break; case 'textarea': var resizable = (typeof param.value.resizable == typeof null || param.value.resizable) ? 'resizable' : ''; <textarea class=(resizable) name=(String.toLower(String.replace(param.key,' ','_'))) style=(param.value.style.form)>param.value.data</textarea> break; case 'datepicker': <input type="text" class="datepicker" name=(String.toLower(String.replace(param.key,' ','_'))) style=(param.value.style.form) value=(param.value.data) /> break; case 'select': if((param.value.data is map) || (param.value.data is list)){ <select name=(String.toLower(String.replace(param.key,' ','_'))) style=(param.value.style.form)> <option value=""> "Select..."</option> foreach(var option in param.value.data){ <option value=(option)> option </option> } </select> } break; default: // ignore. break; } </td> </tr> } <tr> <td> foreach(var param in params){ if(param.value.type!='hidden') continue; <input type="hidden" name=(String.toLower(String.replace(param.key,' ','_'))) value=(param.value.data) /> } </td> <td align="right"> <input type="button" value="Submit" ctor="when($this.click) { var m = { }; Deki.$('#' + {{@form}}).find('input, select, textarea').each(function() { m[this.name] = Deki.$(this).val(); }); Deki.publish('default', m); }" id="saveButton" /> </td> </tr> </tbody> </table> </form> <script type="text/jem">" var dapi = '/@api/deki/pages/='; var dparams = '/contents?abort=never'; var dpath; Deki.subscribe('default', null, function(c, m, d) { // listen for submit events. // -- Modified by carles.coll 2010/12/27 // dpath = Deki.url.encode(M'"..page.path.."' + '/' + m['title'].replace(/ /g,'_'));*/ dpath = Deki.url.encode(MindTouch.Text.Utf8Encode('"..page.path.."' + '/' + m['title'].replace(/ /g,'_'))); var edpath = Deki.url.encode(Deki.url.encode(dpath)); // -- Modified by carles.coll 2010/12/27 // var ddata = ''<pre class=\\'script\\'> Ticket() </pre>''; var ddata = '"..new_page_content.."'; Deki.$.ajax({ type: 'POST', url: dapi+edpath+dparams, data: ddata, complete: function(xhr){ if(xhr.status == 200) saveTicket(dpath, m); else if(xhr.status == 400) alert('Request already exists! Try again with a different title.'); else if(xhr.status == 403) alert('Permission denied. Log in and try again.'); else alert('Error ' + xhr.status); } }); }, null); "</script> <br /><br /> <div id="buttons-panel"> <input type="button" value="Export to CSV" ctor=" var self = $this; $this.click(function() { $({{'#'..@download}}).table2csv({ callback: function (csv, name) { $('<div></div>') .append($('<textarea></textarea>') .css({'width':'500px','height':'400px'}) .text(csv)) .insertAfter('#buttons-panel') .dialog({ height: 480, width: 560 }); } }); }); " /> <input type='button' value='Clear Filters' onclick=" Deki.$('table.display tfoot select, table.display tfoot input').each(function() { Deki.$(this).val(''); oTable.fnFilter(Deki.$(this).val(),$('table.display tfoot th').index(Deki.$(this).parent())); }); " /> </div> <br /><br /> var data = page.subpages; if(#data > 0) { <table class="display" id=(@tickets) cellpadding="3" cellspacing="0" width="100%"> <thead> <tr> foreach(var param in params){ if(! param.value.show || param.value.show == 'both' || param.value.show == 'table'){ <th> param.value.label ?? String.ToCamelCase(param.key) </th> } } </tr> </thead> <tbody> foreach(var p in data){ var class=""; var props = p.properties; if(list.contains(list.collect(params,'key'),'status')){ if(props.status.text && props.status.text != ''){ let class = String.replace(String.toLower(props.status.text)," ",""); } else{ let class = "new"; } } <tr id=(props.id.text) class=(class)> foreach(var param in params){ if(param.value.show && param.value.show == 'ticket') continue; // if not supposed to show in table, skip. var property = Map.values(map.select(props, "$.key=='"..String.replace(String.toLower(param.key)," ","_").."'"))[0]; <td> switch (param.value.type){ case 'datepicker': if(param.key =='title'){ <a href=(p.uri) style=(param.value.style.table)>property.text</a> }else{ <span style=(param.value.style.table)> property.text </span> } break; case 'textarea': <span style="display:none;"> property.text </span> // keep this here so that filtering sees the whole string, not just the first 50 chars. if(#property.text >50){ // A string of over 50 characters is long. Don't show the whole thing. if(param.key =='title'){ <a href=(p.uri) style=(param.value.style.table)>String.Substr(property.text,0,50).."..."</a> }else{ <span style=(param.value.style.table)>String.Substr(property.text,0,50).."..."</span> } } else{ if(param.key =='title'){ <a href=(p.uri) style=(param.value.style.table)>property.text</a> }else{ <span style=(param.value.style.table)>property.text</span> } } break; default: if(param.key =='title'){ <a href=(p.uri) style=(param.value.style.table)>property.text </a> }else{ <span style=(param.value.style.table)>property.text</span> } break; } </td> } </tr> } </tbody> <tfoot> <tr> // Allow filtering for lists and maps. foreach(var param in params){ if(param.value.show && param.value.show == 'ticket') continue; //skip if not supposed to show in table. if(param.value.data is list || param.value.data is map){ <th> <select> <option value=""> "Show only..." </option> foreach(var option in param.value.data){ <option value=(option)>(option)</option> } </select> </th> } else { <th> <input type="text" /> </th> } } </tr> </tfoot> </table> <table id=(@download) style="display:none"> <tbody> <tr> foreach(var param in params){ <td> param.value.label ?? String.ToCamelCase(param.key) </td> } </tr> foreach(var p in data){ <tr> var props = p.properties; foreach(var param in params){ var property = Map.values(map.select(props, "$.key=='"..String.replace(String.toLower(param.key)," ","_").."'")); let property = property[0]; <td>property.text</td> } </tr> } </tbody> </table> } else { "Table contains no data."; }