Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

Wednesday, March 05, 2025

CVS and Publix Coupon Clippers as Userscript

Several months ago, CVS added more security to their site to prevented my bookmarklet solution from injecting the clipper code to clip all the coupons. So, I've revised the CVS clipper as well as the Publix clipper to now be Userscripts. The advantage of this is that the CVS clipper now works again and they both now automatically add a button on the page to perform the "Clip All" action which is easier to use (no need to find the bookmarklet bookmark). The downside is that it requires a browser extension to be installed, and then the clipper script needs to be installed in the extension.

Please go to https://pothoven.net/CvsClipper.html or https://pothoven.net/PublixClipper.html for installation instructions, and please drop me a comment here if you try it out to let me know how it works for you.

Wednesday, December 09, 2020

Angular Clipboard Service

It's about time I post something new! Since my last post I've had to learn and build applications in the Ember, React, and now Angular JavaScript Frameworks.  Along the way I've learned some interesting tips and tricks I can share here.


I needed the ability to place content in the clipboard, but needed it lots of placed throughout the application. So here's a simple Clipboard service that will place any string into the clipboard.  I incorporated the use of MatSnackBar to notify the user that the value has been placed in the clipboard.

import {Injectable, Renderer2, RendererFactory2} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';

@Injectable({
  providedIn: 'root'
})
export class ClipboardService {
  private renderer: Renderer2;

  constructor (
      private rendererFactory: RendererFactory2,
      private snackBar: MatSnackBar,
      ) {
    // Get an instance of Angular's Renderer2
    this.renderer = this.rendererFactory.createRenderer(null, null);
  }

  copy(value: string) {

    // create an temporary text input field to contain the value and select the value
    const input = this.renderer.createElement('input');
    this.renderer.setProperty(input, 'value', value);
    this.renderer.appendChild(document.body, input);
    input.focus();
    input.select();
    input.setSelectionRange(0, 99999); // For mobile devices

    // Copy the selected text inside the text field to the clipboard
    document.execCommand('copy');
    this.snackBar.open(`Copied "${value}" to clipboard`, undefined, { duration: 1000 });

    // remove the temporary text input field
    this.renderer.removeChild(document.body, input);
  }
}

You then simply inject it into any component that needs it:

import { ClipboardService } from 'clipboard.service';

constructor( private clipboard: ClipboardService ) {} 

and then copy whatever values you want to the clipboard simply as:
this.clipboard.copy(value);

Monday, May 13, 2019

2's complement and BCD hex values in JavaScript

I'm sure this is will not be a highly accessed post as these are not a commonly required functions, but in my current job, we process data from IoT GPS devices which send data in compact formats.  To process this data using Node.js in an AWS Lambda function, I had the need to convert a 2's complement hex values and BCD (binary-coded decimal) hex value to regular numbers in JavaScript.  The examples I found were in Java, so I converted it to JavaScript and am sharing here in case anyone else needs it.

       const negativeHexTestPattern = /^[89ABCDEF]/i


        /* parse signed 2's complement hex string to number */
        hexToTwosComplement(hex) {
                let result = parseInt(hex, 16)

                // Check if high bit is set.
                if (negativeHexTestPattern.test(hex)) {
                        // Negative number
                        const subtrahend = (2 ** (hex.length * 4))
                        result -= subtrahend
                }
                return result
        }


        /* parse packed binary-coded decimal (BCD) format where unused
         * trailing digits (4-bits) are filled with all ones (1111). */
        hexBCDToString(hex) {
                let decoded = ''
                for (let i = 0; i < hex.length; i++) {
                        if ((parseInt(hex[i],16) & 0x0F) !== 0x0F) {
                                decoded += hex[i]
                        } else {
                                break
                        }
                }
                return decoded
        }

Here's a few test cases to demonstrate.
                it('converts positive hex values to twos complement', () => {
                        const val = new Processor().hexToTwosComplement('13f6b4eb')
                        chai.expect(val).to.equal(334935275)
                })

                it('converts negative hex values to twos complement', () => {
                        const val = new Processor().hexToTwosComplement('ec094b15')
                        chai.expect(val).to.equal(-334935275)
                })

                it('converts negative 2 byte hex values to twos complement', () => {
                        const val = new Processor().hexToTwosComplement('ffae')
                        chai.expect(val).to.equal(-82)
                })

Monday, March 25, 2019

Activity LEDs

In some of my applications I get ongoing data from websockets and I want some visual indication that the websocket is connected and receiving data, so I added an LED indicator much like a network router or switch would have.

Whereever I want the LED, I add div to show the LED.

<div class="led" id="ws-led">
<div class="led-red-off">
</div>
</div>

then add some CSS rules to make it look like an LED indicator

div.led{
 display     : inline-block;
 vertical-align    : bottom;
}

div.led-red-off,
div.led-red-on,
div.led-green-off,
div.led-green-on {
 border     : 0;
 border-radius    : 50%;
 height     : 1em;
 width     : 1em;
 vertical-align    : middle;
 background-repeat   : no-repeat;
 display     : inline-block;
}

div.led-red-on {
 background    : #F44336; opacity: 1;
}
div.led-green-on {
 background    : #4CAF50; opacity: 1;
}
div.led-red-off {
 background    : #F44336; opacity: .5;
}
div.led-green-off {
 background    : #4CAF50; opacity: .5;
}

and finally, some JS code to make them blink.

    var wsLED = document.getElementById('ws-led');

    // connected to the Websocket server (general)
    function connected(greeting) {
        // change the LED from initial red to green
        if (wsLED) wsLED.firstChild.setAttribute("class", "led-green-off");
    }

    // connection to Websocket server lost
    function disconnected() {
        // if we loose the websocket connection, change the LED to red
        if (wsLED) wsLED.firstChild.setAttribute("class", "led-red-on");
    }

    /**
     * logWSActivity
     *
     * log some WS activity
     */
    function logWSActivity(...args) {
        console.log(...args);
        flashLED(wsLED);
    }

    /**
     * flashLEDs
     *
     * The visual effect of the flashing LEDs is done by switching the
     * CSS class for a 10th of a second per activity.  If a new
     * activity happens within that time, the timeout to turn it back
     * off is reset to a new 10th of a second.
     */
    var ledOffTimeout;
    function flashLED(led) {
        // flash activity LED
        if (led) {
            if (ledOffTimeout) { clearTimeout(ledOffTimeout); }
            led.firstChild.setAttribute("class", "led-green-on");
            ledOffTimeout = setTimeout(function() {
                led.firstChild.setAttribute("class", "led-green-off"); }, 100);
        }
    }

