Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Tuesday, October 28, 2014

The Order of Adapters in Lawnchair

If you are using Lawnchair and you want to explicitly define the order in which adapters are used, then you must open the unminified file and make sure they appear IN REVERSE ORDER THAT YOU WANT THEM in the source.

I have no idea if this is a bug new to version 0.6.1, but you can inspect Lawnchair.adapters in your dev tools of choice to see the order and run tests to confirm they are what you expect -- and I strongly recommend you do this.

For me, I have them pasted in my Lawnchair file as window name, Web SQL (a separate download on the site), then Local Storage, which puts them in the order for actual use: Local Storage, Web SQL, then window name.

Saturday, February 15, 2014

First attempt at a Web Worker for a Lunr.js index

Most of the applications I've been building lately are internal mobile web apps using jQuery Mobile and they function offline using the Application Cache plus a bit of Local Storage. The latest one also has a search requirement using some textual data even if the user is offline. I decided to give Lunr.js a try and it works great. The one problem I've had is that it takes a couple of seconds to index the 400+ reports I'm trying to handle on an iPad 2 running iOS 7. It's not awful, but every time that loading spinner hangs I squirm.

Enter Web Workers.

This seemed like a perfect opportunity to try out a Web Worker. The search page has a couple of other features, so I don't want the user to be forced to wait for indexing to complete to use the page and, of course, I don't want to lock up the UI at all. Index creation only has to happen once, and moving it off to a background thread until complete seemed to make sense. The only issue to keep in mind, and it comes into play in my scenario, is that data is copied from the main thread to the worker thread. I have noticed a bit of a UI hitch when that fires, but it's been a huge improvement.

Here's a stripped down example with comments. You will need to run this from a web server. You cannot run it using local file:// access as far as I know.

The HTML:

<!DOCTYPE HTML>
<html>
<head>
 <title>Web Worker with Lunr.js</title>
</head>
<body>

 <p>Building...</p>

 <script type="text/javascript" src="javascript/lib/underscore-min.js"></script>
 <script type="text/javascript" src="javascript/lib/lunr.min.js"></script>
 <script type="text/javascript" src="javascript/lib/jquery-1.11.0.min.js"></script>

 <script type="text/javascript" src="javascript/SearchIndexWorker.js"></script>
 <script type="text/javascript" src="javascript/SearchMobileModule.js"></script>
 <script type="text/javascript" src="javascript/app.js"></script>

</body>
</html>

A small app.js file to bootstrap the process:

// You'll need to run this from a web server.

var app = app || {};

// I have two search modules: mobile and desktop.
app.isMobileDevice = true;

//  We'll use local data for this example. This is a small set.
//  The benefit comes when you have several hundred of these.
var data = {
    reports: [
        { 'reportId': 1, 'reportTitle': "Jane Doe visited Company ABC to review business renewal." },
        { 'reportId': 2, 'reportTitle': "John Smith visited XYZ, Inc. to review sales over lunch." }
    ]
};

// You'll have to decide when to init the index.
// We currently do it if the user visits the reporting section of the app.
$(function() {
 app.search.mobile.init(data.reports, $('p'));
})

I have two different search modules, one for desktop users which communicates with the server to retrieve the full set of data and one for mobile users which is limited to the past 6 months of data and works offline.

