| | Stumble It! | Add to Mixx! | | diigo it | | Slashdot |

Friday, July 31, 2009

Color Coding Dates in SharePoint Lists - Part 2

Thanks to a comment from Jen on the Part 1 post, I've updated this code as well! This code is easier to work with and understand so it should be much easier to implement. As usual, you'll need to add the Content Editor Web Part to the bottom of the page and then add this code to the "Source Editor..." section:
<script type="text/javascript">
// Change these variables to suit your needs
var dateCols = ['Due Date','Date Completed']; // add Column Titles to the "dateCols" array to color code those columns
var green = 7; // tasks this many days out will be green
var blue = 3; // tasks that are this many days or more are blue
var red = 1; // tasks due in this many days are red
// end user variables
//
var tbls = document.getElementsByTagName("TABLE");
var col = new Array();
for (var t in tbls) {
if (tbls[t].className == "ms-listviewtable") {
var rows = tbls[t].childNodes[0].childNodes;
var hRow = rows[0].getElementsByTagName("TH");
for (i=0;i<hRow.length;i++) {
col[i] = (hRow[i].innerText || hRow[i].textContent) + "";
col[i] = col[i].replace(/\n/,"");
}
for (r=1;r<rows.length;r++) {
for (c=0;c<col.length;c++) {
for (d=0;d<dateCols.length;d++) {
if (col[c] == dateCols[d]) {
colorIt(rows[r].childNodes[c],green,blue,red);
}
}
}
}
}
}
//
function colorIt(x,green,blue,red) {
var strDate = x.innerText || x.textContent;
var today = new Date();
var dateDiff = ((Date.parse(strDate) - Date.parse(today)) / 86400000);
if (dateDiff > green) {
x.innerHTML = "<div style='color:green;'>"+x.innerHTML+"</div>";
} else if (dateDiff >= blue) {
x.innerHTML = "<div style='color:blue;'>"+x.innerHTML+"</div>";
} else if (dateDiff > red) {
x.innerHTML = "<div style='color:red;'>"+x.innerHTML+"</div>";
} else if (dateDiff <= red) {
x.innerHTML = "<div style='color:red;text-decoration:line-through;'>"+x.innerHTML+"</div>";
}
}
</script>

Hide/Remove "Workspace" from SharePoint Forms

One of the more popular posts on this blog has been my description on how to remove the "Workspace" option from SharePoint forms. For those that aren't familiar with this particular annoyance, think of it as SharePoint's appendix - not really useful, but can cause problems if it gets infected ... (that sounded better in my head).

Anyway, I've come up with a cleaner (cleverer!) way to accomplish this task! You'll still need to "pop the hood" on all of the forms that you want to remove the "Workspace" option from by using the "...Form.aspx?ToolPaneView=2" trick and add a Content Editor Web Part (CEWP) to the bottom of the page, but now you only have a very small bit of JavaScript to add to the CEWP.

And here it is:
<script type="text/javascript">
var trs = document.getElementsByTagName("TR");
for (var r in trs) {
var row = (trs[r].innerText || trs[r].textContent)+"";
if (row.match(/^Workspace/)) { trs[r].style.display = "none"; }
}
</script>
If you want to get experimental with this and try to remove other rows from your forms, you can do so by modifying the IF statement as follows:
...
if (row.match(/^Workspace/) || row.match(/^Row Title 1/) || row.match(/^Row Title 2/))
{ trs[r].style.display = "none"; }
}
</script>
NOTE: It's VERY important to include the carat (^). If you don't you'll kill your page. If this happens to you, simply hit the "Back" button & add the ^.

Wednesday, July 22, 2009

formWorks 0.1a - SharePoint Form Editing Tool

A lot of the work that I do revolves around tweaking SharePoint forms and I have been getting tired of typing the same code over and over so I've created formWorks to save me (and you!) the hassle of "popping the hood" on SharePoint forms.

Copy/pasting this code into the top of your Content Editor Web Part "Source Editor..." (or better yet, the linked file) will give the rest of your code access to the elements on the form (save for radio buttons & multiple checkboxes).

Here's the code:
<script type="text/javascript">
/*
#################
formWorks - v0.1a
2009-07-22
Ben Bradley - ben[at]bradleyit.com
#################
Adding formWorks code to a Content Editor Web Part (CEWP) will give your JavaScript
code access to the controls on the form in an easy and intuitive manner

1 - Open the form that you want to manipulate.
2 - Add "&ToolPaneView=2" to the end of the URL.
3 - Add a CEWP below the form.
4 - Add this code to the top of the "Source Editor" or the file that is linked.
5 - You can now reference the rows, cells and controls on the form in the following manner:
row['Title'].row.style.display = "none";
row['Title'].control.value = "New Title!!!";
if (row['Priority'].control.value == "1 - High") then {
alert("HIGH PRIORITY!");
}

Known ToDos -
Make compatible with Radio buttons & Check boxes
*/