JavaScript String functions (toCamelCase, dasherize, and titleize)

I had the need for a few String functions in JavaScript to manage some variances between data sources so I figured I'd share them.  For example, from one source attributes in the JSON data would use underscores to separate words and in another it would uses dashes (my_attribute vs my-attribute).  Also for simple data display, I wanted to titleize the attribute name for a label (My Attribute). 


/**
 * String.toCamelCase
 *
 * Convert a string to camel case, including hyphens and underscores
 */
String.prototype.toCamelCase = function() {
    return this.replace(/^([A-Z])|[\s-_](\w)/g, function(match, p1, p2, offset) {
        if (p2) return p2.toUpperCase();
        return p1.toLowerCase();        
    });
};

/**
 * String.dasherize
 *
 * Dasherize a string, including periods and underscores
 */
if (!String.prototype.dasherize) {
    String.prototype.dasherize = function() {
        return this.replace(/^([A-Z])|[\s\._](\w)/g, function(match, p1, p2, offset) {
            if (p2) return "-" + p2.toLowerCase();
            return p1.toLowerCase();        
        });
    };
}

/**
 * String.capitalize
 *
 * Capitalize the first letter of a string
 */
if (!String.prototype.capitalize) {
    String.prototype.capitalize = function() {
        return this.charAt(0).toUpperCase() + this.slice(1);
    };
}

/**
 * String.titleize
 *
 * Capitalize the first letter of every word in a string, also separates
 * words by spaces if formally separated by dashes or underscores
 *
 */
if (!String.prototype.titleize) {
    String.prototype.titleize = function() {
        return this.replace(/^([A-Z])|[\s-_](\w)/g, function(match, p1, p2, offset) {
            if (p2) return " " + p2.toUpperCase();
            return p1.toLowerCase();        
        }).capitalize();
    };
}

Deep Copy version of Javascript Object.assign

I was working on some Redux work and needed a reducer that would merge in some sparse updates to the current state of an object.

If you're learning Redux you may be familiar with the tutorial example of a TODO list item where it's changing one attribute of the TODO list:

return Object.assign({}, state, {visibilityFilter: action.filter});

Now, instead of changing a single top-level attribute like the visibility filter, assume you have some data that needs to be merged into the existing object both at the top level and in some nested attributes.  For example, presume your current state looks like:

