Tuesday, December 8, 2009

Unified Javascript disableTextSelection | enableTextSelection

Unified Text Selection Disable/Enable Routine


If you're doing any kind of drag and drop operation in Javascript/DHTML, you will need to temporarily disable and re-enable text selection in your document while the drag operation is going on.

I wrote this block of code today and it was such a tedious hassle, I thought it worth blogging so others wouldn't have to endure my pain :{.


This works in all browsers except PC Opera.

It DOES work in Mac OSX Safari, Firefox, Chrome and on PC Internet Explorer, Safari, Firefox and Chrome.


////////////////////////////////////////////////////////////////////////////
// UTILITIES - Section contains general-purpose utilties //
// ========= //
////////////////////////////////////////////////////////////////////////////
utilities : {
savedValueOf : new Object(), // savedValueOf will hold "original" values that we override/restore as needed
disableTextSelection : function() {
switch (true) {
case ( typeof document.onselectstart!="undefined" ) : // IE
this.savedValueOf["onselectstart"] = document.onselectstart;
document.onselectstart=function() { return false; };
break;
case ( typeof document.body.style.MozUserSelect != "undefined" ) : // Firefox
this.savedValueOf["-moz-user-select"] = document.body.style.MozUserSelect || "text";
document.body.style.MozUserSelect="none";
break;
case ( document.body.style["-khtml-user-select"] != "undefined" ) : // Safari
this.savedValueOf["-khtml-user-select"] = document.body.style["-khtml-user-select"];
document.body.style["-khtml-user-select"] = 'none';
break;
}
},
enableTextSelection : function() {
switch (true) {
case ( typeof document.onselectstart!="undefined" ) : // IE
document.onselectstart = this.savedValueOf["onselectstart"]
break;
case (typeof document.body.style.MozUserSelect != "undefined") : // Firefox
document.body.style.MozUserSelect = this.savedValueOf["-moz-user-select"]
break;
case ( document.body.style["-khtml-user-select"]!="undefined" ) : // Safari
document.body.style["-khtml-user-select"] = this.savedValueOf["-khtml-user-select"];
break;
}
}
}


Shannon Norrell



This posting now also on GitHub

Tuesday, October 20, 2009

How to do LDAP authentication using Ruby, How to Run Ruby as a Windows Service and to Write Events into the EventLog (Event Viewer) Service

We needed a way to authenticate users against LDAP and required that the ruby processing running the Authentication code (in a file we called "ad_login_verify.rb") appear as a Windows Service that can be started from services.msc by our networking guys. For *whatever* reasons, when running as a service, it was found that writing to a log file did not work properly.

Anyway, the more network-operations-friendly approach was for me to figure out a way to write into the Windows EventLog Service so that events could be viewed using Event Viewer.

