Interactive Task List

Introduction

 My company required more than a simple grocery list type Task List, so I laid out some requirements that I though every Task List should have.  Hence the birth of this template(s).  This Task List could be included in Project Pages, Meeting Minutes Pages, Personal Pages, in short where ever you need a Task List.  

How do I leave feedback or get support?

To leave feedback or gain support or report a bug please refer to the announcement forum post.

About this Task List

Current Features:

  • Parent/Child style task list.  This creates a page for each task under a "Tasks" sub-page.  This allowed for full customization of the task item and also allows for attachments, sub-pages, and Task Notes, etc.
  • AJAX user Assignment Selection.
  • AJAX Task Item Notes Section Viewer from the Task List.
  • Total Progress indicator (Currently Beta).
  • Allows multiple assignee(s) on a single task.
  • Creates a unique ID for each task system wide (Thanks NeilW for this suggestion).
  • The ability to View the Full Task History (Who's, When's, What's).

Future Features:

  • Some sort of user notification for task assignments or changes
  • Sub-Tasks and/or Milestones
  • Native Task List Sorting/Filtering (Currently no filtering and using TSTables() template for sorting).

History

Version Date Author Description
0.9b.2 10/27/2009 rberinger Changed CSS for progress meter to be a bit less shocking.
0.9b.1 10/20/2009 rberinger Fixed Centering for notes viewer thanks dekc99 for the solution
0.9b 10/14/2009   rberinger  Initial Beta Release

Requirements

  • This template requires MindTouch version: 9.02 or greater
  • jQuery extension needs to be installed
  • Deki AJAX API extension needs to be installed (both these extensions, if installed locally, require additional local .js and other files in res directory under directory where extensions are  located - you can get them here -  http://scripts.mindtouch.com/res/deki/ and http://scripts.mindtouch.com/res/jquery/)
  • LiveQuery jQuery plugin (included on this page).
    • Recommend downloading and saving somewhere on your local wiki for maximum reliablility.
    • Remember to change the reference link location within the template.
  • TSTable() Template.

How do I install it?

  1. Create a template, call it "Template:ToDoMaster" (or rename as you desire).  You must have UNSAFECONTENT permission for this to work.
  2. Create a "DekiScript" block on the template page (use the "Style" menu in the editor")
  3. Copy the code from the end of this page and paste it into the DekiScript block.  To copy, click "expand source", then mouse over the top right corner of the source code, and click the "view source" button.  This will pop up a window with the source code.  Select all, then copy to clipboard.
  4. Make sure there isn't an extra blank paragraph after the DekiScript block! Do this every time you edit!!!  
  5. Search for http://developer.mindtouch.com and replace with http://your_wiki_site.com
  6. Download the LiveQuery.js file attached to this page and save on your own wiki.  Search for "jquery.livequery.js" and change to the new location on your wiki.
  7. Save.
  8. Follow the steps 1-7 above for Template:ToDoItem.

 

A quick note about the examples on this page

For all the examples on this page, the code is shown before the working example.  The code is shown with the syntax extension, and looks like this:

ToDoMaster()

This means that the actual code on the page should be enclosed in a DekiScript block.  If you want to copy the code from this page, then use the same procedure as described in steps 2-4 above. 

How do I use it?

 There are currently 2 Templates that are required for use of this task list.

  • ToDoMaster (This is the master task list template and should be the only one that should be explicitly refrenced).
  • ToDoItem (This is the template for the actual item,  It will be automatically added by the ToDoMaster template where needed).

And 1 Optional but recommended Template to obtain full use.

  • ToDoRollup (This is the template that will search the entire wiki site and roll-up the open task items for an individual or manager).  Due to the requirements of this template it will be covered under a separate documentation page.

 

Suggested Use:

You can use this list however you see fit or where ever you have need but I will explain how we use it at my company.

We currently use this task list in 3 different way.  Personal, Project, and Meetings. 

Personal: Inserted in each users personal area for a personal task list. 

Project:  Referenced in out Project template for tasks associated to a specific project.

Meetings:  Referenced in out Meeting Minutes Template for when action items are assigned in a meeting.

 

Also in each users personal area we inserted the ToDoRollup in the user dashboard area.  This allows for a quick view of every task assigned to a specific user no matter where the task list resides (Project 1, Project 2, Personal, Meetings, etc).  This template can also be used to gather ALL tasks from EVERYONE which is handy for our managers to see what everyone has on their plate.

 

Arguments

There are currently no arguments for the ToDoMaster or ToDoItem Templates

See the ToDoRollup Template documentation page for the details on its arguments.

 

Examples

Where ever you need a task list just insert the following In a DekiScript Block or enclosed within the Double Brackets {{}} somewhere on that page.

ToDoMaster();
Assignee(s):
Assigner: Start Date:
Priority: Percent: Due Date:
Short Description:
Task IDDescriptionAssignee(s)PriorityDue DateProgressOn HoldComplete DateNotes
33379I am a test task itemrberinger110/31/20090%falseN/Aview
35764test!~!winzxc2000202/09/201075%falseN/Aview
40541testingneels103/11/20100%falseN/Aview
34601asdfAnonymous101/06/20100%falseN/Aview
34601asdfzapping101/06/20100%falseN/Aview
34607This is a test Item to see how priorities workarandall101/06/201025%falseN/Aview
39685test to see how to unassignjlpp103/05/20100%falseN/Aview
39685test to see how to unassignrberinger103/05/20100%falseN/Aview
34268testSatu112/14/200975%false12/14/2009view
33536play with task list and determine if it will work for usmaphew210/22/200975%trueN/Aview
33982Yet another entry to test functionality...jbnarducci111/20/2009100%false12/14/2009view

Reference(s)

LiveQuery jQuery Plugin

TSTable() Template

Announcment Forum Post

Credits/Special Thanks

 Everyone on the forums who helped mold this into what it is today.

Template/Extension Source Code

Template:ToDoMaster 

 

// includes
dekiapi();
jquery.ui('smoothness');

<div class="todomaster">
<form>
<table bgcolor="#ecf6fc" id="todoedit" cellspacing="1" cellpadding="1" border="1" width="100%" style="table-layout: fixed;">
    <tbody>
        <tr>
            <td valign="middle" rowspan="3"><span style="align:center;">'Assignee(s):'</span>; <br/>;
                <input type="text" id="user-search" />
                <div id="user-suggest">
                    <div id="userlist">
                    </div>
                </div>
                <select id="assignee" size ="4" multiple="true" style="width:100%">
                    <option value=(user.name) selected='selected'>; user.name; </option>
                </select>
            </td>
            <td align="right" valign="middle">'Assigner: '; <input id="assigner" type="text" value=(user.name)/>
            </td>
            <td align="right" valign="middle">'Start Date: '; <input id="start" type="text" value=(date.format(date.now, 'MM/dd/yyyy')) ctor="$this.datepicker();"/>
            </td>
        </tr>
        <tr>
            <td align="right" valign="middle">'Priority: '; 
                <select id="priority">
                    <option value="3" selected=((3 ? 'selected' : nil))>'3'</option>
                    <option value="2" selected=((2 ? 'selected' : nil))>'2'</option>
                    <option value="1" selected=((1 ? 'selected' : nil))>'1'</option>
                </select>
                '     Percent: '; 
                <select id="percent">
                    <option value="5" selected=((5 ? 'selected' : nil))>'100%'</option>
                    <option value="4" selected=((4 ? 'selected' : nil))>'75%'</option>
                    <option value="3" selected=((3 ? 'selected' : nil))>'50%'</option>
                    <option value="2" selected=((2 ? 'selected' : nil))>'25%'</option>
                    <option value="1" selected=((1 ? 'selected' : nil))>'0%'</option>
                </select>
            </td>
            <td align="right" valign="middle">'Due Date: '; <input id="due" type="text" value=(date.format(date.now, 'MM/dd/yyyy')) ctor="$this.datepicker();"/>
            </td>
        </tr>
        <tr>
            <td align="left" valign="middle" colspan="2">'Short Description: ';<input id="desc" type="text" value="" style="width:30em" maxlength="60" />
            </td>
        </tr>
        <tr>
            <td align="center" valign="middle" colspan="3"> 
                <input id="save" type="button" value="Save" style="margin-top:15px;margin-bottom:15px" ctor="
                    when($this.click) {
                        $('#assignee option').each(function(i) {
                            $(this).attr('selected','selected');
                        });

                        // A bit of Sense Cheking
                        if($('#assignee option').size() == 0){alert('Must have at least 1 Assignee!'); return false;}
                        if($('#desc').val()==''){alert('Must have a Short Description!'); return false;}
                        if($('#due').val()==''){alert('Must have a Due Date!'); return false;}
                        if($('#start').val()==''){alert('Must have a Start Date!'); return false;}


                        var todoitem  =  ({ assignee:  #assignee.val(), 
                                         assigner:  #assigner.val(), 
                                         startdate: #start.val(),
                                         priority:  #priority.val(),
                                         percent:   #percent.val(),
                                         duedate:   #due.val(),
                                         desc:      #desc.val(),
                                         done:      false,
                                         donedate:  '',
                                         hold:      false,
                                         holdreason:'',
                                         projectpage:({{page.id}}),
                                         whochanged:({{user.name}})
                                       });
                        #save.blur();
                        $('div.todomaster').toggle()
                        
                        Deki.publish('ch_save', todoitem);
                    }
                " />
            </td>
        </tr>
    </tbody>
</table>
</form> 
</div>

<script type="text/jem">"

    var dapi = '/@api/deki/pages/=';
    var dparams = '/contents?abort=never';
    var dpath;

// Create page
    Deki.subscribe('ch_save', null, function(c, m, d) {        // listen for submit events.

        dpath = Deki.url.encode('"..page.path..'/Tasks'"' + '/' + m['desc'].replace(/ /g,'_').replace(/\\//g,'//'));
        var edpath = Deki.url.encode(Deki.url.encode(dpath));
        var ddata = '<p><a href=" ..page.path ..">Return To: "..page.title.."</a></p><pre class=\\'script\\'> ToDoItem() </pre><p><h2>Notes:</h2></p>';
        Deki.$.ajax({
            type: 'POST',
            url: dapi+edpath+dparams,
            data: ddata,
            complete: function(xhr){
                if(xhr.status == 200) {    
                    SaveProperty(dpath, m);
                } else if(xhr.status == 400){
                    var response = '';
                    alert('Bad Requst (400): ' + response);
                }
                else if(xhr.status == 403)   alert('Permission denied (403): Are you logged in?');
                else                         alert('Error ' + xhr.status);
            }
       });
    }, null);
"</script>

<script type="text/javascript">"
// Create Property
var SaveProperty = function(page, properties) {
        var pageapi = '/@api/deki/pages/=' + Deki.url.encode(Deki.url.encode(page));

        Deki.Api.CreatePageProperty(pageapi, 'urn:custom.mindtouch.com#todo-item', YAHOO.lang.JSON.stringify(properties), function() {
//        		alert('Thank you. The information has been submitted.');
                        location.href = '/' + page;
        	}, function(result) {
if(result.text == '400') {
    alert('Task may already exist.');
} else {
        		alert('An error occurred trying to create the store (status: ' + result.status + ' - ' + result.text + ')');
}
        	});

}

"</script>

<html><head>


// *** HERE WE START TO DEFINE THE TASK ROLL CALL TABLE ***

// <script type="text/javascript" src="http://jqueryui.com/latest/jquery-1.3.2.js"></script>
<script type="text/javascript" src="http://developer.mindtouch.com/@api/deki/files/4844/=jquery.livequery.js" />
<script type="text/javascript">"
    $(document).ready(function() {

    $('div.todomaster').hide();

        $('div#projectProgress').hide();
        $('div#projectProgress').appendTo('#progressParent');

    $('div#TaskDetailViewer').hide();
    $('div#TaskDetailViewer').click(function(){$(this).hide()});
    $(document).click(function(){$('div#TaskDetailViewer').hide()});
    

            $('#user-suggest #userlist a').livequery('click', function(event) {
            $('#assignee').append('<option selected=\"selected\" value=\"' + $(this).text() + '\">' + $(this).text() + '</option>');
            $('#user-suggest').hide();
            $('#user-search').val('');
            $('#user-search').focus();
            return false;
        });
        
        $('#assignee').dblclick(function() {
            $('#assignee option:selected').remove();
        });

    });

	$('body').ready( function() {
		$('#user-search').keyup( function() {
                        if ($(this).val()==''){
				$('#user-suggest').hide();
				return false;
			}
	
			var q = $(this).val();
			
			clearTimeout($.data(this, 'timer'));
		    var ms = 400; //milliseconds
		    var wait = setTimeout(function() {
		      loadusers(q);
		    }, ms);
	    
	
			$.data(this, 'timer', wait);
		});
	});

function loadusers(u) {
    $('#user-suggest').hide();
    var userapi = 'http://developer.mindtouch.com/@api/deki/users?sortby=username&limit=20&activatedfilter=true&usernamefilter=' + u;

    jQuery.get(userapi,function(xml){
        // clear the previous results
        $('#user-suggest #userlist div').remove();

        // Establish a place holder for our name We need to do this so that we only get 1 unique username per user.
        var theuser;

        $(xml).find('users > user').each(function() {
            $(this).find('username').each(function() {
                theuser = $(this).text();
                return theuser;
            });

            $('#user-suggest #userlist').append('<div class=\"userpick\"><a href=\"#\" id=\"' + theuser + '\">' + theuser + '</a></div>');
        });
    });
    $('#user-suggest').show();
}

function ShowMyDetails(pid) {

    $('div#TaskDetailViewer').hide();

    Deki.$.ajax({
                type: 'GET',
                url: 'http://developer.mindtouch.com/@api/deki/pages/' + pid + '/contents?section=1&include=true',
                dataType: 'xml',
                success: function(data){
                        $('#TaskDetailViewer').html( '<p style=\"text-align:center;\">Click to Close</p>' + $(data).find('content').text() );
                }    
     });

//      centerIt($('div#TaskDetailViewer'));

     $('div#TaskDetailViewer').show();
}

// function centerIt($el) {
//	var frm = $('iframe',top.document.body);
//	var iframeXOffset = 0, iframeYOffset = 0, windowHeight = 0, windowWidth = 0;
//	var i=frm.length;
//	while (i--) {
//		if (frm[i].contentDocument) {
//			doc = frm[i].contentDocument;
//		} else {
//			doc = frm[i].contentWindow.document;
//		}
//		if (doc === document) {
//			//located our iframe!
//			iframeXOffset = $(frm[i]).offset().left;
//			iframeYOffset = $(frm[i]).offset().top;
//			break;
//		}
//	};
//	if (jQuery.browser.msie) {
//		windowWidth = top.window.document.documentElement.clientWidth;
//		windowHeight = top.window.document.documentElement.clientHeight;
//	} else {
//		windowWidth = top.window.innerWidth;
//		windowHeight = top.window.innerHeight;
//	}
//	var elHeight = $el.height();
//	var newTop = ((windowHeight/2) - (elHeight/2)) - iframeYOffset + $(parent.document.documentElement).scrollTop();
//	if ((newTop + elHeight) > $(document).height()) {
//		newTop = $(document).height() - elHeight;
//	}
//	$el.css ({
//		left: ((windowWidth/2) - ($el.width()/2)) - iframeXOffset + $(parent.document.documentElement).scrollLeft(),
//		top: newTop
//	});
// }

"</script>
<style type="text/css">"
    #user-suggest {
                    width=650px;
                    position:absolute;
                    border:1px solid #ccc;
                    background:#fff;
                    z-index:50;
                  }

    #user-suggest div#userlist div.userpick:hover {
                    text-decoration: underline; 
                    background-color:#0099CC;
                 }
    #user-suggest div#userlist div.userpick a {
                    text-decoration: none; 
                 }
    #user-suggest div#userlist div.userpick {
                    text-decoration: none; 
                    background-color:#FFFFcc;
                    padding-left:10px;
                    padding-right: 10px;
                 }

div#TaskDetailViewer{
		position:fixed;
		z-index:100;
		border:1px solid #000000;
		margin: auto;
		overflow: auto;
		background-color:#FFFFCC;
		height: 400px;
		/*width: 600px;*/
		padding: 5px;
		top: 25%;
		left: 25%;
		width: 50%;
		display: none; /* so the element does not get rendered when the page loads. */
	}
    div#projectProgress {
            width:155px;
            float: right;
            position:absolute;
            margin-top:25px;
            overflow:hidden;
        }
    div#progressParent {
            width:155px;
            margin-bottom: 5px;
            float: right;
            background-color:#FFFFFF;
            position:relative;
        }