{ a : "a", deep: { a: "a" }

and you get new data for that state that need to be merged in that looks like:

{ b : "b", deep: { b: "b" } }

The Object.assign function will do a shallow copy merge and the result will be:

> Object.assign({}, { a : "a", deep: { a: "a" } }, { b : "b", deep: { b: "b" } })
{ a: 'a', deep: { b: 'b' }, b: 'b' }

The new value of deep simply replaced the first value.  The nested value of deep wasn't merged together.  The method below will correctly merge nested values as follows:

> deepAssign({}, { a : "a", deep: { a: "a" } }, { b : "b", deep: { b: "b" } })
{ a: 'a', deep: { a: 'a', b: 'b' }, b: 'b' }

Here's the code:

function deepAssign(...objs) {
  const target = objs.shift();
  const source = objs.shift();

  if (source) {
    if (source instanceof Array) {
      for (const element of source) {
        if (element instanceof Array) {
          target.push(deepAssign([], element));
        } else if (element instanceof Object) {
          target.push(deepAssign({}, element));
        } else {
          target.push(element);
        }
      }
    } else {
      for (const attribute in source) {
        // eslint-disable-next-line no-prototype-builtins
        if (source.hasOwnProperty(attribute) && source[attribute] !== undefined) {
          if (source[attribute] instanceof Array) {
            target[attribute] = target[attribute] || [];
            for (const element of source[attribute]) {
              if (element instanceof Array) {
                target[attribute].push(deepAssign([], element));
              } else if (element instanceof Object) {
                target[attribute].push(deepAssign({}, element));
              } else {
                target[attribute].push(element);
              }
            }
          } else if (source[attribute] instanceof Object) {
            if (source[attribute].toString() === '[object Object]') {
              // simple data object so deep copy it
              target[attribute] = deepAssign((typeof target[attribute] === 'object') ? target[attribute] : {}, source[attribute]);
            } else {
              // instance of some class, so just copy over the object
              target[attribute] = source[attribute];
            }
          } else {
            target[attribute] = source[attribute];
          }
        }
      }
    }
  }

  if (objs.length > 0) {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    return deepAssign(target, ...objs);
  } else {
    return target;
  }
};


Update 7/15/19 : Original version didn't handle Arrays correctly.  They become objects.  They now copy correctly including nested array and object in the arrays.  You can't just use the spread operator (newArray = [...oldArray]) to copy it or it wouldn't copy the nested objects as new objects

Update 8/27/21 : Objects that weren't simple data objects were being lost, so now complex objects are brought over to the new copy.

Tuesday, April 05, 2016

Re-enable disabled disable_with fields

I have some forms that do JavaScript based validation before submitting to the server.  The submit button is using the disable_with construct, so when the submit button is pressed, it is correctly disabled, but when the client-side validation fails, it's stuck disabled.

To remedy this situation, I added these lines to the JavaScript code that hides the error messages after they've been displayed for 5 seconds.

// re-enable 'disable_with' fields after validation errors
var disabled_with = jQuery('[data-disable-with][disabled]');
if (disabled_with) {
    jQuery.rails.enableElement(disabled_with);
    disabled_with.val(function(){ return jQuery(this).text()}).prop('disabled', false);
}

The first line is the commonly proposed solution that I found elsewhere online, but when I tried it (in Chrome), I found that it didn't work, leaving the button disabled with the disabled text displayed. However, one important thing it did was to insert the original button text as HTML content of the input element.  I added the second line to use that text as the button value and then actually enable the button.


Tuesday, January 07, 2014

Bishop Swap Puzzle Fixed!

I implemented a Bishop Swap Puzzle back in 2007 (see my prior post), but it has had a bug in it where bishops of the same color could pass through each other.  I had noted it as a 'TODO' in my code comments while I was writing it, but I had neglected to go back and fix it and forgot all about it until I was trying it out again recently.

I'm happy to say that I've now fixed the bug and it works correctly, so happy gaming!


Once you've completed the Bishop Swap puzzle, be sure to check out the others!

Thursday, January 02, 2014

Clip All function for Publix Digital Coupons

Happy New Year!

Sadly, it's been over a year since my last post.  To make up for not contributing for so long, I'll provide a little gift for those of you who have resolved to save more in the new year.  In particular, you need to live in the southeastern United States and do your grocery shopping at Publix.

Last year, Publix introduced a digital coupon site. This site provides a collection of coupons that you can "clip" and then when you're at the checkout lane, you enter your phone number on the credit card reader keypad and it will automatically apply any coupons you have clipped that apply to the groceries you have purchased.  The problem with the site is that they have not provided a "Clip All" function, so you need to look through several pages of coupons and clip any coupon you're interested in one at a time.  This can be a very time consuming process as many pages of coupons can be added each week.

To rectify this problem, I created a bookmarklet that provides a "Clip All" function.  It will clip all on the current page of coupons and continue to navigate through all available pages of coupons and clip them all.  The bookmarklet also works from mobile devices (at least iPads) so you can even quickly clip all the digital coupons while you're in Publix using their WiFi.

Please go to https://pothoven.net/PublixClipper.html for installation instructions, and please drop me a comment here if you try it out to let me know how it works for you.


Thursday, May 10, 2012

Adding and removing multiple associated sub-models dynamically on a single form in Rails

The last couple blog posts have been about PeepNote ... its beginning and its ending.  Now I thought I'd share a snippet of code that I developed for it the form of a simple contact manager.

One of the things Brian pointed out in his concluding remarks about PeepNote that I referred to in my last post was,

As I began interacting more with potential customers I realized that my target audience was not what I thought it was. It wasn’t people like me who were heavily using Twitter for career networking and wanted to keep track of how I met people and what I knew about them. Instead, the only people that would pay for the service were companies. Companies that wanted to use it to track potential customers; a CRM.
Part of the CRM functionality that we had incorporated into PeepNote was to add contact information about your peeps.  The idea of a contact manager is well understood by most audiences and are thus commonly used as a demonstration application.  So, what's special about this one and why I'm I writing about it?

This particular contact manager demonstrates (and improves upon) Ryan Bates “Handle Multiple Models in One Form” recipe (#13) from the Advanced Rails Recipes book.  In Ryan’s original recipe, he added discrete field values (individual tasks on a to-do list) to the form. Any tasks that did not have an identifier associated with it was a new task, and any missing task identifiers from those currently in the database were assumed to be deleted.  This is great when you're essentially adding single values to a list, but what about adding multiple pieces of information that need to stay together as a single entity (like the parts of a contact information - street address, city, state, phone, etc)?

The solution is to use a JavaScript variable to assign temporary unique identifiers to newly added nested models (addresses, phone numbers, urls, etc). These newly added models are assigned a negative value in order for the contact controller to distinguish between new and existing records.  So, new records with a negative id are added, existing records are updated as necessary, and any records with ids that are no longer in the submitted list are removed.

This is one of the problems I solved for PeepNote and have extracted into a simple stand-alone Rails application.   Rather than provide a lengthy description here, I placed the source code on GitHub and have a running demo on Heroku.

Thursday, August 13, 2009

Managing Tags in Rails

In this article I'm going to document a small piece of the administration component of the Ruby Rails Review that I wrote. On Ruby Rails Review we kept a collection of interesting articles regarding Ruby on Rails, and these articles were tagged with various tags so that if you went to browse the articles you could filter them by the various tags. Of course, there's no reason to implement that whole tagging system ourselves since there are plug-ins for that. To make the articles taggable we used the acts_as_taggable_on plug-in. This article just gives a method for easily editing the tags for a given article which works much like the Blogger labeling I used when writing this article. For Ruby Rails Review, the article editor form is shown below, though I've dimmed out everything but the tagging sections: As you can see, there is a text field where you can type in tags in a comma separated list, as well a list of all currently defined tags. Selected tags are shown in blue and underlined to give a visual indication that they are selected, but all tags are clickable to either select or deselect them. As tags from the list at the bottom are selected or deselected, they are added or removed from the text field. If you enter tags in the text field they are either selected or added (or deselected if you remove a tag) from the tag list below. So, how do we do this? First, the form code looks like this:

<% form_for @article_detail, :url => { :action => "update", :id => @article_detail.article_id } do |f| %>
   ...
   <%= f.text_field :tag_list, :onchange => "updateTagList(this.value);" %>
   ...
<% end %>
<div id="tags" style="margin-top:30px;">
<% @tags.each do |tag|%>
<div class="tag<%= ' selectedTag' if @article_detail.tag_list.index(tag.name) %>" onclick="selectTag(this);"><%= tag.name %></div>
<% end %>
</div>
You'll also need a little bit of CSS added to make the selected tags stand out (added to your main.css). I happened to make them blue and underlined, but you can make them highlighted or whatever you please:
.selectedTag {
      color: #247CD4;
      text-decoration: underline;
}
Finally, add the necessary JavaScript in either your application.js or a separate JavaScript file of you choosing:
function selectTag(tagElem) {
    tagElem.toggleClassName('selectedTag');
    var selected_tags = buildTagList();
    $('article_detail_tag_list').value = selected_tags;
    $('article_detail_tag_list').focus();
};

function updateTagList(selected_tags) {
    selected_tags = selected_tags.split(',');
    var tags = $H();
    $$('.tag').each(function(tag) { 
        tags.set(tag.innerHTML, tag);
        tag.removeClassName("selectedTag");
    });
    selected_tags.each(function(tagName) {
        tagName = tagName.strip();
        var tag = tags.get(tagName);
        if (tag) {
            tag.addClassName("selectedTag");
        } else {
            $('tags').insert('<div class="tag selectedTag" onclick="selectTag(this);">'+tagName+'</div>');
        }
    });
};

function buildTagList(tag) {
    var selected_tags = [];
    $$('.selectedTag').each(function(tagElem) {
        selected_tags.push(tagElem.innerHTML);
    });
    if (tag) {
        if (selected_tags.indexOf(tag) === -1) {
            selected_tags.push(tag);
        } else {
            selected_tags = selected_tags.without(tag);
        }
    }
    selected_tags = selected_tags.join(',');
    return selected_tags;
}
That's it! Now you can easily maintain the tags on your taggable items.

Tuesday, May 06, 2008

Keystroke and Field Validation with JavaScript

Yesterday, I provided a simple tool to demonstrate the differences between keydown and keypress events. Today, I'm going to apply that knowledge toward making a lightweight form validation library. This library will prevent invalid characters from being entered in form input elements which is useful toward preventing malicious data from being entered (think XSS), as well as ensuring data integrity (data is in the valid format, all required fields present, etc.).

Sample Form:



Alpha
Alphanumeric*
Date
Decimal
Digit
Email
Hostname
IP address
Integer
Punctuation
Real number
Time
Whitespace
custom
type
format


* denotes required field.




The form above should only allow valid characters in each input field - this is your keystroke validation. Furthermore, for fields that require a specific format, when you leave a field with invalid formatted data it should provide an immediate visual indication of the error by setting a fieldWithErrors CSS class on invalid fields. Additionally, a fv:onInvalid event is fired on the invalid element to allow custom events handlers to provide additional actions. For example, enter a number in the date field and tab out. The field will be outlined in red from the fieldWithErrors CSS class and the message, "Date must be yyyy-mm-dd" will be shown by the handling of the fv:onInvalid event. Note: the CSS class will be removed automatically when the field is corrected, and a corresponding fv:onValid event will be fired to allow the custom code to clean up after itself.

Validation rules are defined as Regular Expressions. If you are not familiar with Regular Expressions here is a good reference. Additionally, many predefined regex rules for various validations can be found at the regex library.

Several common data types and formats are defined by default in the library, but it also provide the mechanism to add you own data types. In the custom entry, the type entry allows you to specify an on-the-fly keystroke validation regular expression, and the format entry allows you to specify an on-the-fly field format validation regular expression.

The Validate Form button demonstrates the action you would want to perform before a form submission. It invokes a validateAllFields function which just runs all the field validations (and invokes corresponding CSS rules and issues events). It also returns an array of the invalid field names in case you want to build a list of errors (like the Rails flash messages).


Usage Instructions

Sample Usage:

<script src="javascript/formValidation.js"></script>
<script>
var vf = new FormValidation();
vf.addValidationForField("date", "date", "date");
</script>

addValidationForField requires 3 parameters:

  1. The identifer of the field to add validation for

  2. The name of the data type to use for keystroke validation

  3. The name of the data format to use for field validation

In this sample all the names are the same, but they usually wouldn't be.

Just doing the above will prevent invalid characters from being entered in the data field and can indicate when the field is invalid.

When a field is changed, the format validation is run and will do two things.

  1. It will mark invalid fields are the fieldWithErrors CSS class. This can be used to appropriately highlight the error to the user. For example, the following CSS rule will outline the field in red:

    .fieldWithErrors {
    border: 3px solid red;
    }

  2. An event will also be issued in the field to indicate whether or not the field is valid. The events to watch for are fv:onInvalid and fv:onValid. These events can be used to hide/show error messages such the following which will display a pre-defined error message:

    $$('input').each(function(inputElem) {
    inputElem.observe("fv:onInvalid", function() {
    $(this.identify() + "-error").show();}.bind(inputElem));
    inputElem.observe("fv:onValid", function() {
    $(this.identify() + "-error").hide();}.bind(inputElem));
    });

    Or any number of other actions. For example, you may wish to display a popup error message such as those provided by Prototype Window or add messages to an error flash area as commonly done in Rails apps or toggle the submit button as enabled/disabled. Using these events allows you to add whatever actions you desire when the
    validation fails (or passes).



Download
formValidation.js

Monday, May 05, 2008

keydown vs. keypress (in JavaScript)

In my next post, I intend to discuss a validation library which performs keystroke and field level validation for HTML forms using JavaScript and regular expressions. However, before you can correctly understand how the validation works, as well as when you can test for various validation conditions, you have to recognize the different behaviors between the keydown and keypress events.

In the following text fields, type whatever you like and notice the different behaviors in the table below it.





EventcharCodekeyCodeDisplayShifted?
Keydown
Keypress


Some notable problem keys when dealing with JavaScript keystroke validation are the arrow keys and other editing keys (delete, home, end, insert) which should be allowed in order to edit a field value, however they can appear to be punctuation symbols which may not be desired in the field. Function keys and number pad keys are also problematic.



Here are some key observations regarding keypress events:

  • Firefox sends regular keys as charCodes and special keys as keyCodes

  • Safari sends both charCode and keyCode for regular keys but does not issue keypress events for special keys

  • Internet Explorer does not send charCodes, only keyCodes, and does not issue keypress events for special keys

  • Opera does not send charCodes, only keyCodes, but also issues keypress events for special keys making it impossible to distinguish between some keys such as '-' vs. Insert and '.' vs. Delete

  • Konqueror will send both charCode and keyCode for regular keys, but only keyCode for special keys


Wednesday, April 30, 2008

Using JavaScript in blogs without <script> support

At one point, Blogger (which is used for this blog) did not allow the <script> tag in blog entries so some people came up with interesting work-a-rounds. This post will showcase two techniques which work well together to provide JavaScript capabilities to your blogs. The first technique supports inline JavaScript with a make-shift <script> tag, while the second technique supports loading external JavaScript files specific to each blog entry which can still be useful even with <script> support. These techniques work well together and almost need to be used together. The inline technique is only handy for adding a line or two of code because Blogger will try to be smart regarding the formatting of your blog entry and add <br/> tags between all your lines of code making the JavaScript interpreter choke. Additionally, Blogger also tries to format any < or > symbols as &lt; and &gt; which don't work too well in your code. The second technique allows you to load as much JavaScript code as you want, however you'll probably want to use the first technique to invoke the included JavaScript functions. Testing inline JavaScript: failed. (wait for page to complete loading) Technique 1 - Allowing Inline JavaScript That previous test line tested my ability to add inline JavaScript to my blog following these instructions from ecmanaut. To use JavaScript in this blog without any <br/> tags, I added the following code to my blogger template:

<script type='text/javascript'>
   Event.observe(window, 'load', function() {
      var c = document.getElementsByTagName('code'), s, i;
      var junk = /^\s*\46lt;\133\133CDATA\133|]]\46gt;\s*$/g;
      for( i=0; i &lt; c.length; i++ ) {
         s = c[i].getAttribute('style') || '';
         if( s.match( /display[\s:]+none/i ) ) {
            eval( c[i].innerHTML.replace( junk, '' ) );
         }
      }
   });