Once everything is set up properly, the objective is to be able to send a query to a url (like http://server:4243/ldap?username=ausername;password=apassword) and to get "AUTH" or "NON-AUTH" back

So the setup is a windows box running Windows Server 2003, with the following base software installed:

Windows 2003 Resource Kit Tools (available here)

ruby (mine is ruby 1.8.6 (2007-09-24 patchlevel 111) [i386-mswin32])

win32-eventlog gem (mine is win32-eventlog (0.4.6) )

ruby-net-ldap gem (min is ruby-net-ldap (0.0.4) )


The contents of ad_login_verify.rb are as follows: (note, sensitive areas surrounded by ** **)


#!C:\ruby\bin ruby

require "rubygems"
require "webrick"
require "net/ldap"
require 'win32/eventlog'
include Win32
include WEBrick

# Write to EventViewer that Service Started
EventLog.open('Application') do |log|
log.report_event(
:source => "CrewResAuthEventSvc",
:event_type => EventLog::WARN,
:category => "0x00000002L".hex,
:event_id => "0x00000003L".hex,
:data => "CrewResAuth Service successfully started"
)
end

class LDAPServlet < HTTPServlet::AbstractServlet
def do_GET( request, response )
username = request.query['username']
password = request.query['password']
ldap_con = initialize_ldap_con("**DOMAIN**\\**USER**","**PASSWORD**")
treebase = "DC= **DOMAIN** , DC =LOCAL"
user_filter = Net::LDAP::Filter.eq( "sAMAccountName", username )
op_filter = Net::LDAP::Filter.eq( "objectClass", "organizationalPerson" )
dn = String.new
ldap_con.search( :base => treebase, :filter => op_filter & user_filter, :attributes=> 'dn') do |entry|
dn = entry.dn
end
login_succeeded = false
unless dn.empty?
ldap_con = initialize_ldap_con(dn,password)
login_succeeded = true if ldap_con.bind
end
response.status = 200
response.body = login_succeeded ? 'AUTH' : 'NON_AUTH'

# Write to Windows Event Log
EventLog.open('Application') do |log|
log.report_event(
:source => "CrewResAuthEventSvc",
:event_type => EventLog::WARN,
:category => "0x00000002L".hex,
:event_id => "0x000003E9L".hex,
:data => "#{username}/#{password} LOGIN #{login_succeeded ? 'SUCEEDED' : '**FAILED**' }"
)
end
end

alias do_POST :do_GET

def initialize_ldap_con(username, password)
Net::LDAP.new( {:host => '**LDAP FQDN**', :port => 389, :auth => { :method => :simple, :username => username, :password => password }} )
end

end

server = HTTPServer.new(:Port => 4243)
server.mount('/ldap', LDAPServlet)

%w(INT TERM).each do |signal|
trap(signal) {server.shutdown}
end

server.start

The event_id values are generated when we build the .dll that will be used to propogate messages from Ruby into the Event Viewer.



STEP 1 Copy Files

On the box that will be running the authentication service, create the following folder: c:\crewres\services
br/>
Copy the following three files into c:\crewres\services:

  • ad_login_verify.rb
  • crewResAuthStart.bat
  • CrewResAuthEventSvc.dll

Once these files are coped over, you can test that things work by dropping to a command prompt, going to the c:\crewres\services folder, typing crewResAuthStart.bat and then going to a web browser and typing in the url above.


The contents of crewResAuthStart.bat is simply the following command:


c:\ruby\bin\ruby.exe c:\crewres\services\ad_login_verify.rb

The reason this is done will become apparent as we set things up to run as a windows service.


STEP 2 Create a Service

This is a two-step process. NOTE – for this section, I essentially followed the instructionshere

2.1) First we create a “stub” Windows Service using SRVANY.

From a command prompt, type:

INSTSRV "CrewResAuthSvc" "C:\Program Files\Windows Resource Kits\Tools\srvany.exe"

This will create a service in Service Manager called “CrewResAuthSvc”. Note the path to srvany may differ.

2.2) Second, you need to edit the registry, telling it what command to run when the service is started.

Navigate to the registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CrewResAuthSvc
Add a key called Parameters
Underneath Parameters, add a Value called Application with a value being C:\crewres\services\CrewResAuthStart.bat

Now, you should be able to start the service using services.msc

NOTE ON STOPPING THE SERVICE - Please note that a SRVANY solution does not have a way to actually stop a service. You can click on “Stop Service” in services.msc and you will see that it indicates that the service has stopped.

HOWEVER, SRVANY does not have a way to stop ruby (or *whatever* it had launched originally).

Therefore, you have to go to the Windows Task Manager and kill the ruby.exe Process manually.

Note – a tricky way to determine the PID of the process (assuming you have MKS toolkit installed) is to do the following:
ps -a | grep "ruby.exe" | head -1 | awk '{print $1}'

this will print out the PID, after which you can do kill -9 *PID*

STEP 3 Register the .dll as an Event Source for Event Viewer

There is a list of Services that are allowed to send messages to the EventLog Service.

This list may be found under the following registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\Sources

