Was this page helpful?

Integrated Bug and Issue Tracker

    Follow the discussion and send feedback in this forum post

    This little (just kidding) template is really easy to customize. With the same Template I was able to make a Bug-tracker for the codemonkeys at my office, an Issue-tracker for the project managers, an IT Support request system and a Library database for the bookies. As I said, its easy to customize. The TicketForm template creates a form for new additions to the system and a table of existing systems (Note that you can also use the Datatable template for this. Just a bit of modification). The Ticket template creates a page that holds the Ticket properties and allows you to change them dynamically using JQuery and AJAX access to the API. Of course, there are comments (or 'notes' if you prefer the Mantis terminology) as with any wiki page.

    The system as a whole just creates new pages for Tickets and stores the data in page properties. The information is displayed on the ticket page with the Ticket template. This way Lucene can pick up on them. The table on the Ticket form page is pretty much the Datatable template, just integrated. Though you don't need to have to datatable template, it would make the code for the TicketForm much shorter!

    This uses my favorite table sorting and filtering system ever. That uses JQuery, my favorite javascript library ever. So this project is my baby.

    NOTE: This was written for 9.02, but I'm sure it can be rewritten to work for other versions.

    About

    I started this template when I had just learned about using AJAX and Dekiscript in a template. I had just picked up interest in forms and I wanted to play with creating a form that submits through AJAX. I began work at home because I didn't want to waste my employer's time and money on something that may not pan out.

    Well, it did pan out. It panned WAY out. Since posting here, it has grown ten-fold and is now a large application that we here at Multiphase use for many, many purposes. I left it here and continue to support it because I felt that the exponential growth of the application came from the bug-fixes and suggestions from users here. I could not have rigorously tested the script nor could I have thought of all of the features to add to it without your help. So, thank you!

     

    Don't forget to send feedback! If you liked it, tell me! If you would like to see changes, tell me! Bugs? New features? Ideas? TELL ME!

    Templates

    Here are the templates necessary:

    TicketForm

    Changelog

    VersionChange
    v2.8- Solved the bug with special chars on Title - Added a parameter to TicketForm wich allows to pass subpage content ( insted of adding Ticket(), add for instance MyPersonalizedTicket() )
    v2.7Added a suggestion field type. (Similar to Auto-complete)
    v2.6Added a resizable option to textareas. Thanks graynotgrey!
    v2.5.2Fixed an option-escaping bug.
    v2.5.1Removed download csv option. Instead, it outputs the csv code.
    v2.5Added even more customization (see here). Added more filtering options.
    v2.0Added extreme customization options, added export to csv, fixed a bug where pages don't redirect properly if there are more than 1 space in the title.
    v1.2Added Sorting images and pagination images (in the boring version of the pagination)
    v1.1.4Fixed a bug where the whole url wasn't being encoded, just the page name.
    v1.1.3Cleaned up more code - Added JSON.Emit calls (I was doing it by hand. OOPS)
    v1.1.2Form now redirects to the newly created page properly. Cleaned up the code a bit.
    v1.1.1Fixed Show-only selects (They weren't filtering by column only), added stacking to show-only selects and added Clear filter button.
    v1.1Added dynamic options. Tickets now read the options from the parent page's options (i.e. - the TicketForm's options.)
    v1.0.3No longer have to use the modified Datatables.js file. Added Show-Only selects that filter by status, type, assignee and urgency.
    v1.0.2Fixed bug in IE which prevented Datatables from initiating. Added a new style of Pagination which shows page numbers and next/previous buttons.
    v1.0.1Removed <SELECT> from table because sorting and searching did not work.
    v1.0Posted to this Wiki

    Code

    /**
    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.";
    }
    

     Ticket

    Changelog

    VersionChange
    v2.7Added a suggestion field type. (Similar to Auto-complete)
    v2.6Added a resizable option to textareas. Thanks graynotgrey!
    v2.5Added even more customization (see here). Added more filtering options.
    v2.0Added extreme customization options, added export to csv, fixed a bug where pages don't redirect properly if there are more than 1 space in the title.
    v1.1Added dynamic options. Tickets now read the options from the parent page's options (i.e. - the TicketForm's options.)
    v1.0Posted to this Wiki

    Code

    /**
    Author: Blake Harms
    Version: 2.7
    See http://developer.mindtouch.com/DekiScript/FAQ/How_do_I..._Build_an_Integrated_Bug_and_Issue_Tracker for a changelog.
    */
    
    dekiapi();
    jquery.ui("smoothness");
    // Gather options from parent (form)
    var options = String.eval(page.parent.properties.options.text);
    var params = string.deserialize(options.params);
    
    <html><head>
    <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">"
    Deki.$(document).ready(function() {    //when the document is ready...
        Deki.$('.datepicker').datepicker();
        Deki.$('.resizable').resizable();
        Deki.$('#ticketProperties').find('input, select,textarea').change(function(){
            $self = Deki.$(this);
            var prop = 'urn:custom.mindtouch.com#'  + $self.attr('name');    // url that retrieves the page properties
            Deki.Api.ReadPageProperty(null, prop, function(result) {
                if(result.etag) {    // page property exists, write over it.
                   Deki.Api.UpdatePageProperty(result.href, $self.val(), result.etag, function() {
                        reportSuccess($self);
    	        }, 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, $self.val(), function() {
                        reportSuccess($self);
                    }, function(result) {
    	            alert('An error occurred trying to create the store (status: ' + result.status + ' - ' + result.text + ')');
    	        });
    	    }
            }, function(result) {
    	    alert('An error occurred trying to read the store (status: ' + result.status + ' - ' + result.text + ')');
            });
        });
    });
    "</script>
    <script type="text/javascript">"
    var reportSuccess = function($self) {
        $self.addClass('value_updated');
        setTimeout('$self.removeClass(\\'value_updated\\')',1000);
    }
    "</script>
    <style type="text/css">"
    #ticketProperties table {    border-spacing:0;border-collapse:collapse;    }
    td.label {    background-color:#F0F0F0; font-weight:bold; width:150px;    }
    .value_updated {    background-color:#FFFFA7 !important;    }
    "</style>
    </head></html>
    <div id="top-nav-bar">
        <a href=(page.parent.uri) class='to-table'> "Back to form" </a>
    </div>
    <div id="ticketProperties">
        <table cellspacing="0" cellpadding="3" border="1" width="70%" style="table-layout: fixed;">
            <tbody>
                foreach(var param in params){
                    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';
                    }
                    if(param.value.show == 'table' || param.key=='title')    continue;      // skip if not supposed to be seen here.
                    <tr>
                        <td class="label"> param .value.label ?? String.toCamelCase(param .key) </td>
                        <td>
                            var property = Map.values(map.select(page.properties, "$.key=='"..String.replace(String.toLower(param .key)," ","_").."'"))[0];
                            if(param.value.editable !== false){
                                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.ticket) value=(property.text) />
                                        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.ticket)>property.text</textarea>
                                        break;
                                    case 'datepicker':
                                        <input type="text" class="datepicker" name=(String.toLower(String.replace(param.key,' ','_'))) style=(param.value.style.ticket) value=(property.text) />
                                        break;
                                    case 'hidden':
                                        property.text;
                                        break;
                                    case 'title':
                                        <input type="text" class="title" name=(String.toLower(String.replace(param.key,' ','_'))) style=(param.value.style.ticket) value=(param.value.data) />
                                        break;
                                    case 'status':    // follow through.
                                    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.ticket)>
                                            <option value=(property.text)>property.text</option>
                                            <option value=""> ""</option>
                                            foreach(var option in param.value.data){
                                                <option value=(option)> option </option>
                                            }
                                            </select>
                                        }
                                        else{
                                            <span style=(param.value.style.ticket)>param.value.data</span>
                                        }
                                        break;
                                    default:    // ignore.
                                        break;
                                }
                            }
                            else {
                                <span style=(param.value.style.ticket)>property.text</span>
                            }
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
    

    How to Install

    Note: this requires permission to save UNSAFECONTENT on your wiki.

    Notice that there are images and Javascript files linked from other websites. If you want your Ticket system to be self-contained, you need to download those files to your computer and upload them somewhere on your wiki.

    1. Create a new template called TicketForm and paste the code in this section into that template (Inside a Dekiscript block) and save it.
    2. Create a new template called Ticket (this one is important as the template name 'Template' is embedded inthe TicketForm code) and paste the code in this section into that template (Inside a Dekiscript block) and save it.
    3. Create a new page somewhere on your Wiki and call the function TicketForm (either with options or without options. see example below).

    Here are some example of what a call to the TicketForm template looks like. You can call the template with no options to use the defaults like so:

    {{ TicketForm(); }}

    See usage for more information.

    Future releases

    I hope to put the following in this already awesome template:

    • Set newly created page ('Ticket') permission to allow a certain group + the creator to make changes and all to comment on the 'Ticket'
    • Create a search form to search through the body and comments of Tickets.
    • Add JQuery ui objects to the code.

     

    Usage

    With version 2.0, many more features were added. For instance, you can easily manipulate this form into just about anything without changing the template. 

    Parameters:

    Parameter Type Description
    options map (optional) allows for custom field creation. See here for customization tips.
    new_page_content string (optional, by default: '<pre class=\\'script\\'> Ticket() </pre>' ) The content of the new subpage created .

     

     

    Customization examples:

    More tips here.

    Here are some examples of types of systems you can create:Customization Tips

    Example

    (Because you KNOW you want one...)

    Now, this DOES NOT work entirely... yet. I still have to get the permission to add templates to make the tickets display properly... Anybody wanna make the templates for me?

    There is an example of the Ticket at Example of a templated ticket

    The TicketForm example has been moved.

    Thanks

    I want to thank SteveB for providing the inspiration and assistance with setting up the TODO Template.

    Everyone who has participated in the growth of this application by using and submitting bugs!

    Was this page helpful?
    Tag page

    Files 4

    FileVersionSizeModified 
    Viewing 15 of 32 comments: view all
    This is great!!
    Posted 13:13, 16 Nov 2009
    Hello,

    I am just trying out mindtouch and this bug tracking script.

    I discovered a bug, but now I don't know if it's a mindtouch problem, or a problem due to this particular script.

    On my wiki I created a page entitled "Problèmes utilistateurs" (user problems in French). Notice the accent on the letter 'e'.
    The hyperlink to acces this page is http://mydeki.wiki/Informatique/Problèmes_utilisateur

    Now I set up this bug tracking script on the "Problèmes utilisateurs" page. Here's the problem: when I add a ticket, it creates a new page like so: /Informatique/Probl%ef%bf%bdmes_utilisateur/ and another one /Informatique/Probl%ef%bf%bdmes_utilisateur/ticket_name

    Both these pages contain the following text: Page title (Informatique/Probl�mes_utilisateur) is an invalid title - it contains illegal characters.

    These pages cannot be deleted through the wiki interface.
    Posted 06:01, 15 Dec 2009
    Hi,

    How can I make several different versions with different formproperties of bugtracker? If I just copy two template and rename them it fails with errors. Is there some place in the code that you could define own template names for different forms?
    Posted 22:57, 27 Jan 2010
    I had the same problem that Dimitri describes. But I found it using the "Interactive task list" extension, so maybe it's a dekiapi problem :-?
    Posted 05:15, 8 Mar 2010
    Have implemented this for a job list and works well but,

    for 1200 items this is a bit slow. Can anyone suggest how to speed it up?
    Posted 05:53, 7 May 2010
    Updating tickets yields an error when in HTTPS. I created a forum post on it: http://forums.developer.mindtouch.com/showthread.php?p=41910#post41910
    Posted 10:54, 5 Aug 2010
    @Dimitri and @tsao Solved on the 2.8 release ( a bit late maybe ... )
    Posted 07:54, 27 Dec 2010
    First of all, awesome tool. I can't wait to get it working.

    I've got everything rendering - went through the whole template setup, ended up missing some of the extensions, got that going, and it's happy. But when I try to create a new issue, clicking submit gives me this message in IE:

    Message: 'ddata' is undefined
    Line: 362
    Char: 9

    Which appears to be directly related to this line:

    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);
    }
    });

    I see that the 'var ddata' declaration directly above it is commented out, but uncommenting it just gives me an error about a missing semicolon.

    Sorry for my noobish-ness on this; I haven't played with the AJAX stuff before, so it's very new territory.
    Posted 10:43, 21 Mar 2011
    @carles.coll
    I have exactly the same issue as jen.r.magas.

    Just uncommenting the line gives another error as already mentioned above!

    The code line looks like this:
    // -- Modified by carles.coll 2010/12/27
    // var ddata = ''<pre class=\\'script\\'> Ticket() </pre>'';
    Posted 23:34, 29 Mar 2011
    @SeGo and @jen.r.magas Try it now.
    Posted 00:55, 30 Mar 2011
    @carles.coll
    Works great!!!

    The special character fix is so useful!!! I had to delete so many pages via the database, because they weren't accessible via wiki interface because of the german ä ö ü.
    Good job Carles!
    Posted 01:05, 30 Mar 2011
    @SeGo Your welcome, yes the special chars thing it's allways a problem when the code comes from the States, in Spanish an Catalan we have also the Special Chars nigmare ;)

    Carles.
    Posted 01:07, 30 Mar 2011
    This is working great for me! The only thing I want to know is if there is a way to show the currently assigned entries in another page, filtered for a specific user.

    Basically, we want to create a central task list logger but that each person can see in their own home page.
    Posted 10:44, 21 Jun 2011
    This looks like an awesome tool. I do have to say I have some trouble installing it...
    I'm not sure where I should start looking, but if I paste the code in a deki script block (in source mode), and save the page, for TicketForm I get:
    "Callstack:
    at Template:TicketForm
    MindTouch.Deki.Script.Compiler.DekiScriptParserException: invalid XmlNode: pre, line 56, column 1"

    For the Ticket template, I get something similar.
    I see people mention that some extensions need to be installed, but these are not mentioned. Maybe this is at fault? Which extensions and other dependencies does this rely on? I'm working with a completely vanilla install of 10.1.0 edited 17:40, 28 Jul 2011
    Posted 17:39, 28 Jul 2011
    Managed to install it on 9.02. Forms load properly but I'm not able to save. Am I missing out on something? All required extensions have been installed.
    Posted 23:44, 11 Aug 2011
    Viewing 15 of 32 comments: view all
    You must login to post a comment.

    Copyright © 2011 MindTouch, Inc. Powered by