"</style>
</head></html>
<div style="width:100%;">
</div>
<table border="0" width="100%">
    <tbody>
        <tr>
            <td style="vertical-align:bottom; width:33%;">
                <div><strong><a style="float:left"  href="#" ctor="$(this).click(function(){$('div.todomaster').toggle(); return false;});" >'New Task'</a></strong></div>
            </td>
            <td style="vertical-align: bottom; text-align:center; width:33%">
                <div><strong><a href="#" ctor="$(this).click(function(){$('div.todolist').toggle(); return false;});" >'Current Task Item(s)'</a></strong></div>
            </td>
            <td style="vertical-align:bottom; text-align:center;">
                <div id="progressParent"><strong><a href="#" style="float:right" ctor="when($this.click) {return false;}; when($this.mouseover){ $('div#projectProgress').show()}; when($this.mouseout) {$('div#projectProgress').hide()}">'Total Progress'</a></strong></div>
            </td>
        </tr>
    </tbody>
</table>

var percentmap = {1:'0%',2:'25%',3:'50%',4:'75%',5:'100%',default:'0%'};
var prioritymap = {1:'1',2:'2',3:'3',default:'Unk'};
var CurProgress = 0;
var ProgressPossible=0;
var OverAllProgress=0;

<div class="todolist">
if(wiki.pageexists(page.path .."/Tasks") || #wiki.pageexists(page.path .."/Tasks")>0) {
tstable();
<table id="TaskItems" border="1" width="100%">
    <tr>
        <th>
            'Task ID'
        </th>
        <th>
            'Description'
        </th>
        <th>
            'Assignee(s)'
        </th>
        <th>
            'Priority'
        </th>
        <th>
            'Due Date'
        </th>
        <th>
            'Progress'
        </th>
        <th>
            'On Hold'
        </th>
        <th>
            'Complete Date'
        </th>
        <th>
            'Notes'
        </th>
    </tr>

    foreach(var taskitem in wiki.getpage(page.path ..'/Tasks').subpages) {

        var taskdetail = json.parse(taskitem.properties['todo-item'].text ?? "") ?? [];

foreach(var a in taskdetail.assignee) {
    let ProgressPossible = ProgressPossible + 4;
    let curprogress = (curprogress + taskdetail.percent -1);

        <tr>
            <td>
                web.link(taskitem.uri, taskitem.id);
            </td>
            <td>
                taskdetail.desc;
            </td>
            <td>
                a;
            </td>
            <td>
                prioritymap[((taskdetail.priority==nil || taskdetail.priority == '') ? 'default' : taskdetail.priority)];
            </td>
            <td>
                taskdetail.duedate;
            </td>
            <td>
                percentmap[((taskdetail.percent==nil || taskdetail.percent=='') ? 'default' : taskdetail.percent)];
            </td>
            <td>
                (taskdetail.hold==true);
            </td>
            <td>
                (date.isvalid(taskdetail.donedate) ? date.format(taskdetail.donedate,'MM/dd/yyyy') : 'N/A');
            </td>
            <td>
                <a href="#" id=(taskitem.id ..'-'..__index) taskid=(taskitem.id) ctor="when($this.click){ ShowMyDetails($this.attr('taskid')); return false; };" >'view'</a>
            </td>
        </tr>
}
    }

</table> 

} else {
<div>'No Tasks Yet Created...'</div>
}
</div>

