Latest Entries

Forgive the recent downtime


You'll have to excuse the recent downtime.

My previous webhost moved my domain to a new server without notifying me ahead of time (they notified me after the fact), and in the process, also moved my hosted email to another server as well. This not only resulted in unexpected downtime, but also lost email I did not have the chance to download before they did (so I lost two days).

As a result, I am moving as much over to the Azure platform as possible and have already transferred my DNS to allow for this to come back up. It took a few days longer than expected.

But as I have seen that the MVC and iCal article was popular, I did what I could to bring this domain back up at least. I think that particular article is helping people out, and now that I have this page up again on a new provider, I hope it will continue to help people out.

Thankfully I kept data sources separate from the previous web host. So all I had to do was point DNS, wait a little, and we were good to go.

Email will be a different story. But hey, as much spam was making it through unfiltered, no big loss, right?

Pseudo-Linear Gradient Top and Bottom Borders in CSS3 and LESS


While doing yet another tweak to my portfolio, I decided to see if there was a native way in CSS to get a linear gradient on a border of an element. Inherently, no. Not really.

However... there is a way to mimic this effect in CSS3 for the top and bottom borders, through the magic of the ::before and ::after pseudoclasses.

For the purposes example, I'm going to put this in LESS as well, as that's how I did it. You can use pure CSS3 if you so desire, however.

In this example, we are using a <div> element with the class foo, and having it stretch across the screen with negative margins to get around the padding-left and padding-right inherent in Bootstrap's container-fluid class.

<div class="foo">
    <p>Here is some content</p>
    <p>Here is some more content.</p>
</div>

Now, let's set up the initial CSS for foo:

.foo {
    background: linear-gradient(darken(@white, 30%), darken(@white, 5%), darken(@white, 30%));
    margin-left: -15px;
    margin-right: -15px;
    padding: 0 25px;
}

But we don't have our borders... or linear-gradient top and bottom borders for that matter. Well, this is where the ::before and ::after pseudo-classes come in!

.foo::before {
    display: block;
    height: 2px;
    width: calc(~"100% + 50px");
    margin: 0 -25px 10px;
    background: linear-gradient(to left, lighten(@black, 5%), lighten(@black, 15%),lighten(@black, 60%),lighten(@black, 5%));
content: "";
}

.foo::after {
    display: block;
    height: 2px;
    width: calc(~"100% + 50px");
    margin: 10px -25px 0;
    background: linear-gradient(to left, lighten(@black, 5%), lighten(@black, 15%),lighten(@black, 60%),lighten(@black, 5%));
content:""
}

What happens is, before the <div>, the ::before is creating a block-level element with a height of 2px, a width of 100% plus the 50px to ensure our border will be long enough to compensate for the 25px padding, and setting margins for no top margin, side margins of -25px (to further compensate for the main <div>'s padding), and a 10px bottom margin to emulate what would have been a top padding of 10px on the main div. (Note: in LESS, you will need a tilde and to put the 100% + 50px in quotes, otherwise the LESS compiler will interpret it as 150%, and that tends to cause horizontal scrollbars to show up, which we don't want. Pure CSS3 if using a .css file will not require that.).

IMPORTANT: The content: ""; part is VERY important. Without that line in your ::before and ::after, the pseudo-gradient border will not render!

The ::after on the <div> is similar but uses the margin of 10px for the top (to simulate 10px bottom padding on the main <div>), -25px side margins as before, and no bottom margin.

The linear-gradient on the background for the ::before and ::after is where the magic happens. You can set this gradient to be what you want. In this case, I'm lightening a LESS class of @black, or hex color code #000000 (or #000 if you want the shorthand notation).

Below is the non-LESS CSS version of the above:

.foo {
  background: linear-gradient(#b3b3b3, #f2f2f2, #b3b3b3);
  margin-left: -15px;
  margin-right: -15px;
  padding: 0 25px;
}

.foo::before {
  display: block;
  height: 2px;
  width: calc(100% + 50px);
  margin: 0 -25px 10px;
  background: linear-gradient(to left, #0d0d0d, #262626, #999999, #0d0d0d);
content: "";
}

.foo::after {
  display: block;
  height: 2px;
  width: calc(100% + 50px);
  margin: 10px -25px 0;
  background: linear-gradient(to left, #0d0d0d, #262626, #999999, #0d0d0d);
content: "";
}

Feel free to copy it into your code and try it out. You may need to adjust width if you're not using Bootstrap, or not using container-fluid in your Bootstrap code (or if you've adjusted the margins and padding on those classes).

Slight Redesign


Not much to report on. Just gave the site another slight redesign. Decided the reflective header was too large so I opted for the current logo placement.

I've also redone the Portfolio again somewhat, and experimented somewhat more with some CSS3 techniques. It's actually pretty cool. I'll be making another entry later in the day to explain how to do a pseudo-gradient border (top and bottom only) in CSS3 for elements.

Durandal and Google Analytics


Something I've noticed in the development of a recent application of mine, (the new front-end for DracoTal, was that Google Analytics wasn't recording all of the hits in the real-time monitor when testing on my iPhone. Granted, I had a filter set up for my home IP, but browsing through my cell network should have been landing more hits than it should have.

As it turns out, Google Analytics was only seeing the initial page hit, but not the further navigation. At least, not until the user reloaded the page, and then it would only grab the hit for the page that was reloaded. This was a bit of a problem as I wanted to see all of the hits during navigation! Fortunately I managed to get through this problem.

For the purposes of describing how I resolved this, I'll make the assumption that the code is using ASP.NET MVC as the front-end template, like say if you're using the Durandal Starter Kit as a template.

In the Home/Index.cshtml view, I had to ensure that I had this code in the head section of the HTML:

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    <meta name="format-detection" content="telephone=no" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="fragment" content="!">
    <meta name="p:domain_verify" content="390ee599d576a99e7419550da9282ae5" />
    <link rel="icon" href="http://dracotal.nodeomega.com/favicon.ico" />
<!-- snip irrelevant metas -->
    <title>DracoTal</title>
    @Styles.Render("~/Content/durandalcss")
    @Scripts.Render("~/bundles/modernizr")
    
    <!-- Google Analytics -->
    <script>
        (function (i, s, o, g, r, a, m) {
            i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
                (i[r].q = i[r].q || []).push(arguments);
            }, i[r].l = 1 * new Date(); a = s.createElement(o),
            m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m);
        })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

        ga('create', 'UA-{my Google Analytics ID}', '{specific site}');
        ga('send', 'pageview');

    </script>
    <!-- End Google Analytics -->

    <script type="text/javascript">
        if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
            var msViewportStyle = document.createElement("style");
            var mq = "@@-ms-viewport{width:auto!important}";
            msViewportStyle.appendChild(document.createTextNode(mq));
            document.getElementsByTagName("head")[0].appendChild(msViewportStyle);
        }
    </script>
</head>

That {specific site} part turned out to be very important. The default in Google's example was 'auto', which didn't work for me.

Anyways, the next step. One example by Peter Morlion I did see that referenced Durandal and Google Analtyics seemed to put the next code in the router activate method in the shell.js file like this:

router.on('router:navigation:complete', function (instance, instruction) {
    ga('create', 'UA-XXXXXXX-X', 'example.com');
    ga('send', 'pageview');
}

I have opted not to use the create line, and I wanted more control over the page title that would be sent, as the way it was, it going to be the default title not formatted for parameters in the Products page (which I wanted to record when they were hit). If not for this, I could have left it in the above call. For my purposes, instead of placing my solution in the router.on('router:navigation:complete' ...) part, I decided to place the code in my viewmodel's attached: property instead. Observe:

// Other Durandal viewmodel setup...
var section = ko.observable();

return {
    section: section,
    displayName: 'Products', // a default value for this page.
    GetProductImageByColorId: getProductImageByColorId, // later function
    attached: function () {
        var that = this;

        ga('set', { [age: window.location.pathname, title: that.displayName + ' | [Site Name]' }); // matches my Title convention for this site
        ga('send', 'pageview');
    },
    activate: function (mainsection, subsection, subsubsection, subsubsubsection) {
// more code...
}

The ugly part is, I would have to do this in every page's attached: call, and fortunately it was just a couple pages. If you have dozens of views or more, you would want to come up with a different way. For my purposes, though, this will do.

Once I implemented this and deployed, and then checked my Google Analytics realtime monitoring with my phone, I did see each individual hit recorded as I wanted them to be. And later on in the day, I checked the Behavior overview for that same day, and the site hits were in fact recorded! Granted, my analytics data for today and yesterday aren't going to be accurate, but it was in the name of testing and experimentation, and it's one of my sites, so in this case I feel it was worth it.

I suppose you could use the create line too. However, according to Google Analytics Unversal Analytics Guide section on single page applications:

Do not create new trackers in a single page app in an attempt to mimic what the analytics.js snippet code does for traditional websites. Doing so runs the risk of sending an incorrect referrer as well as incorrect campaign data as described above.

Going by that, that could skew your results depending on your goals. It seems to work fine for me without having the ga create part in the attached: portion of my viewmodel, so I'm going with that.

P.S. Yes, that activate has some pretty ugly names for parameters, but it made sense for the purposes of the site and how they were used. And yes, you can do multiple optional parameters in Durandal routing!

And to demonstrate, in the shell.js...

define(['plugins/router', 'durandal/app', 'knockout'], function (router, app, ko) {
    return {
        copyright: ko.observable('© 2009 - '
            + new Date().getFullYear()
            + ' [Site Name].  Store powered by <a href="http://www.cafepress.com">CafePress</a>.'),
        router: router,
        activate: function () {
            router.map([
                { route: '', title: 'Home', moduleId: 'viewmodels/home', nav: true },
                { route: 'Products(/:section)(/:subsection)(/:subsubsection)(/:subsubsubsection)', moduleId: 'viewmodels/Products/index', title: 'Products', hash: '#Products', nav: true },
                // Snip other routes...
                { route: 'NotFound', moduleId: 'viewmodels/not-found', title: '404 - Not Found', nav: false}
            ]).buildNavigationModel()
            .mapUnknownRoutes('viewmodels/not-found', 'not-found');
            
            return router.activate({ pushState: true });
        }
    };
});

Have fun with Durandal, and feel free to leave feedback! (Once I implement commenting on this blog, that is.)

Creating iCal files in ASP.NET MVC and C#


Recently, during a project I was working on for a client, I was tasked with adding events to a user's iCal in addition to being able to add events to Google Calendar. In researching this, I found that this would necessitate creating a .ics file for each event. Given that my client's site had hundreds if not thousands of events, that would be a lot of .ics files sitting around. There had to be a solution to this.

Fortunately, there is, and ASP.NET MVC and C# makes this surprisingly painless.

First, we need to know what goes into a iCal .ics file. This PasteBin page gives a very rough overview of a partial iCal format (the parts that I needed, as a bonus). It's at the bottom of the window, below the Google Calendar API and Yahoo Calendar API if you need those as well.

Now, while I had some of the spec, I needed a way to put it together into a .ics file. Now, there's some libraries out there, but I ran into this post at Balajiprasad's useful codes that had some sample code that I could adapt. He used a library for his solution. I opted for a simple Stringbuilder approach to start.


/// <summary>
/// Generates an iCalendar .ics link and returns it to the user.
/// </summary>
/// <param name="downloadFileName">Name of the download file to return.</param>
/// <param name="evt">The Event.</param>
/// <returns>The .ics file.</returns>
[System.Web.Mvc.HttpPost]
public ActionResult AddToICalendar(string downloadFileName, int eventId)
{
    // replace db with however you call your Entity Framework or however you get your data.
    // In this example, we have an Events collection in our model.
    using (
        var db =
            new ExampleEntities(
                ConfigurationManager.ConnectionStrings[YourConnectionString].ConnectionString))
    {
        // Alternatively, you may use db.Events.Find(eventId) if this fits better.
        var demoEvent = db.Events.Single(getEvent => getEvent.ID == eventId);

        var icalStringbuilder = new StringBuilder();

        icalStringbuilder.AppendLine("BEGIN:VCALENDAR");
        icalStringbuilder.AppendLine("PRODID:-//MyTestProject//EN");
        icalStringbuilder.AppendLine("VERSION:2.0");

        icalStringbuilder.AppendLine("BEGIN:VEVENT");
        icalStringbuilder.AppendLine("SUMMARY;LANGUAGE=en-us:" + demoEvent.EventName);
        icalStringbuilder.AppendLine("CLASS:PUBLIC");
        icalStringbuilder.AppendLine(string.Format("CREATED:{0:yyyyMMddTHHmmssZ}", DateTime.UtcNow));
        icalStringbuilder.AppendLine("DESCRIPTION:" + demoEvent.Description);
        icalStringbuilder.AppendLine(string.Format("DTSTART:{0:yyyyMMddTHHmmssZ}", demoEvent.StartDateTime));
        icalStringbuilder.AppendLine(string.Format("DTEND:{0:yyyyMMddTHHmmssZ}", demoEvent.EndDateTime));
        icalStringbuilder.AppendLine("SEQUENCE:0");
        icalStringbuilder.AppendLine("UID:" + Guid.NewGuid());
        icalStringbuilder.AppendLine(
            string.Format(
                "LOCATION:{0}\\, {1}\\, {2}\\, {3} {4}",
                evt.LocationName,
                evt.Address,
                evt.City,
                evt.State,
                evt.ZipCode).Trim());
        icalStringbuilder.AppendLine("END:VEVENT");
        icalStringbuilder.AppendLine("END:VCALENDAR");

        var bytes = Encoding.UTF8.GetBytes(icalStringbuilder.ToString());

        return this.File(bytes, "text/calendar", downloadFileName);
    }
}

And then, within the .cshtml file in question, we call it like this:


@using (Html.BeginForm("AddToICalendar", "Home", new { downloadFileName = "thisEvent.ics", eventId = Model.TheEvent.ID }))
{
    <input type="submit" value="Add to iCal" class="ical-button" id="button" /><br />
}

This will generate a form and will, when clicked, call the AddToICalendar function. It will then retrieve the event data and populate the fields in the generated .ics file with the relevant data. As a bonus, should you have the actual location data, someone on an iPhone or iPad can actually call up the map and get directions to go to wherever the event is located. (Now, how to call something other than Apple Maps as a default for this, I've yet to figure out and would prefer Google Maps or, better yet in my case, Waze).

The new Guid for the UID line will assign a new ID to this event. Now, if you had an event that would be updated, and wanted someone to just click on the same link and update their calendar that way, I would wager that you would have a different way of setting the UID earlier on and keeping it the same for the same event. I haven't had the opportunity to test this yet, but that's my understanding based on my research.

As an additional bonus, you can even download the .ics file on a Windows system and if you have Outlook, you can import that as an event into that calendar. I haven't tested this with other calendar applications or even Google Calendar. So in the end, this proved to not just be for iCal.

Also, if you wanted to keep the start and end date/times to a local time, you could remove the Z part in the string.Format strings for each, which would relegate it to be local to the user's time. Still, it's much better to use UTC-based time for everything in my opinion when feasible. As far as the formatting string, the {0:yyyyMMddTHHmmssZ} part would map to 20150601T123000Z if given a date/time of June 1st, 2015 at 12:30 UTC. Eliminate the Z part, and it becomes June 1st, 2015 at 12:30PM local time.

You'll also notice the double-backslash in front of the commas. For some reason, iCal needs those commas to be escaped, and a single backslash just won't do the job using this method. I've tested this without doing that in the generated iCal file, and it didn't work very well. On my iPhone, it would only put the LocationName before the first comma into the Location field on the Event entry in my iCal. With the proper escaping, it puts the entire address. I haven't tested what happens if you have a comma in one of those fields, so if you run into any problems you could easily put a .Replace(",", "\\,") onto the appropriate lines. The DESCRIPTION line, however, did not have this problem and did not seem to require the escaped commas, so it may just be for the LOCATION line. Have no worries about importing this into Outlook, as the backslashes won't show in the Location line on those events, at least not on my local Outlook with Office 365.

In closing, this should be a help to anyone who needs a basic iCal file to share events and reminders. I imagine the process would be similar if you wanted to make Reminders for iPhone/iPads the same way, and there are other fields that are available to put in these files too. This documentation at kanzaki.com should also be useful, however I don't really know if this is up to date. It's worth a shot though if you want to experiment. Happy coding.

Links

Archive