Now, since this is weird registry type that you cannot directly edit (it’s a bunch of null-terminated strings), you have to run some code that will insert CrewResAuthEventSvc into the list of “approved” event sources.

Do this by running the following command from the command prompt (this file is also in .svn)

ruby registerEventSource.rb

The contents of registerEventSource.rb are as follows:

require 'win32/eventlog'
include Win32

dll_file = 'C:\crewres\services\CrewResAuthEventSvc.dll'

EventLog.add_event_source(
"source" => "Application",
"key_name" => "CrewResAuthEventSvc",
"category_count" => 2,
"event_message_file" => dll_file,
"category_message_file" => dll_file
)

After running this, the service "CrewResAuthEventSvc" is on the list of allowed services who can write events into the event log.

How to Create/Build the .DLL

CrewResAuthEventSvc.dll is a .dll that marshals event messages into the Windows EventLog. ("marshal" may not be the right word, but that's what I'm calling it)

I essentially followed the instructions found at
http://rubyforge.org/docman/view.php/85/1734/mc_tutorial.html

To build the .dll, you need to first create an .mc file and then compile that into a .dll

The .dll really does nothing other than to accept messages from Ruby and posts them to the event viewer.

You will need Visual Studio installed and a program called mc.exe that creates header (.h) files and .RES files, which are used by the linker to create a .dll.

I created a file, “CrewResAuthEventSvc.mc” to this end.

The contents of CrewResAuthEventSvc.mc are as follows:

LanguageNames =
(
English = 0x0409:Messages_ENU
)


;////////////////////////////////////////
;// Eventlog categories
;//
;// These always have to be the first entries in a message file
;//

MessageId = 1
SymbolicName = CATEGORY_ONE
Severity = Success
Language = English
First category event
.

MessageId = +1
SymbolicName = CATEGORY_TWO
Severity = Success
Language = English
LDAP Authentication Event
.


;////////////////////////////////////////
;// Events
;//

MessageId = +1
SymbolicName = EVENT_STARTED
Language = English
CrewResAuth Service successfully started
.


;////////////////////////////////////////
;// Additional messages
;//

MessageId = +1
SymbolicName = CREWRES_AUTH
Language = English
XFO AUTH: %1
.


Note that there must be an extra CRLF at the end of the file.

To build the .mc file into a .dll, you must execute the following three commands, which I put into a batch file for convenience:

Contents of buildDll.bat

@ECHO OFF
call "C:\Program Files\Microsoft Visual Studio 8\VC\bin\vcvars32.bat"
mc CrewResAuthEventSvc.mc
rc -r CrewResAuthEventSvc.rc
link -dll -noentry -machine:x86 -out:CrewResAuthEventSvc.dll CrewResAuthEventSvc.RES


Once the .dll is built, copy it into c:\crewres\services folder and do the above voodoo to register CrewResAuthEventSvc as a Registered Event Source.

Note that you need to look into the generated header (.h) file to see which event_id to use.


Here is the contents of the generated .h file:

////////////////////////////////////////
// Eventlog categories
//
// These always have to be the first entries in a message file
//
//
// Values are 32 bit values layed out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---+-+-+-----------------------+-------------------------------+
// |Sev|C|R| Facility | Code |
// +---+-+-+-----------------------+-------------------------------+
//
// where
//
// Sev - is the severity code
//
// 00 - Success
// 01 - Informational
// 10 - Warning
// 11 - Error
//
// C - is the Customer code flag
//
// R - is a reserved bit
//
// Facility - is the facility code
//
// Code - is the facility's status code
//
//
// Define the facility codes
//


//
// Define the severity codes
//


//
// MessageId: CATEGORY_ONE
//
// MessageText:
//
// First category event
//
#define CATEGORY_ONE 0x00000001L

//
// MessageId: CATEGORY_TWO
//
// MessageText:
//
// LDAP Authentication Event
//
#define CATEGORY_TWO 0x00000002L

