Gray's Matter
Justice Gray - North America's favorite metrosexual software consultant

I Wish These People Updated More Than Once a Year

We resume our saga from the previous chapter, where the three amigos are going through some growing pains in coding the Autosave for DasBlog 1.9...
DasblogAutosavePanel03.jpgDasblog Autosave panel 03: Spider-man: 'Coming in to steal my thunder, Jesus?  What makes you more special than Spider-Man??   I don't see *you* sticking to walls and shooting webs!' Jesus: 'Well, I...' Denzel: 'Lay off Jesus, Spider-Man!!  We can use all the help we can get on this autosave.' Jesus: 'Denzel, how many times do I need to tell you...I can fight my own battles!!!  For once, let me take care of this myself.  Now step off!' Denzel: 'Sorry, Jesus.  Stepping off now.
I think EDMUG started somewhat like this

In the constructor to the ajaxdelegate, we store all of the values in an Array (which will be passed to the callback object).

this.callbackArguments = new Array();
this.callbackArguments[URL] = url;  
this.callbackArguments[CALLBACK] = callback;  
this.callbackArguments[ENTRYID] = entryId;  
this.callbackArguments[TITLE] = title.replace(/]]>/g, "]]>");  
this.callbackArguments[AUTHOR] = author;  
this.callbackArguments[POSTTEXT] = postText.replace(/]]>/, "]]>");  

