Tuesday, November 4, 2014

Cool MySQL for an update conditional upon a previous update within the same query block.

I was writing an update script where I needed to do a second update only if the first update actually affected the target row.

I added additional filters in the first update where clause so that it would only update on certain consitions, then I capture the ROW_COUNT() and add that to filter the where clause on the second update.

This works great so that I don't have to run additional select queries and conditionals, or more complicated SQL statements, to get my desired results.

A mock situation is outlined below. I set additional variables only to capture the state of the row_count at various places during the rest.

Keep in mind that row_count does not accumulate on multiple update statements.


set @rc = 0;
set @rc1 = 0;
set @rc2 = 0;
set @rc3 = 0;
set @rcx = 0;

drop table if exists testpeople;
create table testpeople (contactid INT(3) not null default 0, firstname varchar(100)) ENGINE=InnoDB;
insert into testpeople (contactid, firstname) values (234, 'Joe');

update testpeople set firstname = 'Joe99' where contactid = 234; ## no effect
update testpeople set firstname = 'Joe1' where contactid = 234;
set @rc = ROW_COUNT();  ## = 1
set @rc1 = @rc; ## = 1
#set @rc = ROW_COUNT();    #### If we called this again here it would clear it and prevent the next update from completing
set @rcx = @rc; ## = 1 or -1 if the line above is commented out

update testpeople set firstname = 'Joe2' where contactid = 234 and @rc = 1;  #### Could be @rc > 0 or whatever you expected from previous update
set @rc = ROW_COUNT();
set @rc2 = @rc;

select @rc as rc, @rc1 as rc1, @rc2 as rc2, @rcx as rcx, (select firstname from testpeople where contactid = 234) as endname;

drop table if exists testpeople;



That's a wrap!

Wednesday, October 29, 2014

Fullcalendar Upgrade to process Google Calendar API V3 JSON

I posted this in the Google Code issue tracker for FullCalendar. But that thing it overloaded! This may never be seen.

I hope people who need this find it. This is my best stab at implementing GCal API V3 in Fullcalendar's gcal.js. The implementation I am using on my site is slightly different than this. I have removed the old 'gcal' references and I have a custom url in the events.push function in place of the the actual entry.htmlLink value so that the edit link points back to my server. I also have my fullcalendar sources pointing to my server and my server handles all the google oauth v3 authentication and google calendar API v3 calls for events list/create/update/delete for secure communications. Because my implementation is slightly different, I have not tested this exact code below in a production environment, but the processing logic for the GCal API V3 JSON is the same. In this sample code you will also see that I tried to retain backward compatibility with GCal API V1 (even though Gcal API V1 is being deprecated in one month). In searching for upgrading my own system, all my googling finds only request for V3 implenentation. This is really needed!

Thank you Adam Shaw for the amazing FullCalendar app! I hope I can give back a little by contributing this.

I have to give a little bit of credit to this guy's gist for just the right hint to help me figure out how gcal.js was completing the events list processing into the format FullCalendar.je uses. I stumbled on this during my research. Thanks hong-jen kao!
https://gist.github.com/cades/d194af6d2be7398cad75
No explanation or comments in that gist, but looking at it and the json coming back from Google Calendar V3 API - I got it. I didn't use his .map for processing, maybe it's better/faster. I don't know.

I hope whoever needs this is able to modify it to fit their needs.

This is a sample gcal.js replacement - again, not fully tested as my implementation was much more customized, this is my best guess at intended functionality.

Best of luck!

/*!
 * FullCalendar v2.1.1 Google Calendar Plugin
 * Docs & License: http://arshaw.com/fullcalendar/
 * (c) 2013 Adam Shaw
 * Modified by G. Bach 2014-10-28
 * Changed to process GCal API V3 JSON Events list
 */

