Grades distribution graph

Another interesting (and very useful) customization I’ve been recently asked to do is a visual representation of the stored grades distribution by school/year/grading period (store code).

You start by choosing the school from a drop down box of schools in your district.

gd_001

After choosing the school
gd_002
you will be presented with another drop down box listing the relevant school years.gd_003

Again, after choosing the year
gd_004
you’ll see yet another drop down appear, a drop down containing all the grading periods/bins (for the previously chosen school and year) that have stored grades in them.
gd_005

Once you’ve chosen the store code/grading bin you’ll have the option (the drop down on the second row) to order your grades a certain way.

gd_006

What the options mean:

  1. If you leave the field blank than the grades on the graph will be ordered “alphabetically” (A,A+,A- a.s.o. for letter grades and 1,2,3 a.s.o. for number grades).
  2. “From 1 to 7” – grades from 1 to 7 are listed first, from left to right, everything else comes afterwards ordered alphabetically.
  3. “From A to F” – proper ordering of letter grades (A+,A,A- a.s.o.), everything else comes afterwards ordered alphabetically.
  4. “By total number” – it counts the number of occurrences of each grade and orders them “ascending” (from 1 occurrence to 1000, let’s say).
  5. “By custom string” – allows you to choose, from the list of possible grades, the order in which you’d like them to be displayed (use drag & drop to move the grades around and change their left-to-right display order).
    gd_007

Whatever your choice was at the previous step, you can now press the submit button to see the results.

Below are some examples of how the graphs could look like, depending, of course, on your own grades in the database (and the order you choose).

Letter grades, ordered “From A to F”:
gd_008

Letter grades, ordered “By total number”:
gd_009

Letter grades, ordered “By custom sorting”:
gd_010
gd_011

Number grades, ordered “From 1 to 7”:
gd_012

Number grades, ordered “By custom sorting”:
gd_013
gd_014

You could also report on, based on the same logic, the number of failed or incomplete grades (vs. total grades), or even create an extra option to the order drop down called “failed grades first” (to show first the failed grades and then the rest), for example.

There is virtually no limit to what can be done (counted, displayed and analyzed) on the pattern above as long as the data is there, in your database, and it is consistent (that’s extremely important when you’re doing statistics/graphs).

You can download this customization (as a PowerSchool plugin) here or here. Once installed, you will find its main page called grade_distribution.html under /admin/dashboard. You can then create a link to this page and add it to the left-hand menu or pretty much anywhere you want.

You can email me any comments or questions you might have.

Health Office visits statistics using graphs

Here is a customization I’ve done recently for the Health Office department of an international school.

Its purpose is to have a visual representation of the data collected every time a student enters the Health Office (office visits). In our case it will display statistics over “visit types” (as “categories”) and over “visit time” (as the difference between “time_out” and “time_in”).

The initial screen displays the title of the page, two date entry fields and a submit button.

ho_vt_01

You can bound your statistics to a certain time window by filling in both the “from” and “to” fields (or either one or the other, depending on your needs) or leave them both blank in order to get a statistics over the entire history of your office visits records (a “total over time”).

Once you chose your time window you can go on and press the submit button to display the results.

The result page showing the statistics will be split into two graphs (example given for time window: Jan 1st – Jul 1st, 2016):

  1. Number of Health Office visits
    Includes the total number of office visits and total number of (unique) students during the Health Office over the specified time window.ho_vt_02
  2. Average Health Office visit times
    Includes the time spent by students and average visit time duration during the specified time window.ho_vt_03

The statistics could be further refined/split in the future by, for example, school name (Middle School, High School a.s.o.) or by school year (thus eliminating the need of having tho choose the from/to dates yourself). Of course statistics similar to the above can be done for Immunizations and Screenings, should you be needing them.

There is virtually no limit to what can be done (counted, displayed and analyzed) on the pattern above as long as the data is there, in your database, and it is consistent (that’s extremely important when you’re doing statistics/graphs).

You can download this customization (as a PowerSchool plugin) here or here. Once installed, you will find its main page called HO_visits_graphs.html under /admin/dashboard. You can then create a link to this page and add it to the left-hand menu or pretty much anywhere you want.

You can email me any comments or questions you might have.

Make the school calendar page default to the current month with today’s date sticking out

Tested in PowerSchool 9.0.x

By default, the PowerSchool stock page for the school calendar (/admin/schoolsetup/calendarsetup/calendarsetup.html) shows the first month in the current school year when you access it (via School Setup -> Calendar Setup).

We can change this behavior, though, by means of the scheddate parameter, making the page open at the current month instead. We can then identify the row that defines today’s date and make it stick out.

First we want to make sure that today’s date is passed as a parameter to the page itself.
So we check if there already was a variable called scheddate that got sent to the page.
If there wasn’t, we send it straight away (by means of adding it to the current URL string and then redirecting the browser to the newly created URL).

