Knockout.JS: AsDictionary

I frequently find that I have an array of objects in JavaScript that I want to display in a particular order and also have the ability to quickly locate an object by an ID or a key (and not use the indexOf function). As my recent project is using Knockout.JS, I decided to throw together a function that makes having keyed lookups based on an array simple to maintain.

Here’s an example ViewModel definition:

var ViewModel = function () {
    this.uniqueNumber = ko.observable();
    this.list = ko.observableArray([]);      
    this.list_by_keys = this.list.asDictionary('id');
};

The definition includes an array, a unique number (more on that a bit later), the list (to which the code will bind the UI to), and finally the keyed list.

After a few attempts at a good name, I settled for something that I hated the least. Smile In any case, usage is simple.

After creating the observable array:

this.list = ko.observableArray([]); 

The code creates a second field which will contain all of the objects in the original array, but in a quickly accessible index (thanks to the nature of JavaScript objects).

this.list_by_keys = this.list.asDictionary('id');

In the preceding line, the asDictionary function (which I’ve added to the observableArray definition as you’ll see below) is used and passed the string ‘id’. The ‘id’ is the name of the property of the JavaScript object that is later added to the list that will contain the key (the primary key, although it’s not checked for duplicates).

As you’ll note below, an instance of the ViewModel is created and bound to the UI.

var vm = new ViewModel();
ko.applyBindings(vm);

$("#btnAdd").on("click", function () {
    var id = vm.uniqueNumber.inc()();
    vm.list.push({ id: id, 
                   time: new Date().toLocaleTimeString() }); });

With a click of a button (using jQuery syntax), a new sample object containing an ‘id’ and ‘time’ property is added to the master list. When the new object is added, the asDictionary code is executed. Why? Because of the use of the computed function as shown below. Knockout.JS has computed observables which automatically track dependencies and execute any time that the source property changes. In this case, it’s tracking the “this” object, which just happens to be the observableArray (list).

ko.observableArray.fn.asDictionary = function (keyName) {
    return ko.computed(function () {
        var list = this() || [];    // the internal array
        var keys = {};              // a place for key/value
        ko.utils.arrayForEach(list, function (v) {
            if (keyName) {          // if there is a key
                keys[v[keyName]] = v;    // use it
            } else {
                keys[v] = v;
            }
        });
        return keys;
    }, this);
};

The function loops through each of the elements of the array and stores each object by the key (if provided, otherwise by the value).  Unfortunately, because there are many ways to adjust an array in JavaScript, this isn’t as efficient as I’d like. Every time something is added to the array, the entire “dictionary” is recreated. While this isn’t terrible in reasonable cases, it’s still a bit annoying. You could add a bit of code to disable the rebuilding conditionally though if performance is going to be a big concern.

I also was experimenting with a unique number generator. It’s really quite dumb, but I ‘m posting in nonetheless.

ko.observable.fn.inc = function (incExtra) {
    incExtra = incExtra || 1;            
    var current = this() || 0;
    current += incExtra;            
    this(current);
    return this;
};

ko.observable.fn.dec = function (decExtra) {
    return ko.observable.fn.inc(decExtra || -1);
};

To use it and retrieve the value, call it like this:

var id = vm.uniqueNumber.inc()();

imageThe odd syntax calls the inc (increment) function which returns the original object (in support of chaining). Then, to get the value, it calls the properties’ getter function (the second set of parentheses).  (As I said, it was just messing around).

The HTML for the data binding looked like this:

<div class="log" data-bind="foreach: list" >
    <div class="item">
        <span data-bind="text: id" class="id"></span>
        <span data-bind="text: time"></span>
    </div>
</div>

Some code from SnugUp.Browser (an album browser for SmugMug)

I’ve been doing some tinkering recently with SmugMug again.

Through testing SnugUp for the past 4 years, I’ve made quite the mess of my SmugMug account. Literally hundreds of poorly organized and often completely junk albums. I’ve been wanting to clean it up, but SmugMug’s UI for that is so obnoxiously slow and tedious that I decided I wanted to write a tool to make it easier to manage.

Admittedly, given the amount of time I’ve spent on writing the tool (which I’m not yet finished with), I could have cleaned up my SmugMug account dozens of times. However, with my developer hat on, I thought, how fun would that be? Smile

 

image

So, I’ve started to create the SnugUp.Browser. It’s a bit of an interesting beast in that it is a Windows application that hosts a web browser to display its UI. I’d tried using WPF/XAML and just couldn’t get the look I was wanting in a reasonable amount of time and effort.