</script>
I made a couple of slight variations to the original code snippet on the instruction page. First of all, before I added that code, I also included prototype.js in my template to have access to it's handy extensions. I was then able to utilize protype's Event.observe function to execute this code when the page is loaded without messing up and other onload actions. Executing this code during the onload is recommended in the instructions, but not explained how to do it. Finally, since the template requires valid XML, the i < c.length line isn't valid until you change the < to &lt; Then, in order to run this JavaScript test, I added this line to the bottom of this blog entry:
<code style="display:none"> <[[CDATA[ $('jsTestResult').innerHTML = 'passed'; ]]></code>
which changes the word failed to passed in my test line above. <[[CDATA[ $('jsTestResult').innerHTML = 'passed'; ]]> Technique 2 - Loading External JavaScript Files In order to load JavaScript files, you could edit your Blogger template to include all the files you want as I did for prototype.js and script.aculo.us. However, then every view of your blog will load ALL your JavaScript files. That's fine if you have some JavaScript library that you want to be able to utilize in many/all your blog entries, but if you're adding entries like mine which are just showcasing various JavaScript techniques in separate blog entries, you only want to load the specific blog entry JavaScript if it's being viewed. Enter Dynamically Loading External JavaScript Files. Right after the previously mentioned code, I added the dhtmlLoadScript function. However, since I have prototype.js loaded, I modified it a little bit. Here's my version:
function dhtmlLoadScript(url) {
   var e = new Element("script", {src: url, type: "text/javascript"});
   document.getElementsByTagName("head")[0].appendChild(e);
}
So, in any blog entry which I want to use an external JavaScript file I simply add the line:
<code style="display:none"> <[[CDATA[ dhtmlLoadScript('http://some.domain.com/jsfile.js'); ]]></code>
and I can use any of the included function. Furthermore, since the inline script won't be evaluated until the onLoad event it triggered, I don't have to worry about registering it for an onLoad event itself.

Friday, February 22, 2008

busyProcess: a visual indicator for long JavaScript tasks

Some time ago I wrote an article to demonstrate a simple method of displaying a processing/loading indicator for Ajax requests using prototype. That works great for Ajax requests, but let's assume you have some CPU intensive task that will be processed on the client side in JavaScript. This task will take several seconds to complete and you want to add some sort of visual indicator to the page while it's working so the user knows something is happening. enter the busyProcess function. You could code your particular JavaScript task to display a processing indicator itself and remove it when it's done, however there are two problems you'll encounter. First, you'll probably never see the processing indicator since your task will immediately do its thing and not give control back to the browser in order to display the indicator before the task finishes and your code removed the indicator. Second, you'll end up adding the same code over and over around each function that may take a while to complete (DRY - Do not Repeat Yourself!). busyProcess handles both of these situations. It is a flexible wrapper for any function passed in to it so that it can be used for any task, and it defers the execution of the function in order to allow the browser to render the visual indicator. Example Click this to be busy for 3 seconds. Get to the code already!

/**
 * busyProcess
 * 
 * Add a busy indicator over an element while running the function it invokes
 * @param {Object} element clicked on to invoke the task
 * @param {Function} function to invoke after adding visual indicator
 */       
function busyProcess(element, func) {
   var busyIndicator = new Element("div", {id: "busy"});
   busyIndicator.setStyle({zIndex: "100",
                           position: "absolute",
                           fontWeight:"bold",
                           height: "16px"});
   Position.clone($(element), busyIndicator, {setWidth: false, setHeight: false});
   busyIndicator.innerHTML = '<img src="images/wait.gif" style="width:16px;height:16px;" /> Processing...';
   document.body.appendChild(busyIndicator);           

   // function needs to be deferred in order for the browser to render the
   // busy indicator, but we need to wrap it in order to remove the busy indicator
   // when it's done
   func = func.wrap(
      function(proceed) {
         proceed();
         busyIndicator.remove();
      });
   func.defer();
}

Ok, so how does it work? The function takes in two arguments. The first is the element clicked on. This is used as the location of the popup processing indicator as the item clicked on is what invoked the action for the user. Since the prototype $() function is used, this can be an element id as well. The second argument is the function to invoke. busyProcess starts by building the processing indicator. This can be customized to your preferences. Just be sure the indicator has a z-index greater than anything other layers on the screen, and that is has absolute positioning. I then utilize the prototype Position.clone to position the indicator over the element clicked on. Now we get to the more interesting part. The indicator needs to go away when the task is done. But how do we know when it's done? You could make the task issue a custom event and register an event listener here to catch it in order to remove the processing indicator. However, then any function you use busyProcess with will need to issue that event when it's done. That could get messy, and easy to forget. So, instead we wrap the original function to have it remove the processing indicator when it's done. So, how do you use this? For a simple example, let's assume we want to sort some large table by different columns when the user clicks on little triangle icons indicating ascending or descending. For simplicity I'm just going to add the code inline on an onclick attribute in the HTML. As a general practice I usually try to register event handlers in my JavaScript code so that the HTML has no JavaScript in it.
<td id="zipcode">Zip Code
<img src="images/sort-asc.gif" alt="ascending sort icon" 
     onclick="busyProcess(this, function() {sortTableBy(this.up('td').identify());}.bind(this));" />
</td>
I intentionally made that a little more complicated than it had to be just to demonstrate that you can use inline anonymous functions as well. Optional (but useful) addition In order to give a more obvious indication that work is being done and to prevent the user from clicking on anything else until it's done you may want to either darken or lighten the rest of the page. To do this, add another layer at the beginning of the busyProcess function like:
   var lightenScreen = new Element("div", {id: "lightenScreen",
                                          'class': "lightenBackground"});
   document.body.appendChild(lightenScreen);           
where the lightenBackground class is defined in CSS as:
.lightenBackground {
    background-color: white;
    opacity: 0.5; /* Safari, Opera */
    -moz-opacity: 0.50; /* FireFox */
    filter: alpha(opacity = 50); /* IE */
    z-index: 20;
    height: 100%;
    width: 100%;
    background-repeat: repeat;
    position: fixed;
    top: 0px;
    left: 0px;
    cursor: wait;
}
or the corresponding darkening version:
.darkenBackground {
 background-color: black;
 opacity: 0.2; /* Safari, Opera */
 -moz-opacity: 0.20; /* FireFox */
 filter: alpha(opacity = 20); /* IE */
 z-index: 20;
 height: 100%;
 width: 100%;
 background-repeat: repeat;
 position: fixed;
 top: 0px;
 left: 0px;
 cursor: wait;
}
Just be sure to remove this layer as part of the wrap by adding the line: lightenScreen.remove();

Tuesday, January 22, 2008

My ImageFlow with Lightbox packaged sample

Per a request from Clemens to my last post, I've packaged my revised version of ImageFlow and Lightbox2. In addition, I've made my sample application issue an Ajax request to get the list of images to demonstrate how you can dynamically build the photo album. Here is the code:

// sample application
var MyApp = function() {
 var imgTemplate = new Template('<img alt="#{caption}" longdesc="javascript:LightBoxLite.displayImage(\'#{filename}\')" src="reflect.php?img=#{filename}" />');

    function initializeAlbum(response) {
        var imageList;
        var imgHTML = '';

        try {
            imageList = response.responseJSON; 
        } catch (e) {
            console.error(e, "\n", response.responseText);
        }

        // IE bug : for all other browsers an imageList.each() works great here,
        // however IE doesn't like it so we revert to a for loop
        for (var i = 0; i < imageList.length; i++) {
            var filename = imageList[i];
            // IE bug : IE computes an incorrect value for imageList.length resulting 
            // in undefined filenames in the loop, check for that
            if (filename) {
                // for simplicity, extract the base filename for the caption.  
                // Caption could also be defined for each image in the JSON response data.
                var caption = filename.split('/').pop().split('.')[0].gsub("%20", " ");
                imgHTML += imgTemplate.evaluate({filename : filename, caption : caption});
            }
        }

        $('images').innerHTML = imgHTML;
        // IE bug : IE needs a moment to let the innerHTML change register before proceeding
        // so we add a small timeout to let it catch its breath
        setTimeout(ImageFlow.initialize, 10);
    }

    return {
        loadAlbum : function(albumName) {
            $('startButton').hide();
            $('photoAlbum').show();
            var myAjax = new Ajax.Request('imageList.php?albumName=' + albumName, {
                method: 'get',
                onSuccess: initializeAlbum
                });
        }
    };

}();
The initial button you see in the sample application, invokes the loadAlbum() function when clicked as shown:
<input type="button" value="Click here to load photo album" onclick="MyApp.loadAlbum('FlickrEyeCandy');"></input>
Download the packaged sample application shown below. Update January 23, 3pm EST: It was reported that the sample application didn't work in IE. This was due to some deficiencies in IE which I have accounted for now. I have pointed them out in the code with the comments beginning with IE bug

Friday, January 18, 2008

ImageFlow with Lightbox Lite

I like the idea of using ImageFlow with Lightbox2, however, I don't like that it required a tweaked version of ImageFlow, nor that I then had to modify my image list to wrap the Lightbox2 elements around them. Additionally, Lightbox2 adds a lot of functionality that's not being using in this case (all the next/previous image controls, image caching, etc), and it also wants to initialize itself during the window.onload event which I don't want in my Ajax apps.

So, wrote my own Lightbox Lite. Well, technically, I extracted my own Lightbox Lite from the Lightbox2 code with a few modifications.

To begin with, I added this block of HTML to my web page. The Lightbox2 code constructs this dynamically in its initialize function, but I don't see the advantage of that vs just hard coding it into the page to begin with.


<div id="photo" style="display:none">
<div id="lightbox">
<div id="outerImageContainer">
<div id="imageContainer">
<img id="lightboxImage" style="display:none"></img>
<div id="imageloading">
<img src="images/loadingImage.gif">
</div>
</div>
</div>
<div id="imageDataContainer" style="display:none">
<div id="imageData">
<div id="imageDetails">
<span id="imagecaption"></span>
</div>
<div id="imageBottomNav" onclick="LightBoxLite.reset();" style="float:right" >
<img src="images/close-button.png" alt="Close" title="Close">
</div>
</div>
</div>
</div>
</div>

Then I added the relevant CSS rules to my stylesheet:

#photo {
z-index: 51;
position: absolute;
top: 5px;
left: 1px;
width: 100%;
}

#outerImageContainer {
background-color: white;
width: 250px;
height: 250px;
margin: 0 auto;
}

#imageContainer {
padding-top: 10px;
}