////////////////////////////////////////
// Events
//
//
// MessageId: EVENT_STARTED
//
// MessageText:
//
// CrewResAuth Service successfully started
//
#define EVENT_STARTED 0x00000003L

////////////////////////////////////////
// Additional messages
//
//
// MessageId: CREWRES_AUTH
//
// MessageText:
//
// XFO AUTH: %1
//
#define CREWRES_AUTH 0x000003E9L

Do you see the two id's I used in my Ruby code? CREWRES_AUTH is id 0x000003E9L and the one I use to tell event viewer that the service has started is id of 0x00000002L

Anyway, the ids you will use will be generated by mc.exe from the .mc file and will appear in your header file.

That's it!

Lots of steps. Somewhat complicated. But the good news is that it works!

Good luck,

Shannon Norrell

Thursday, September 10, 2009

Command Prompt Here Registry Hack

Here's how to get a "Command Prompt Here" action added to Windows Explorer so that it will drop to a cmd.exe prompt on any folder you have navigated to.

Copy/paste the below code segment. Save it as CmdPromptHere.reg somewhere, right click on it and choose "Merge" and OK the changes it will make to your registry.

-- CUT ----------------------------------------------

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\shell\Command]
@="Command &Prompt Here"
[HKEY_CLASSES_ROOT\Directory\shell\Command\command]
@="cmd.exe"

-- CUT ---------------------------------------------

This is a simple operation that I end up doing to every machine I have and use.

-shannon norrell

Wednesday, September 9, 2009

Pervasive SQL PSQL v10 Custom Functions

I am having to struggle with hooking into a Pervasive SQL (Btrieve) database and have developed several custom functions that I thought I would share, as I did not find the answers on the internet forums and had to develop them myself. Perhaps I can save someone else a few hours of frustration.

The first, "PadWithZeroes" answers a question posed by many users

----------------------------------------------------------------------------------------------
-- PadWithZeroes(:N Integer, :NZ Integer) - Pad with Zeroes
-- Takes integer :N and returns a string padded with integer :NZ number of zeroes
----------------------------------------------------------------------------------------------
DROP FUNCTION PadWithZeroes;
CREATE FUNCTION PadWithZeroes(:N Integer, :NZ Integer)
RETURNS VARCHAR(25)
AS
BEGIN
DECLARE :retVal VARCHAR(25)
DECLARE :nString VARCHAR(25)
SET :nString = CONVERT(:N,SQL_CHAR)
IF LENGTH(:nString)>:NZ THEN
SET :retVal = :nString;
ELSE
SET :retVal = CONCAT(
RIGHT('0000000000000',:NZ - LENGTH(:nString)),
:nString
);
END IF
RETURN :retVal;
END;

The second ConvertToFOSDate, takes a date stored a long integer which is, in fact the number of days offset from January 1st, 1900 (aka a Julian Date) and converts it into a usable Date object:

----------------------------------------------------------------------------------------------
-- ConvertToFOSDate(:D integer) - converts a date stored like 40026 and converts it to a date
-- Takes NumberOfDays :D since 1900 to get the Date
----------------------------------------------------------------------------------------------
DROP FUNCTION ConvertToFOSDate;
CREATE FUNCTION ConvertToFOSDate(:D integer)
RETURNS DATE
AS
BEGIN
DECLARE :theDate DATE;
SET :theDate = DATEADD(DAY,:D,CONVERT('1899-12-31', SQL_DATE));
RETURN :theDate;
END;

The third takes a number of minutes and returns a string formatted as HH:MM

----------------------------------------------------------------------------------------------
-- ConvertToHHMM(:T integer)
-- Takes NumberOfMinutes :T and returns a string formatted as HH:MM
----------------------------------------------------------------------------------------------
DROP FUNCTION ConvertToHHMM;
CREATE FUNCTION ConvertToHHMM(:T integer)
RETURNS VARCHAR(5)
AS
BEGIN
DECLARE :theTime VARCHAR(5)
IF (:T = 0) THEN
SET :theTime = '00:00';
ELSE
SET :theTime = CONCAT(
CONCAT(PadWithZeroes(CONVERT(FLOOR(:T/60),SQL_INTEGER),2), ':'),
PadWithZeroes(CONVERT(ROUND(MOD(:T,60)/60,1)*60,SQL_INTEGER),2)
);
END IF;
RETURN :theTime;
END;