[Spider-Man: 'All right, Jesus...I'll cut you some slack for now, but you've got to prove yourself to me.' Jesus: 'I've heard that one before somewhere...' Denzel: 'TITLE and POSTTEXT seem to have some funk going on.  What is that about?' Jesus: 'That's because we're using CDATA tags for storing those values.  CDATA tags instruct the XML parser to treat this as character data and not markup!' Spider-man: 'Always have to be the know-it-all,don't you, Jesus?  What a jerk!!'

As Jesus points out, later on in our Fetch command we're constructing a small piece of XML data like so:

var xmlSubmit = "<savedpostdata><entryid>"+callbackArguments[ENTRYID]+"</entryid>";
xmlSubmit += "<title><![CDATA["+callbackArguments[TITLE]+"]]></title>";
xmlSubmit += "<author>"+callbackArguments[AUTHOR]+"</author>";
xmlSubmit += "<posttext><![CDATA["+callbackArguments[POSTTEXT]+"]]></posttext>";
xmlSubmit += "</savedpostdata>";                
request.open("POST", this.url, true);
request.setRequestHeader('Content-Type', 'text/xml');
request.send(xmlSubmit);

You might be thinking that this isn't a big deal, but the XML parser that needs to read this data will *break* on any occurence of the &nbsp; character (of which there will be many, unless you like writing your posts without any spaces).  That's just one potential problem Jesus and Denzel are avoiding by wrapping these values in a CDATA tag.  

Now let's get back to that weird-@$$ed callbackArguments initialization code.  There is still one way the XML parser can break: if we insert a "]]>" character sequence somewhere in the text of either the title or the HTML content of the post text itself.  You see, CDATA tags are bordered with "<!CDATA[" on the left side, and "]]>" on the right side.  If we threw an extra "]]>" in the middle of this, we'd end the CDATA segment early and pass malformed XML to the AutoSaveEntry handler.  Thus, we use a regex that looks for any instance of "]]>" and escapes it to "]]&gt;", &gt; being the escape sequence for the right angle bracket (greater than).  While it's very likely that the user is only typing this sequence when making a typo or a deliberate attempt to break the system, it's better for us to be safe than sorry.

In the request, we set our request method to "POST" (as we really don't want to be shoving this in the query string), and set our 'content-type' to 'text/xml', so that the request knows what format this should be sent in.  And then we are off!!!

[Before we go on, the exciting part of something like this is that you can actually accomplish the remainder of our auto-save with *any* sort of server-side language that allows some sort of writes to storage (read: pretty much all of them).  DasBlog uses XML to store its entries, but you could just as easily use PHP, Java, or whatnot to read our XML and store it somewhere.  The remainder of this text goes into our ASP.NET HttpHandler specific solution.]

Jesus: 'I don't need to take this, Spider-Man! Aside from being a master haxx0r, I have also come back from the *dead*.' Spider-Man: 'Big deal!  I've come back from the dead too!!  Just check Spider-Man #17, 1991.' Jesus: 'Yes, but you're a *fictional character*, Spider-Man.' Denzel:  'HA!!  You tell him Jesus!!
Jesus Christ pwns the n00bs

After this exchange, the group bands together and whips up a brief HttpHandler that would take incoming requests and use those requests to save data.  The first step: to define a new class called AutoSaveHandler, that implements the IHttpHandler interface.  After this, they put an entry in the web.config to ensure that any requests to autosaveEntry.ashx were handled like so:

<add verb="*" path="autoSaveEntry.ashx" type="newtelligence.DasBlog.Web.Services.AutoSaveEntryHandler, newtelligence.DasBlog.Web.Services" />

Now we didn't get any failures when hitting "autosaveentry.ashx", but it still isn't doing anything.  So they add some simple code to the ProcessRequest() method.

First, we read the XML data received from the AjaxDelegate.  We use an XmlTextReader since it's less overhead than the XmlDocument class, and we only need forward read-access anyway.  

System.Xml.XmlTextReader xtr = new System.Xml.XmlTextReader(context.Request.InputStream);
            try
            {
                while (!xtr.EOF)
                {
                    xtr.Read();
                    if (xtr.Name=="entryid")
                    {
                        entryId = xtr.ReadInnerXml();
                    }
                    if (xtr.Name=="author")
                    {
                        author = xtr.ReadInnerXml();
                    }
                    if (xtr.Name=="title" && xtr.NodeType == System.Xml.XmlNodeType.Element)
                    {
                        xtr.Read();  // Brings us to the CDATA inside "title"
                        title = xtr.Value;
                    }
                    if (xtr.Name=="posttext" && xtr.NodeType == System.Xml.XmlNodeType.Element)
                    {
                        xtr.Read();
                        textToSave = xtr.Value;
                    }
                }
            }
            finally
            {
                xtr.Close();
            }

Once we've read all of these values into their respective variables, we save our entry, provided that our entryId value isn't null or empty.  There are two cases we need to take care of here.  If this is a new entry, we need to create a brand new entry object, assign it our parameters, and call the SaveEntry() method of the DataService.  If it's a previously edited/autosaved entry, we get the existing entry, change its Modified time, and save it.  

Entry entry = dataService.GetEntry(entryId);
if ( entry != null )
 {
     entry = dataService.GetEntryForEdit( entryId );
     Entry modifiedEntry = entry.Clone();
modifiedEntry.Content = textToSave;
modifiedEntry.Title = title;
modifiedEntry.Author = author;
modifiedEntry.Syndicated = false;
modifiedEntry.IsPublic = false;
modifiedEntry.ModifiedUtc = DateTime.Now.ToUniversalTime();
modifiedEntry.Categories = "";
dataService.SaveEntry(modifiedEntry, null);
}

                else // similar code, but creating a new entry instead.

Whew!!  With this in place, we now take a large circle back to our Javascript callback function AutoSaveResult() (mentioned in part 1), which does absolutely nothing but write a small note to the label we inserted under the textbox, saying "Post auto-saved: [current date and time]".  Two notes here:
the "post auto-saved" part is actually localizable; we're grabbing it from a variable called autoSaveText which has been set to the value in our .NET localization tables like so:

string autoSaveText = resmgr.GetString("text_autosave");

sb.Append("function AutoSaveResult(url, response)  " + "\n");
sb.Append("{ \n");
sb.Append("var dt = new Date(); \n");
sb.Append("document.getElementById('" + this.autoSaveLabel.ClientID + "').innerHTML = '" + autoSaveText + " ' + dt.toLocaleString(); \n");
sb.Append("} \n");
sb.Append("</script> \n");


With all of this in place, the Autosave is ready to run!!  Thanks to the triumphant trio for figuring this out!  The sad truth, however, is that due to the conspiracy of silence surrounding DasBlog, I don't think it'd likely that we'll see a "Thanks to Spider-Man" from Scott Hanselman anytime soon.  But our group has moved on to other projects, as you can see...

Spider-Man: 'Well, Jesus, I have to admit it, you sure worked a miracle on *this* project.' Denzel: 'No doubt!  You were basically the Savior of Autosave!!' Jesus Christ: 'Thanks Denzel and Spider-Man.  I'm glad I could make a believer out of you guys, and glad that we've added to DasBlog!  We make a good team!  So what's next for us?' Spider-Man: 'I could use some help with the Green Goblin if you guys aren't too busy!!' Jesus Christ: 'We're on it, Spider-Man!!  Green Goblin, Venom, Dasblog, Mysterio - they don't a chance against Jesus Christ, Spider-Man, and Denzel Washington!'
And so great teams are born...

Friday, August 25, 2006 #


As you might have heard, DasBlog 1.9 is almost out and with it comes a pretty nifty feature - the ability to autosave drafts of your blog posts!  

I really didn't want to come out and say this publically, but Scott Hanselman is a liarSteven Rockarts and I actually had nothing to do with the autosave feature.   I've been under a lot of pressure to reveal the *true* authors of the autosave code and tell their story...the guilt has been too much for me, and so I'm coming clean.  I hinted at their identities in my last Dasblog post on previousEntry and nextEntry, but now I am just coming out and saying it so that no matter what Scott says, *you* know the real story.

Without further adieu, the Dasblog autosave team:  Denzel Washington, Spider-Man, and Jesus Christ!!!

DasblogAutosavePanel01.jpg
The beginning of great things

Denzel and Spider-Man start with the page that has our textbox edit control in question: EditEntry.ascx.  They make two changes here.

1) Adding a label underneath the textbox control in EditEntry.ascx named AutoSaveLabel (this comes into play later).
2) Adding a new method in the code-behind called SetupAutoSave().  SetupAutoSave uses a StringBuilder to write a block of Javascript to the EditEntry page, consisting of 3 different functions:

  • StartInterval() - sets a timer to call AutosaveTimer() once a minute.
  • AutosaveTimer() - this is where the "heavy lifting" happens (detailed later).  
  • AutoSaveResult() - this is our callback function, which will activate on the completion of the Fetch() call of the AjaxDelegate.  The only thing this does is output a message to the AutoSaveLabel.