let OverAllProgress = ( num.round( ((curprogress / progresspossible) * 100), 0) );
<div id="projectProgress">
//    GoogleStatusIndicator{value: OverAllProgress, text: '' ..OverAllProgress ..'%', height: '200', width: '120'};
var text = '' ..OverAllProgress ..'%';
// web.image('http://chart.apis.google.com/chart?chs=200x120&cht=gom&chd=t:' ..OverAllProgress ..'&chl=' ..web.uriencode(text));
var theUrl = 'http://chart.apis.google.com/chart?chs=200x120&cht=gom&chd=t:' ..OverAllProgress ..'&chl=' ..web.uriencode(text);
<img style="margin-top:-15px;" src=(theUrl) />
</div>

<div id="TaskDetailViewer" height="30%"></div>

Template:ToDoItem

 

 dekiapi();
jQuery.ui("smoothness");
<script type="text/javascript">"
"</script>
var todo = json.parse(page.properties['todo-item'].text ?? "") ?? [];
'This task has been assigned and ID of: ' ..page.id;
<div>
<form>
<input id="projpage" type="hidden" value=(todo.projectpage) />
<table bgcolor="#f0ffff" id="subtodoedit" cellspacing="1" cellpadding="1" border="1" width="100%" style="table-layout: fixed;">
    <tbody>
        <tr>
            <td valign="middle" rowspan="4"><span style="align:center;">'Assignee(s):'</span>; <br/>;
                <input type="text" id="user-search" />
                <div id="user-suggest">
                    <div id="userlist">
                    </div>
                </div>
                <select id="subassignee" size ="4" multiple="true" style="width:100%">
                    foreach(var u in todo.assignee) {
                        <option value=(u) selected=('selected')>u</option>
                    }
                </select>
            </td>
            <td align="right" valign="middle">'Assigner: '; <input id="subassigner" type="text" value=((todo.assigner ?? user.name)) disabled=((todo.assigner==nil) ? nil : 'disabled') />
            </td>
            <td align="right" valign="middle">'Start Date: '; <input id="substart" type="text" value=(((todo.startdate==nil) ?  date.format(date.now, 'MM/dd/yyyy') : todo.startdate)) disabled=((todo.startdate==nil) ? nil : 'disabled') ctor="$this.datepicker();"/>
            </td>
        </tr>
        <tr>
            <td align="right" valign="middle">'Priority: '; 
                <select id="subpriority">
                    <option value="3" selected=(((todo.priority == 3) ? 'selected' : nil))>'3'</option>
                    <option value="2" selected=(((todo.priority == 2) ? 'selected' : nil))>'2'</option>
                    <option value="1" selected=(((todo.priority == 1) ? 'selected' : nil))>'1'</option>
                </select>
                '     Percent: '; 
                <select id="subpercent">
                    <option value="5" selected=(((todo.percent == 5) ? 'selected' : nil))>'100%'</option>
                    <option value="4" selected=(((todo.percent == 4) ? 'selected' : nil))>'75%'</option>
                    <option value="3" selected=(((todo.percent == 3) ? 'selected' : nil))>'50%'</option>
                    <option value="2" selected=(((todo.percent == 2) ? 'selected' : nil))>'25%'</option>
                    <option value="1" selected=(((todo.percent == 1) ? 'selected' : nil))>'0%'</option>
                </select>
            </td>
            <td align="right" valign="middle">'Due Date: '; <input id="subdue" type="text" value=(((todo.duedate==nil) ? date.format(date.now, 'MM/dd/yyyy') : todo.duedate)) ctor="$this.datepicker();"/>
            </td>
        </tr>
        <tr>
            <td align="left" valign="middle" colspan="2">'Short Description: ';<input id="subdesc" type="text" value=(((todo.desc==nil) ? "" : todo.desc)) disabled=((todo.desc==nil) ? nil : 'disabled') style="width:30em" maxlength="40" />
            </td>
        </tr>
