Template:TicketForm

    Table of contents
    No headers

    Version as of 17:09, 3 Nov 2024

    to this version.

    Return to Version archive.

    View current version

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