To save an entry, they need the following information:
  • The text of the post (obviously)
  • The ID of the post (needed to use the DataService SaveData() in DasBlog).
  • The author of the post
  • The title of the entry

To get at the values of the textbox controls for Title and post text in Javascript, they write the following:

sb.AppendFormat("var editTextBox = document.getElementById('{0}');", this.editControl.Control.ClientID);
sb.AppendFormat("var titleBox = document.getElementById('{0}');", this.entryTitle.ClientID);

                
This then ensures that the javascript variables for editTextBox and titleBox refer to those controls.  

At the tail end of the code, they create a new AjaxDelegate that takes the arguments we've listed above, and a couple of others:
* the url we're going to hit asynchronously (in this case, "AutoSaveEntry.ashx")
* our callback function

sb.AppendFormat("var ajax = new AjaxDelegate(url, AutoSaveResult, '{0}', titleBox.value, '{1}', editTextBox.value, label);", CurrentEntry.EntryId, this.Context.User.Identity.Name);

At this point, things got a little ugly...

DasblogAutosavePanel02.jpg
To be continued...

Wednesday, August 23, 2006 #


For those of you who use DasBlog, this might be interesting.  For the rest of you this will be boring, which is why I've spiced it up with random imagery:

Marvel Civil War #03
This picture has nothing to do with DasBlog, but I need something here for the non-technical readership

While most of you reading this through RSS will be unable to tell, I'm made some slight additions to this blog to enable navigation to past entries and future entries (for an example, either click on one of the popular posts to the left or the title of this entry).  How else to ensure that my classic Charles Atlas post is not eternally lost to the ages?  As well, the "recent entries" that are listed on the right side of this page are tweaked in comparison to the standard <%frontpagetitlelist%> macro that comes with DasBlog.   These macros may or may not show up in the upcoming 1.9 release of DasBlog, but in the event you're in a rush and want to use them now, you can download this DLL to use the following macros:

Denzel Washington - The Black Justice Gray
Denzel Washington - The Black Justice Gray
Previous Entry and Next Entry

Sample syntax: <%previouslink("&lt;&lt; ")|codivation%>, <%nextlink(" &gt;&gt;")|codivation%>

These display a link to the previous entry and the next entry.  They *only* display if the user has loaded a page with a single entry on it, i.e. a permalink.  After all, it's pretty useless to have previous and next links if you're on the main page of the site, or looking through a full category worth of entries.  Obviously, if you are on the most recent post the Next Entry Link will not display, and if you are on the very first post you ever made the Previous Entry Link will not display. 

The string argument indicates what you'd like added to the link text (left side for the previouslink, right side for the nextlink).  e.g. Using the sample syntax with a post creatively called "My Post" will produce "<< My Post" or "My Post >>" depending on if it is next in line.

My collection of rarer Transformers
Note the packaged Megatron in the top left corner
PreviousNextSeparator