<tr>
<td>
    'Task On Hold: '; <input type="checkbox" id="onhold" value="" checked=(((todo.hold == true) ? 'checked' : nil)) />;
    <br/>;
    'Reason: '; <input type="text" id="holdreason" value=(((todo.holdreason==nil) ? "" : todo.holdreason)) />;
</td>
<td>
    'Task Complete: '; <input type="checkbox" id="tododone" value="" checked=(((todo.done == true) ? 'checked' : nil)) ctor="
                            when($this.click) {
                                if($this.attr('checked')){
                                    if(#subpercent.val() != '5') {
                                        alert('Task not marked 100% done');
                                        $this.attr('checked', false)
                                        return;
                                    } else {
                                        #onhold.attr('checked',false);
                                        #holdreason.attr('value', '');
                                        #donedate.attr('value', ({{date.format(date.now, 'MM/dd/yyyy')}}));
                                    }
                                } else {
                                    #donedate.attr('value', (''));
                                }
                            }
                            "/>;
    <br/>;
    'Done Date: '; <input type="text" id="donedate" value=(((todo.donedate==nil) ? "" : todo.donedate )) />;
</td>
</tr>
        <tr>
            <td align="center" valign="middle" colspan="3"> 
                <input id="subupdatetodo" type="button" value="Update" style="margin-top:15px;margin-bottom:15px" ctor="
                    when($this.click) {
                        $('#subassignee option').each(function(i) {
                            $(this).attr('selected','selected');
                        });

                        var todoitem  =  ({ assignee: #subassignee.val(), 
                                         assigner:    #subassigner.val(), 
                                         startdate:   #substart.val(),
                                         priority:    #subpriority.val(),
                                         percent:     #subpercent.val(),
                                         duedate:     #subdue.val(),
                                         desc:        #subdesc.val(),
                                         done:        #tododone.attr('checked'),
                                         donedate:    #donedate.val(),
                                         hold:        #onhold.attr('checked'),
                                         holdreason:  #holdreason.val(),
                                         projectpage: #projpage.val(),
                                         whochanged:  {{user.name}}
                                       });
                        #save.blur();

                        Deki.publish('ch_updatetodo', todoitem);
                    }
                " />
            </td>
        </tr>
    </tbody>