Here are a couple more. They are not world changing, but may be of help to someone:

----------------------------------------------------------------------------------------------
-- GetTimeInHundredths(:A integer)
-- Returns Minutes as a percentage of a day
----------------------------------------------------------------------------------------------
DROP FUNCTION GetTimeInHundredths;
CREATE FUNCTION GetTimeInHundredths(:A integer)
RETURNS VARCHAR(10)
AS
BEGIN
DECLARE :theTime VARCHAR(10)
IF (:A = 0 ) THEN
SET :theTime = '0';
ELSE
SET :theTime =
CONCAT(
CONCAT( CONVERT( FLOOR(:A/60), SQL_CHAR), '.'),
CONVERT( ROUND(MOD(:A,60)/60,1)*10, SQL_CHAR)
);
END IF;
RETURN :theTime;
END;
----------------------------------------------------------------------------------------------
-- MinutesBetween(:D1 DateTime, :D2 DateTime)
-- Computes the integer numberOfMinutes between Date1 :D1 and Date2 :D2
----------------------------------------------------------------------------------------------
DROP FUNCTION MinutesBetween;
CREATE FUNCTION MinutesBetween(:D1 DateTime, :D2 DateTime)
RETURNS INTEGER
AS
BEGIN
DECLARE :minutesBetween INTEGER;
SET :minutesBetween = CONVERT( DATEDIFF(MINUTE, :D1, :D2), SQL_INTEGER);
RETURN :minutesBetween;
END;


-shannon norrell

Saturday, July 4, 2009

Changing Default Download Folder for Internet Explorer 6

How to Change the Default Download Folder for Internet Explorer 6

I just had the pleaser to use a computer that had IE6 still installed on it. I needed to change the default download directory, which ended up being a "home-made hack" I figured out on my own.

I know this information is a bit useless/out of date inasmuch ad Microsoft is about to release IE8, but there may be someone out there who needs this information.

So here is how to change the default downosd folder for IE6.

1. START/Run/Regedit
2. Navigate to this key: HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer
3. Right click on the right pane.
4. Choose New / String Value (REG_SZ)
5. Name the new key "Download Directory"
6. For the string value, enter the folder you want, in my case "C:\Dropzone"

Make sure to close all instances of IE that may be running for the change to take effect.

Shannon Norrell

Monday, May 18, 2009

Badass Javascript Date Validator.
Useful for Rails.

Haven't posted for a while. I should remember to post cool routines like the one below after I write them as they are useful to many.
I have seen quite a lot of good examples of folks using (and expanding upon) routines I wrote for some of the Gadgets that ship with Vista (to be found in library.js if you have a Vista Box) and for some of AOL's (apparently deprecated) Mac OSX Widgets.
Functions like showOrHide, getLocalizedString, getSpinner (emulates Vista-like spinner image) I have seen in use all over the place.

Here is a somewhat badass Date Validator routine I wrote today that takes in either a string date value or an <INPUT> Element Object and validates the date to be formatted as DD-MM-YYYY.

I wrote it for use by a Ruby on Rails application, but it's pretty handy as a general library function.

Using this (simple) Regex:
var DATE_REGEX = /^(\d{2})-(\d{2})-(\d{4})$/