(function (app, $, _, lunr) {

    // Private
    
    var reportsPointer = [];
    
    var index;

    // Public
    
    var search = {};
    
    search.setIndex = function (serializedIndex) {
        // Source: http://www.garysieling.com/blog/building-a-full-text-index-in-javascript
        index = lunr.Index.load(serializedIndex); 
    };

    search.init = function (reports, $uiNotice) {
        console.log('Attempting to init the Lunr.js index.');
        
        // We're going to hold a pointer to the reports.
        reportsPointer = reports;

        // Create the Web Worker.
        // You may see a browser error in Chrome or Safari while testing that says:
        //   'Uncaught ReferenceError: importScripts is not defined'
        // Not sure why that happens, but it works.
        var worker = new Worker("javascript/SearchIndexWorker.js");
        
        // Here we're adding a listener for messages coming back from the worker to us. 
        worker.addEventListener('message', function (evt) {
            // The evt.data property has the response from the worker.
            // The Web Worker cannot modify a global, so it passes back the index for us to use.
            search.setIndex(JSON.parse(evt.data)); 

            // We'll update the UI here for now.
            $uiNotice.html('Ready! Open the console and try something like: app.search.mobile.query("Jane")');
            console.log('The Lunr.js index is ready.');
            
            // Memory footprint will be large with all that data copied around. This kills the worker.
            worker.terminate(); 
        }, false);
        
        // Sends a message to the worker and passes it *a copy* of the data it needs. 
        // I'm sending a string to be consistent across browsers.
        worker.postMessage(JSON.stringify({ reports: reports }));
    };

    search.query = function (query) {
        if (!query) return [];

        // We're going to keep things simple and handle all the results here.
        // Searching with Lunr.js isn't really the point here.
        var lunrResults = index.search(decodeURIComponent(query));
        _.each(lunrResults, function(el, idx, list) {
            $('<pre>').text(JSON.stringify(_.findWhere(reportsPointer, { reportId: parseInt(el.ref, 10) }))).appendTo('p');
        });
    };

    app.search = app.search || {};
    app.search.mobile = search;

}(window.app = window.app || {}, jQuery, _, lunr));

Finally, the Web Worker itself.

// This is the Web Worker script. No DOM, window, or document access at all.

// Import the scripts we'll need in this worker.
importScripts('lib/lunr.min.js', 'lib/underscore-min.js');

var index = lunr(function () {
    this.field('reportTitle');
    this.ref('reportId');
});

var buildIndex = function (reports) {
    if (!reports || reports.length === 0) return;

    for (var i = 0; i < reports.length; i++) {
        index.add(reports[i], false); // Don't emit any Lunr events.
    }
};

// This is a listener on the worker for incoming messages.
self.addEventListener('message', function (evt) {
    // evt.data has the data passed to this worker.
    var data = JSON.parse(evt.data);

    if (data.reports) {
        buildIndex(data.reports);
    }

    // Now we send a message back to the script that created this worker.
    self.postMessage(JSON.stringify(index.toJSON()));

    // Memory footprint can be large with a lot of data copied around. This kills the worker.
    self.close(); 
}, false);

I hope someone else finds this useful!

Thursday, October 11, 2012

IIS7, JSON Compression, and You

How do you know if your requests are being compressed?

Fiddler. Select resource > Response Section > Transformer tab. Also, the Headers tab should have a Transport section listing Content-Encoding: gzip.

How do I enable it in IIS 7?

First, just get Dynamic and Static Compression running by going to the IIS level or the site level, selecting the Compression feature, and checking the boxes. You may need to install it if you don’t see these options.

How come my JSON requests aren’t being compressed?

The JSON MIME type isn’t compressed by default. That MIME type is application/json.

Great. So how do I add it?

You can use the appcmd.exe or you can edit the file directly. C:\Windows\System32\inetsrv\Config\applicationHost.config is locked by the system fairly tightly, but I did the following. Make a backup. From the Start menu, right click Notepad and start as administrator. Use File > Open to go to the config file. Go to the <httpCompression> node. In both <dynamicTypes> and <staticTypes>, add both <add mimeType="application/json" enabled="true" /> and <add mimeType="application/json; charset=utf-8" enabled="true" />. Note the charset in the second item. That has to match what is sent from the server, exactly. Again, I pulled that from Fiddler by inspecting one of the responses' headers and checking the Content-Type value. I restarted at the IIS level to get the changes recognized.

Tuesday, June 14, 2011

Beware Old Posts

I noticed that many of my older posts are woefully out of date. I added a little JavaScript snippet to warn you about that. There's probably a more elegant way to do this, but it'll work for now.
var message = " <span style='font-size: 20px; font-weight: bold; color: red;'>STOP! This is an old post. Are you sure it's still relevant?</span>";
var currentDate = new Date();
var thisYear = currentDate.getFullYear();
var lastYear = currentDate.getFullYear() - 1;

function stringDoesNotContainYear(myString, myYear) {
return myString.indexOf(myYear) == -1;
}

$(document).ready(function() {
$(".date-header").each(function(){
current = $(this).text();
if(stringDoesNotContainYear(current, thisYear) && stringDoesNotContainYear(current, lastYear)) {
$(this).append(message);
}
});
});