</table>
</form> 
</div>



<script type="text/jem">"
    var dapi = '/@api/deki/pages/=';
    var dpath = Deki.url.encode('"..page.path.."');

    Deki.subscribe('ch_updatetodo',null, function(c, m, d) {

        UpdateProperty(dpath,m);

    }, null);
"</script>

<script type="text/javascript">"
// Create Property
var SaveProperty = function(page, properties) {
        var pageapi = '/@api/deki/pages/=' + Deki.url.encode(Deki.url.encode(page));

        Deki.Api.CreatePageProperty(pageapi, 'urn:custom.mindtouch.com#todo-item', YAHOO.lang.JSON.stringify(properties), function() {
        		alert('Thank you. The information has been submitted.');
        	}, function(result) {
        		alert('An error occurred trying to create the store (status: ' + result.status + ' - ' + result.text + ')');
        	});

}


var UpdateProperty = function(page, properties) {

        var pageapi = '/@api/deki/pages/=' + Deki.url.encode(Deki.url.encode(page));

        Deki.Api.ReadPageProperty(null, 'urn:custom.mindtouch.com#todo-item', function(result) {
            if(result.etag) {    // page property exists, write over it.
               Deki.Api.UpdatePageProperty(result.href, YAHOO.lang.JSON.stringify(properties), result.etag, function() {
                    alert('Your task has been updated!');
                }, function(result) {
	            alert('An error occurred trying to update the store (status: ' + result.status + ' - ' + result.text + ')');
	        });
	    } 
        }, function(result) {
	    alert('An error occurred trying to read the store (status: ' + result.status + ' - ' + result.text + ')');
        });
}