////////////////////////////////////////////////////////////////////////////////
// //
// validateDate([object|string] oHTMLInputElement) - Validates a date value //
// =============================================== //
// You can pass in the id to an <input> Element, an actual //
// oHTMLInputElement or an actual date value in the form of a string //
// //
////////////////////////////////////////////////////////////////////////////////
function validateDate(oHTMLInputElementOrDateValue) {
var theDateValueToValidate = "";
var theValidatedDateValue = false;
var bIsInputElement = false;
if (typeof(oHTMLInputElementOrDateValue)=="string") {
var oHTMLInputElement = document.getElementById(oHTMLInputElementOrDateValue);
if (oHTMLInputElement && typeof(oHTMLInputElement=="object") && (oHTMLInputElement.tagName=="INPUT")) {
theDateValueToValidate = oHTMLInputElement.value;
bIsInputElement = true;
} else {
// Presumably they just passed in a date string
theDateValueToValidate = oHTMLInputElementOrDateValue;
bIsInputElement = false;
}
} else {
// They have passed in an Object
if (typeof(oHTMLInputElementOrDateValue=="object") && (oHTMLInputElementOrDateValue.tagName=="INPUT")) {
var oHTMLInputElement = oHTMLInputElementOrDateValue;
theDateValueToValidate = oHTMLInputElement.value;
bIsInputElement = true;
}
}
// If we have a date value to actually validate, let's get started
if (theDateValueToValidate != "") {
var theDateValue = theDateValueToValidate.replace(/\//g,"-"); // Ruby-side only accepts hyphens
if (DATE_REGEX.test(theDateValue)) {
theValidatedDateValue = theDateValue;
} else {
if (theDateValue.indexOf('-') > -1) {
var theMonth = theDateValue.split('-')[0];
var theDay = theDateValue.split('-')[1];
var theYear = theDateValue.split('-')[2];
theMonth = parseInt(theMonth).PadWithZero(); // If they've entered a single digit for either Day or Month, pad with zero accordingly
theDay = parseInt(theDay).PadWithZero();
if (theYear.length==2) { theYear = "20" + theYear; } // If they've entered a two-digit year, assume it's in the 21st century
theDateValue = theMonth + "-" + theDay + "-" + theYear; // Rebuild the date. Hopefully this will work
if (DATE_REGEX.test(theDateValue)) {
theValidatedDateValue = theDateValue;
}
}
// If we get to this point and none of our efforts have worked to format the date as "required", throw an error (if they are using an INPUT element)
if (!theValidatedDateValue && bIsInputElement) {
alert("ERROR: Date must be formatted as follows:\n\n\tDD-MM-YYYY\n\nPlease correct before continuing");
oHTMLInputElement.focus();
}
}
}
if (bIsInputElement && theValidatedDateValue) {
oHTMLInputElement.value = theValidatedDateValue;
}
return theValidatedDateValue;
}

Hope you find this one useful as well.

Shannon Norrell


Now on GitHub as well

Monday, April 13, 2009

Rnadom Flex Note - Event handlers Nightmares

It is onerous to say the least to determine which events to handle for many objects.

 

For instance today, I was trying to get an <mx:HSLIDER> control to change the size of something in real time (as the thumb drags around)..

 

Let me explain how I figured out which one to use.

 

Here is how I started out:

 

<mx:HSlider id="mySlider" />

<mx:Text id="foo" text="{mySlider.value}"/>           

 

With this setup, there is some magic that occurs so that when you stop dragging the little thumb, the binding takes into effect and the value in the text box updates.  Easy enough, right?

 

So now, what if I want the value in “foo” to update AS I DRAG THE THUMB.  Should be possible (it is).

 

I counted, and there are no less than 63 events that an HSLIDER control can fire.  Which one to trap?

 

Well, not finding an obvious answer on the internet, I began attaching handlers to events that seemed to make sense:

 

So here is my first iteration:

 

<mx:HSlider id="mySlider" change="sliderChange(event)"/>

 

Notice that with Flex, you always have to pass in the (event) object. 

 

Now in your handler, you have to “handle” the specific type of event that has fired.

 

So let’s say I wrote my handler as follows:

 

    internal function sliderChange(event:StateChangeEvent) : void {

      var theSlider:Slider=Slider(event.currentTarget);

      foo.text = String(theSlider.value);

    }

 

THIS WILL NOT WORK AS I HAVE NOT USED THE RIGHT EVENT TYPE.

 

There are two ways to determine what type of event to put in your handler.

 

1)       put *something* and let the IDE tell you:

     “Error implicit coercion of a value of type mx.Events.SliderEvent to an unrelated type mx.Events.StateChangeEvent”

