Thursday, February 18, 2010

TinyMCE and jQuery: The onLoad conundrum

In a recent project, I've had the challenge of trying to get the usage of a WYSIWYG editor, TinyMCE to actually be WYSIWYG by applying the styles that are used on the website within the editor itself.

This was relatively easy. I have an id attribute called #contentpad that contains most of the content that users are allowed to edit. So I updated my tinyMCE init options to include my public.css, and then I made sure that where ever I used #contentpad, I also used .mceContentBody. I did have to update a few other elements to suite, since styles applied to body for the website were not necessarily appropriate for applying to the body of the TinyMCE iframe.

It's not the tidiest of solutions, since I know I'll run into troubles if I ever switch WYSIWYG editors (I've been having a gander at CKEditor).

The next bit was harder. A part of making the editor closer to WYSIWYG is adjusting the width of the editor so it is more like what is available on the actual website. The thing is, we have a couple of spots where the width can vary.

The solution is simple enough once TinyMCE is loaded. We already had a solution to use jQuery to monitor the selection of a checkbox to change the width, after the TinyMCE editor was loaded.

 // check when clicking the option
  if ($(this).attr('checked')){
   $('iframe#UnpublishedContentContent_ifr').contents().find('.mceContentBody').css('width', '366px');
  } else {
   $('iframe#UnpublishedContentContent_ifr').contents().find('.mceContentBody').css('width', '766px');

However, the bit of code we had to check apply the same setting when the page was first loaded did not work.

  // check when first time on the page
  if ($('#UnpublishedContentSetWidthForDualHomePage').attr('checked')){
   $('iframe#UnpublishedContentContent_ifr').contents().find('.mceContentBody').css('width', '366px');

There were many suggestions out there, like having Javascript in the iframe to notify when it had loaded, or load the iframe via jQuery, or even use LiveQuery. However, none of them really suited, since I had no control over the iframe.

Eventually, I found a reference to the TinyMCE oninit config item that will run a Javascript callback when the editor has finished loading.

So I wrapped the above code in a tinyMCECallback() function, and added the oninit property to the TinyMCE config, and it worked! Make sure you don't include the parentheses in the oninit call (i.e. oninit : "tinyMCECallback"), and make sure you include the callback code before including the tinyMCE.js.

Just as a note for the numbers used in my widths. The physical width available on the public website is 800px, so I added 2px to cater for the TinyMCE margins. The available content space actually has a margin of 17px either side, to I set the .mceContentBody to cater for this, and reduced it to 766px. This makes the editor look a bit more natural, with regards to the margins.

Wednesday, February 10, 2010

jQuery, innerHTML and IE8 follow up

I thought I'd better do a test script to follow up on my earlier post about Silverlight, jQuery and IE8.

And it looks like IE8 definitely has a problem when it comes to dynamically generated block objects and innerHTML, unless I've got a problem in the script that Firefox seems to handle without complaint.

Anyway, here's the script I put together.
    <script type="text/javascript" src="jquery-1.4.1.js"></script>
<p>Using plain old JA to insert an inline object into a dynamically generated block object, via innerHTML</p>
<p><img id="plain" src="video.jpg"></img></p>
<p>Using plain old JA to insert an inline object into a dynamically generated block object, via jQuery</p>
<p><img id="flat" src="video.jpg"></img></p>
<script type="text/javascript">
//doing it the manual way via jQuery
$(document).ready(function() {
 var collection = jQuery("img#flat");
 if (collection){
  var i = 0;
  collection.each(function() {
   var id = "flat" + i;
         var newDiv = 
          "<div id=" + id + " >" +
           "If you're reading this, it did not work." +
         $('div#'+id).html('<p>It worked! The id is ' + id + ' but different.</p>');
// doing it the manual way via innerHTML
$(document).ready(function() {
 var collection = jQuery("img#plain");
 if (collection){
  var i = 0;
  collection.each(function() {
   var id = "plain" + i;
         var newDiv = 
          "<div id=" + id + " >" +
           "If you're reading this, it did not work." +
         document.getElementById(id).innerHTML = '<p>It worked! The id is ' + id + '.</p>';

I would seem that if jQuery can manage to change some innerHTML without running into problems in IE8, then perhaps the Silverlight developers could take a leaf from their book and do the same thing.

Silverlight, jQuery and IE8

When I'm writing the ability for end users to include video in their content via TinyMCE, I like to try and keep it as simple as possible.

Usually, the user has their video file and a preview image to display via the player.

What I usually do is allow a <a> or <img> tag to be the place holder for the video file, and then I use jQuery to find <a> or <img> tags with the particular extension in mind, and replace that with a <div> tag to used as a placeholder for the player.

Up until now, the video files have typically been .flv, and I've been using the JW Player to play the .flv file within a Flash player.

But my latest project has the user wanting to play .wmv files, because they're not terribly tech savvy and are putting the movie together with MS Movie Maker. So I started down the road of doing a similar solution for .flv files, but using the Silverlight JW Player option. The Longtail products have been pretty solid and a single license will allow you to use the Flash and Silverlight players on your public website.

So off I went, did the coding, did the usual testing in Firefox. This time I used the <img> tag, rather than the <a> tag, and allowed the height and width attributes from the <img> tag to be passed to the Silverlight player.

I then thought I'd better take a look in IE, just to make sure. Typically, I run IE8, but always have it in compatibility mode, and always force the websites that I build to indicate that it should use compatibility mode. This is mostly because I refuse to deal with another browser option while IE6 and IE7 are still being used as much as they are.

Well, it didn't work. In fact, I was getting an "Unknown runtime error" from the silverlight.js provided by Longtail. I updated the silverlight.js to the latest one provided on the MSDN website. Still no joy.

After a bit of a search around, I came across this article. So it seems IE8 will crack the shits if you try to place a block object into an inline object via innerHTML. And specifically, it applies to the definition of the object when it was loaded, not what it looks like at runtime (so using CSS to force the display property to something else doesn't work).

In my case, <img> tag, an inline object, will be replaced with a <div> tag, and then Silverlight will replace the contents of the dynamically generated <div> tag with the ≶object> tag needed to display the player. It looks like IE8 is having issues with the dynamically created <div> tag, because it wasn't there at load time, and is unable to correctly identify it as being a block object. However, even this doesn't fit, because the <object> tag is an inline object, and you're allowed to insert those into a block object.

In the end, I've had to give instructions to the users to change the <img> tag to a <div> tag (and provide a proper closing </div> tag) and allow TinyMCE to save these tags with src, height and width attributes. IE8 doesn't seem to have a problem if the <div> tag is there from the get go.

I'll probably do a bit more digging around, since it will mean interesting things for jQuery libs that insert inline objects into dynamically generated block objects via the innerHTML property in IE8. Maybe jQuery does a different DOM manipulation for IE.

Wednesday, February 3, 2010

Oh, bloody glorious syntax highlighting

Finally! I've found a syntax highlighter that works great in Blogger!

Alex Gorbatchev has done a great job putting together the SyntaxHighlighter.

And Carter Cole has put a nice article together for how install it in your Blogger template.

For my own reference, here's what I do.
<pre class="brush: php">
echo "sweet sweet syntax highlighting"; 

Results in

echo "sweet sweet syntax highlighting";

Update: 24-Oct-2011. Originally, this used a <script> tag and a special type. I've since come the conclusion that it does not degrade nicely, and it's better to use <pre> tags. Fortunately, the same syntax highlighter still works. I just need to revisit all my code and make sure it is escaped appropriately.

Bootstrapping Lithium for a more flexible location

I've just started fiddling around with Lithium, a relatively new PHP framework that takes advantage of namespaces in PHP 5.3. At the time of writing, I'm playing with the 0.5 release.

Lithium, and it's collaborators, has it's roots in CakePHP, a framework that has pulled me out of the PHP doldrums, with a reasonable MVC implementation that is easy to get running, and doesn't make me want to tear my eyes out.

Lithium as also introduced me to Git, a distributed source control tool that has been around for quite a while, but is gathering momentum (if it hasn't done so already), as a replacement for SVN.

One of the things I look at when developing a new project is the ability for multiple developers to share the code base without having to configure it specifically for their environment. For example, there are only three developers where I work, and we use SVN. It's nice to be able to checkout a project, set up a virtual host on our local Apache installations and hook into the same database for development or bug fixing. If we're all running off the same codebase, then I shouldn't have to have a specific config just for my machine. In the few cases were we do, we usually have some sort of config directory with configs for each of our systems, so at least that is committed to the SVN, and we make sure the appropriate config file is installed for the particular environment.

One of the things that I want to do, and haven't been able to do with CakePHP, is keep the framework separate from the application, to the point where I do not check in the framework with the project. With Lithium, it looks like this a possible thing to do. I'm hoping that I'll be able to use the PHP include_path to control which version of the Lithium framework the application uses, and then I can use Git to update the Lithium frame, and Git on my application, and neither have to worry about getting in the way of each other.

I should point out that most, if not all, of my development work is done on a Windows XP machine. However, most of my production deployments go to Linux installs. Platform independence is something I always look for, but it's not always a given. I'm pointing this out, just in case you see something that doesn't look right.

My first task, before starting on my new app, was to get Lithium. After a little messing around with ssh keys, I managed to clone the Git rep into c:\dev\libs\lithium.

I then created the basics of my project in c:\dev\projects\appbook and copied the c:\dev\libs\lithium\app directory in there. I also set myself up a VirtualHost in Apache for the app, and set the include path to include the Lithium framework.

php_value include_path .;c:\dev\libs\lithium

The next part was to change the bootstrap so that the include_path could be used. The default bootstrap assumes that the libraries directory containing the Lithium framework will be at the same level as the app directory. In my case, that's not so. What I didn't want to do is hardcode the directory of the lithium framework in the bootstrap, because if someone else wants to work on this, then they'll have to customize the bootstrap for their environment.

So, I let the include_path do it's job and changed the one liner for the LITHIUM_LIBRARY_PATH to the following:

$path = dirname(dirname(__DIR__));
$libraries = realpath_ip(DS.'libraries'.DS.'lithium'.DS.'core'.DS.'Libraries.php');
if ($libraries) {
  $path = str_replace(DS.'libraries'.DS.'lithium'.DS.'core'.DS.'Libraries.php', '', $libraries);
define('LITHIUM_LIBRARY_PATH', $path.DS.'libraries');

This makes the system lookup a known Lithium framework file on the include_path, and determines the appropriate real path from there. realpath_ip() is a function that mimics what realpath does, except it searches the include_path. This was an adaption of file_exists_ip(), a handy bit of code found in the comments of file_exists().

I had to use the platform independent DIRECTORY_SEPARATOR, otherwise the str_replace() would not work.

And here's the function, which I put at the bottom of the app/config/bootstrap.php.

function realpath_ip($filename) {
  if(function_exists("get_include_path")) {
   $include_path = get_include_path();
  } elseif(false !== ($ip = ini_get("include_path"))) {
   $include_path = $ip;
  } else {return false;}
  if(false !== strpos($include_path, PATH_SEPARATOR)) {
   if(false !== ($temp = explode(PATH_SEPARATOR, $include_path)) && count($temp) > 0) {
       for($n = 0; $n < count($temp); $n++) {
           if(false !== @file_exists($temp[$n] . $filename)) {
               return realpath($temp[$n] . $filename);
          return false;
      } else {return false;}
  } elseif(!empty($include_path)) {
      if(false !== @file_exists($include_path)) {
          return realpath($include_path);
      } else {return false;}
  } else {return false;}

I'm hoping that when the next release of Lithium is made in two weeks time, I can update, but not have to make any changes to the work done so far, because of it (unless there are major changes in the framework structure, and I'm using namespaces that no longer exist).

Note to Self: Get a plugin or something to help edit and display PHP code, without having to change greater than or less than signs to html entities.

MongoDB as a Service on Windows

If you're going to install MongoDB as a service on Windows, then remember to include the whole path name when you do the command. Also set your dbpath and anything else you want to do at this point.

C:\mongodb\bin\mongod --dbpath c:\path\to\db --install

And if you're like me, and have already tried it without the full path, you can either uninstall the service (sc delete MongoDB) and reboot your computer, or look up the registry and edit the path key (HKEY_LOCAL_MACHINES\SYSTEM\CurrentControlSet\Services\MongoDB\ImagePath).

Unfortunately, the --remove option only seems to disable the service, not actually remove it.

I wish I'd had the good sense to edit the registry key, instead of deleting it with the hope of not having to reboot.