"</script> 

<html><head>
// <script type="text/javascript" src="http://jqueryui.com/latest/jquery-1.3.2.js"></script>
<script type="text/javascript" src="http://developer.mindtouch.com/@api/deki/files/4844/=jquery.livequery.js" />
<script type="text/javascript">"
    $(document).ready(function() {
        $('#user-suggest #userlist a').livequery('click', function(event) {
            $('#subassignee').append('<option selected=\"selected\" value=\"' + $(this).text() + '\">' + $(this).text() + '</option>');
            $('#user-suggest').hide();
            $('#user-search').val('');
            $('#user-search').focus();
            return false;
        });
        
        $('#subassignee').dblclick(function() {
            $('#subassignee option:selected').remove();
        });

    });

	$('body').ready( function() {
		$('#user-search').keyup( function() {
                        if ($(this).val()==''){
				$('#user-suggest').hide();
				return false;
			}
	
			var q = $(this).val();
			
			clearTimeout($.data(this, 'timer'));
		    var ms = 400; //milliseconds
		    var wait = setTimeout(function() {
		      loadusers(q);
		    }, ms);
	    
	
			$.data(this, 'timer', wait);
		});
	});

function loadusers(u) {
    $('#user-suggest').hide();
    var userapi = 'http://developer.mindtouch.com/@api/deki/users?sortby=username&limit=20&activatedfilter=true&usernamefilter=' + u;

    jQuery.get(userapi,function(xml){
        // clear the previous results
        $('#user-suggest #userlist div').remove();

        // Establish a place holder for our name We need to do this so that we only get 1 unique username per user.
        var theuser;

        $(xml).find('users > user').each(function() {
            $(this).find('username').each(function() {
                theuser = $(this).text();
                return theuser;
            });

            $('#user-suggest #userlist').append('<div class=\"userpick\"><a href=\"#\" id=\"' + theuser + '\">' + theuser + '</a></div>');
        });
    });
    $('#user-suggest').show();
}