Sample syntax: <%previousNextSeparator(" | ")|codivation%>
Depending on your formatting desires, there is also a PreviousNextSeparator macro that will display a separator string of your choice if:
a) the user is on a page with only one blog post (e.g. a permalink)
b) that page is not displaying either the most recent blog entry or the earliest (because, obviously, you're not going to have both a previous and a next at the end of the spectrum).

e.g. <%previousNextSeparator(" | ")|codivation%> = " | " when in the situations described above.  I don't use this myself but it might be helpful depending on how you choose to style your blog.

Jesus Laughing.jpg
Even Jesus likes these macros!
RecentEntries

Sample syntax: <%recententries()|codivation%>
This is basically identical to the FrontPageTitleList macro with 2 notable changes:

a) the table cell alignment no longer defaults to "center", so that you can use CSS to align it
b) the links it produces are now permalinks to individual entries (which happens to work well with the previous and next links)  rather than anchor tags on the main front page.

2 more steps and you are all set.
1) Uncomment the following line in your web.config:
<section name="newtelligence.DasBlog.Macros" type="newtelligence.DasBlog.Web.Core.MacroSectionHandler, newtelligence.DasBlog.Web.Core" />
2) Add the following line to your web.config:
<add macro="codivation" type="Codivation.DasBlogExtras.DasBlogMacros, Codivation.DasBlogExtras" />

Now you are ready to bring your blog to a new unprecedented level of popularity!!

Saturday, August 12, 2006 #


Steven recently brought to my attention that there is a "new" piece of blogging software out there called ThinkJot.  I use new in quotes because it's apparent from the web page that the author has taken the DasBlog 1.8 source and then made several modifications of his own in order to make it more user-friendly.  Some of these changes probably *are* pretty useful, like the DatePicker control (I'm confused too - why would a DatePicker control want full trust?), and I'm all for making the install of DasBlog a *lot* easier for an end user.  However, I guess I have some concerns about his reasons for forking the source:

Lots of source code changes. It would be difficult to get it on to the dasBlog source tree without risking its stability.

Why not just make small changes gradually?  Or even just ask the DasBlog dev team about it?

I wanted to provide a lighter version of dasBlog, removing all the features that nobody used. That includes the support for the other blogging APIs.

Considering that the blogging APIs are used in a lot of remote posting applications (BlogJet, w.Bloggar, and now Performancing), is it really safe to say no one is using this feature?  To go even further with it, how does Jeswin know which features are being used and which aren't?  I don't recall any call to the current DasBloggers to ask which features they didn't really use all that often.

.Net version 1.1 is not a concern. The application will target v2.0.

You're worried about having an easy install for users but you don't care about the users that only have 1.1. hosting?  I'm not saying that this is common, but how is the "average" user you're targeting going to really know the difference here?  Wouldn't it be better to have it compatible with both?

Speeeed! I have plans to add features or modifications, which may take time to be integrated to the main source tree since it might break a lot of other code. Planned features include multi-user support, and maybe an optional Sql Server backend. Maybe, support Asp.Net's new theme system.

There's already multi-user support.  I built it.  And again, if you're thinking about other backends, why not just work with the existing developers?

Right now, I don't consider dasBlog to be very shared-hosting friendly. Yes, it needs full-trust, while most ISPs will allow only medium trust. I think it is important to provide a version for people who don't own their boxes.

This is the only idea I like, but realistically then, you should be supporting both 1.1 and 2.0 for those people who want to install DasBlog but their server only has one version.  Once again, not saying this is common, just good practice.

Saying that "in the long term, ThinkJot will move away from the original dasBlog source code",  implies that all you've done is make some minor project ports, throw in a new DatePicker and call this your own project.  For sure, it's probably legally fine given the terms of the open-source license, but it leaves a bad taste in my mouth.  How would you feel if you worked on something for a long time and then someone just took all the work you did, rebranding it and called it their own?  I mean, c'mon!



Wednesday, January 25, 2006 #


Hmm - well, at least I can post from w.bloggar, which is also an excellent free-standing utility! Anything that allows me to avoid using the HTML text box. ;)
Monday, January 23, 2006 #


Today, I tried out Performancing's plugin for Firefox.  Originally, I was really excited that I could post directly from Firefox using an easy Blogjet-like API but without having to pay a ridiculous amount of money.  I pointed Performancing to the Blogger API on DasBlog, gave it my credentials and it seemed we were good to go.  However, all of my attempts to publish my post failed.  Every time I published, the Performancing plugin would indicate success, but when going to my blog I would not see the new posts.

Any DasBloggers out there know what I'm doing wrong?  Have you been able to get it to work?

Monday, January 23, 2006 #


I know Scott has already pointed to this, but I wanted to congratulate Tom Watts for doing so much work in the last little while in terms of getting some of the documentation for DasBlog centralized.  Finally, some macro documentation too! This is awesome and you definitely deserve a bunch of praise.

For those of you who haven't seen Tom's work, go to the following:

  • http://dasblog.us
    • The new user forums for DasBlog.  
  • http://dasblog.info
    • Documentation for DasBlog.  At last, no more broken wiki links that make me cry!  And hey, thanks for the shout-out there, too!