var row = new Object();
var aTables = document.getElementsByTagName("TABLE");
for (var t in aTables) {
if (aTables[t].className == "ms-formtable") {
var aRows = aTables[t].getElementsByTagName("TR");
for (var r in aRows) {
if (aRows[r].getElementsByTagName && aRows[r].getElementsByTagName("TD")[0].className == "ms-formlabel") {
var rowTitle = trim(aRows[r].getElementsByTagName("TD")[0].innerText) || trim(aRows[r].getElementsByTagName("TD")[0].textContent);
row[rowTitle] = new newRow(aRows[r]);
}
}
}
}

function newRow(x) {
this.title = trim(x.getElementsByTagName("TD")[0].innerText) || trim(x.getElementsByTagName("TD")[0].textContent);
this.titleCell = x.getElementsByTagName("TD")[0];
this.control = x.getElementsByTagName("INPUT")[0] || x.getElementsByTagName("SELECT")[0] || x.getElementsByTagName("TEXTAREA")[0];
this.controlCell = x.getElementsByTagName("TD")[1];
this.row = x;
}

function trim(x) {
x = x.replace(/(^\t)(\t*)/,"").replace(/(\t*)(\t$)/,"") // tabs
.replace(/(^\n)(\n*)/,"").replace(/(\n*)(\n$)/,"") // new lines
.replace(/(^\r)(\r*)/,"").replace(/(\r*)(\r$)/,"") // return characters
.replace(/(^
)(
*)/,"").replace(/(
*)(
$)/,"") // html returns
.replace(/(^ )( *)/,"").replace(/( *)( $)/,"") // spaces
.replace(/(^ )( *)/,"").replace(/( *)( $)/,""); // html spaces
return x;
}

<:/script>

Attaching JavaScript Events to SharePoint Form Controls

A project that I've been working on-again, off-again for the last year or so is finally about to be put into production. As a result, the people who have interests in the system have started making their requests (demands) for how to tweak the system to meet their needs. None of these requests have been anything unreasonable and I believe it's good practice to change your code to meet the needs of your users instead of trying to change your users to meet the needs of your code.

Because of these last-minute requests, I've had to come up with a couple cool tricks to "extend" the functionality of SharePoint. One fairly simple hack that has a lot of potential is one that will let you attach JavaScript events to the controls on a SharePoint form.

I used this bit of code to attach an onChange() event to a select drop-down that would show or hide a textbox based on the selection that had been made. I know there's a lot going on in these screen caps, but for the moment, just focus on the Status drop-down and the displaying of the Outcome row.

(before)

(after)
var theTRs = document.getElementsByTagName("TR");
var rowTitle = "";
for (var r in theTRs) { // loop through every table row on the page
if (theTRs[r].getElementsByTagName("TD")[0]) {
rowTitle = theTRs[r].getElementsByTagName("TD")[0].innerText || theTRs[r].getElementsByTagName("TD")[0].textContent;
rowTitle = (!rowTitle) ? "" : trim(rowTitle);
}
// START PER/ROW CODE //
if (rowTitle.match(/^Status$/)) {
var cellStatus = theTRs[r].getElementsByTagName("TD")[1]; // the cell holding the select dropdown
/** This is where the magic happens. It's not clean or pretty, but it works. **/
cellStatus.innerHTML = theSel.innerHTML.replace(/\<select /i,"<select onChange='statusChange(this.value)' ");
/** I had trouble working with attachEvent & addEventListener so I did it this way **/
theTRs[r].id = "rowStatus" // it's generally ok to assing IDs to rows in SharePoint, but it's very bad form to assign IDs to form controls
}
else if (rowTitle.match(/^Outcome$/)) {
if (cellStatus.getElementsByTagName("SELECT")[0].value.match(/^Pending$/) {
theTRs[r].style.display = "none"; // hide the outcome row if the status is pending
}
theTRs[r].id = "rowOutcome"; // for show/hiding in the statusChange() function
}
// END OF PER/ROW CODE //
}

function statusChange(x) {
if (x.match(/^Completed$/)) {
document.getElementById("rowOutcome").style.display = "";
} else {
document.getElementById("rowOutcome").style.display = "none";
}
}

function trim(x)
{
x = x.replace(/(^\t)(\t*)/,"").replace(/(\t*)(\t$)/,"") // tabs
.replace(/(^\n)(\n*)/,"").replace(/(\n*)(\n$)/,"") // new lines
.replace(/(^\r)(\r*)/,"").replace(/(\r*)(\r$)/,"") // return characters
.replace(/(^<br>)(<br>*)/,"").replace(/(<br>*)(<br>$)/,"") // html returns
.replace(/(^ )( *)/,"").replace(/( *)( $)/,"") // spaces
.replace(/(^&nbsp;)(&nbsp;*)/,"").replace(/(&nbsp;*)(&nbsp;$)/,""); // html spaces
return x;
}

Tuesday, July 21, 2009

Cat bath

(from Katie)

Merlin is pouting now.
-Ben

Sent from my BlackBerry

Sunday, July 19, 2009

Vacation Bible School

One of those little ankle-biters is mine =)
-Ben