#imageloading {
position: absolute;
top: 40%;
left: 47.5%;
}

#imageDataContainer {
font: 10px Verdana, Helvetica, sans-serif;
background-color: white;
margin: 0 auto;
line-height: 1.4em;
overflow: auto;
width: 100%;
padding-bottom: 5px;
}

#imageData {
padding: 0 10px;
color: #666;
}

#imageData #imageDetails {
width: 80%;
float: left;
text-align: left;
}

#imageData #imageCaption {
font-weight: bold;
}

Next I added the Lightbox Lite code to my JavaScript (note, you still need prototype.js and script.aculo.us):

LightBoxLite = function() {

var borderSize = 10;
var resizeDuration = 0.6;

/**
* resizeImageContainer
*
* @param {Number} desired width
* @param {Number} desired height
*/
function resizeImageContainer( imgWidth, imgHeight) {

// get current width and height
var widthCurrent = $('outerImageContainer').getWidth();
var heightCurrent = $('outerImageContainer').getHeight();

// get new width and height
var widthNew = (imgWidth + (borderSize * 2));
var heightNew = (imgHeight + (borderSize * 2));

// scalars based on change from old to new
var xScale = ( widthNew / widthCurrent) * 100;
var yScale = ( heightNew / heightCurrent) * 100;

// calculate size difference between new and old image, and resize if necessary
var wDiff = widthCurrent - widthNew;
var hDiff = heightCurrent - heightNew;

if (!( hDiff === 0)) {
new Effect.Scale('outerImageContainer', yScale, {scaleX: false, duration: resizeDuration, queue: 'front'});
}
if (!( wDiff === 0)) {
new Effect.Scale('outerImageContainer', xScale, {scaleY: false, delay: resizeDuration, duration: resizeDuration});
}

$('imageDataContainer').style.width = widthNew + "px";

showImage();
}

/**
* updateDetails
*
*/
function updateDetails() {
var caption = $('lightboxImage').src.split('/').pop().split('.')[0].gsub("%20", " ");
$('imagecaption').innerHTML = caption;

new Effect.Parallel(
[ new Effect.SlideDown( 'imageDataContainer', { sync: true, duration: resizeDuration, from: 0.0, to: 1.0 }),
new Effect.Appear('imageDataContainer', { sync: true, duration: resizeDuration }) ]
);
}

/**
* showImage
* Display image and begin preloading neighbors.
*/
function showImage() {
$('imageloading').hide();
new Effect.Appear('lightboxImage', { duration: resizeDuration, queue: 'end', afterFinish: updateDetails });
}

function checkForPreloadComplete(imgPreloader) {
if (imgPreloader.complete) {
$('lightboxImage').src = imgPreloader.src;
resizeImageContainer(imgPreloader.width, imgPreloader.height);
} else {
setTimeout(checkForPreloadComplete.bind(this, imgPreloader), 100);
}
}
return {
/**
* displayImage
* display an image in a lightbox
*
* @param {String} URL of image
*/
displayImage : function(imageUrl) {
$('imageloading').show();
$('lightboxImage').hide()
$('imageDataContainer').hide()
$('photo').show();

var imgPreloader = new Image();
imgPreloader.src = imageUrl;

// once image is preloaded, resize image container
setTimeout(checkForPreloadComplete.bind(this, imgPreloader), 100);
},

reset : function() {
$('photo').hide();
$('imageloading').show();
$('lightboxImage').hide()
$('imageDataContainer').hide()
$('lightboxImage').src = "";
$('outerImageContainer').style.width = "250px";
$('outerImageContainer').style.height = "250px";


}
};
}();