(function(factory) {
    if (typeof define === 'function' && define.amd) {
        define([ 'jquery' ], factory);
    } else {
        factory(jQuery);
    }
})(function($) {


    var fc = $.fullCalendar;
    var applyAll = fc.applyAll;


    fc.sourceNormalizers.push(function(sourceOptions) {
        if (sourceOptions.dataType == 'gcal'
        || (sourceOptions.dataType === undefined
            && (sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//))) {
                sourceOptions.dataType = 'gcal';
                if (sourceOptions.editable === undefined) {
                    sourceOptions.editable = false;
                }
        } else if (sourceOptions.dataType == 'gcalv3'
        || (sourceOptions.dataType === undefined
            && (sourceOptions.url || '').match(/^(http|https):\/\/www.googleapis.com\/calendar\/v3\/calendars\//))) {
                sourceOptions.dataType = 'gcalv3';
                if (sourceOptions.editable === undefined) {
                    sourceOptions.editable = false;
            }
        }
    });


    fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) {
        if (sourceOptions.dataType == 'gcal') {
            return transformOptions(sourceOptions, start, end, timezone);
        } else if (sourceOptions.dataType == 'gcalv3') {
            return transformOptionsV3(sourceOptions, start, end, timezone);
        }
    });


    function transformOptions(sourceOptions, start, end, timezone) {

        var success = sourceOptions.success;
        var data = $.extend({}, sourceOptions.data || {}, {
            singleevents: true,
            'max-results': 9999
        });

        return $.extend({}, sourceOptions, {
            url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?',
            dataType: 'jsonp',
            data: data,
            timezoneParam: 'ctz',
            startParam: 'start-min',
            endParam: 'start-max',
            success: function(data) {
                var events = [];
                if (data.feed.entry) {
                    $.each(data.feed.entry, function(i, entry) {

                        var url;
                        $.each(entry.link, function(i, link) {
                            if (link.type == 'text/html') {
                                url = link.href;
                                if (timezone && timezone != 'local') {
                                    url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + encodeURIComponent(timezone);
                                }
                            }
                        });

                        events.push({
                            id: entry.gCal$uid.value,
                            title: entry.title.$t,
                            start: entry.gd$when[0].startTime,
                            end: entry.gd$when[0].endTime,
                            url: url,
                            location: entry.gd$where[0].valueString,
                            description: entry.content.$t
                        });

                    });
                }
                var args = [events].concat(Array.prototype.slice.call(arguments, 1));
                var res = applyAll(success, this, args);
                if ($.isArray(res)) {
                    return res;
                }
                return events;
            }
        });

    function transformOptionsV3(sourceOptions, start, end, timezone) {

        var success = sourceOptions.success;
        var data = $.extend({}, sourceOptions.data || {}, {
            singleevents: true,
            'max-results': 9999
        });

        return $.extend({}, sourceOptions, {
            url: sourceOptions.url,
            dataType: 'json',
            data: data,
            startParam: 'start-min',
            endParam: 'start-max',
            success: function(data) {
                var events = [];
                if (data.feed.entry) {
                    $.each(data.feed.entry, function(i, entry) {

                        events.push({
                            id: entry.id,
                            title: entry.summary || '', // must allow default to blank, if it's not set it doesn't exist in the json and will error here
                            start: entry.start.dateTime || entry.start.date,
                            end: entry.end.dateTime || entry.start.date,  // because end.date may be the next day, cause a '2-all-day' event, we use start.date here.
                            url: entry.htmlLink,
                            location: entry.location || '', // must allow default to blank, if it's not set it doesn't exist in the json and will error here
                            description: entry.description || '' // must allow default to blank, if it's not set it doesn't exist in the json and will error here
                        });

                    });
                }
                var args = [events].concat(Array.prototype.slice.call(arguments, 1));
                var res = applyAll(success, this, args);
                if ($.isArray(res)) {
                    return res;
                }
                return events;
            }
        });

    }


    // legacy
    fc.gcalFeed = function(url, sourceOptions) {
        return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
    };


});

Tuesday, May 20, 2014

[SOLVED] Darwin CalendarServer Install ./bin/develop silently fails

Follow the readme's.

But it's detect_python_version() function isn't looking in the right places for python on Ubuntu.
This causes the CalendarServer/trunk/bin/develop to fail silently and frustratingly.

vim CalendarServer/trunk/bin/_py.sh

in vim do '/detect_python_version' to find the function.

Add these two bold lines at the top of the function:
detect_python_version () {
  echo "/usr/bin/python2.7";   ## or wherever yours is actually located
  return 0;

  ...
}

Then run CalendarServer/trunk/bin/develop again and it should work.


Friday, March 7, 2014

Javascript & Hash-Less Page Navigation

When adding a new row to a form I was dealing with the "Add" button being at the top of the form and the new row being added to the bottom. After the user has added a lot of new rows I wanted to user location.hash in the javascript adding the new row html to make sure the new row is in view.

However, I didn't want the hash #newrow to be left in the URL bar.

If you try to use location.hash = "newrow"; followed by location.hash = ""; you still have the "#" sign in there AND it scrolls the page position back to the top. Not good.

I found some javascript which resets the url history state without the hash:

history.pushState("", document.title, window.location.pathname + window.location.search);

Using this immediately after setting the hash works perfectly for Hash-Less page navigation:

location.hash = "newrow";
history.pushState("", document.title, window.location.pathname + window.location.search);

This works in both FF and Chrome.