Disabling touchscreen in Win 8.1

Having somehow managed to crack the screen on an Asus T100 on my way to work this morning, I was faced with the cracked area being interpreted as near-continuous touches, making the machine almost unusable. Thinking I’d turn off the touch on the screen, I found numerous posts suggesting that Human Interface Devices in Device Manager (in Control Panel) is the way to go and to look for and disable one of the USB Input Device entries. Having accidentally switched of the keyboard and trackpad several times (make sure you only try then one at a time!), I then spotted that my Asus has a device called HID-compliant touch screen. I disabled this and now have a fully working no touch notebook while I organise a repair.

Peer marking systems

With a couple of groups looking at peer marking for:

  • formative essays;
  • formative short answer questions

now seemed like a sensible time to look at what’s out there to make this possible so have created the following Google Spreadsheet – please feel free to add to/modify (apologies for any errors):

 

Authenticate (SSO) Vanilla users with a CakePHP 2 application using Vanilla’s jsConnect Plugin

Using your existing CakePHP website to authenticate users into a Vanilla forum (so they don’t have to setup another account) is fairly simple…once you know how:

In your Cake site:

  1. Download the PHP jsConnect client library as described here
  2. Create a ‘vanilla’ directory in app/Vendor and copy into it functions.jsconnect.php from the download in 1.
  3. In UsersController.php add:
    App::import(
     'Vendor',
     'FunctionsJsconnect',
     array('file' => 'vanilla' . DS . 'functions.jsconnect.php')
    );
  4. Paste index.php from the download in 1 into View/Users and rename it as vanilla_authenticate.ctp. Edit so that it contains only:
    // 1. Get your client ID and secret here. These must match those in your jsConnect settings.
    $clientID = "your_client_id_from_js_connect_plugin_settings_page";
    $secret = "your_secret_from_js_connect_plugin_settings_page";
    // 3. Fill in the user information in a way that Vanilla can understand.
    // CHANGE THESE FOUR LINES.
    $user['uniqueid'] = $user_data['your_cake_id_field'];
    $user['name'] = $user_data['your_cake_username_field'];
    $user['email'] = $user_data['your_cake_email_field'];
    $user['photourl'] = $user_data['your_cake_avatar_field'];
    // 4. Generate the jsConnect string.
    // This should be true unless you are testing. 
    // You can also use a hash name like md5, sha1 etc which must be the name as the connection settings in Vanilla.
    $secure = true; 
    WriteJsConnect($user, $_GET, $clientID, $secret, $secure);
  5. In UsersController.php, add a vanilla_authenticate action:
    public function vanilla_authenticate(){
        if ($this->Auth->loggedIn()){
             //user is logged in so pass necessary data through to view
             $this->layout = 'ajax';
             $user_data = $this->Auth->user();
             $this->set('user_data', $user_data);
             }
         }
  6. Again in UsersController.php, modify your login action:
    public function login() {
        if ($this->request->is('post')) {
            ...
            //Do your normal login business which includes the line
            $this->redirect($this->Auth->redirectUrl()); //after successful login
            ...
            }
        //add following to handle get request from jsConnect
        else{
            if(isset($this->request->query['source'])&&$this->request->query['source']=="vanilla"){
    	    //we need to redirect to vanilla after login so change redirectURL
    	    $this->Auth->redirectUrl('http://www.yourvanillasite.com/index.php?p=/entry/jsconnect&client_id=your_client_id_from_js_connect_plugin_settings_page&Target=http://www.yourvanillasite.com'); //big thanks to @hgtonight for this snippet
    	    }
    	}
        }

In your Vanilla forum:

  1. Download and enable the jsConnect plugin (at the moment, the advice is to use 1.03 as 1.4.1 is still buggy, reporting a ‘regex’ error);
  2. In the jsConnect plugin settings:
    • autogenerate (using the button at the bottom) a ClientID and Secret;
    • the Site Name: Your Cake Site – this will appear as ‘Sign in with {site name}’ when users go to the Vanilla forum
    • Authenticate URL:  http://www.yourcakesite.com/users/vanilla_authenticate
    • Sign In URL: http://www.yourcakesite.com/users/login?source=vanilla
    • Register URL: http://www.yourcakesite.com/users/register

Well, that’s what worked for me anyway….