The only alteration you have to do to your ImageFlow images is to add a call to the LightBoxLite.displayImage function in the longdesc attribute of your images, which should currently have the address of the original image to display when you click on it in the image flow. So, instead of:

<img src="reflect.php?img=myimage.jpg" longdesc="myimage.jpg" alt="myimage" />

It becomes:

<img src="reflect.php?img=myimage.jpg" longdesc="javascript:LightBoxLite.displayImage('myimage.jpg')" alt="myimage" />


Here is an example of it being used.

Update January 20: I tweaked a couple CSS rules to correctly center the loading indicator for Internet Explorer.

Update January 23: I received a request to package my modifications. See my new blog entry where I have done that in addition to demonstrating how to add Ajax to receive the image list.

Thursday, January 17, 2008

ImageFlow (improved)

Finn Rudolph has created a very nice "cover flow" type of control in JavaScript called ImageFlow (which is an improvement of Michael L. Perry's Cover flow).

It's a very nice package and quite easy to use! However, it currently has a few code shortcomings. My primary issues with it was that it defined everything in a global scope (with common names that easily clash with other functions) and that it assumes the photos are in a static page and thus initializes itself when the web page loads. The later issue was the biggest thing I needed to fix since I wanted to use it in an Ajax application where I'm getting the images as a result of an XMLHttpRequest.


  • I added an "ImageFlow" scoping around the whole package so that all the variables and particularly the functions aren't global to my whole application (and potentially clashing).

  • I added "var" declarations for many of the variables which didn't specify "var" so that they would not become fully global variables, but only global to the ImageFlow scope.

  • I changed the "onload" function to be an "initialize()" function so that I could execute it when my page was ready

  • I made all the variables and functions totally private to ImageFlow with the only publicly visible function being "intialize()"

  • Since the images in the image flow div are now loaded sometime after the page is loaded, I added a check to the initialize function determine when all images are really loaded (otherwise some would be sized wrong).


Now, whenever you're ready for the ImageFlow to be displayed, just call "ImageFlow.initialize()"

If you're interested in these updates, you can download my altered imageflow.js. I have also submitted them to Finn, so hopefully they will be adopted into the official version.

NOTE: The file was updated on January 18, 10:00am EST after running it through JSLint -- which I should have done before posting it originally.

Update January 18: I've added a new article describing the addition of a scaled down version of Lightbox2 without modifying ImageFlow.

Wednesday, December 19, 2007

Accordion select list

Kevin Miller has created an accordion widget using prototype and script.aculo.us. In this article I'll demonstrate how to build a selection list that has sub-sections utilizing the accordion control. Here is a demonstation comparing a basic select list control and the accordion select list: To make the control work, you first add a read-only text box with the drop down button image floating to the right of it, for example:

<span style="float: right">
 <img id="accSelectToggle" src="images/btn_dropdown.png" width="21" height="16" alt="Expand list" title="Expand list" />
</span>
<input type="text" id="accSelect" title="List" readonly="readonly" value="Option 1.1" />
You then define a div which contains the accordion control, such as:
<div id="accSelectOptions" class="accordion_container" style="display: none">
<h1 class="accordion_toggle">Category 1</h1>
<div class="accordion_content">
<ul>
 <li value="option11">Option 1.1</li>
 <li value="option12">Option 1.2</li>
 <li value="option13">Option 1.3</li>
</ul>
</div>
...
</div>
Add your preferred CSS styling. In my example the CSS looks like:
#accSelect {
 width: 169px;
 height: 14px;
 border-style: none;
 padding-left: 4px;
}

