Dustin
AnasAbdin

if i look back, i am lost
todays bird

Origami Around
Acquired Stardust

❣ Chile in a Photography ❣
dirt enthusiast

Discoholic 🪩
art blog(derogatory)

shark vs the universe

★
tumblr dot com
I'd rather be in outer space 🛸
d e v o n
Show & Tell
No title available
DEAR READER

pixel skylines
he wasn't even looking at me and he found me
No title available

seen from United States

seen from South Africa

seen from United Kingdom

seen from Qatar

seen from Malaysia
seen from Malaysia

seen from United States
seen from United States

seen from Malaysia

seen from United States

seen from United States
seen from United States
seen from Canada
seen from United States

seen from Venezuela
seen from United States
seen from United States

seen from United States

seen from United States
seen from United States
@sweitzephreniac
Dustin
TDS 15 Compatibility Review
It's that time of year again. That time of year when Steve goes through all the scripts he's posted to this website and implements them all on a site running the new version of Terra Dotta software. That time of year when he forgets to notify the support team that he's doing this on one of the TDS15 test environments causing them completely freak out when the software is doing all kinds of absolutely crazy things - including playing music by Rick Astley.
The github.com repository has been updated with all the testing updates so if you've cloned all the scripts down, you can just pull the latest commits from the master branch, and you'll be good to go. I know that articles haven't been getting posted here as often as they used to be, and I'm going to try to turn that around after we've gone through the TDS 15 release and things calm down a bit. After all, nothing big on my plate after that; just this little "SEVIS" thing that we've been poking at over the last couple cycles.
These are the TDS scripts that will require an update to run in TDS 15.0:
WRITE AN ESSAY THAT IS 1 FILE LONG
Okay, let's start with the big one; the script that changes an essay question item into a single 'Upload file' button. This will require an update due to the update to the WYSIWYG Editor resulting in a couple of the numbers needing to be updated. Eventually I'll update this so that it will also work with multiple question items in the same questionnaire too.
EssayQuestionToFileUpload.html
SHOW ME THE DOCUMENTS
This script is used to add a panel to the reviewer's view that has a link to the 'Documents' tab of the application being viewed. The script needed an update to the selector due to a change in the page structure.
AddingLinkToDocumentsToReviewerView.html
ABROAD WITH NO ADDRESS ON FILE?! EXPLAIN! EXPLAIN!
This is the script that would perform an AJAX call in the abroad phase to make sure that there was an address type that was filled out. It required a pretty considerable update because the URL of the address page for applicants now requires a user ID#. Fortunately, it's pretty easy to grab the logged-in user's ID# off the applicant home page.
AddressCheckWhenAbroad.html
CLUTTER-UP ADMIN HOME WITH DATA
The dashboard widget on the admin home page required an update due to the reporting engine requiring a search ID# to be submitted along with the report ID#. Not a huge deal, but it will require in the need to re-implement your script.
AdminHomeDashboard.html
LET GOOGLE DRAW THAT FOR YOU
As you may have guessed, since the simple dashboard widget required an update, so did all the Google Chart API implementations of it. I also had to make some adjustments to the positioning of the new widget due to the introduction of the tasking system in TDS 15.
AdminHomeDashboardAreaChart.html AdminHomeDashboardBarChart.html AdminHomeDashboardGeoMap.html
PROFILE PICS PLEEZ
The script that checks to make sure a user has a photo image uploaded and re-directs them if they don't needed an update to the redirect due to the profile URL requiring the user ID# to be included in TDS 15.
DailyPhotoCheck.html
HAPPY BIRTHDAY TO EVERYONE
This script needed the same sort of update as the others that require the user ID#, but there was some unfortunate fallout. I can no longer support using this script on the admin home page since there isn't a reliable way to get an admin user's ID#.
HappyBirthdayWidget.html
PESKY SPONSOR DATE RECORDS
So, I was at a bit of a loss with this one. I needed to switch around the single/double quotes in the jQuery command. Why did I have to do this? ... haven't a clue... maybe an issue with the new version of jQuery being used in TDS 15.
SponsorDateRecordToggle.html
There are two scripts that are no longer 'supported' in TDS 15.0:
Not because they don't work, and not because I'm too lazy to test them. They are no longer necessary because they're been included in the TDS 15.0 codebase.
PAINLESS PARAMETER OPTIONS
If you used this script to allow option values for single and multi-select parameters and question items to be created/edited in batches, you will want to remove the script from the text interface fields 6025 and 6143 because TDS 15 has this functionality built in.
PROCESS MANAGEMENT MANAGEMENT (PMM)
This is another script that you can remove from the text interface. If you were using this to provide a search filer and collapsable heading for your process element listings, you'll want to remove the script from fields 8535, 3971, 11788, and 11787.
Recovering From TDU 2015
I know that I usually post a pre-TDU article, but as you may have noticed from the lack of activity on this blog over the last few months, things have gotten pretty busy, and I haven't had a lot of time to dedicate to my articles here.
Anywho, this year's TDU was just as much fun as the last nine, and I still find it very shocking that I've been a part of ten of these conferences. It doesn't seem like it has been that long, but I can't argue with the math. As usual, I took a bunch of bad pictures and used various apps on my iDevices to make them less boring.
In addition to the study abroad new user workshop, I got to do three sessions, and I was pretty excited about all of them. Workflow and principal/dependent are going to be exciting features to release this summer, and I've already got a few ideas brewing around in my head about how I can make them more interesting with a few well placed scripts. I also felt like my javascript session went over really well, but I'm also wondering if it's about time to seriously refactor the whole thing. I've done that session a few times now, and I'm pretty sure I can approach the same topic from a different point of view next year; not just give an essential repeat of the same presentation with new example scripts.
Now that one of the major events of the summer is done, I think I might be able to return to dedicating more time to this blog and posting the last couple of scripts for Version 14. I've got them just about ready to go. I just need to take the time to polish the articles and record the screencasts.
Then, I've got the big undertaking of May; testing all of these scripts in TDS 15.0. As I've promised, I'll definitely have an article here that covers all the scripts that need to be updated to work in the new version along with a list of scripts that really are no longer compatible with the new version.
... and there are two that I know won't be compatible with TDS 15.0, but it's for a happy reason. They've been added to the software itself. The development team needed some lightweight tests of our code review and version control processes, and I volunteered to commit a couple of my scripts in to the codebase to be incorporated into the new version of the software. So it looks like three years later, Sweitzephrenia code is getting absorbed into the main TDS code branch. Does this mean I'm "selling out"?
Check Your Question Items
Sometimes the multi-select question items are better displayed as a series of checkboxes rather than a multi-select listbox where you need to option-click each option after the first one and hope you didn't mess up the other options you've selected. This script will make that transformation.
Here is a screencast of me demonstrating how to implement the script and what it will look like:
There are two steps involved in implementing the script. The first is to copy/paste this script into text interface field#259:
<script> /* Tested in TDS 14.0 */ /* Add to text interface field # 259 */ /* Add <input type="hidden" name="custom-UI" value="checkboxes" /> to question item instructions to activate */ $(document).ready(function () { // check for any multi-select questions tagged to be checkboxes $('input[name="custom-UI"]').each(function () { if ($(this).val() == "checkboxes") { // grab the checkbox options that will be necessary var qstName = $(this).parent().next().find('select').attr('name'); var qstOptions = new Array(); var selOptions = new Array(); $(this).parent().next().find('option').each( function() { qstOptions.push($(this).val() + "|" + $(this).text().trim()); if ($(this).attr('selected') == "selected") { selOptions.push($(this).val() + "|" + $(this).text().trim()); } }); // hide the existing listbox $(this).parent().next().find('select').css('display','none'); // build the new checkbox interface $(this).parent().next().prepend('<div name="custom-UI-' + qstName + '"></div>'); for (var i=0; i < qstOptions.length; i++) { if (i > 0) { $('div[name="custom-UI-' + qstName + '"]').append('<br />'); } var inpOptions = qstOptions[i].split('|'); $('div[name="custom-UI-' + qstName + '"]').append('<label><input type="checkbox" name="' + inpOptions[1] + '" value="' + inpOptions[0] + '">' + inpOptions[1] + '</label>'); if (selOptions.indexOf(qstOptions[i]) != -1) { $('div[name="custom-UI-' + qstName + '"]').find('input[name="' + inpOptions[1] + '"]').prop('checked', true); } } // assign the event handlers to the checkboxes to keep the existing listbox updated $('div[name="custom-UI-' + qstName + '"]').find('input').change(function () { checkboxChange($(this).parent().parent().attr('name'), $(this).val()); }); } }); }); function checkboxChange (questionitem, valuechanged) { // this will be the event handler for the changed checkboxes var isselected = false; if ($('div[name="' + questionitem + '"]').find('input[value="' + valuechanged + '"]').prop('checked') == true) { isselected = true; } $('div[name="' + questionitem + '"]').next().find('option[value="' + valuechanged + '"]').prop('selected',isselected); } </script>
This script will now be embedded in the applicant's questionnaire view. You are now ready to add the following tag to any multi-select question items that you want to change from a multi-select listbox to a series of checkboxes:
<input type="hidden" name="custom-UI" value="checkboxes" />
That's it. Now, whenever an applicant opens up a questionnaire with that question item, the interface will be transformed by the script you embedded in text interface field#259.
Here's what the script is doing...
When the page finishes loading, it is looking for any 'input' tags that have the name of 'Custom-UI' and a value of 'checkboxes' and running a function on each one. This will find of question items on the page that need to be changed.
First, the function will create a couple of arrays. One with all the option values for the multi-select question item, and another with all the options values for that question item that have been selected by the user because this might be a questionnaire the user has 'saved' and is returning to finish.
Next, it hides the existing listbox for the question item. We'll still need it though. It is going to function as the element that is getting passed in to the server.
Then, the script builds the checkboxes based on the first array and then marks the selected checkboxes based on the second array.
Finally, the script assigns an event-handler to fire off a function whenever one of those checkboxes change. That function will synchronize the checkbox's state with the hidden listbox.
As I mention in the screencast video, there are a few potential updates to this in the future. The obvious one is to put the checkboxes in multiple columns instead of a single column. This would preserve vertical space and also look nicer. Another potential way to update this script would be to enforce a maximum # of selections in a multi-select question item. Yet another potential update to this would be to display the options in a different manner. Maybe instead of having them appear as checkboxes, they could be displayed as a row of round buttons that you can click on and off. When it comes to ways to improve the UX on the questionnaire page, there are a lot of ideas that could be tossed around and implemented with just a bit of javascript.
This one has been tested in TDS 14.0.
Auto-assess Your Applicants
Here's a script that connects a learning content page with an assessment so that you can automatically quiz an applicant on her/his comprehension of the material read as soon as it has been marked as such.
I know it's been a long time since I've posted something here. Things have just been crazy busy lately, but don't think I'm abandoning this blog quite yet. I've got a few projects in the pipeline right now, and I really just need the time to fully test and document them out.
So here's a screencast I recorded showing what this script does and how it works:
The code is a bunch of nested loops and 'if' blocks. Just copy and paste this into a plain text editor:
<script> /* Tested in TDS 14.0 */ /* Append to text interface field# 56 (for pre-decision) */ var linkedLCPAsmnts = [ ['NAME OF LCP','NAME OF ASSESSMENT'] ]; $(document).ready(function () { $('a[href*="FuseAction=Students.ContentView"]').each(function () { for (var i=0; i < linkedLCPAsmnts.length; i++) { if ($(this).text() == linkedLCPAsmnts[i][0]) { if ($(this).parent().next().find('div').attr('title') == 'Not Read') { $('a[href*="FuseAction=Assessments.Preface"]').each(function () { if ($(this).text() == linkedLCPAsmnts[i][1]) { $(this).parent().parent().css('display','none'); } }); } else { $('a[href*="FuseAction=Assessments.Preface"]').each(function () { if ($(this).text() == linkedLCPAsmnts[i][1]) { if ($(this).parent().next().find('div').attr('title') == 'Not Taken') { alert('You have completed the learning content page "' + linkedLCPAsmnts[i][0] + '". Please complete the assessment "' + linkedLCPAsmnts[i][1] + '".'); window.location.href = $(this).attr('href'); } } }); } } } }); }); </script>
Then you need to build your array of learning content pages and assessments by editing these three lines of code:
var linkedLCPAsmnts = [ ['NAME OF LCP','NAME OF ASSESSMENT'] ];
You'll be putting the name of the LCPs and assessments in it as an array of arrays (which is the interesting way that javascript implements multi-dimensional arrays). As an example, I want to pair the 'How to get a passport' learning content page with the assessment called 'Getting a passport', and I also want to pair the LCP called 'Safety abroad' with the assessment called 'Safety quiz'. My array would look like this:
var linkedLCPAsmnts = [ ['How to get a passport','Getting a passport'], ['Safety abroad','Safety quiz'] ];
A couple important details:
First, the code is case-sensitive - the name needs to match EXACTLY. One day, I'll get around to writing code that accounts for it, but don't hold your breath. The second thing to notice is the comma after the closing brackets after the first pair. That's an easy character to miss (Yes... voice of experience).
Now I'm ready to append that script to text interface field# 56 (pre-decision tips text). I'm assuming you want to do this in pre-decision, but it will work in any other phase as well. Once implemented, the script builds the array with the LCP and assessment name pairs and fires off a jQuery command when the document is completely loaded. This command is going to find every hyperlink on the page with a destination URL that includes, 'FuseAction=Students.ContentView' and run a block of code on each one of them:
Each anchor link with that URL destination is a learning content page link, and the block of code is going to loop through all learning content page names included in that array we built to see if the page is linked to an assessment.
If it turns out that there is a match between the LCPs on that application page and one that is configured to be linked to an assessment, we do a second check to see if that LCP is marked as read or not.
If it hasn't been marked as read, we then hide the assessment that it is linked to because the applicant shouldn't take that assessment until reading the LCP. If it has been marked as read, we check to see if the assessment it is linked to has been completed. If it hasn't, we generate the alert letting the applicant know that s/he needs to take the assessment on the LCP that has been marked as read.
This script demonstrates how you can create process elements that can be considered dependencies of each other. There are other ways to take this same concept and apply it in other ways. For example, if you wanted to have a questionnaire that should only be filled out after a signature document has been signed, you could have a similar script check the sig doc and then hide the questionnaire if it hasn't been marked as completed.
An important thing to remember though... this is a pure interface adjustment. It doesn't impact what the software considers when generating reminder emails. If you are hiding process elements, they will still appear as incomplete application requirements in the reminder emails. So you might get some confused applicants wondering how they are supposed to finish process elements that aren't appearing on the application page. However, if your linkages are pretty intuitive, it shouldn't be something that can't be figured out by the user.
This has been tested in TDS 14.
Really Ready for Review
The 'My Reviews' panel has an odd quirk to it. It displays the applications that aren't ready to be reviewed yet. If your reviewers have immediate access to review applications, that's not a problem, but if you want them to wait until you've marked the application as 'ready for review', there's no reason to have them displayed on the page. This script will hide the 'not ready for review' applications in that panel.
This is a very simple script to implement, just append it to text interface field# 3551:
<script> /* Tested in TDS 14.0 */ /* Add to text interface field # 3551 */ $(document).ready(function() { $('a[href="javascript:alert(\'This application is not ready for review..\')"]').each(function() { $(this).parent().parent().css('display','none'); }); /* uncomment these lines to also hide the submitted reviews $('b:contains("Review submitted:")').each(function() { $(this).parent().parent().parent().css('display','none'); }); */ }); </script>
Now, the reviewers will only see the applications that have been marked as ready for review or have had a review submitted. All the others will be hidden.
The script is selecting every anchor link on the page that generates the javascript alerts letting the reviewer know that the application hasn't been marked as ready for review. For each one, it is traversing up the DOM to get the the row element for that application and changing the css to hide the row.
However, let's not stop there. There's another way this listing gets cluttered up. After a reviewer has submitted the review, the application still appears in the reviewer's list showing what their recommendation was. If this really isn't necessary information, you can hide those rows as well by 'uncommenting' the lines:
/* uncomment these lines to also hide the submitted reviews $('b:contains("Review submitted:")').each(function() { $(this).parent().parent().parent().css('display','none'); }); */
This means that the final script you implement in the text interface will look like this:
<script> /* Tested in TDS 14.0 */ /* Add to text interface field # 3551 */ $(document).ready(function() { $('a[href="javascript:alert(\'This application is not ready for review..\')"]').each(function() { $(this).parent().parent().css('display','none'); }); $('b:contains("Review submitted:")').each(function() { $(this).parent().parent().parent().css('display','none'); }); }); </script>
A common method for coders to use to make something in their code optional is to use the commenting characters to deactivate lines of code instead of creating a bunch of configuration variables at the beginning of it. When you remove the commenting characters, you are adding an additional command to the script. This looks for any bold tags with the text 'Review submitted:' in them. These are the tags in rows where the review was marked as completed by the reviewer. It will then iterate over each of them to traverse up the DOM and hide the row by manipulating the 'display' attribute.
This has been tested in TDS 14.0.
Fixing a Funky Fact Sheet
The display of program parameters on the public brochure page can sometimes be a bit difficult to massage into an aesthetically-pleasing format. There aren't a lot of settings to mess with, and trying to fix it up with CSS is not easy because of the way that the data is rendered on the page. This script says, "To heck with it all - let's parse the data on the page - nuke and pave!"
So, this was my first screencast, and I know the sound quality and organization could be little bit better, but rather than spend weeks polishing up a more rehearsed script and re-re-re-recording something till I get bored and quit, I decided to just take a quick whack at it and see how it turns out.
The code I've posted to the repository just needs to be downloaded and appended to text interface #3, and that will take your existing 'Fact Sheet' on the brochure page and re-render it using semantically classed 'div' tags rather than a cumbersome table:
It will also not display any program parameters that have option values that turn out to be more than 200 characters. However, if you don't like that aspect of the script, you can modify that behavior by adjusting this line in the script:
if (paramvalue.length < 200) {
By adjusting the logic in this line, you can determine whether or not a parameter should be displayed. The 'if' statement is just looking for a condition that makes the parameter acceptable to render.
Most likely, the part of the script you'll want to modify is the 'style' block at the end. By adjusting these CSS statements, you can change the way your program parameters appear on the page. By default, it's rather boring, but here are some alternative ways to display your program parameters.
First, this configuration will make them appear in three columns instead of two:
<style> #custom-fact-sheet { padding: 10px; } #custom-fact-sheet .cfs-parameter { float: left; width: 33%; padding-bottom: 15px; } #custom-fact-sheet .cfs-param-name { text-decoration: underline; } #custom-fact-sheet .cfs-param-value { padding-left: 5px; } #custom-fact-sheet .cfs-glosslink { color: blue; } </style>
All you have to do is adjust the 'width' of the parameter class so that three parameters can fit in a single row (33% instead of 50%):
Next, this configuration will put each parameter in a rounded square with a drop shadow and pack them in as tight a space as possible.
<style> #custom-fact-sheet { padding: 10px; } #custom-fact-sheet .cfs-parameter { background-color: lightblue; border-radius: 10px; box-shadow: 0px 0px 5px black; float: left; margin: 10px; padding: 10px; } #custom-fact-sheet .cfs-param-name { text-decoration: underline; } #custom-fact-sheet .cfs-param-value { padding-left: 5px; } #custom-fact-sheet .cfs-glosslink { color: blue; } </style>
This involves adding a bunch of decorative CSS statements and removing the 'width' statement from the parameter class:
Finally, this configuration will adjust the font size and create a 'hover' state for the parameter 'div's:
<style> #custom-fact-sheet { padding: 10px; font-size: 15pt; } #custom-fact-sheet .cfs-parameter { float: left; width: 50%; padding-bottom: 15px; } #custom-fact-sheet .cfs-parameter:hover { text-shadow: 0px 0px 5px lightcoral; } #custom-fact-sheet .cfs-param-name { font-weight: bold; text-decoration: underline; } #custom-fact-sheet .cfs-param-value { padding-left: 5px; } #custom-fact-sheet .cfs-glosslink { color: blue; } </style>
In this one, after adding some statements to the parameter class, I've added a new style block for the 'hover' pseudo-selector that adds the colored shadow under the text when the mouse is over it:
Definitely feel free to experiment even more with this and combine the various techniques. Let me know if there are other variants of this that you'd like to see. I think the next step in this script would be to update the 'pop-up' for the glossary entries so that they use a modal window rather than a separate window/tab (those are sooooo 2004). Obviously, another potential mod for this would be truncating long parameter value options with a toggle to display the full value.
This has been tested in TDS 14.0.
Leveling-up the API-driven Program Search
I finally got around to updating the API-driven program search that uses the public interface API to build a dynamic Google map of results from a series of checkbox panels. It will now support program parameters as search criteria with just a couple adjustments.
It's been a long time coming, but the update has been posted to the github.com repository and is ready for use. Now, along with the region and terms, you can have program parameters appear as search filters in this dynamic program search.
Just like before, implementing this search is really easy. You just need to download the HTML file from the repository and then open it in a plain text editor. There are three primary configuration options:
var sitedomain = "//#SITE DOMAIN HERE#/"; var searchparameters = ""; var mapsapikey = "#GOOGLE API KEY HERE";
These are the ones you have to fill out. Although, you can leave the 'searchparameters' one empty if you don't want to implement any as search filters. When you're done, it should resemble something like this:
var sitedomain = "//studyabroad.terradotta.com/"; var searchparameters = "10000,10002"; var mapsapikey = "AIazSyB05xlejW9d0JVE0hTLIaRys0Do5MzbVJU";
If you don't know what your program parameter ID#s are, you can quickly find out what they are by just going to the Maintenance > Data Import/Export Utility and exporting the program template. The header of that template will have each program parameter with the ID# after it. Put them in there as a comma-delimited list and you're ready.
Don't forget about the other configuration option though. There is a variable later in the script where you can identify specific terms that you want to filter out of the search:
var excludeTheseParamsArray = []
You can remove your extra terms that might not make sense for an outgoing program search. So if I didn't want 'summer2' or 'winter-spring' to appear in my list even though they are terms on my site, it would look like this:
var excludeTheseParamsArray = ["summer2", "winter-spring"];
This can sometimes happen when you've got other program types that add terms that don't make sense, and while we're on the topic, this search is for the 'Outgoing' program type. If you've implemented the outgoing with side trips and OneStep program type as a separate program type, it won't find programs assigned to that type.
This is actually an issue I've reported to the Terra Dotta developers already. You can't search multiple program types with a single API request. Technically, if I wanted to, I could make multiple API requests and combine them into a single set of results, but I'd rather just wait for the API to support multi-program type requests and then add that capacity to the code. It keeps the code cleaner and faster.
Once your configuration is done, you just copy/paste that HTML into the WYSIWYG Editor of a page on your site, and you're good to go.
This update took a bit of work and some refactoring of existing code, but the last round of refactoring it went a long way towards making this update a lot easier. Program parameters are a bit more complicated to work with because you need to identify the type of program parameter it is and also the construction of the parameter in the URL is bit more complicated.
I'm thinking the next major update to this could be one of two things. Either I can get rid of the dependencies on the Terra Dotta CSS classes and structures so that the code is truly portable and able to be implemented anywhere, or I can turn the filters into 'buttons' that open a modal interface where you make the changes to the checkboxes and then updates the search results when you close that modal. This would save on the vertical space and allow the map to just appear without the user having to scroll down.
Till then, feel free to let me know if you see other areas where this could be improved upon.
This has been tested in TDS 14.0.
Dooby-dooby Due Dates
If you've got process elements in your application process that have due dates that don't fit the natural four-phase schedule of Terra Dotta software, this is a bit scripting magic that can create due dates for process elements on the application pages.
This system will allow you to designate specific process elements to have specific due dates for each application cycle on a program by program basis. Now, before you start getting REAL excited about this, let me point out that all this script does is put these colored due date elements on the application pages. It isn't an actual data point in the software that you can query and report on, and while I tried to figure out a method to put a notification like this on the applicant home page, I wasn't able to come up with a feasible solution for that. If you want to have notifications and such go out to your users, you'll still need to manage that through query watches and such. This script is simply a way to have a due date appear on the application pages without you having to put that information in the content of the process element itself or, heaven forbid, actually put it in the name of the process element. It is also more effective than having the due dates all stuffed in the application instructions panel. Even though, coincidentally, that is actually where the information is stored.
So here are the steps to implementing due dates for a program:
1.) Download the application page script from the github.com repository and save it to your local drive.
2.) Put that script in text interface field 56 and/or 12318 depending on whether you need these due dates for the pre-decision or post-decision process elements.
3.) Download the due date template file from the github.com repository and save it to your local drive.
4.) Modify the due date template file so that it has your necessary due dates for process elements.
This is really the work involved in implementing. You're going to take the template that is provided and looks like this:
<span style="display:none"> <!-- Tested in TDS 14.0 --> <!-- Process element due dates - add to phase instructions--> <input type="hidden" class="process-due-date" value="NAME OF PROCESS ELEMENT|TERM,YEAR|MM/DD/YYYY" /> </span>
... and you will change the 'input' tag so that it has the name of the process element, the app cycle of the due date, and then the due date itself. If you have more than one due date, just copy/paste that input tag as often as you need. So that you end up with something that looks like this:
<span style="display: none"> <!-- Tested in TDS 14.0 --> <!-- Process element due dates - add to phase instructions--> <input type="hidden" class="process-due-date" value="Outgoing Cond. Pre-Dec Sig Doc 1|Fall,2015|08/01/2014" /> <input type="hidden" class="process-due-date" value="Outgoing Pre-Dec Sig Doc 2|Fall,2015|08/08/2014" /> <input type="hidden" class="process-due-date" value="Outgoing Pre-Dec Sig Doc 1|Fall,2015|08/28/2014" /> <input type="hidden" class="process-due-date" value="Mobile Number|Fall,2015|08/01/2014" /> <input type="hidden" class="process-due-date" value="Outgoing Pre-Dec Mat Sub 2|Fall,2015|08/14/2014" /> </span>
5.) Copy/paste this to the program application instructions in the phase in which you need the due dates to appear. It'll look like kinda like this:
If you only have a few due dates to set up and they're pretty much consistent across all your programs, you might be able to just embed this in a text interface field on the page rather than in the program application instructions. Basically, as long as it is on the page somewhere, the script you embedded in the tip will find it.
Also, if the app cycle you are creating the due date for is dual-year term, you'll need to include both years. So if the app cycle is 'Academic Year, 2014-2015', your value for it in the input tag would be 'Academic Year,2014-2015'.
... and like most scripts like this, it is case-sensitive so you need the name of the process element to be an EXACT match.
So what is the script actually doing?
First, the script is getting the current data and the app cycle of the application. It does this by grabbing the app cycle out of the information panel and splitting it into a term/year array. This is why the input tag's version of the year needs to include both years for dual-year; so it will match what was scraped off the interface.
Next, it looks for all the hidden input tag and iterates through each of them with this loop:
$('.process-due-date').each(function () { var duedatedata = $(this).val().split('|'); if (duedatedata[1] == apptermyear) { var status = $('a:contains("' + duedatedata[0] + '")').parent().next().find('div').attr('title'); if (status == "Not Received" || status == "This questionnaire is incomplete." || status == "Not Read" || status == "Not Taken") { processcheck(duedatedata); } } });
It first checks to see if the application matches the input tag's app cycle, then checks to see if the process element in that input tag is an incomplete process element in that application. If that's the case, it runs the 'processcheck' function for that process element.
The function 'processcheck' is a routine that compares that due date with the current date. First, it just adds the due date to the right of the process element's name. It will appear in the non-threatening blue color scheme.
Then, it checks to see if the due date is less than the current date (It's today or in the past), the due date class is replaced with a due date alert so it will appear to the right of the process element's name on the page in red.
If the due date isn't less than the current date, another check is done where the due date has 604800000 subtracted from it. That's how many milliseconds there are in a week which is really what a javascript date object is ... the number of milliseconds since Jan 1st, 1970. Now, if the ue date is less than the current date, it means this process element will be overdue within the next week. In this case, the due date class is replaced with a due date warning class.
After all the hidden due date input tags are checked, there is the final check to see if there were any due dates warnings or alerts added to the page, if there were, it puts the proper box at the top of the page.
Finally, there's a 'style' block at the end of the code that can be manipulated if you want to adjust how these due dates appear:
.duedate-alert { color: #D8000C; background-color: #FFBABA; font-weight: bold; } .duedate-warning { color: #9F6000; background-color: #FEEFB3; font-weight: bold; } .duedate { color: #00529B; background-color: #BDE5F8; } span[class*="duedate"] { padding: 1px 5px 2px 5px; white-space: nowrap; border-radius: 10px; }
You can adjust the font size, weight, colors, etc. to your liking. Feel free to mess around with them in case you feel like they aren't eye-catching enough for your applicants.
A few things to keep in mind about the way this works though:
If you have a lot of due dates and app cycles to manage, they might not all fit in the instructions box for the program. In these cases, you'll want to seek out other text interface fields on the page to use. However, that will make them less transparent/accessible to your other admins who work in the program builder.
If you use the data import/export utility for program data, be careful about re-importing the content. It won't break it, but it will strip out all the line breaks and turn this template into one long block of text.
An alternative system for this would be to have this script and due dates stored in the document center as objects that are fairly easy to manipulate and then just have the application pages fetch their content to check the dates. However, that involves additional server requests, and part of what is nice about the system I've posted here is that everything needed is on the application page when it's done loading.
As I mentioned before, I tried putting in a routine on the applicant home page that would check all the prior and current year applications so that applicants could know on their home page where there are applications with warning and alerts, but unfortunately, that started getting into bad territory where I felt the potential for bombarding the server with a bunch of background requests and expecting the current page to be able to process them reliably was getting too risky. I might return to this again in the future though should I come up with a less heavy-handed approach than 'loading all your application pages in the background'.
This has been tested in TDS 14.0
Record-breaking Numbers of Date Records
If you've been using the software for a few years, something you might have noticed is that the 'Dates' tab of the program builder can get a bit cluttered. Here is a script you can embed on that page that will help manage the length of that table so that you aren't having to scroll past data that's a few years old.
To implement this change, append this script to text interface field# 4445:
<script> $(document).ready(function () { // get the current year var today = new Date(); var curyear = today.getFullYear(); var cutoffyear = curyear - 2 // set the unchanged flag var changed = 0; // select all the rows in the table $('table[summary="This table shows program dates."]').find('tr').each(function () { // for each cell in each row, check to see if it matches a year more than a year in the past var rowdata = $(this).find('td'); var celldata = $(rowdata[1]).text().trim(); if (celldata.slice(4,5) == "-") {celldata = celldata.slice(0,4)} if (celldata.length == 4) { if (celldata <= cutoffyear) { // if it is, then assign it the 'old year' class $(this).addClass('old-year-row'); // check for a program date record notes row after it if ($(this).next().find('td:contains("NOTE:")').length == 1) { // if there is one, assign it the 'old year' class too $(this).next().addClass('old-year-row'); } // update the unchanged flag changed = 1; } } }); // did we change things? if (changed = 1) { // get rid of the gray background cells $('table[summary="This table shows program dates."]').find('.bg-gray-color').removeClass('bg-gray-color').addClass('bg-white-color'); // yes, hide the old year class elements $('.old-year-row').css('display','none'); // create the button for the toggle $('table[summary="This table shows program dates."]').find('tbody').append('<tr>,<td colspan="8" class="left-bdr "><input type="button" value="toggle older date records" id="date-toggle" /></td></tr>'); $('#date-toggle').click(function () { toggleDates(); }); } }); function toggleDates() { $('.old-year-row').toggle(); } </script>
It's quite a bit of code to parse through all the dates in the table but once it's done, manipulating them is pretty straightforward. It's starting off by getting the current year and setting the 'cutoffyear' to the current year minus two. So, if it is 2014, it will be hiding the program date records from 2012 and earlier.
After setting a flag for determining if anything was manipulated, a jQuery command sets up a loop to go through each row in the table where we display the program dates. It grabs the second cell in each row which should be the year of the date records. Then, it checks it to make sure it isn't a dual-year term (chopping off the extra part of the year if it is).
If the the year is equal to or less than the cutoff year, that row in the table gets assigned a class of 'old-year-row'. Then, there's a check to make sure the next row in the table isn't the notes for that program date record. If it is, that row also gets the class 'old-year-row'. Then, since we've changed something, we set that flag to indicate we've changed a row.
Once we've gone through all the rows and assigned a class to any of the rows that we want to hide by default, the rest is pretty straightforward. First, we get rid of the zebra-striping in the table since it is going to look funny when we hide the old date records. Then, we just hide all the rows with that class 'old-year-row'. We add a new button to the interface that has a click event handler that will toggle any rows with that 'old-year-row' class.
If you've been running a site for more than a few years, this will very likely make things look a LOT nicer when you go to the dates tab, and maybe a future project of mine to improve this script will be not just to hide the older date records... but to actually sort the rows by the application deadline dates properly rather than alphabetically, but for now, I can at least make it a shorter trip to the drop-down menu for creating new program date records.
This has been tested in TDS 14.0.
Rankled by Ranking
So Terra Dotta got some feedback about the way that the 'Ranking' for outgoing programs is auto-enabled when you upgrade to TDS14, but do you really want yet another program-level setting that you have to go manipulate program by program? How about a script that not only hides the ranking interface on the applicant home page, but also corrects that awkward padding on the left side of the 'Applications' panel?
You just need to add this to text interface field#39 and it will remove the whole thing from your applicant home page:
<script> $(document).ready(function() { $('form[name|="rank"]').find('input').each(function () { $(this).css('display','none'); }); $('input[value="Update Rank"]').parent().parent().remove(); $('div[id|="PT"]').find('td[width="15%"]').each(function () { $(this).attr('width','1%'); }); }); </script>
The script is simple. The first line is targeting all the fields in the ranking forms and setting the display attribute to 'none'; which hides it from view. The next line looks for all the 'update rank' buttons and the page and removes the parent nodes that contain them. Finally, instead of leaving an unnecessary margin on the left side of the applications panel, the third line of the script finds all the table cells that have the wasteful width assignment and changes it to the much less wasteful '1%'.
This has been tested in TDS 14.0
TDS 14 Compatibility Check
Better late than never, right? I've gone through all the scripts and such that I've released, and fortunately, most of them work just fine in TDS 14, but there are few that I've had to update in order to get them working again.
I also took this opportunity to update the Github repo so that it's a little bit better organized. I still have all the old versions that I've posted there in their respective version folders, but I've created new folders that I'll be working with going forward, and I'll put all the update/compatibility notes in a code comment at the top of them. I updated the README.md file on the repo as well so that you can quickly locate the article to anything that is posted there.
Here are the things that needed to be updated for TDS 14.0:
IMPORTANT AND INCOMPLETE Since we updated the type of modal-graybox interface we use in TDS 14, this one just needed a couple adjustments to make it use the new plug-in.
ABROAD WITH NO ADDRESS ON FILE?! EXPLAIN! EXPLAIN! This one needed a little bit of tweaking because it wasn't quite working as expected. However, given what I had to fix... it made me wonder how the version I had posted ever worked in the first place. Strange.
MY OWN PRIVATE BROCHURE PAGE I needed to make a few adjustments to the CSS of this template to get it to look right. I also needed to adjust the structure of the bottom of the panels for some reason. It wasn't really 'broken', but didn't look quite right.
PROFILE PICS PLEEZ This one needed an update because the image of the question mark for users without a profile photo changed to some text - easy fix.
HOT, STICKY NAVIGATION - EW! Another minor tweak to get repair the icons -just a little CSS adjustment needed.
ONE CLICK BROCHURE TEMPLATES Okay, this one was a little bit strange because I don't think it was actually broken... but it didn't work at first, so I think I might have just done something wrong in my initial testing... either way - it got re-factored a little bit and now it works in 14.0.
READING REPORTS WITHOUT A MICROSCOPE This is another one where I think I actually didn't 'fix' it for 14.0, but only forgot how it worked and thought it was broken, but if you find it isn't working 14.0 anymore... I've got the working version of it posted in the repo now.
LOCATION SEARCH, LOCATION SEARCH, LOCATION SEARCH This one did require an update since the interface that was used to get the full location list of the site got changed. It was really just a simple adjustment to the URL though.
TDS14 CORRECTION FOR ESSAY HACK This is one that I already posted the updated version for a few weeks ago since I knew it was being used by a number of people. The WYSIWYG Editor got a much needed update, and that resulted in the javascript that transforms it into a single button file upload getting a much needed update too.
I've got a few new scripts that I'm going to be posting here over the next week or so for TDS 14.0. I'm know I'm definitely going to be busy digging into what will become TDS 15.0, but I'll do my best to keep coming back here and sharing my various side projects and experiments.
No More Type-os in the WYSIWUG
You may have noticed that your browser's native spellchecking isn't functional in the TDS WYSIWYG Editor. Well, this is because CKEditor has its own check-as-you-type spellchecking... but it's kinda flakey. So, it hasn't been implemented.
However, there is still a bit in the code that prevents your browser from doing spellchecking in the WYSIWYG Editor. You can re-enable it by making a bookmark in your browser to this link: Enable Spellcheck
I would actually recommend dragging it to your bookmark bar to make it easily accessible:
Now, whenever you are working in a WYSIWYG Editor, and you want to see the 'red squiggles' under misspellings - click this bookmark in your browser.
This is called a 'bookmarklet' ... a bookmark that executes javascript when you navigate to it. In this case, it is executing javascript that will find the WYSIWYG Editor's attribute for 'spellcheck' and set it to 'true':
javascript:(function(){var sc = $('iframe').each(function() {$('body', this.contentWindow.document||this.contentDocument).attr('spellcheck','true');});})();
Pretty neat, eh? This is something I've been getting interested in because it involves creating javascript that is really just executed privately within your own browser. It's essentially a private-hack. There might be some adjustments that you want to make to the interface, but not for everyone.
For example, there was a script posted a while back that added a drop-down menu to the application search results page that changed the tab of the interface you would land on when clicking on applications. Instead of implementing this script by adding it to the text interface, the jQuery could be turned into a bookmarklet that finds and modifies the page's hyperlinks.
This spellcheck enable script could likely be trimmed down a little bit, but it isn't too long so there's no potential length problems. I encapsulate the code in an anonymous function to avoid potential conflicts, and I found I had to have the jQuery command assigned to a variable to avoid navigating to an empty page (not sure if this is the right way to avoid this, but it works). The jQuery command itself just looks for the 'iframe' tags on the page and then searches for the 'body' tags within each one. It sets the 'spellcheck' attribute to all of them to 'true' which overrides the WYSIWYG Editor's assignment of this attribute of 'false'.
Now, the question, "Why not just implement this as a script in the text interface and do this automatically?" ... That definitely could be done, but it would be tricky because I would need to set the jQuery command to execute on a delay. The WYSIWYG Editor is built by a script during the page load and might not be ready to be adjusted when the document's 'ready' event is fired off. I figured I'd remove all potential misfires and make it a user-initiated action.
... and I've needed an excuse to build a bookmarklet for while anyway ...
This has been tested in Version 12.
TDS14 Correction for Essay Hack
Terra Dotta software 14 has been released, and I'm going through my scripts to find the incompatibilities. However, there's one that I think I want to get out the door a little bit ahead of schedule, and that's my script for changing an essay question item in a questionnaire into a single button for uploading a file.
This one seems like it has been requested and implemented a bit more frequently. It is also not going to be compatible with this summer's release. TDS14 contains a major update to the WYSIWYG Editor, and while I thrilled about how much smoother the next WYSIWYG Editor is, it changes a bit of the toolbar structure. This breaks the script that transforms it into a single button that triggers the file upload process.
If you aren't familiar with this one, it's a pretty easy thing to implement, you just add the block of code to an essay question item's instructions, and the TDS14 fixed code is nearly identical. If you did use this in Version 12, just go to the question item and click the edit pencil. Then, you replace the block of code with this one:
<input type="button" value="Upload File" id="uploadbutton" href="javascript:void('Document Center')" title="File Upload" tabindex="-1" hidefocus="true" role="button" onclick="fubutton(this); return false;"><script language="JavaScript" type="text/javascript">$(document).ready(function(){$("input[value='ESSAY']").prev().css("display", "none");$("#uploadbutton").click(function(){$("input[value='ESSAY']").prev().css("display", "block");$(".cke_top").css("display", "none");$(".SA_error").css("display", "none");});});function fubutton(d){var bn='29';if($('#cke_19').attr('title')=='Document Center'){bn='33';}CKEDITOR.tools.callFunction(bn, d);}</script>
When you click to update the question item, remember to cascade the change to all the questionnaires that used it.
IMPORTANT: This should be implemented AFTER the upgrade to TDS14 or very shortly before it when you won't have applicants filling out the questionnaire. If you change your question item to use this code in Version 12, it will stop working until your site is upgraded to TDS14.
So what specifically changed here?
1.) The ID attributes of the document center buttons - the script was no longer calling the right function anymore.
2.) The container tag for the WYSIWYG Editor's toolbar - the script was no longer hiding the toolbar after the button was clicked and the editable area was revealed.
BTW, the updated WYSIWYG Editor is really nice. It isn't as dramatic an update as when we switched it from the old HTMLArea version to CKEditor, but the interface is a bit more polished, and there are a bunch of bug fixes that hopefully will prevent javascript issues when you have unusual content in there.
Final note, just like with the previous version of this code, it can only be used in a questionnaire once, and there shouldn't be any other essay question items in the questionnaire. It isn't designed to ignore other WYSIWYG Editors on the page yet. I might include that as an update when I post it to the GitHub repo, and I'll post an article on this blog explaining that should I make that tweak to the code.
This has been tested in TDS14.
Reasonable Withdrawing Reasons
Instead of a big text area where the students can just type in 'Just cuz' and then withdraw their applications, make them choose from a drop-down menu and give your site more consistent data about these withdrawn applications.
This is an interface modification that will have a default option provided to the applicant when they click on the 'withdraw' button, but they can then select other options. This includes an 'other' option which reveals the free-form text area that allows them to type in whatever reason they want.
To implement this script, copy/paste this script to a plain text editor:
<script> $(document).ready(function() { var reasonarray = ["financial","course credit","just cuz"]; $('#taComments').parent().css('display','none').before('<div class="padding left-bdr rightbdr"><select id="reasondd"></select></div>'); for (var i=0; i < reasonarray.length; i++) { $('#reasondd').append('<option value="' + reasonarray[i] + '">' + reasonarray[i] + '</option>'); } $('#reasondd').append('<option value="other">other</option>').change(function() { var reason = $('#reasondd').val() if (reason != 'other') { $('#taComments').val(reason).parent().css('display','none'); } else { $('#taComments').val('').parent().css('display','block'); } }); }); </script>
Adjust this line in the script so that it has an array that is the list of reasons that you want to have offered in the drop-down menu. The first one will be selected by default:
var reasonarray = ["financial","course credit","just cuz"];
Append the script to text interface field#351, and it's ready to go.
The script isn't too complicated. First, it is hiding the existing interface and adding in a drop-down menu (the 'select' tags). Then, it iterates through all the options you provided in the array and creates each one as an 'option' tag in the drop-down menu. When it's done with that, it adds the 'other' option to the drop-down menu. Finally, it assigns an event handler to the drop-down menu.
The event handler is fired off whenever the drop-down menu is changed. It grabs the current value of the drop-down menu, and if it isn't the 'other' option, it assigns that value to be the value submitted as the reason. It also makes sure that the native interface's textarea tag remains hidden (in case you selected 'other' and switched back to another reason). If it IS the 'other' option, it reveals the native interface's text area tag after clearing out its contents (in case you had previously selected another option in the drop-down menu).
This has been tested in Version 12.
Tag-Teaming an Awesome Program Search
If you thought this blog might be never updating again, never fear - I am still planning on posting updates in the future. Unfortunately, the last couple of months have been crazy busy with getting all the aspects of this summer's TDS release in order so I haven't been able to dedicate quite as much time as I would like to brushing up on my own coding skills.
I'm pretty sure I'll get at least one more article posted here within the next couple weeks, but then the big project will be going back through everything I've posted here and testing it in the TDS 2014 release for compatibility. I'm not expecting a lot of issues to arise, but you never know what little wrinkles might throw an error here and there. In fact, I found one today that I think might be a bit of a difficult fix (hiding the WYSIWYG Editor interface for essay questions to make it just a file upload button).
If you keep an eye on the Github repository, you'll notice that there were some updates posted to the API-driven program search. I'd like to give a shout-out to Antonio Carella for being the first pull-request on the Sweitzephreniac Github repo. He added a couple very significant changes to the code.
First, he added a way to exclude specified terms from the interface. If your site has some extraneous term names that really aren't used for study abroad programs, you wouldn't want them appearing in the panel as checkbox options. If you want to exclude them, just look for these lines in the code:
// Add names of parameters you wish to not show up to the list below, inside quotation marks, and separated by a comma, e.g. ["Proposal", "Other", "Fall-Winter Terms", "Summer-Fall Terms"] var excludeTheseParamsArray = []
You can put the terms you don't want to appear in there, and your search widget no longer will include them as checkboxes.
Second, he made a major change to the info windows that appear when hovering over pins in the Google map. A significant problem with the way it worked before is that if you had more than one program in the same city, only one program would appear in the info window. My version of the code just overwrote the previously parsed program so that only the last program in the data returned for that city would be displayed.
Antonio's change has code that goes through all the marker data and checks for duplicates. When it finds them, it concatenates the data so that there will just be one marker with the data for multiple programs. It's a pretty nice bit of code that he's commented verbosely to explain what it is doing.
If you've implemented that custom program search I posted and you aren't using this updated version, I would strongly recommend checking it out and updating with these additions.
Now, all that's really left for me to do is add the ability to have program parameters appear as criteria options as well. Hopefully, going through all the articles I've posted here over the next few weeks for the upgrade won't take too much time, and I'll be able to get a 'feature-complete' version of this program search posted.
Process Management Management (PMM)
If you have a lot of process elements on your site, going through the listings in Process Admin can be a bit of a pain. Here's a script that will make that interface a little bit easier to work around. It isn't going to create 'folders' for you, but it's going to give you a handy way to get at what you want to find a bit more easily.
The script is easy to implement. You're going to append this to the tips text of each of the four process element types - 8535, 3971, 11788, and 11787:
<script> $(document).ready(function(){ $('table[summary*="table showing existing questionnaires"]').first().prev().remove(); $('a[href*="FuseAction=ProcessAdmin.MaterialsView"]').attr('title','Click to Preview'); $('.data-header').click(function() { $(this).next().toggle(); }); $('a[href*="ViewRetired=1"]').before('Filter: <input id="element-filter" type="text" value="" maxlength="20"> '); $('#element-filter').keyup(function() { var filter = $(this).val().toLowerCase(); $('a[title="Click to Preview"]').each(function (){ var elementname = $(this).text().toLowerCase(); if (elementname.indexOf(filter) != -1) { $(this).parent().parent().css('display', 'table-row'); } else { $(this).parent().parent().css('display', 'none'); } }); }); }); </script>
Once you got this script added, you'll see the 'Filter' field appear at the top of the listing to the right of the link to view the retired process elements. As you type into it, your view will only show the process elements that match it. You'll also have the ability to click on the section headings to collapse and expand them.
The first couple lines of the script are making adjustments on the pages so that all four of the process element pages have the same structure and attributes. This removes a border table that only appears in the data table of the Process Admin -> Questionnaires tool:
$('table[summary*="table showing existing questionnaires"]').first().prev().remove();
This adds the 'Click to Preview' tooltip to all the preview links in the data table of the Process Admin -> Materials tool:
$('a[href*="FuseAction=ProcessAdmin.MaterialsView"]').attr('title','Click to Preview');
If didn't have these two lines, the same script wouldn't work for all four process elements. I'd need separate scripts for the questionnaires and materials. Why have three scripts when you can have just one, right?
Next, I put this event handler on all the section headers:
$('.data-header').click(function() { $(this).next().toggle(); });
It uses the jQuery 'toggle()' method on the data table below the header to hide and reveal each section. This is why we needed that first line for the questionnaires page. If it wasn't there, the first header's event handler wouldn't be toggling the right part of the page.
Next, I create the label and input field with the id 'element-filter' for the filter:
$('a[href*="ViewRetired=1"]').before('Filter: <input id="element-filter" type="text" value="" maxlength="20"> ');
Instead of defining what happens with this field in the 'input' tag, I attach this event handler to it:
$('#element-filter').keyup(function() { var filter = $(this).val().toLowerCase(); $('a[title="Click to Preview"]').each(function (){ var elementname = $(this).text().toLowerCase(); if (elementname.indexOf(filter) != -1) { $(this).parent().parent().css('display', 'table-row'); } else { $(this).parent().parent().css('display', 'none'); } }); });
Every time there is a 'keyup' in that field (which means every keystroke), this routine is going to run. It will check every process element on the page by looping over all the preview links. That is why we had to add that second line for the materials page. It is checking each of them to see if the contents of the filter are in the name of that process element. If the name of the process element contains the text in the filter, it has the 'display' attribute set to display the table's row. Otherwise, it gets hidden.
I also remembered to make the filter case-insensitive by making all the strings I'm comparing lowercase letters. I've forgotten that in past 'filters' I've built. It's such a small detail, but it really makes a different in usability.
An interesting thing about this script is that I was able to get the basic script written in about 15 minutes. It was a piece of cake. Figuring out why it didn't work on the questionnaires and materials pages took another thirty minutes. Finally, figuring out the way to manipulate those two pages so that I could use a single script on all four pages took another hour. It's not that those two lines two an hour to type. That time was spent determining that was the way I wanted to do it.
This has been tested in Version 12.