<RANT>WPF desperately needs a VirtualizingWrapPanel. Microsoft needs to ship it. </RANT>

So, I created a WinForms project to host the IE Web Browser. (Arrgh, the WPF WebBrowser sucks still. It’s not nearly as feature complete as the one from WinForms!). Internally, the web pages are served via an HttpListener.

try { int portSuggest = GetAvailablePort(); portSuggest = 40000; _listener.Prefixes.Add(string.Format("http://localhost:{0}/", portSuggest)); _listener.Start(); while (maxConnections-- > 0) { _listener.BeginGetContext(HandleRequest, _listener); } } catch (Exception ex) { Debug.WriteLine(ex); }

The HandleRequest method responds in two ways:

  1. Respond with JSON data (as if it were a web service). 99% of the code needed to access SmugMug’s APIs was already written in a C# library I wrote for SnugUp.
  2. Respond with binary data, providing a “proxy” to a service. This was needed to handle downloading images from the application. When the request was directly made from the WebBrowser to SmugMug, it was refused as the http-referer header was not a valid source apparently (SmugMug didn’t like “http://localhost:####” as the referrer.
resourceRequested = context.Request.Url.LocalPath.Replace('/', '.'); if (resourceRequested.StartsWith(".")) { resourceRequested = resourceRequested.Substring(1); } Debug.WriteLine(string.Format("Requested: {0}", resourceRequested)); using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(string.Format("SnugUp.Resources.{0}", resourceRequested))) { if (stream == null) { //context.Response.StatusCode = 404; if (url.LocalPath.StartsWith("/proxy")) { var proxyUrl = url.Query.Substring("?url=".Length); extension = Path.GetFileName(proxyUrl); try { WebClient client = new WebClient(); byte[] buffer = client.DownloadData(proxyUrl); context.Response.Headers.Add("Expires", DateTime.UtcNow.AddDays(30).ToString("R")); context.Response.ContentType = GetContentType(context, extension); context.Response.OutputStream.Write(buffer, 0, buffer.Length); } catch { context.Response.StatusCode = 404; } } } else { string contentType = GetContentType(context, extension); context.Response.ContentType = contentType; stream.CopyTo(context.Response.OutputStream); } // Close the Response to send it to the client. // } }

If the resource can’t be found as an Embedded Resource, it tries a proxy.

I built a tiny router for the web services so that I could easily plug-n-play new functionality:

public virtual object Route(string path, dynamic data) { if (path.StartsWith("/")) { path = path.Substring(1); } var paths = path.Split('/'); if (paths.Length > 0) { var controllerName = paths[0]; var controllerType = Type.GetType(string.Format("SnugUp.Controller.{0}Controller", controllerName), false, true); if (controllerType != null) { var instance = InitializeControllerInstance(controllerType); // default? if (paths.Length > 1) { MethodInfo method = controllerType.GetMethod(paths[1]); if (method != null) { try { object results = method.Invoke(instance, new object[] {data}); return results; } catch (Exception ex) { Debug.WriteLine(ex); } } } } } return null; }

It just looks up a request dynamically, maps to a method, and calls it (the response is eventually serialized as JSON).

For UI, I’m currently using Bootstrap (although that may be removed), Knockout.JS, and jQuery. I’m doing a bit of trickery to make sure that only what’s visible on the screen is loaded (even when the user scrolls up and down … it’s pretty smart).  I’ll likely post more about that in a future update.

The app is becoming larger… and bloated a bit when compared to my original needs. Smile

 

image

It now can show the images from the gallery as well. Smile

image

Blank Dashboard and Administration Screens on WordPress

image

Apparently, there are a number of potential causes of a blank or empty screen when trying to access the Dashboard or administration screens of a self installed version of WordPress.

Before doing anything to your installation, I’d strongly suggest making a complete backup of your Database and installation folders. If your web host offers a handy way to do that, take advantage of it. There are plenty of walk-throughs on the Internet available with explanations of how to manually perform a backup. Don’t skip this step.

The most common apparently is a bad or incompatible plug-in. To test that theory, using an FTP application, just rename the plug-in folder that’s located in the wp-content folder.

image

Navigate to the root of your WordPress installation, then the wp-content folder. Rename the folder currently named plugins to something like plugins-test. This temporarily disables the plug-ins. Don’t worry, the settings for the plugins aren’t stored here.

Try again to navigate to the Dashboard.

Some suggest that a bad theme can also cause this problem. Assuming you haven’t directly modified the original copies of the themes of WordPress that are included with the default installation, twentyeleven and twentyten, rename the folder of your current theme (just add “-test” to the end again for example). This causes WordPress to revert to the default (which currently is twentyeleven).

Try again to navigate to the Dashboard.

Another option is to delete several of the folders of your installation and copy in the current versions. (Follow this if you’d like to try it). While fun to do, it didn’t help with my problem.

In my case, and the reason I’m posting this, is that it wasn’t any of those things. Somehow, the configuration file located in the root of the WordPress installation was no longer compatible with the current version of WordPress. I have no idea how this happened.

I made a copy of the original file wp-config.php (by copying it locally to my computer). If you just make a backup on your web site host, don’t give it an extension that makes it downloadable as it contains the keys to your installation. Smile

Open the copy of the wp-config.php file that you’ve made locally on your computer. By doing that, it will be much easier to grab the values from there that you’ll need shortly.

Now, as soon as you perform the next step, your web site will be unavailable for  a few moments while you get things running again. It’s up to you if you want to do something special during this time (you could make a backup of index.php for example and edit the file to say that your site is undergoing a bit of maintenance).

I chose not to as I knew that it would take about 30 seconds to create a new configuration file.

Delete the file, wp-config.php from the root installation directory. Your web site is now officially down.

Now, using your browser navigate to the root folder of your WordPress installation and append wp-admin/install.php to the URL. It might look something like this:

www.example.com/blog/wp-admin/install.php

“blog” represents the root of the WordPress installation in the example above.

WordPress notices that your installation is missing a configuration file. Go ahead and walk through the few steps. Refer to the original wp-config.php file that you opened locally to provide answers to its questions. Pay attention to the table prefix question in particular and make sure that it matches with what you were using before. Look for a line that looks like this in the configuration file:

$table_prefix  = ‘wp_’;

The value in single quotes (wp_ in the example above) represents the prefix for the database tables that were created and being used by your WordPress installation. If you’re using a shared database, it’s very likely that you didn’t use the default of wp_. If you don’t match these up, you’ll end up with a completely fresh installation of WordPress, which isn’t likely what you want. (Don’t worry, if that happens, delete the config file and start over and this time, be sure to enter the correct prefix).

The other questions it asks should all have equivalents in the wp-config.php file you’ve got opened locally.

image

When it’s done, try logging in again. As soon as I completed the steps above, the WordPress Dashboard became available again. I renamed the plugin folder (removed the “-test” I’d added) and renamed my theme folder (again, by removing the “-test” I’d added). I went to the Plugins to re-activate each of them (as apparently, they became deactivated by default).

I verified the web site again was working and behaving as expected, and went on my regularly scheduled day. Hours lost: 3. Sad smile

Hopefully this will help someone else.

(FYI, the new configuration file that was generated by the “fresh install” was syntactically and structurally different from the original. I don’t know why this was the case or when it happened, but I was happy to get things working again).

Adobe Lightroom and exporting to subfolders

For some reason, versions 1 – 3 of Adobe Lightroom cannot export images in a way that mirrors the original structure of your photo library. I can’t offer a reason why other than it was missed by the development and design teams. There’s been enough interest in it apparently that there are more than a few free and pay solutions to the problem.

I looked at a few of the options and for one reason or another, I decided that I didn’t want to use the plug-ins/extensions and came up with a simple solution using a bit of naming trickery. I use this technique for SnugUp.

Here’s what I did in Lightroom version 3.

  1. I selected the photos I wanted to export.  (I generally use the Quick Collection Feature)
  2. File > Export (CTRL + SHIFT + E)
  3. Adjust the Export To option to point to a specific folder of your choosing (in the example below, I set the folder as E:\PhotosBackupJPG
    image
  4. You may optionally put them in a subfolder of your choice (I did not)
  5. Next click the Rename To option and select "Edit…
    image
  6. Clear any value that may already be in the text box below the Example (just highlight the text and press delete).
    image
  7. Then, edit the template to be Folder Name and then Original filename. Select those by using the Image Name grouping. Click the Insert button after each.
    SNAGHTML5521140b
  8. Then, I added some text that I knew would be unique to be used as a separator (=-= equals minus equals). To add the text, just click with your mouse  between the two values you just inserted. I know that none of the file names in my library have this exact combination of characters in them. It’s important later. You can confirm this by using the search feature in Lightroom if you’re not sure.
    SNAGHTML5522af12
  9. Click on the Preset (yours may say something else) and then click “Save Current Settings as New Preset
    image
  10. Give it a name that you’ll remember (I called mine Folder-Filename) and hit Create.
    SNAGHTML5524784a
  11. Adjust the remaining settings per your export needs and begin the process by clicking the Export button.
  12. Wait patiently. Then go do something else as you realize it’s going to take a lot longer than you had expected/wanted.
  13. Now, the next step is the simple trickery. What we’ve done is named all of the files so that they include the folder name as well. So, using a Powershell 2.0 script (Powershell is available for all modern versions of Windows as part of a package download here). So, download it now if you don’t already have it. (You can check for it by looking for Powershell ISE as described in the next step if you’re not sure. You probably have it if you’re using Windows 7).
  14. Start Windows Powershell ISE (in Vista and Windows 7, just type “power” into the Start menu search):
    imageThe reason I suggest the ISE (integrated scripting environment) is that it’s easy to just get stuff running without a lot of hassle.
  15. The ISE will start:
    SNAGHTML552c2cf8
  16. I performed the move/rename in two steps so I could verify things between each step. If you’re familiar with Powershell, feel free to combine them into one step. It’s simple enough. First grab the entire script below and paste it into the top pane (under the tab labeled Untitled1.ps1):
    $root = #"E:\PhotosBackupJPG" $items = Get-ChildItem $root foreach($item in $items) { $path = [regex]::split($item.Name, "(=-=)") if ($path.length -eq 3) { $destPath = $root + "\" + $path[0] if (!(Test-Path -path $destPath)) { New-Item $destPath -type directory "New folder: " + $destPath } Move-Item $item.FullName $destPath } }

    I’ve intentionally left an error in the file so that you’re forced to make the change. Unless you have an “E” drive and the path, “PhotosBackupJPG”, the script needs a tiny modification to make it work.

  17. The first line of the script, $root = #”E:\PhotosBackupJPG” needs to be changed to match with the export location you selected earlier AND also the “#” needs to be removed (it’s a comment character in Powershell script). So, if you exported your photos to C:\Users\Steve\ExportedPhotos, remove the “#” and remove the text inside of the quotes on the first line and replace it with your photo location.
  18. After confirming the location points to where you exported your photos (and videos), hit the Run button (the green arrow in the screen shot below). Or hit the F5 key on your keyboard.

    image

  19. The output (or any errors if you made a typo) appears in the light blue box in the center and when it’s complete, the word “Completed” appears in the status bar at the bottom of the application. It may take a few minutes to complete. I’d suggest looking at your export directory using Windows explorer to confirm everything looks fine before continuing. At this point, you should have some subfolders and all of the files should have been moved into the proper subfolder. Next, renaming.
  20. Click the “New” icon (first icon on left shown above) to create a new Powershell script file (or just replace the existing one, you won’t need it again for this process). In the new file, copy this Powershell script and paste it:
    $root = #"E:\PhotosBackupJPG" $items = Get-ChildItem $root -recurse foreach($item in $items) { $path = [regex]::split($item.Name, "(=-=)") if ($path.length -eq 3) { # $destPath = $root + "\" + $path[0] Rename-Item $item.FullName $path[2] } }

  21. Again, fix the $root to match with the export folder you created.
  22. Confirm it, and hit the Run button.
  23. All of your photos (and videos) will be renamed to only the original file name. The extra folder name and =-= are removed.
  24. Bask in the glory of Powershell and your file wizardy. No plug-ins necessary.

If you’d like to use something other than =-= as the delimiter between the folder and file names, you’ll need to fix the Powershell scripts. The delimiter is on the line:

$path = [regex]::split($item.Name, "(=-=)")

But, unless you understand how Regular Expressions in Powershell work, you may want to avoid this change … it’s not necessarily as simple as just replacing the text.

The scripts above do these things with each file:

  1. Check to see if it has the right pattern
  2. If so, grab the folder name and check to see if the folder exists.
  3. If it does not, create it.
  4. Move the file to the new folder.
  5. Rename the file name, removing the folder and delimiter

If you have questions, please leave a comment.

While this shouldn’t cause any harm, (especially if you just point it at a folder of exported photos and videos, which worse case you just re-export), you use technique and code this at your own risk. I can say I successfully used the script on over 30,000 exported photos without a single problem. Smile

WPF & System.Windows.Baml2006.TypeConverterMarkupExtension "The image format is unrecognized"

SNAGHTML4534e6a2If you recently added an icon to your WPF project (any .NET version, including .NET 3.5, and .NET 4.0) and it has support for an alpha channel (often referred to as the Vista icon format), stop. Why? Your WPF application won’t run on the latest service pack of XP as it’s not capable of decoding the format unfortunately. It’s a very frustrating error and a stupid "feature that Microsoft overlooked. I’ve hit this a few times unfortunately.

To fix, remove all of the alpha channel images from the ICO file and recompile.