.accordion_container {
 z-index: 10;
 position: absolute;
 width: 198px;
 border: solid 1px black;
}

.accordion_toggle {
 display: block;
 padding: 0.3em 0.5em;
 font-weight: normal;
 font-size: 1em;
 text-decoration: none;
 outline: none;
 border-bottom: 1px solid #def;
 color: #000000;
 cursor: pointer;
 margin: 0px;
 background-image: url('images/expand.gif');
 background-position: 99% 50%;
 background-repeat: no-repeat;
 padding-right: 5px;
 background-color: #9BE;
}

.accordion_toggle_active {
 color: #ffffff;
 font-weight: bolder;
 background-image: url('images/ns-expand.gif');
 background-color: #47B;
}

.accordion_content {
 background-color: #ffffff;
 overflow: auto;
 display: block;
}

.accordion_content ul {
 padding-left: 1em;
 margin: 0;
 padding-right: 0;
 padding-bottom: 0px;
 list-style-type: none
}

.accordion_content li {
 margin: auto;
 padding: 0px;
}

.highlight {
 background-color: #DDD;
}
And finally add a little JavaScript to make the magic happen:
    function  toggleAccordionSelect(event) {
        var image = event.target;
        var collapseIcon = "images/btn_dropdown-selected.png";
        var expandIcon = "images/btn_dropdown.png";

        if ($('accSelectOptions').style.display === 'none') {
            image.src = collapseIcon;
            image.alt = "Collapse List";
            image.title = "Collapse List";
            $('accSelectOptions').show();
        }
        else {
            image.src = expandIcon;
            image.alt = "Expand List";
            image.title = "Expand List";
            $('accSelectOptions').hide();
        }
    }

    function toggleHighlight(event) {
        var item = event.target;
        item.toggleClassName('highlight');
    }
    
    function selectAccordionOption(event) {
        var selectedOption = Event.element(event);

        // update the selection input field value to the selected display value
        $('accSelect').value = selectedOption.innerHTML;

        // internal value is stored as an attribute, do whatever you need to with it
        var realValue = selectedOption.getAttribute('value');
        
        // hide the accordion list
        toggleAccordionSelect({target: $('accSelectToggle')});       
    }
    

    var accordionSelect = new accordion('accSelectOptions', {
        classNames : {
            toggle : 'accordion_toggle',
            toggleActive : 'accordion_toggle_active',
            content : 'accordion_content'
            },
        onEvent : 'mousedown'
        });

    // By default, open first accordion in the drop down
    var accordionToggles = $$('#accSelectOptions .accordion_toggle');
    accordionSelect.activate(accordionToggles[0]);

    // register event observers        
    Event.observe('accSelectToggle', 'mousedown', toggleAccordionSelect);
    $$('#accSelectOptions .accordion_content li').each(function(accSelectOption) {
        Event.observe(accSelectOption, 'mousedown', selectAccordionOption);
        Event.observe(accSelectOption, 'mouseover', toggleHighlight);
        Event.observe(accSelectOption, 'mouseout', toggleHighlight);
    });