Vanilla Community’s x00 tells me that you can avoid the complicated redirect in Cake Step 6 above (and replace with:

$this->Auth->redirectUrl('www.yourvanillasite.com');

if you use the jsConnect AutoSignIn Plugin…but I haven’t tried this yet.

Good luck.

IOS 7 status bar with fixed footer navbar in Cordova/JQuery Mobile

To avoid your content scrolling underneath the new IOS 7 status bar, you have a few choices but in most situations, org.apache.cordova.statusbar is the best solution.

From the CLI:

$ cordova plugin add org.apache.cordova.statusbar

And you can then turn the transparent IOS7 status bar into one which behaves as it does in IOS6 with the following in your config.xml:

<preference name="StatusBarOverlaysWebView" value="false" />
<preference name="StatusBarBackgroundColor" value="#000000" />

However, if you have a fixed navbar in the footer such as this to approximate the look of a tab bar in the new IOS7  Human Interface Guidelines:

<div data-role="footer" data-id="allot_footer" data-position="fixed">
    <div data-role="navbar" data-iconpos="bottom">
        <ul>
            <li><a href="#browse" data-id="footer_browse" data-theme="d" data-icon="grid">Browse</a></li>
            <li><a href="#find" data-id="footer_find" data-theme="d" data-icon="search">Find</a></li>
        </ul>
    </div>
</div>

then you’ll find that the statusbar plugin pushes the navbar down off the bottom of the page by the height of the status bar. So, for a fixed bottom navbar, the solution I’ve adopted inn the end is to forget the statusbar plugin and to allow the status bar to overlay my content. However, to create a bit of space at the top of the page for it to sit, I used this Gist from Shazron to arrive at:

function onDeviceReady() {
    if (parseFloat(window.device.version) >= 7.0) {
        $("div.ui-header").css("padding-top", "20px"); 
        }
    ...
    }

Note that you’ll need to install org.apache.cordova.device using the CLI:

$ cordova plugin add org.apache.cordova.device

It now looks fine on startup but if you need to scroll your content, it’ll still get overlayed by the IOS7 status bar so make your header fixed too:

<div data-role="header" data-id="allot_header" data-position="fixed" data-theme="d">
    <h1>Browse</h1>
</div>

Hoping that IOS7 issues will all become a distant memory very soon, but this is what works for me at the moment.

QuestionMark Perception’s GetScheduleListByParticipant method in QMWISe

PLEASE NOTE: I’m looking further into why this may be happening after a very useful chat with Questionmark’s Richard Ham so watch this space…

If you’re using QuestionMark Perception’s GetScheduleListByParticipant method through their QMWISe API and the Questionmark Basic LTI Community Connector (I don’t know about the LTI Connector for Questionmark OnDemand) then this is a quick heads up about a problem that I’ve just encountered in v5.4. We use this method to work out whether students are currently scheduled for a given assessment through a (rather inelegant) check of whether the AssessmentID they are trying to access is the same as the AssessmentID of one of the assessments they are scheduled for and, if that schedule is time-limited, whether it is currently open.

The problem is that, for reasons that are probably die to the way that PHP deals with a SOAP return , GetScheduleListByParticipant returns a different object structure depending on whether the one or more schedules are returned. For one schedule (with print_r()):

stdClass Object
(
    [Schedule] => stdClass Object
        (
            [Schedule_ID] => 111111111
            [Assessment_ID] => 1111111111111111 
            [Participant_ID] => 0 
            [Group_ID] => 111111111 
            [Schedule_Name] => Quiz 1 
            [Restrict_Times] => 1 
            [Restrict_Attempts] => 
            [Max_Attempts] => 0 
            [Monitored] => 0 
            [Schedule_Starts] => 2013-12-03T00:00:00 
            [Schedule_Stops] => 2013-12-04T00:00:00 
        )
)

Whereas with more than one schedule:

stdClass Object
(
    [Schedule] => Array
        (
            [0] => stdClass Object
               (
                   [Schedule_ID] => 1111111
                   [Assessment_ID] => 1111111111111111
                   [Participant_ID] => 0
                   [Group_ID] => 111111111
                   [Schedule_Name] => Quiz 1
                   [Restrict_Times] =>
                   [Restrict_Attempts] =>
                   [Max_Attempts] => 0
                   [Monitored] => 0
                   [Schedule_Starts] => 0001-01-01T00:00:00
                   [Schedule_Stops] => 0001-01-01T00:00:00
               )
            [1] => stdClass Object
               (
                   [Schedule_ID] => 2222222
                   [Assessment_ID] => 2222222222222222
                   [Participant_ID] => 0
                   [Group_ID] => 222222222
                   [Schedule_Name] => Quiz 2
                   [Restrict_Times] => 1
                   [Restrict_Attempts] =>
                   [Max_Attempts] => 0
                   [Monitored] => 0
                   [Schedule_Starts] => 2013-12-03T00:00:00
                   [Schedule_Stops] => 2013-12-04T00:00:00
               )
            [n] => and so on...

The real problem with is when you compare desired AssessmentID with scheduled AssessmentID in a for loop:

$schedules = get_schedule_list_by_participant($participant_id)) //method which calls GetScheduleListByParticipant
foreach($schedules->Schedule){
    ... //check each schedule
    }

It works fine until you come across a student with only one schedule, at which point it breaks as it tries to iterate through the properties of the only schedule returned. Until we can work out something else, the simplest solution is to check the number of schedules returned before deciding how to evaluate them:

if(count($schedules->Schedule)>1){ 
    foreach($schedules->Schedule as $schedule){
        ... //check each schedule
        }
    }
elseif(count($schedules->Schedule)==1){ 
    ... //check this schedule
    }

Haven’t checked whether behaviour is the same with the other QMWISe methods but I wouldn’t be surprised….

Ajax form submission from a JQuery UI modal dialog in CakePHP

I just wanted to share with you two approaches to bringing up an add or edit form in a JQuery UI modal dialog and closing the dialog on a successful submit or keeping it open and displaying errors in case of validation or other save problems.

A non-Cake approach

Ingredients

CakePHP 2+
JQuery 1.9+
JQuery UI
JQuery Form Plugin

The Views

Main page

This is the page from which we want to launch our modal add or edit dialog:

<!-- overlayed element -->
<div id="dialogModal">
     <!-- the external content is loaded inside this tag -->
     <div class="contentWrap"></div>
</div>
...
<div class="actions">
    <ul>
        <li>
            <?php echo $this->Html->link(__('Add user', true), array("controller"=>"users", "action"=>"add"), array("class"=>"overlay", "title"=>"Add User"));
        </li>
    </ul>
</div>
...
<script>
$(document).ready(function() {
    //prepare the dialog
    $( "#dialogModal" ).dialog({
        autoOpen: false,
        show: {
            effect: "blind",
            duration: 500
            },
        hide: {
            effect: "blind",
            duration: 500
            },
        modal: true
        });
    //respond to click event on anything with 'overlay' class
    $(".overlay").click(function(event){
        event.preventDefault();
        $('#contentWrap').load($(this).attr("href"));  //load content from href of link
        $('#dialogModal').dialog('option', 'title', $(this).attr("title"));  //make dialog title that of link
        $('#dialogModal').dialog('open');  //open the dialog
        });
    });
</script>

Our add or edit form

This is shown in the dialog

echo $this->Form->create('User');
echo $this->Form->input('name');
echo $this->Form->end('Submit');
<script>
$('#UserAddForm').ajaxForm({ 
    target: '#contentWrap', 
    resetForm: false, 
    beforeSubmit: function() { 
        $('#contentWrap').html('Loading...'); 
        }, 
    success: function(response) { 
        if (response=="saved")) { 
            $('#dialogModal').dialog('close');  //close containing dialog    
            location.reload();  //if you want to reload parent page to show updated user
            } 
        } 
    });
</script>

Controller

For our add or edit action

function add() {
    ...
    if (!empty($this->request->data)) {
        $this->User->create();
        if ($this->User->save($this->request->data)){ 
            return "saved"; 
            } 
        }
    ...
    }

 

A more Cakey approach

With thanks to RXC on StackOverflow

Ingredients

CakePHP 2+
JQuery 1.9+
JQuery UI

The Views

Main page

This is the same as above

Our add or edit form

echo $this->Form->create('User');
echo $this->Form->input('name');
echo $this->Js->submit('Save', array(  //create 'ajax' save button
    'update' => '#contentWrap'  //id of DOM element to update with selector
    ));
if (false != $saved){ //will only be true if saved OK in controller from ajax save above
    echo "<script>
        $('#dialogModal').dialog('close');  //close containing dialog         
        location.reload();  //if you want to reload parent page to show updated user
    </script>";
    }
echo $this->Form->end();
echo $this->Js->writeBuffer(); //assuming this view is rendered without the default layout, make sure you write out the JS buffer at the bottom of the page

Controller

For our add or edit action

function add() {
    ...
    $this->set('saved', false); //false by default - controls closure of overlay in which this is opened
    if (!empty($this->request->data)) {
        $this->User->create();
        if ($this->User->save($this->request->data)){ 
            $this->set('saved', true); //only set true if data saves OK
            } 
        }
    ...
    }

Hope that’s helpful. Would be very interested in alternatives that have worked for you…and corrections to inevitable errors above.

Moving/copying large MySQL databases between servers with or without phpMyAdmin

I don’t suppose I’m alone in restricting the majority of my interactions with MySQL to what you can do through the wonderful phpMyAdmin. However, one place where it really lets you down is when you are trying to move large databases between servers. Exporting doesn’t seem to cause any problems (with the 100MB or so that I’ve tried so far anyway) but importing is where things start to go wrong.

phpMyAdmin seems to default to a maximum file upload size of 8MB but this, and other settings, are actually set in php.ini. You can zip (or gzip, etc.) your exported SQL file to bring down its size and/or change:

post_max_size = 8M
upload_max_filesize = 2M

in php.ini (just make sure you’re editing the right one – you will often find more than one) to values appropriate for your file.

However, you may still run into problems with execution timeouts and out of memory errors. You can fiddle around trying to change these settings in php.ini:

max_execution_time = 30 (in seconds)
memory_limit = 128M

If this fails, or if you’re just more of a GUI kind of person, and before you take the desperate step of trying to import table by table (if you do go down this route, make sure you have exported with ‘Disable foreign key checks’ ticked with InnoDB tables, to prevent missing foreign key errors), take a look at the free, cross-platform MySQL Workbench (http://www.mysql.com/products/workbench/). We already use this as a great visual DB modelling tool which takes care of all the complexities of foreign key constraints but I have only just realised today that it’s also a great way to import large databases. Simply connect to your target MySQL DB and:

  1. Click ‘Data Import/Restore’ under ‘Management’ in the ‘Navigator’ pane;
  2. Choose ‘Import from Self-Contained File’ and browse to your sql file;
  3. Choose the pre-existing database name (from ‘Default Target Schema’) if you have already created it or allow it to create it for your if you have exported with the DB creation code in there.

It takes a while, and the progress bar doesn’t move ’til it’s finished but it does the job beautifully.

 

MSDLT Update May-Oct 2013

Teaching Excellence Project Award for mobile technology

New user-friendly interface for My Workspace Resources for Biomedical Sciences students in Sakai/WebLearn

Bringing Biochemistry TLTP software up to date

And progress with: online maths assessment; project choosing; clinical sign-off and the History of Medical Sciences website

Get your copy here: ETSGMSDLTNewsMaytoOct2013.

CakePHP 2.x: Migrate from 1.3

The CakePHP 1.3 to 2.0 migration guide does probably cover most of what we have found below…but it appears to be written by and for the developers of CakePHP rather than for people who use CakePHP to develop their own applications. So here are some of the things we are doing during our 1.3 to 2.4 migration (although I can’t guarantee that they were all actually necessary), in case they help others. Please note that this is a work in progress 🙂

Upgrade Shell

Upgrade Shell – this promised considerably more than it delivered but did at least rename the controllers and changed the case of some of the folders under app. The rest were corrected using the Folder Names section of the migration guide, including the Views sub-folders and renaming ‘helpers’ to ‘Helper’

Files to copy from 2.x code

Copy the following from your download to the same place in \app:

  • \app\webroot\index.php
  • \app\View\Helper\AppHelper.php
  • \app\Config\core.php (contains a new error handling config. info)
  • \app\Config\routes.php (contains a couple of new setting to load plgin routes and ‘default’ CakePHP routes.

Update Paginator code

In view:

echo $this->Paginator->counter(array(
    'format' => __('Page %page% of %pages%, showing %current% courses out of %count% total', true)
    ));

changes to:

echo $this->Paginator->counter(
    'Page {:page} of {:pages}, showing {:current} records out of
     {:count} total'
);

In controller, this syntax:

$this->paginate['Event'] = array(
            'order'=>array(
                ..
                ),
             'conditions'=>array(
                ..
                 )
             );

$this->set('events', $this->paginate());

changes to:

$this->paginate = array(
                'order'=>array(
                     ..
                    ),
                 'conditions'=>array(
                    ..
                     )
                 );
$this->set('events', $this->paginate('Event'));

Change all ‘action’ functions in ctp files from:

function index(){}

to

public function index(){}

Auth Component

Now required to explicitly log users in – not done automagically any more. Where we use WebAuth to authenticate and manually log users in:

if($this->Auth->login($user_id))
     {
     $this->Session->setFlash('Logged in');
     }

becomes:

$this->request->data['User'] = array_merge($this->request->data['User'], array('id' => $user_id));
if($this->Auth->login($this->request->data['User']))
     {
     $this->Session->setFlash('Logged in');
     }

Form2Helper

Although, some of this is specific to the Form2Helper (to make MySQL enum fields show enumerated values rather than a number), some of these points will apply to all helpers:

  1. Change name of helper to Form2Helper.php, instead of Form2.php;
  2. Change App::import(‘Form’) to App::uses(‘FormHelper’, ‘View/Helper’) above class definition (replacement of App::import in 2.x with App::uses)
  3. $this->_introspectModel($modelKey, ‘fields’, $fieldKey) instead of $this->_introspectModel($modelKey) (changes to _introspectModel)
  4. After an hour or two of trying to work out why the first enum input in a form was being output as a standard text box while subsequent inputs for the same field in the db, were coming out correctly as selects, I realised that:
    isset($this->fieldset[$modelKey])

    was not returning false when:

    $this->fieldset[$modelKey]['fields']

    was returning ‘null’ so I have replaced it with:

    if (!isset($this->fieldset[$modelKey]['fields'][$fieldKey]))

    and it’s all working nicely now.

Request Object

Use $this->request->data instead of $this->data

FormHelper

FormHelper::select(string $fieldName, array $options, mixed $selected, array $attributes)

becomes

FormHelper::select(string $fieldName, array $optionsarray $attributes)

and

$html->link

becomes

$this->Html-link()

Three MSDLT talks as part of IT Services ‘Make:’ series

Make: Grow your own technology – 21st Century vegetable and fruit growing

08 May from 12:30 to 13:30

Allotmentor (www.allotmentor.com) is a free online tool that Damion Young has put together in his spare time. Use it to plan your bed, allotment, smallholding or windowbox and replace that tattered vegetable notebook with something altogether more hi-tech. He will show you how it is possible to take freely available online technologies and resources (such as Google Maps, Simile Timelines, Facebook and Creative Commons images) and create something novel, without having access to a huge development team. He will also show you how, with remarkably little effort, and something called PhoneGap, you can easily transform a portion of a website into a free cross-platform mobile app so you can take your website with you, even if you do not have a signal.

Make: Turning Film Noir into Fun Noir

14 May from 12:30 to 13:30

Pauline Woolley, (Medical Sciences Learning Technologies Group) played a key part in the creation of The Blackened Mirror, a film noir drama in the form of an online video series. She will be talking about the techniques used to create the series, the use of various forms of social media in publicising the show and driving the narrative forward (with characters having their own Twitter or Live Journal streams). She will also be talking about the evolving nature of the games and how they moved from practical rewards to rewarding with information with the ultimate prizes being the opportunity to enter the environment.

Make: Medlearn – it’s not just for medics

22 May from 12:30 to 13:30

Medlearn is currently used by the Medical Sciences Division to provide interactive learning resources on a wide variety of topics. It is based on a free, open source content management system (Magnolia CMS), but has been adapted to enable easy creation of self-test questions alongside multi-media teaching content. Learning content is created and managed online, with no need to retain local templates, or upload completed packages, e.g. to WebLearn. Having recently made some significant improvements to the system, we are keen to make it available to other divisions/departments. Come along and see what you can do!