So, in this case, it’s telling you to use “SliderEvent”, not “StateChangeEvent”

2)       The second (preferred) way is to simply hover over the “event” part in your line:

<mx:HSlider id="mySlider" change="sliderChange(event)"/>

      *sometimes* a tooltip will appear telling you what event handler type to use

   3) A third way is to launch in debug mode, get an error and then you can hover over stuff and figure out what type of event handler to use:  See Screenshot:

 

 

One would think that this tells me the event type is SliderChange.

 

The actual event handler type is “SliderEvent”

 

The reason I am telling you all of this is that it is extremely frustrating to do some simply shit in the Flex IDE at the moment. Surely there must be a better way.

 

Anyway, moving on.

 

So now we have:

 

<mx:HSlider id="mySlider" change="sliderChange(event)"/>

 

And an event handler

    internal function sliderChange(event:SliderEvent) : void {

      trace(“sliderChange eventg fired”);

      var theSlider:Slider=Slider(event.currentTarget);

      foo.text = String(theSlider.value);

    }

By adding “trace” calls, when you run in debug mode, the text dumps to the console.

 

So, in this case, guess what?  This event does not fire until the user has released the thumbdrag handle!

 

So I try another event han dler

 

<mx:HSlider id="mySlider" currentStateChange="sliderStateChange(event)"/>

 

Notice I have to use a different handler because (you guessed it), the event fired is different.  So, going through the same agony as before, I determine that the event type being fired is, in fact, called “StateChangeEvent”. (Bascically by trying to run the app, letting it error out and reading the error message.

 

    internal function sliderStageChange(event:StateChangeEvent) : void {

      trace("sliderStageChange");

    }

 

Near as I can tell, this event does not fire.

 

So, thinking that it is not really the “slider” that is changing but the thumbdrag handle, I thought to add an event handler to that one as well:

 

Now, iteration #4:

 

        <mx:HSlider id="mySlider" thumbDrag="thumbDrag(event)" />

        <mx:Text id="foo" text="{mySlider.value}"/>           

 

And writing another event handler:

 

    internal function thumbDrag(event:SliderEvent) : void {

       trace("thumbDrag");

    }

 

 

I can see in my debug window that this is, in fact, the event that fires as you move the thumbDrag thingie around.

 

Therefore, I fix the event handler to read As follows:

    internal function thumbDrag(event:SliderEvent) : void {

       trace("thumbDrag");

      var theSlider:Slider=Slider(event.currentTarget);

      foo.text = String(theSlider.value);

    }

 

 

GUESS WHAT?  IT STILL DOESN’T WORK!

 

Turns out all you have to do is add “livedragging=”true” to the HSlider control and you do not have to write any event handlers.

 

 

So,

        <mx:HSlider id="mySlider" liveDragging="true"/>

        <mx:Text id="foo" text="{mySlider.value}"/>           

 

Does the trick..

 

 

Why do I tell you all about this?  So hopefully you can share in my pain and perhaps to learn something from my stupidity.

Flex is cool, but there is a pretty steep learning curve.

 

-shannon norrell

 

 

Wednesday, February 25, 2009

Fast JavaScript Max/Min on an Array Taking Arbitrary Number of Arguments

What's the fastest way to find the largest, or smallest, number in an array?

Array.max = function( array ){
return Math.max.apply( Math, array );
};
Array.min = function( array ){
return Math.min.apply( Math, array );
};

By using JavaScript's .apply() method on a built-in function you can pass in an array of unlimited arguments.

Shannon Norrell