Sunday, February 8, 2009

Highlight a DIV Element onClick

This is just a quick script to highlight a DIV element when a user clicks on an enclosed link. It's a little small/silly, but I've had more than request for something like this come in.


<html>
<head>
<title>Highlight</title>
<style type="text/css">
.highlight { background-color:#ffff00; }
</style>
</head>
<body>
<div id="div1">Row one <a href="javascript:changeHighlight('div1');">HIGHLIGHT</a></div>
<div id="div2">Row two <a href="javascript:changeHighlight('div2');">HIGHLIGHT</a></div>
<div id="div3">Row three <a href="javascript:changeHighlight('div3');">HIGHLIGHT</a></div>
<input type="hidden" id="currentDiv" />
<script language="javascript" type="text/javascript">
function changeHighlight(myElement)
{
document.getElementById(myElement).className = 'highlight';
var lastHighlight = document.getElementById('currentDiv').value;
if(lastHighlight != '')
{
document.getElementById(lastHighlight).className = '';
}
document.getElementById("currentDiv").value = myElement;
}
</script>
</body>
</html>

UPDATE: This is OLD. You should look for a better solution.

Tuesday, April 15, 2008

LibraryThing's JSON API

This morning I was tinkering with LibraryThing's JSON API just to demonstrate a proof of concept to some people internally. I used FireBug to take a look at what the service was actually returning. Here's the bare-bones script.

<html>
<head>
<title>LibraryThing Tests</title>
</head>
<body>
<p>This is an example of information we can get from LibraryThing.</p>
<h3>Christmas on Television</h3>
<script language="javascript" type="text/javascript">
function LTHandler(LTData)
{
for(i in LTData)
{
var book = LTData[i];
// Just display all the data we know might be there.
document.write("<p>");
if(book.id) document.write("<b>ID:</b> " + book.id);
if(book.type) document.write(" (" + book.type.toUpperCase() + ")<br />");
else document.write("<br />");
if(book.work) document.write("<b>LibraryThing (LT) work id:</b> " +
book.work + "<br />");
if(book.link) document.write("<b>LT link:</b> <a target='_blank' href='" +
book.link + "'>" + book.link + "</a><br />");
if(book.copies) document.write("<b>LT copies:</b> " + book.copies +
"<br />");
if(book.reviews) document.write("<b>LT reviews:</b> " + book.reviews +
"<br />");
if(book.rating) document.write("<b>LT rating:</b> " +
Math.round(book.rating/2));
if(book.rating_img) document.write(" or <img src='" + book.rating_img + "'/>");
document.write("</p>");
}
}
<script src="http://www.librarything.com/api/json/workinfo.js?ids=0785271295&callback=LTHandler">
</script>
</body>
</html>

Thursday, April 12, 2007

JavaScript Link to Nowhere

I am completely incapable of remembering this for the rare times I need it.

<a href="javascript:void(0);">my link to nowhere</a>

Sunday, February 4, 2007

Random Table Background Image

This was a quick hack for a friend to get a random background image set for a table. You should be able to add this just about anywhere in the <body> tag. This would also work just for a table cell.

<script language="javascript">
<!--
onload = function() {
/* If you change the directory, make sure you update it here. */
var imageDirectory = "http://www.yourwebsite.com/images/";
/* If you want to add more images on your own, put the the file name in the middle
of the list here. Make sure there are quotes around it and a comma at the end. */
var imageArray = new Array("image3.gif",
"image6.gif",
"image1.gif",
"image4.gif",
"image2.gif",
"image5.gif");
var imageArrayLength = imageArray.length; // Leave these next few lines alone
var pseudoRandomNumber = ( Math.round( Math.random() * (imageArrayLength - 1) ) );
document.getElementById("tableImage").style.backgroundImage="url('" +
"imageDirectory + imageArray[pseudoRandomNumber] + "')";
}
//-->
</script>
<noscript>
<style type="text/css">
#tableImage {
background-image:url('http://www.yourwebsite.com/images/image3.gif');
}
</style>
</noscript>

<table id="tableImage" height="431" width="465">
<tr><td> </td></tr>
</table>

UPDATE: This is OLD. You should look for a better solution.