We accomplish this by adding the 3 lines below to the head section of the page, just after the <head> tag:

~[if.~(gpv.scheddate)=]
    ~[redirect:~[self]?scheddate=~[date]]
[/if]

Then we need to format the current date (~[date]) to fit the formatting of the dates as they are displayed on the first column of the big table (ex. Thu, Nov 5).

Once this is done, we need to look through all the cells in the first column of the table and find the one that matches today’s date.

Then, once the matching cell/row is identified, we will change its background color so it would be easier to “spot it in the crowd”.

We will accomplish all of these through the small piece of JavaScript/jQuery code below:


//once the content of the page has been loaded into the browser
$j(document).ready(function(){
//create a Date Object from the date string outputted by the PowerSchool code “~[date]”
var now = new Date(“~[date]”);
//create a Date Object from the date string passed via the “scheddate” parameter
var scheddate = new Date(“~(gpv.scheddate)”);

//create an array of 3-letter week day names
var weekdayNames = new Array(“Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”);
//create an array of 3-letter month names
var monthNames = new Array(“Jan”, “Feb”, “Mar”, “Apr”, “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”);
//return the Date Object as a string, using locale conventions
var dateString = now.toLocaleDateString();
//get the 3-letter week day corresponding to the day of the week we are currently in -> getDay() returns 0 – 6 corresponding to Sunday – Saturday
var weekday = weekdayNames[now.getDay()];
//get the 3-letter month name corresponding to the month we are currently in -> getMonth() returns 0 – 11 corresponding to January – December
var month = monthNames[now.getMonth()];
//build the date string that will look like this: “Thu, Nov 5”
var dateString = weekday + ‘, ‘ + month + ‘ ‘ + now.getDate();
//only if the current month is equal to the month of the passed “scheddate” parameter then do

if (now.getMonth() == scheddate.getMonth()){
//loop through all rows in the main table
$j(“table tbody tr”).each(function(){
//if the text in the first cell of the current row is equal to the date string above
if ($j(this).find(“td:first”).text() == dateString){
//remove the color of the row it was found on
$j(this).removeAttr(‘bgColor’);
//change the background color of the row to “blue-gray”
$j(this).css(‘background-color’, ‘#BAD3E5’);
//once a match is found, break/exit the loop
return false;    

}
});
}
});

Easy, isn’t it? 🙂

Please let me know if you found this small customization useful or if it gave you any ideas you would like to pursue (and perhaps would want to share).

Customized solutions that bring out the full potential of your PowerSchool platform

Do you want to make the most of your PowerSchool platform?
Do you need help developing and implementing PowerSchool projects for your school?
Would you like to train your staff to use PowerSchool to its full potential?
Are you interested in finding out about customizations other schools have been using?
Would you like to see how your school could benefit from the experience of others?
Do you have a customization that stopped working and you need it fixed?

If your answer is Yes to at least one of the questions above, then you came to the right place.
For any questions and finding possible solutions to your PowerSchool needs, please contact: pshelp911@gmail.com

We will help you:

Make your PowerSchool Public Portal an unique experience by allowing your students and their families to have access to:

Bell Schedules

  • Fully customizable look & feel
  • Extensive info on periods and day/time of day, color coded, easy to read and print

Report Cards and Progress Reports

  • Web and printer-friendly versions
  • Based on Historical Grades, Standard Grades or a mix of the two

Transcripts

  • Web and printer-friendly versions (Head’s signature and seal of the school applied on it)
  • Fully customizable look & feel by grade or by division

IB programme

  • “Predicted grades” web and printer-friendly versions
  • Print official letters for the prospective universities

Data entry pages

  • Health Office, Field Trip, Travel, Activities information
  • On-the-fly update in the database or possibility to check and approve by an Admin staff first
  • Help parents help you with keeping their children important information up to date

Honor Roll and Student Recognition

  • Using built-in or customized data
  • Fully customizable look & feel, you can pull in any relevant data available on the student
  • Possibility to print the page as a “letter of recognition” (Head’s signature and seal of the school applied on it)

Empower your Admin staff and help them accomplish more by means of:

SQL-based reports

  • Extract, present and analyze any kind of student data
  • Simple and intuitive interface
  • Possibility to export the reports as text, CSV, TAB or Excel files

Data input and update pages

  • Input, view and update any kind of student data (built-in or customized)
  • Security based on customized group access lists
  • Applying to areas such as: Learning Support, Health-related information, school activities, IB diploma a.s.o.

Custom alerts

  • Quickly identify a student’s status pertaining to attendance, health, learning support, grades a.s.o.
  • Visible and suggestive alert texts and icons
  • Pop-up messages displayed on the student’s page
  • Email sent to designated addresses when a student reaches a specific status (defined at the school level)

IB programme progress monitoring

  • IB predicted grades coming from Gradebook or Historical Grades
  • Grades tracked over multiple sessions across both years of the IB programme
  • Compare the IB predicted grades for a student over multiple sessions or between students
  • Web and printer-friendly versions
  • Download/print official letters for the prospective universities

Keep your Teachers in the loop by ensuring their access to the necessary information:

Teacher’s Schedule

  • Fully customizable look & feel
  • Extensive info on periods and day/time of day, color coded, easy to read and print
  • Possibility to save the schedule as an “.ics” file to be imported in any type of calendar (Outlook, Google a.s.o.)

Custom alerts

  • Quickly identify a student’s status pertaining to attendance, health, learning support, grades a.s.o.
  • Visible and suggestive alert texts and icons
  • Pop-up messages displayed on the student’s page in PowerTeacher
  • Email sent to the email addresses all the teachers of a student when the student reaches a specific status (defined at the school level)

Access to important student data

  • Learning Support (reading/writing accommodations, counseling, special education a.s.o.)
  • Health (allergies, life-threatening conditions, what to do in case of an emergency a.s.o.)
  • Activities (after school, vocational, athletics a.s.o.)

Change a PowerSchool student field value in the database without submitting the form it lies on

Remember to always back up your data before making any changes to it.
Always try out your customizations on a test server first, NOT the live one.
The following customization was tested in PowerSchool 7.11.1, so it might not work in the newer versions.
The following customization was not tested in Internet Explorer.

Are you tired of having to submit an entire page’s worth of data only because you (un)checked a check box, clicked a radio button or changed the selected value of drop-down on that page?
Would you like to be able to make your users happy by providing them with pages that work faster and submit data in the background (without them having to click the “submit” button or them being taken away from the current page)?
Have you ever wondered how cool it could be if the value of certain fields would get saved into the database on the fly (i.e. as you type)?

Well, the code below offers you a possible solution to the above by taking advantage of the powerful $.get(…) and $.post(…) jQuery functions.

The scenario starts from the assumption that the field you are trying to modify (i.e. pass as a parameter via the POST or GET methods) lies inside a form that has both its action and method properties properly defined. So, let’s use the Demographics page (/admin/students/generaldemographics.html) as an example. The field we are trying to modify on the fly is the one holding the student’s Last Name (the first textbox on the first row).
Below is an excerpt of the page showing the elements we need to focus on – the form and the field:

<form action=”/admin/changesrecorded.white.html” method=”POST” onSubmit=”return validateInput()”>
    [ … HTML code … ]
<input type=”text” name=”[01]last_name” value=”” size=”25″ maxlength=”50″ id=”lastName”>

    [… HTML code … ]
    <input type=”hidden” name=”ac” value=”prim”>
    [… HTML code … ]
</form>

Below you will find the code that allows you to modify a student’s Last name field straight into the database without you having to submit a form or refresh the page.
So, open a customized version of the Demographics page in a text editor or CPM’s online editor and do the following:

  1. Just above the </head> tag and between two <script></script> tags, add a “on document ready” jQuery function $j(document).ready(function() { …… } (if there isn’t one, add it). Inside the function you just decalred add the following piece of code:
    //define the mandatory fields (as an array of fields as HTMLObjects) that need to be passed along with our target field (in order for the data to be successfully saved)
    //for the customized pages on the Admin folder – you’ll have to pass the hidden field with the name “ac”
    var mandatoryFields = [$j(“input[name=’ac’]”)];
    //this function sends our target field together with the mandatory fields out to the page that will do the saving of the data
    function changeFieldInDB(myField,mandatoryFields){
    //get the form this element belongs to
        var form = myField.closest(“form”);
    //get the action of the above-mentioned form
        var url = form.attr(“action”);
    //get the method of the above-mentioned form
        var method = form.attr(“method”);
    //get the data of the fields (and their values) in a serialized format (as expected by the POST or GET functions)
    //first get the serialized data in the target field
        var data = myField.serializeArray();
    //then, go through the array of mandatory fields and append the serialization of each element to “data”
        for (var i = 0; i < mandatoryFields.length; i++){
            data = $j.merge(data, mandatoryFields[i].serializeArray());
        }
    //define the result of our “passing of parameters” function
        var doAction;
    //use the right method to pass the parameters (POST or GET)
        if (method == “POST”) doAction = $j.post(url, data);
        if (method == “GET”) doAction = $j.get(url, data);
    //on successfully passing the parameters
        doAction.done(function(data){
    //do something (display alert, make the border of the field/element flash a couple of times, display an icon (that slowly fades out) next to the target field a.s.o.
        });
    }
  2. Then call the above function on the field you are targeting (in our example: <input type=”text” name=”[01]MyStudentField” value=””>) in order to change the field’s value in the database upon a certain event.
    //when the user types something in the first textbox on the first row field (i.e. “Last Name”)
    $j(“[name=’~(JSFieldParam;[01]last_name)’]”).on(“keyup”,function(){
        changeFieldInDB($j(this),mandatoryFields);
    });
    //OR on leaving the target field (when the field loses the focus)
    $j(“[name=’~(JSFieldParam;[01]last_name)’]”).focusout(function(){
        changeFieldInDB($j(this),mandatoryFields);
    });
    As you can see, there are ways to do things quicker & smarter. Now it’s up to you to find new ways of implementing (and, why not, enriching) the above-presented functionality/code.

Display students’ photos (with options)

Remember to always back up your data before making any changes to it.
Always try out your customizations on a test server first, NOT the live one.
The following customization was tested in PowerSchool 7.11.1, so it might not work in the newer versions.
The following customization was not tested in Internet Explorer.
For this customization you will also need an icon/image (preferably square and less or about 32×32 pixels) for the “settings/options” functionality (the “wheel” in the top-right corner). You may also want to consider having a photo to be used by default if there’s no photo for a particular student in the database (because our student numbers are 5-digit long mine is called 00000.jpg and it is a 400×600 “no-image” JPG file).

First off let’s admit it: we all like photos. The visual representation that it’s added to whatever data you’re presenting makes the data (which is sometimes quite dry and abstract) not only more comprehensible, more entertaining, it also adds a human dimension to it. And when it come to student photos it’s usually the photo itself (rather than the name) that triggers that opens folder in your head that holds all the important files on the student.

Well, I don’t know about you, but people in my school are really fond of their students’ photos, that’s why they’ve asked me to put them on almost every student report. That’s really an easy task, even with PowerSchool‘s out-of-the-box features.
But what if you want to have a list of photos of several students all on one page/report? Like if you want to have a list with the photos of all the students in a division (full name, grade, homeroom) or if you’d want to print the photos of all the students (name, dob, grade) going on a field trip or if you simply want to identify a, let’s say, 9th grader (could even be a new student, to make your task more complicated) that you saw on the field doing a no-no during his/her PE class.

We all agreed that an entirely new (custom) page, built from scratch, that will allow us to do just that (and, with a little bit of tweaking even more than that) could come in really handy at times.

"Student Photos" page

“Student Photos” page

There’s also a menu/options pop-up that allows you to show/hide student or school data on the page.

"Student Photos" menu/options

“Student Photos” menu/options

I thought it would be a good idea to place a link to this “Student Photos” page on the Admin home page (so to display the photos all the students in that division)

"Student Photos" link on the home page of the Admin portal

“Student Photos” link on the home page of the Admin portal

but also add it as an option in the Group Functions drop-down menu (so to easily display the photos of the students in the selection you just made).

"Student Photos" option in the Group Function drop-down

“Student Photos” option in the Group Function drop-down

Now let’s get to the juicy part – the HTML page. The way the code is written allows you to call your page (the HTML file) anyway you want and place it pretty much anywhere you want in the Admin folder (or sub-folders) – I chose to call it photos_new.html and I placed it in /admin/students.

<!DOCTYPE html>
<html>
<head>
    <title>Student Photos</title>
    <link href=”/images/css/screen.css” rel=”stylesheet” media=”screen”>
    <link href=”/images/css/print.css” rel=”stylesheet” media=”print”>
    ~[wc:commonscripts]
    ~[SetPostValue:dothisfor=selected]
    ~[x:GetDoThisForStudents]
    <!– define here only the default values, otherwise value assumed blank, 0 or unchecked –>
    <!– only if the form hasn’t been submitted already –>
    ~[if#main.~[gpv:btnSubmit]=]
        ~[if#1.~[gpv:cols]=]~[SetPostValue:cols=5][/if#1]
        ~[if#2.~[gpv:showSN]=]~[SetPostValue:showSN=1][/if#2]
        ~[if#3.~[gpv:showYN]=]~[SetPostValue:showYN=1][/if#3]
        ~[if#4.~[gpv:photoW]=]~[SetPostValue:photoW=200][/if#4]
        ~[if#5.~[gpv:showLN]=]~[SetPostValue:showLN=1][/if#5]
        ~[if#6.~[gpv:showFN]=]~[SetPostValue:showFN=1][/if#6]
    [/if#main]
<style type=”text/css” media=”print”>
thead {display: table-header-group; }
tfoot {display: table-footer-group; }
thead th, thead td {position: static; }
table { -fs-table-paginate: paginate; }
</style>
<style>
/* hide the elements of the no-print class when printing the page*/
@media print
{    
    .no-print, .no-print *
    {
        display: none !important;
    }
}
html, body {
    overflow: auto;
}
/* make the menu wheel fixed on the page – always sticking to the top-right corner*/
#fixedElement {
    position: fixed !important;
    top: 20px;
    right: 20px;
}
span.chkOpt{
    text-indent: 20px;
    display: block;
}
/* set the width of the menu DIV that pops-up */
div#hiddenDivDialog{
    width: 500px;
}
#mainTable th{
    text-align: center;
vertical-align: middle;
    font-size: 2em;
    font-weight: bold;
    line-height: 2em;
    background-color: transparent;
}
/* set the size of the menu link (“the weel”) */
img#menuLink{
    width: 32px;
    height: 32px;
}
</style>
<script>
    //will be used to point to the default (relative) path to the “no image” photo (shown by default if the actual student photo doesn’t exist or in case of a loading error)
    //in this case the “no image” photo resides in the same folder as the html file itself
    var noStudentImage = “00000.jpg”;
    //will be used to point to the path to the “options icon” (shown in the top-right corner)
    //in this case the “options icon” resides in the same folder as the html file itself
    var optionsIcon = “settings.png”;
    //displays the current value of a slider in the span element next to the appropriate label and then sets the slider itself to the corresponding value
    function displayCurrentSliderValuePhoto(spanId,sliderId,value){
        $j(“#” + spanId).html(value + ” px”);
        $j(“#” + sliderId).val(value);
    }
    function displayCurrentSliderValueCols(spanId,sliderId,value){
        $j(“#” + spanId).html(value);
        $j(“#” + sliderId).val(value);
    }
//once the content of the page has been loaded into the browser
$j(document).ready(function(){
    //set the action property to the form to point to the page itself
    $j(“#myForm”).attr(“action”, window.location.protocol + “//” + window.location.hostname + “/” + “~[self]”);
    //set the 2 sliders to the right values (either default or passed to the page via submitting)
    $j(“#photoW”).val(~[gpv:photoW]);
    $j(“#cols”).val(~[gpv:cols]);
    //change the text in the associated spans to reflect the values of the sliders
    $j(“#phWspan”).html(~[gpv:photoW] + ” px”);
    $j(“#noColspan”).html(~[gpv:cols]);
    //on clicking the Reset button all options are reset (i.e. a call without any parameters is made to the page)
    $j(document).on(‘click’, ‘#btnReset’, function() {
        window.location.href = window.location.protocol + “//” + window.location.hostname + “/” + “~[self]”;
    });
    //on changing/moving the sliders the associated span’s text changes accordingly
    $j(document).on(‘change’, ‘#photoW’, function() {
        $j(“#phWspan”).html($j(this).val() + “&nbsp;px”);
    });
    $j(document).on(‘change’, ‘#cols’, function() {
        $j(“#noColspan”).html($j(this).val());
    });
});
</script>
</head>
<body>
<!– the menu/page settings (“the wheel”) –>
<a class=”dialogDivM no-print” id=”fixedElement” title=”Options” href=”#hiddenDivDialog”><img src=”settings.png” id=”menuLink” class=”no-print” /></a>
<!– building the big table that will actually hold the photos –>
<table id=’mainTable’>
    <thead>
        <tr>
            <th colspan=’~[gpv:cols]’>~[if.~[gpv:showSN]=1]~(schoolname)[/if] ~[if.~[gpv:showYN]=1]~(yearname) [/if]~[if.~[gpv:showTA]=1]~(termabbr)[/if]</th>
        </tr>
    </thead>
    <tbody>
        <tr>
        ~[tlist_sql;SELECT studid, studlastname, studfirstname, grlvl, studdob, studhr, CASE WHEN REMAINDER(row_num,~[gpv:cols])=0 then ‘</tr><tr>’ ELSE ” END AS rowend
                    FROM (SELECT s.id AS studid, s.last_name AS studlastname, s.first_name AS studfirstname, s.grade_level AS grlvl, to_char(s.dob,’MON DD, YYYY’) AS studdob, s.home_room AS studhr, ROW_NUMBER() OVER (ORDER BY last_name) AS row_num FROM students s WHERE ~[if.~[f.table_info;table=students;dothisfor=currentsel;fn=value;field=id;delim=cma]=]schoolid=~(schoolid)[else]s.ID in (~[f.table_info;table=students;dothisfor=currentsel;fn=value;field=id;delim=cma]-1)[/if] AND s.enroll_status=0)]
            <td style=’text-align:center’><img src=’/admin/stp/~(studid)ph.jpeg’ width=’~[gpv:photoW]’ onError=\”this.src = noStudentImage;\”><br />~(count;-;if.test<~[gpv:cols];then=;else=</tr><tr>;).&nbsp;<span style=’display:~[if.~[gpv:showLN]=1]inline[else]none[/if]’><b>~(studlastname)</b> </span><span style=’display:~[if.~[gpv:showFN]=1]inline[else]none[/if]’><b>~(studfirstname)</b> </span><span style=’display:~[if.~[gpv:showGL]=1]inline[else]none[/if]’>(~(grlvl))</span><br /><span style=’display:~[if.~[gpv:showDOB]=1]block[else]none[/if]’>~(studdob)</span><span style=’display:~[if.~[gpv:showHR]=1]block[else]none[/if]’>~(studhr)</span></td>~(rowend)
        [/tlist_sql]
        </tr>
    </tbody>
</table>
<!– the menu DIV with the options on it that pops-up when “the wheel” is clicked–>
<div id=”hiddenDivDialog” class=”hide”>
<!– the form with the options/check boxes, sliders and the two Reset/Submit buttons on it –>
    <form id=”myForm” name=”myForm” method=”post”>
    <table id=”menuTable”>
            <tr>
                <td colspan=”2″>
                    <label>Show</label>
                </td>
            </tr>
            <tr>
                <td style=”vertical-align:top”>
                    <span class=”chkOpt”>
                        <input type=”checkbox” name=”showSN” id=”showSN” value=”1″ ~[if.~[gpv:showSN]=1]checked[/if]>School&nbsp;Name
                    </span>
                    <span class=”chkOpt”>
                        <input type=”checkbox” name=”showYN” id=”showYN” value=”1″ ~[if.~[gpv:showYN]=1]checked[/if]>Year&nbsp;Name
                    </span>
                    <span class=”chkOpt”>
                        <input type=”checkbox” name=”showTA” id=”showTA” value=”1″ ~[if.~[gpv:showTA]=1]checked[/if]>Term&nbsp;Abbreviation
                    </span>
                </td>
                <td style=”vertical-align:top”>
                    <span class=”chkOpt”>
                        <input type=”checkbox” name=”showLN” id=”showLN” value=”1″ ~[if.~[gpv:showLN]=1]checked[/if]>Last&nbsp;Name
                    </span>
                    <span class=”chkOpt”>
                        <input type=”checkbox” name=”showFN” id=”showFN” value=”1″ ~[if.~[gpv:showFN]=1]checked[/if]>First&nbsp;Name
                    </span>
                    <span class=”chkOpt”>
                        <input type=”checkbox” name=”showGL” id=”showGL” value=”1″ ~[if.~[gpv:showGL]=1]checked[/if]>Grade&nbsp;Level
                    </span>
                </td>
                <td style=”vertical-align:top”>
                    <span class=”chkOpt”>
                        <input type=”checkbox” name=”showDOB” id=”showDOB” value=”1″ ~[if.~[gpv:showDOB]=1]checked[/if]>Date&nbsp;Of&nbsp;Birth
                    </span>
                    <span class=”chkOpt”>
                        <input type=”checkbox” name=”showHR” id=”showHR” value=”1″ ~[if.~[gpv:showHR]=1]checked[/if]>Home&nbsp;Room
                    </span>
                </td>
            </tr>
            <tr>
                <td>
                    <label>Photos’ width: </label><span id=”phWspan” style=”display:inline”></span>
                </td>
                <td colspan=”2″>
                    <table>
                        <tr>
                            <td style=”vertical-align:center”>
                                <span style=”display:inline”>50&nbsp;px</span>
                            </td>
                            <td style=”vertical-align:center”>
                                <input type=”range” id=”photoW” name=”photoW” min=”50″ max=”350″ step=”10″>
                            </td>
                            <td style=”vertical-align:center”>
                                <span style=”display:inline”>350&nbsp;px</span>
                            </td>
                        </tr>
                    </table>
                </td>
            </tr>
            <tr>
                <td>
                    <label>No. of columns: </label><span id=”noColspan” style=”display:inline”></span>
                </td>
                <td colspan=”2″>
                    <table>
                        <tr>
                            <td style=”vertical-align:center”>
                                <span style=”display:inline”>1</span>
                            </td>
                            <td style=”vertical-align:center”>
                                <input type=”range” id=”cols” name=”cols” min=”1″ max=”10″ step=”1″>
                            </td>
                            <td style=”vertical-align:center”>
                                <span style=”display:inline”>10</span>
                            </td>
                        </tr>
                    </table>
                </td>
            </tr>
    </table>
<!– hidden field that tells us if the page was submitted or not (yet)  – in this case, the page loads with the default values/parameters –>
    <input type=”hidden” name=”btnSubmit” value=”1″>
    <div class=”button-row”>
        <button type=”button” id=”btnReset”>RESET</button>
        <button type=”submit”>SUBMIT</button>
    </div>
    </form>
</div>
</body>
</html>

No more hassle with printing out student photos, this 100% customized PowerSchool page helps you achieve that in a couple of clicks. Simple as that! 😉

Summary/count of logged in users by type

Remember to always back up your data before making any changes to it.
Always try out your customizations on a test server first, NOT the live one.
The following customization was tested in PowerSchool 7.11.1, so it might not work in the newer versions.
The following customization was not tested in Internet Explorer.

Did I ever mentioned to you that I love statistics? Probably not. I’m really fascinated with grouping, ordering and analyzing raw data and I can’t be the only one out there. That’s why, when I look to the Current Users page (home.html found in /admin/logins) I get all sorts of ideas.

For example, I wanted to know how many parents and students were logged in to our system after the release of the Report Cards or the Online Forms on our Parent Portal. So I asked myself: why not, rather than having to eye-ball through the whole table/list on the page (that could reach up to 300 entries on some occasions), have a one-line summary at the top, listing the totals by type/category?

Summary of logged in users by type/category

Summary of logged in users by type/category

And here is what I answered back:

Let’s start by editing home.html file in /admin/logins (I assume that you already have a copy of it in your PowerSchool customization folder).

  1. Open the file in a text editor and locate the closing head tag (</head>).
  2. The entire customization is based on JavaScript/jQuery code, so just before the </head> tag add a pair of script tags (<script></script>). All the code to follow on this page will be placed in between those <script> tags.
  3. Let’s define our variables first.
    //get the text in all first columns from each row with the exception of the first one and remove the “.” at the end
    var totalUsers = $j(“table tbody tr:last”).find(“td:eq(0)”).text().slice(0,-1);
    //a 2-dimensional array that containing the category/type name and the respective default total value (zero)
    var typeRows = [[“Faculty”,0],[“Teacher”,0],[“Parent”,0],[“Student”,0]];
    //the string (holding the summary) that will be added after the default text at the top (within the H1 tag)
    var myString = “”;
    //total number of defined/identified entries
    var totalIdentified = 0;
    //total number of non-defined/non-identified entries
    var totalOthers = 0;
    //get all second columns from each row with the exception of the first one
    var myTDs = $j(“table tbody tr:gt(0)”).find(“td:eq(1)”);
  4. Inside the “on document ready” jQuery function $j(document).ready(function() { …… } (if there isn’t one, add it) add the following piece of code:
    //iterating through the text in all second columns from each row with the exception of the first one
    myTDs.each(function(){
      for (var i = 0; i < typeRows.length; i++){
          //check if there is a match
        if ($j(this).text() == typeRows[i][0]){
            //no of matches for that particular type/category is incremented
            typeRows[i][1]++;
            //total no of matches is incremented
            totalIdentified++;
        }
      }
    });
    //start building the string that will be displayed at the top
    for (var i = 0; i < typeRows.length; i++){
        if (typeRows[i][1] > 0) myString += typeRows[i][1] + ” ” + typeRows[i][0] + “, “;
    }
    //get all “others” (no match found for them)
    totalOthers = totalUsers – totalIdentified;
    //if there really are “others” (value > 0) put their number at the and of the string
    if (totalOthers > 0) myString += totalOthers + ” others”;
    //if not, leave the string as it is (removing the two characters – “,” and the ” ” – at the end)
    else myString = myString.slice(0,-2);
    //add the total number of users at the beginning of the string
    myString = totalUsers + ” users (” + myString + “)”;
    //add the string above to the text that is already found in the H1 tag
    $j(“h1”).html($j(“h1”).html() + “&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;” + myString);

Not too complicated, is it?

Display Google Map details for public IP address on the Current Users page

Remember to always back up your data before making any changes to it.
Always try out your customizations on a test server first, NOT the live one.
The following customization was tested in PowerSchool 7.11.1, so it might not work in the newer versions.
The following customization was not tested in Internet Explorer.
For this customization you will also need an icon/image (preferably square and less or about 16×16 pixels) for the map pin to be placed after the IP address.

Here’s customization I did just to play around a little with Google Maps API.

The code you’re about to go through in a moment displays a map pin next to every public IP address found in the big table on the Current Users page. Using a free service like http://freegeoip.net we will, by supplying their page with a public IP address, retrieve all sorts of info on that particular IP address (like the latitude and longitude, city, region and country).

Display map pin next to every public IP address on the Current Users page

Display map pin next to every public IP address on the Current Users page

Upon clicking on the map pin we will pass the geographical coordinates (lat and long) to the Google Maps service and have it draw us a map centered on the coordinates above. Furthermore, we will tell Google Maps to have a small info window displaying the location’s details popping up from the said center of the map. Last but not least we will instruct the page to hide the map when we close the info window or when we click the map pin a second time (toggle).

Display map below the map pin when clicked

Display map below the map pin when clicked

Let’s start by editing home.html file in /admin/logins (I assume that you already have a copy of it in your PowerSchool customization folder).

  1. Open the file in a text editor and locate the closing head tag (</head>).
  2. The customization is based on JavaScript/jQuery code but has some CSS added to it too. We’ll start with the latter first.
  3. Add the following piece of CSS code just above you </head> tag.
    <style>
    /* the map pin image */
    img.myMapPin{
        /* set the size */
        width: 16px;
        height: 16px;
        /* no border */
        border: 0px;
        /* make it look like a link when hovered */
        cursor: pointer;
        cursor: hand;
    }
    /* the div that will hold the GMap instance */
    div.GMapDiv{
        position:absolute;
        /* by default the div is not displayed */
        display:none;
        /* set the size */
        width:340px;
        height:240px;
        /* slightly rounded grey border */
        border:2px solid #CCCCCC;
        border-radius:10px;
    }
    </style>
  4. Still inside the <head> tag and just below the closing </style> tag add the link to the Google API.
    <script src=”http://maps.googleapis.com/maps/api/js”&gt;
    </script>
  5. Just between the ending </style> and </head> tags add a pair of script tags (<script></script>). All the code to follow on this page will be placed in between those <script> tags.
  6. Define the function that initializes the Google Map instance and drop a pin on the point identified by “latit” and “longit” and add an info window with text “infoText”.
    function initialize(latit,longit,myDiv,infoText){
        //set the center of the map to the point identified by “latit” and “longit”
        var myCenter = new google.maps.LatLng(latit,longit);
        //map’s properties
        var mapProp = {
                        center:myCenter, //center the map on “myCenter”
                        zoom:8, //zoom level
                        disableDefaultUI:true, //don’t show any controls
                        mapTypeId:google.maps.MapTypeId.ROADMAP //map type
                    };
      //creates the new instance of the map
        var map = new google.maps.Map(myDiv[0],mapProp);
        //define a pin (marker) in the center of the map
        var marker = new google.maps.Marker({
                                            position:myCenter,
                                        });
        //put the pin on the map
        marker.setMap(map);
        //create a (GMap pop-up) window dispalying “infoText”
        var infowindow = new google.maps.InfoWindow({
                                                        content:infoText
                                                    });
        //make the window created above pop up from the marker itself (i.e. the center of the map)      infowindow.open(map,marker);
        //define what should happen in the event of clicking the close button (the “X”) on the pop-up (info) window
        google.maps.event.addListener(infowindow, ‘closeclick’, function() {  
            //hide the layer that’s holding the map (hides the map too)
            myDiv.hide();
        });
    }
  7. Define the function that will show the Google Map right underneath the corresponding pin image.
    function onClickGMapLink(latit,longit,myIPelem,infoText){
        //listen for the event of clicking on the pin image
        myIPelem.find(“img[class=’myMapPin’]”).on(‘click’, function(){
            //position the div in relation to the position of the pin image
            $j(this).next().css({
                                left: $j(this).position().left + Math.round(($j(this).outerWidth(false) – $j(this).next().outerWidth(false))/2),
                                top: $j(this).position().top + $j(this).outerHeight(false)
                            }).toggle();
            //initialize the map
            initialize(latit,longit,$j(this).next(),infoText);
        });
    }
  8. Define the function that adds an image next to the public IP addresses and a (hidden) div to hold the Google Map.
    function setGMapLink(latit,longit,city,region,country,myIPelem){
        if (city != “”) city += “, “; //if “city” is not blank add a comma after it
        if (region != “”) region += “, “; //if “region” is not blank add a comma after it
        var infoText = city + region + country; //build the text that will be displayed in the pop-up window
        infoText = infoText.replace(” “,”&nbsp;”);
        //after each IP address add an image (to display in case of public addresses) and a div (to hold the map)
        myIPelem.append(” <img src=’map.png’ class=’myMapPin’ /><div class=’GMapDiv’></div>”);
        //set the title of the image to display the coordinates
        myIPelem.find(“img[class=’myMapPin’]”).prop(“title”,”(” + latit + “,” + longit + “)”);
        //wait for the image next to the IP address to be clicked
        onClickGMapLink(latit,longit,myIPelem,infoText);
    }
  9. Define the function that will get the details on the IP address from the free web service.
    function getIPdetails(myIPelem){
        //send a request to freegeoip.net for an IP address and get the results back in JSON format
        $j.get(“http://freegeoip.net/json/&#8221; + myIPelem.text(), function(data){
            //display the pin images next to the public IP addresses in the table setGMapLink(data.latitude,data.longitude,data.city,data.region_name,data.country_code,myIPelem);
        });
    }
  10. Inside the “on document ready” jQuery function $j(document).ready(function() { …… } (if there isn’t one, add it) add the following piece of code:
    //get all the 7th cells found on every row but the first one (table headers)
        var myIPelem = $j(“table tbody tr:gt(0)”).find(“td:eq(6)”);
        //for each and every one of them
        myIPelem.each(function(){
            //if the IP address is a public one
            if (!/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$j)/.test($j(this).text()))
                //get and display the details gathered from the IP address
                getIPdetails($j(this));

Quite fun, isn’t it? And quite reliable too.