You rock, Tom!  Thanks again.

Friday, December 09, 2005 #


As referred to in my earlier DasBlog plugin post, I've been working on setting up a friend's blog over the last little while.  His old blog was on Blogger and we've moved him over to a hosting solution with Dasblog.  However, he came from a Blogger background and was used to certain features of Blogger - namely, the ability to have multiple contributors to his blog.  Now, DasBlog supports multiple users, but in its current state it required every user to be an administrator.  Maybe in the case of just 2 people running the blog you can run like that, but when you have 5 different people posting and having configuration/edit/delete rights on every post, you can see the potential for disaster in the making.

As this friend is a good one, I began looking through the source code to see how hard it would be to add a different "contributor" level of access into DasBlog...one that couldn't wreak havoc on the blog itself!  The end result was the addition of a "contributor" role in DasBlog.  This role only has permission to add entries to the blog, and edit/delete on only the posts that they themselves have posted - pretty much what Blogger has in a uniquely DasBlog way!  :-) 

The code changes have already been checked into DasBlog, so whenever the new release comes out, you can enjoy the "contributor" level of access.   if you're one of those early adopter types (and aren't we all?) I am posting the altered binaries here on the site so you can download them for yourself.  Here are the instructions for altering your release of DasBlog 1.8.  I can't guarantee anything with an earlier build!  Please keep in mind that you're testing out something without an official release as of yet!  I'm willing to help debug, but you are still entering this scenario at your own peril.

Instructions:

Download the zip file.

  1. Take the three DLLs inside the zip and load them into your dasblog/bin folder.  Overwrite your existing DasBlog binaries with these three.  These changes allow for correct behavior of the "contributor" role in your DasBlog.
  2. Move the aspx files inside the "ftb" folder and replace your aspx files inside your dasblog/ftb folder.  You'll need to do this in order to give the "contributor" role appropraite permission to use the image gallery uploader, etc. etc.
  3. Edit your sitesecurity.config file, adding the user whom you wish to grant "contributor" level access.  Instead of the "admin" role, give them a "contributor" role instead.
  4. Load your blog, log in as the "contributor" and watch the magic at work!

I don't know how many people really wanted or needed this feature in DasBlog, given DasBlog is largely used when setting up technical blogs (given the background you seem to need to even set this thing up in the first place!  ;) )  However, it's now here and you can use it if you like!  Definitely let me know if you have any problems.

Dasblog1_8_with_contributors.zip (155.9 KB)
Monday, October 31, 2005 #


In an enhancement that I'm pretty sure only one person was actually clamoring for, I've created a custom macro for DasBlog 1.8 that will display a picture on each post that is related to the user who created that post.  This allows for a small customization (a la Penny Arcade) to differentiate between posters.  Here are the instructions for its use:

1)  Download the zip file at the bottom of this post, and place the unzipped DLL into the /bin directory of your DasBlog installation.

2)  You'll need to uncomment or add the following text in the web.config file located in your DasBlog root directory:

<section name="newtelligence.DasBlog.Macros"
type="newtelligence.DasBlog.Web.Core.MacroSectionHandler, newtelligence.DasBlog.Web.Core" />

3)  Uncomment or add the following section to your web.config file (the default web.config I received with DasBlog had this section in it already, just commented out). 

<newtelligence.DasBlog.Macros>
<add macro="userimage" type="Codivation.DasBlog.Plugins.UserImageMacros,Codivation.DasBlog.Plugins"/> </newtelligence.DasBlog.Macros>

4)  Wherever you want to be placing this picture in your itemTemplate.blogtemplate file, add one of the following:

a)  <%UserImageControl(format)|userimage%> - format should be the file extension you're going to be using, be it .gifs, .jpgs, or otherwise.
b)  <%UserImageControlWithDimensions(format,x,y)|userimage%> - where format is the file extension,
x is the image width, and y is the image height.

5)  Place the image file you wish to represent that user in your binaries directory (this should be /content/binary).  Ensure that the name of the file is the same as the name of the user you want it associated with: (e.g if your user's name is "justicegray", make sure that the image file in the binaries directory is "justicegray.xyz", where "xyz" is the file extension you chose).

There are a couple of limitations with this; one is the restriction on the name of the file in step 5, while the second is that for multiple users, every user has to have the same file type: everybody has gifs or everybody has jpgs, etc.  Hopefully I'll be able to rectify this down the road in a future release!

Codivation.DasBlog.Plugins.zip (2.04 KB)

 

Monday, October 24, 2005 #