"</script>
<style type="text/css">"
    #user-suggest {
                    width=650px;
                    position:absolute;
                    border:1px solid #ccc;
                    background:#fff;
                    z-index:50;
                  }

    #user-suggest div#userlist div.userpick:hover {
                    text-decoration: underline; 
                    background-color:#0099CC;
                 }
    #user-suggest div#userlist div.userpick a {
                    text-decoration: none; 
                 }
    #user-suggest div#userlist div.userpick {
                    text-decoration: none; 
                    background-color:#FFFFcc;
                    padding-left:10px;
                    padding-right: 10px;
                 }
"</style>
</head></html>


<html><head>
<script type="text/javascript">"
    $(document).ready(function() {
        $('#taskhistory').hide();

        $('a#showhistory').click(function() {
           $('#taskhistory').toggle('slow'); 
        });

    });
"</script>
<style type="text/css">"
    #taskhistory{
        border:1px solid #CCCC33;
        padding:10px;
        margin-top:10px;
        width:500px;
        font-size:10px; 
        overflow:scroll;
    }
"</style>
</head></html>


// *** HERE WE DEFINE THE BODY OF THE TASK ITEM ***

var prevprop = {};
var summary = '';
var curprop = {};
var validkeys =['assignee','assigner','startdate','priority','percent','duedate','desc','done','donedate','hold','holdreason'];