Aborting Ajax requests (for prototype.js)

Sometimes your slick Ajax application may have submitted a request that you no longer care about and want to abort. For example, perhaps you've implemented a type-ahead feature and the user has now typed another character prior to the first results returning. Or perhaps you fetch values for other parts of a form based on user selections and the user changed their choice prior to the first response returning.

I was surprised to see that the prototype.js library does not include an abort method for its Ajax.Request object. So, here's my implementation of Ajax.Request.abort():


/**
* Ajax.Request.abort
* extend the prototype.js Ajax.Request object so that it supports an abort method
*/
Ajax.Request.prototype.abort = function() {
// prevent and state change callbacks from being issued
this.transport.onreadystatechange = Prototype.emptyFunction;
// abort the XHR
this.transport.abort();
// update the request counter
Ajax.activeRequestCount--;
};


To use this function, you need to keep a handle on the Ajax request you want to abort. So, somewhere in you code you'll have something like:

var myAjaxRequest = new Ajax.Request(requestUrl, {[request options]});


If you want to abort that request, simply call:


myAjaxRequest.abort();


Update - Feb. 22, 2008:

It was pointed out in the comments that the Ajax.activeRequestCount can occasionally become negative. I have been able to replicate that situation, while at the same time confirming that it does not consistently happen. This leads me to believe that it's most likely a timing issue such as the abort is issued after the response has already started to be received and/or processed so that both the response processing decrements the counter as well as the abort.

My personal work-around for this is to add these lines to the end of my onComplete handler:

if (Ajax.activeRequestCount < 0) {
Ajax.activeRequestCount = 0;
}

I don't want to remove the counter decrement from the abort function or else cleanly aborted requests will leave the activeRequestCount > 0 when there are no real outstanding requests.

If anyone has a better solution, I'd be interested to hear from you.