Sent from my BlackBerry

Friday, July 17, 2009

Jungle Jane

-Ben

Sent from my BlackBerry

Thursday, July 16, 2009

We've been Benched

Meet the newest addition to our deck.
-Ben

Sent from my BlackBerry

Thursday, July 9, 2009

EeePC 701 Desktop Redesign

I got a little tired of looking at the same desktop so I decided it was time to have a little "digital makeover" and I'm pretty happy with how it turned out.

The first pic here shows the desktop right after boot:Now let me explain what I've got going on here. The EeePC 701 has a 7 inch screen so every pixel counts and I've tweaked the interface quite a bit to reclaim as much screen real estate as I could:
  1. Removed the Gnome panel: gconf-editor > /desktop/gnome/session/required_components > panel = trayer
  2. Installed Trayer for tray icons: sudo apt-get install trayer
  3. Installed Gnome-Do for application launching.
  4. Embedded a terminal in the desktop.
  5. Configured Conky - (fonts are Ger4ronL Cond and Envy Code R)
    # .conkyrc
    own_window_title econky-clock
    update_interval 1.0
    short_units yes
    double_buffer yes
    own_window yes
    own_window_transparent yes
    border_width 0
    use_xft yes
    xftfont Monospace:size=8
    maximum_width 400
    minimum_size 400
    #default_color #666666
    alignment tm
    uppercase no
    gap_y 150
    draw_shades no
    pad_percents 0

    TEXT
    ${color #FF2222}${font Ger4ronL Cond:size=58}${time %R}${color #88AAFF}${font Ger4ronL Cond:size=24}
    ${voffset -75}${alignr}${time %A}
    ${alignr}${time %e %B}${font Envy Code R:size=8}
    ${hr}
    wlan0 - ${addr wlan0} - ${upspeed wlan0}k / ${downspeed wlan0}k${alignr}${wireless_essid wlan0} ${wireless_link_bar 5, 100 wlan0}
    cpu - ${freq}MHz ${cpubar 5, 100}${alignr}ram - ${memmax} ${membar 5, 100}
    sda - ${fs_free /} ${fs_bar 5, 100 /}${alignr}bat - ${battery BAT0} ${battery_bar 5, 100 BAT0}
  6. Trimmed Dark Wood to 800x480 for the background.
  7. And just for good measure, I like the "Slickness Black" GTK theme.

Saturday, July 4, 2009

Wednesday, July 1, 2009

Remote Desktop via SSH and MyEnTunnel

I've written before about how awesome SSH tunnels can be, primarily for remotely viewing my lappy's webcam, but the awesome isn't limited to just snooping on webcams. It's also pretty easy to set up mini-VPNs so that you can provide support to remote locations.

To get this working, all you need is an SSH server connected to a public IP address. In my setup, I have a desktop computer running Ubuntu 9.10 and OpenSSH and port 22 is forwarded from my Linksys router to this computer. It's also a good idea to create a shell-less account specifically for ssh tunnels.
sudo useradd sshtunnel
sudo passwd sshtunnel
Once that's done, edit the passwd file entry for this user by changing the end of the line to say
/bin/false
This will allow the user "sshtunnel" to connect via SSH, but it won't be able to execute commands.

To get things set up, you'll need to initiate an SSH connection from the remote LAN to your server. I've come to prefer MyEnTunnel for Windows clients as it will run as a service and will automatically re-initiate a connection if it drops. To set up MyEnTunnel, run the "Edit Service Profile INI" from the Start Menu. On the "Settings" tab, fill in the specifics of your server and connection - I have "Connect on Startup", "Reconnect on Failure", "Infinite Retry Attempts = 300" configured on mine. Then on the "Tunnels" tab, make the following entry in the "Remote:" text box:
12345:localhost:3389
Once you connect MyEnTunnel, it will open port 12345 on your server and connect it to port 3389 (the RDP port) on the localhost (the machine you're setting MyEnTunnel) up on. Alternatively, if you want to be able to connect to a different PC on the network instead of the one that you're configuring MyEnTunnel on, simply swap "localhost" for the LAN IP of that computer. For example:
12345:192.168.1.10:3389
Save your settings and register the service (from the Start Menu entry for MyEnTunnel).

To make it work, from your server, open a Terminal Services Client and for the address use:
localhost:12345
Hit "connect" and you should see a login window for the device you specified in the previous step.

Enjoy!