<p>
<a href="#" id="showhistory">'Task History'</a>
</p>
<div id="taskhistory" class="hideable">
    foreach(var prop in page.properties) {
        foreach(var rev in prop.revisions) {
            if(rev.name == 'todo-item') {
//                date.format(rev.date,'u'); ' - '; rev.revision; <br/>; json.parse(rev.text);
let curprop = json.parse(rev.text);
let summary = '<b>' ..curprop.whochanged ..'</b> updated this task on:  ' ..date.format(rev.date,'u') ..'<br/>';

if(rev.revision == 1) {
    // This is the initial created task
    let prevprop = curprop;
    let summary ..= '<b>Task Created!</b><br/>'
} else {
    // Compare against previous property
    foreach(var k in validkeys) {
        if(typeof curprop[k] == 'list') {
            var prevlistprop = list.sort(prevprop[k]);
            foreach(var li in list.sort(curprop[k])) {
                if(li not in prevlistprop) {
                    let summary ..= ((curprop[k] == prevprop[k]) ? '' : 'Changed: <b>\"' ..k ..'\"</b> New Value(s): <b>\"' ..curprop[k] ..'\"</b><br/>');
                    break;
                }
            }
        } else {
            let summary ..= ((curprop[k] == prevprop[k]) ? '' : 'Changed: <b>\"' ..k ..'\"</b> New Value(s): <b>\"' ..curprop[k] ..'\"</b><br/>');
        }
    }
    let prevprop = curprop;
}
web.html(summary); <br/>;

            }
        }
    }
</div>

 

 

 

Disclaimers

None.

Tag page (Edit tags)

Files 1

FileSizeDateAttached by 
 jquery.livequery.js
No description
6.54 kB16:00, 14 Oct 2009rberingerActions
Viewing 2 of 2 comments: view all
Thanks for sharing this with the community rberinger.

I'm having trouble getting the ToDoMaster template working on MindTouch Core v9.08.3. I've installed the jQuery and Deki AJAX API extensions per the instructions on their pages. I've also created the TsTable template and that works without error. When I copy the ToDoMaster template source, create a new template, switch to Source mode, change the H1 to contain Template:ToDoMaster, add the <pre class="script"></pre>, paste the template source inbetween the pre tags and save, I get the error "pre, line 5, column 83: EOF expected". This is on saving the template. I've tried the same but also replacing the developer.mindtouch.com URLs to be my local server's and get the same error so I don't think that's the issue. I've tried ignoring the error and using the template on another page and get a similar EOF error on that page.

Does anyone know what I'm doing wrong?

Another thing I tried was manually manipulating the source to see on what line it was failing. The first line that generates an error when saving the template is line 29 (of the template source provided on this site). That is <option value="2" selected=((2 ? 'selected' : nil))>'2'</option>. I tried adding a semi-colon after the '2' and that got it past that line. I don't know if that's a useful hint.

Any help would be really appreciated.

Thanks!
Posted 00:23, 5 Mar 2010
I have the same problem with this extension that Dimitri describes to "Integrated Bug and Issue Tracker" extension here:

http://developer.mindtouch.com/App_Catalog/Integrated_Bug_and_Issue_Tracker#comment21

Both extensions use dekiapi. ¿Maybe the source of the bug?
Posted 13:19, 8 Mar 2010
Viewing 2 of 2 comments: view all
You must login to post a comment.