iPad Pro

Well, I did finally upgrade to an iPad Pro about 3 weeks ago, used, and then recently added the iPad Pro Pencil too. Beautiful pieces of hardware. I didn’t get the iPad as a productivity device, although I actually do a little bit of real work on it thanks to it’s portability. Trouble is, everything takes about twice as long as it should. I am still hoping they’ll put MacOS on a tablet with mouse support some day. Sigh. One of the biggest iOS problems is downloading and transferring and sharing files between apps. Sure you can do it but it’s not standardized and varies for each app. No control over the file system. A constant struggle. 🙁

Then there is the irritating iOS policy of sometimes forcing users to use apps instead of the web. Apparently Apple would actually like to replace the web with iOS. That is doomed to failure. More and more I see signs that Apple marches to their own agenda and listening to customers and providing options is just not the top priority.

My next move is to purchase a Surface Pro and retire my 2011 vintage MacBook Air (which runs Windows natively, with a partition for MacOS too). My Air is so fast and that’s why I have kept it so long. It has aged better than any computer I have ever seen. I just need more SSD, USB 3, touch screen, and higher resolution — and in a tablet instead of a notebook. It’s just crazy that MacOS has no touch screen support. Dinosaurs go extinct.

I’ll keep my iPad though, at least for awhile. For what it does, it is nice. The Surface Pro is evolving though in a better direction and at a greater speed. I can easily envision that in a couple of years the Surface Pro will be my one and only.

iPad Pro vs Surface Pro 3 (Review)

 

I had been really looking forward to an “iPad Pro”. Then I heard Apple would most likely release it with iOS, maybe a “souped up” iOS with dual screen multi-tasking.

No deal.

I have had iPads since the very beginning. Heck, I pre-ordered the iPad 1. I like iPads BUT they are useless as a productivity device. It’s only good as consumer consumption device IMHO. So when Jeri needed a new computer and a tablet I suggested she go with the Surface Pro 3 laptop/tablet hybrid. She loves it. And so do I. It is a productivity machine with a full OS (Windows 8), has a finger touch screen, real keyboard (that tucks away), track pad, and stylus. The stylus was the biggest surprise. That thing is neat. It senses the screen (or vice-versa) without touching it and is pressure sensitive too. SurfacePro3Another nice surprise is the docking station. It is really convenient and well worth the extra price. You can just grab your computer and run. Later, it slips right back in and supports all your peripherals. No problemo. It makes perfect sense that the keyboard is not bundled with it. Not needed and no need to pay for it if you have another keyboard you like. Not needed if your intention is to use it strictly as a tablet. The keyboard is worthy too. I don’t agree with reviewers who pan the keyboard and then complain if they pay extra for it. I think it works great.Dock

Just two days ago I saw the Apple announcement of the new super-thin and powerful MacBook. Really nice. Best laptop ever made. I don’t want a laptop. I want a hybrid. Apple is really behind the curve on this.

I am really looking forward to the Surface Pro 4.


4/5/2016 Update

Well the Surface Pro 4 has been available for awhile now. Very very impressive. And there are two iPad Pro’s now also.

I still feel the same way. The iPad Pro’s are not a pro device. iOS is for consumers and phones, not professionals. End of story.

Right now, my circa 2011 Macbook Air running Windows 7 and OS X Yosemite is so blazing fast and rock solid that I see no need to upgrade quite yet to the Surface Pro. I’ll continue to use the MacBook Air for “pro” stuff and the iPad 4 for consumer stuff. Eventually I’ll upgrade of course with the Surface Pro and have it all in one device.

AngularJS Is For Real

Using web technologies is a roller coaster experience. New things roll out every week. That can be very exciting and fun, actually. Always learning. Reinventing. However, I can’t totally keep up with it all so I have to choose my focus from time to time. Right now I choose AngularJS. I don’t know that AngularJS is totally the future, but the direction AngularJS it is taking with HTML data binding totally is. I think.

AngularJS_logoI’m currently working on a small AngularJS project — a product order form, with API’s to PayPal, etc. So far I am loving Angular and I think it has a big future. Here is why:

  • Reason 1: It can be simple. It’s like when I first learned HTML. It was attractive because you could do something simple right away, knowing just a little about it. AngularJS is like that — and it is integrated right into HTML. However, it’s not totally simple. If you want to go very far with AngularJS, you’ll need to be JavaScript savvy, and yet the JavaScript is more like glue than the actual app, so code redundancy is reduced because Angular does so much — invisibly!
  • Reason 2: It is comprehensive. Unlike HTML alone, AngularJS really can do everything without additional tools. AngularJS scales well and supports a robust MVC design paradigm. It just depends on how far you need to go with it.
  • Reason 3: It is well supported and extremely well documented. Even the best tool can fail to gather broad support if there is a lack of resources behind it. AngularJS is supported by Google so that should be no issue. Google has created a winner.

My Web Portfolio

I love web development and I started doing it professionally in 2005. In all, I have created, or helped create, nearly 100 websites. I’ll include only the more interesting ones here…

TheHolyScriptures.info

Esther GenesisCompareS
This SPA (Single Page App) acts like a desktop application (no page refreshes). The UI is completely handled by JavaScript and is mostly hand-coded, including the AJAX (RESTful get/put) calls. There are zero page refreshes. The only library I used was Scriptaculous and then only for the vertical and horizontal accordion effects. It may be simpler to make a library AJAX calls but I just wanted to do it manually — plus get the maximum flexibility. One of the significant features of the web page includes the interactive text comparison. I used PHP on the back end to execute complex MySQL queries and build XML formatted data returned to the JavaScript client. XML was a better format than JSON in this case due to the hierarchical format of the data. If I were doing over, I would look to AngularJS for the accordion effect and remove the Scriptaculous.

Interactive Educational Game – Geography

Oregon This is a relatively simple game written in JavaScript for the interaction and animation, and PHP for initialization. I have three versions of this and it’s actually very addictive as well as educational. These games are part of LaGrandeOnline.com, as community site.

SIMILE Time Charting

Simile
Simile is a remarkable tool for presenting time-lines. The display can be interactively scrolled horizontally by dragging the mouse. The only trouble is that the tool has a lot of defects. The one that really stopped me was the inability to handle BC dates for any browser except Internet Explorer. Finally, in order to use the tool I had to examine the code and patch it myself.

jQuery

I try to use whatever presentation tools are best and appropriate for the task. Often this means jQuery because it has so many tools built upon it, including tools/widgets that come in a suite, like jQueryUI. Here are two jQuery grid tools I have implemented. My actual work is behind corporate firewalls and user login accounts, but below are some sample screens from these tools.

CU Web Portal

DataTables This is an industrial strength grid that can handle large data sets. It has many built in options and additional features that can be added. I employed this grid for a CU web portal.

CollectorTech.com

techblog This one is a very elegant grid that is indeed easy to use. It has a number of defects which I was able to fix, and so I put it to good use on the CollectorTech website, to replace simple non-interactive PHP grids. I sent my bug fixes to the developer who never responded. Apparently he wasn’t interested.

WordPress

I am a heavy user of WordPress as a blogging platform and a CMS. Almost 20% of the entire web is based on WordPress so it is important to understand WordPress. I have created many WordPress sites, and custom themed them. WordPress’s great advantage are the many plugins available. However, as any WordPress user knows, custom hand written html is often needed as TinyMCE is not enough. Also, it’s not uncommon to manually change plugin code to meet a specific need. With WordPress, security concerns are less and regular updates are easy.


techblog

Zoom This is the site you are reading right now. I’m usually too busy to post much here but enjoy sharing when I can or when I need to record something important.

ldsblog

This heavily themed site is where I comment on religious subjects and post biographies. It uses a wide variety of plugins to display tooltips and media. The final step is to build and format the reference notes the way I like. I wrote a C# program that parses the html to create references, build reference links and format complex content automatically. I just copy/paste in, convert, and then paste back.

UCRCC.org

This is a heavily trafficked political site where I post regular updates. Because it is based on WordPress, it is very easy to update.

LaGrandeONLINE.com

Originally I created a custom website for LaGrandeONLINE.com from scratch. Later, I converted LaGrandeONLINE.com to WordPress. I was able to theme it to look much the same only now it is easier to maintain. The menu system is extensive and hierarchical.

JayMackley.com

This is my personal website. Written in PHP/JavaScript/jQuery, it introduces my activities on the web.

Other Stuff

I have written a few web programs that do not reside on websites. A large website I wrote was once hit with a virus so I wrote a virus scanner called scan.php which searches an entire web site for virus signatures and file permissions problems — and then eradicates the malware. It is much more effective and time efficient than trying to do it by hand!

I also have a program in VB6 that scans a website, collects data, and places it in a database. It was fairly easy to do. It obeys file metadata instructions and the Robot Exclusion Protocol standard (robots.txt).

iPad 4 Review

I purchased a 64 GB WiFi only iPad 4 about a month ago to replace my “old” iPad 1. Both of my iPads are black and at first glance one would hardly see the difference. There is a positive difference in functionality though and I’ll just mention my impressions and not get into much technical detail.

First of all, the “Retina” display on the iPad 4 is AMAZING – just as good as magazine printed text and sometimes better.

Also now I can now keep more apps in memory (without restart) because the iPad 4 has 4 GB of RAM whereas the iPad 1 had only 256 MB. Then of course there es the processor speed: faster on the iPad 4 and definitely noticeable, however I am not sure how much the extra zippyness is due to the A6X processor and how much to the additional RAM (does it matter?). Both iPads are powerful enough to run video with no problem so the primary speed advantage to me is in browsing the web with the Chrome browser.

Then there is the weight – the iPad 4 feels (and is) lighter. I find now that I use the iPad 4 even more than before (which was considerable) and am starting to even do some actual work on the iPad whereas before I treated it strictly as a consumer device.

Finally, there are the camera’s (front and back). The iPad 1 didn’t have this and I really like having it.

I think the Apple supplied “cover” is both cheesy and over priced. Below is the case I purchased from Amazon.com for about $16.

Accessorise Ultra Slim and Lightweight Leather Case Folio (Black) with
Automatic Wake-up Closure and 3-in-1 built-in Stand for Apple iPad 4 / iPad

iPad Stylii Review

About 5 months ago I came to the conclusion that my fingers are not ideal for the iPad. There are three reasons:

  1. Fingers are too fat. Precision pointing is often required for speed and accuracy.
  2. Fingers are too greasy. Who wants a smeared up, greasy screen?
  3. Fingers are too slow. A pencil or pen provides quicker directional changes.

After reading stylus reviews I narrowed down my choice and picked the Wacom Bamboo Stylus for $25 from Amazon.com. Nicely crafted with aluminum and brass, it has good heft and balance and is quality made — but it is a horrible stylus for the iPad. Oops.

So I tried again. This time I choose the “Universal Touch Screen Stylus”, also from Amazon.com at $2.50 for a 3-pack. Much better and here’s why:

  1. Price: No comparison. The current price for the Universal is $1.05 for a 3-pack.
  2. Weight: Turns out weight is not your friend. The Universal is much much lighter, faster, and less tiresome over time.
  3. Size: Smaller is better for “poking” the screen, which is what you mostly do with an iPad. The Universal is just right. The Wacom too big.
  4. Rubber tip: Firmer is better. The Wacom has a “flat tire” feel to it. I want to be able to jab the screen without hitting the rims!
  5. Rubber tip:  Slipperier is better. The Wacom has more “grip” and thus is the inferior choice for handwriting and art apps. The stylus needs to slide like a pencil, not stick and grab. The Universal is better at this, but not perfect
  6. Capacitance: Stylii work with a small electrical charge. It’s is easier for current to flow through a little metal rather than a lot of metal. The thin shelled Universal wins again. It is more reliable in registering screen taps
  7. Peace of mind: stylii are like pencils – easy to misplace or lose. With three super inexpensive Universals, I can spread them around and don’t have to worry about it. The $25 Wacom is always a worry.

Well there you have it. My opinion. Sometimes the less expensive product is better!!

Wacom Bamboo
Wacom Bamboo
Stylus

Universal Touch
Screen Stylus

GUI For PICK Solved

Years ago, even before I graduated from BYU in Computer Science, my first computer related job involved the PICK operating system. Very fortuitous, as it turned out, for it lead to a long term position at ADP Inc, now an 8 billion dollar company in annual sales. The PICK system is a marvel and that is why it is still very much alive and active in the world of business computing. Today it is called a MultiValue NoSQL type database system. There are multiple flavors, all based on the “Microdata REALITY” original: Reality, CoRA, NorthGate, jBase, and Universe are some varieties that I have used.

Originally, MultiValue systems used character based terminals and, later, PC based terminal emulators for the user interface (UI). With the advent of Macs, PC’s, and then the web, it became a little embarrassing from a marketing perspective for companies with MV systems to continue on with character based (green screen) interfaces. Most MV systems are extremely robust and efficient so they end up running a lot of intricate business processes on the back end. Over the years MV legacy systems grow to such complexity that it is cost prohibitive and almost impossible to replace them with “modern” relational databases and GUI systems, which, when compared to legacy MV systems tend to be “all hat and no cattle.”

ADP tried to replace their MV systems (CoRA) several times by re-writing it into a 3-tiered GUI system — and failed. Eventually, they punted and developed “Web Suite” which simply overlayed a graphical screen directly on top of existing character based screens. This allowed them to continue with all the old underlying software and architecture and introduce a somewhat graphical look and feel. Ingenious really, but not as capable as a modern GUI should be. Lipstick on a pig really, but good enough.

All companies with MV systems have struggled in a similar fashion because MV systems do not have a built in communications layer that works seamlessly with internet based communications. My current company, Columbia Ultimate, has also searched for a good solution to this problem. Finally, after all these years, the problem has been solved. A company called Bluefinity International has created a product called MV.Net that lets MV/Pick communicate on the network in the native way: using the telnet protocol. The end result is a browser running a Silverlight (C#) based GUI that talks directly to the MV back end. It works beautifully and, yes, MV programming is required to separate the obsolete UI code from the business logic code — not a minor task, depending on how many functions are moved over — yet it’s doable and at the same time tends to simplify MV systems significantly.

The problem in the MV/Pick world that has existed since Windows 95 has finally been solved in a quality way.

New Macbook Air

To support Mac OS X development, I purchased the 64 bit MacBook Air 13″ last week. I have not been disappointed as it is just plain faster than any computer I have ever used or seen. Blistering fast. I would have chosen the MacBook Pro if I had a spare $1000 to throw at it but I am happy with the Air. The smaller form factor has it’s advantages and it’s actually newer generation hardware than the Pro right now. I told one person the Air was so thin I could cut cake with it. He laughed and suggested maybe I could chop wood. I don’t think I’ll try that.

However, I had some interesting little glitches early on, so I just thought I would share since others might have the same… Since I need both Windows and Mac OS, I decided to install Windows via Boot Camp. I considered Parallels or VMWare VM (Virtual Machine) but decided that, as a developer, I won’t be switching that often. I’ll spend long periods in each OS and I like the idea of running natively. Besides, Mac OS X boots up in only 8 seconds or so. I can handle that!! Also 4GB of memory is fine for one OS but a bit tight for two. The Air’s memory is not upgradeable because it is soldered in to achieve the ultra thin styling.

I purchased an OEM version of Windows Home Premium as the price is right and I don’t need anything offered by the Pro version for my development or support tasks. Not having a ready external optical drive for installing Windows I decided to create an ISO image on an external notebook drive connected via USB. Didn’t work. I created the ISO image OK but the drive just wouldn’t boot successfully into the Windows start-up via Boot Camp. After some frustration (and vain googling) I gave up on that and just hooked up an internal DVD drive borrowed from a desktop computer. I used an inexpensive USB cable with power supply and it worked like a charm. BTW, I consider a USB to hard drive cable like this one to be simply indispensable to owning a computer. Five dollars. Don’t leave home without it.

The Air has a solid state hard drive and it’s “only” 128 GB and I wanted to leave plenty of room for Mac OS X. When setting up the Windows partition via Boot Camp, it defaults to 20 GB. I thought that should be fine since I don’t need that much and since I’ve read people claim they can run Win7 in 8GB. WRONG. The Windows install itself chewed up 15 GB plus, then after installing Visual Studio C# I had less than 3GB remaining. After a few more installs of essentials, I got down to 1.7 GB left and then — BOOM. Windows would not reboot successfully. It could not be repaired or restored from a restore point either. Dead.

The wild thing is that it damaged the Mac OS X partition also! — and this was just after I got everything set up over there. After going through the Command-R (restore utilities) process without success, I placed a call to Apple and got immediate and friendly help but after some consultation, they confirmed what I already pretty much knew. I was toast.

So, after re-partitioning the drive with the Mac OS repair utility (Command-R at boot-up). I got a chance to try Apples new internet based “no DVD needed” approach to system restores. Worked great, although some patience was required to download the entire OS.

The second time around I partitioned Windows for 50GB, installed Windows 7, and it’s worked great ever since. I consider my MacBook Air to be worth every dime I paid for it and then some. And it’s nice to be leading edge, at least for as long as it lasts…

Simile Bug Resolved!

Simile is a browser app with a great presentation for displaying timelines. See the Simile timeline examples. I first tried Simile at version 1.0 but got frustrated with the many defects it still contained. It’s now at version 2.3 so I thought I would try again but was still stopped by a major defect – it can’t handle BC dates correctly!!

I looked into it and found the issue. Here is an example of what fails:

var objDate = new Date(“Mar 10, 600 BC 01:00:00”);

That’s a major flaw for a timeline app!! The built-in JavaScript date creation fails on BC and negative year dates for Firefox & Chrome but not Internet Explorer (at least IE versions 8 and 9 are OK).

The fix is to first create the desired date object as a non-BC date, then extract the year, make it negative, and finally set the date object to the negative year. I wrote a new function “getDatesBC” to it this way.

Simile coders put in a terrible hack that only allowed BC dates if provided in whole years in the format “yyyy BC”. My fix allows any valid format, including optional month, day, and time and should work for any browser since the exception code is only executed if the date creation fails (therefore IE works just as it did before). The fix is made to the simile-ajax-bundle.js file and is shown below. The parseGregorianDateTime function is re-written and the getDatesBC function is new.

FINAL NOTE: To use BC dates with this fix, specify all dates (including Timeline.createBandInfo dates) in the format MMM DD YYYY BC. Example: Apr 06 0600 BC  or  Apr 6 600 BC. If you leave out the month and day it may still fail.

The compressed code is the original and the non-compressed indented code is the new code for the fix.

SimileAjax.DateTime.parseIso8601DateTime=function(A){try{return SimileAjax.DateTime.setIso8601(new Date(0),A);
}catch(B){return null;
}};
// start of patch
function getDatesBC(myDate) {
  var orig = myDate;
  var isGood;
  try {
    isGood = !isNaN(myDate.getUTCFullYear()); // BC dates may return NaN on getUTC, if non-IE browser.
  } catch (BBB) {
    isGood = false; // BC dates may fail totally on getUTC, if non-IE browser.
  }
  if (isGood) {
    return myDate; // BC date succeeded. Probably IE browser.
  } else {
    var isBC = false;
    orig = orig.replace("B.C.","BC");
    if (orig.indexOf(" BC") != -1) {
      isBC = true;
      orig = orig.replace(" BC","");
    }
    if (orig.indexOf(" -") != -1) {
      isBC = true;
      orig = orig.replace(" -","");
    }
    if (isBC) {
      // BC date failure so special handling here
      if (orig.indexOf(":") == -1) {
        orig = orig + " 00:00:00";
      }
      myDate = new Date(orig);  // Create the date as non-BC date. Should always work.
      BCyear = myDate.getUTCFullYear();
      BCYear = (1 - parseInt(BCyear)); // set the year to negative
      myDate.setUTCFullYear(BCYear); // assign the neg year to the already created date object (this works)
      return myDate; // return the valid BC date object
    } else {
      return myDate; // Date failed for non BC reason so just returned failed date
    }
  }
};
SimileAjax.DateTime.parseGregorianDateTime=function(F){
  if (F==null) {
    return null;
  } else {
    if (F instanceof Date) {
      return F;
    } else {
      F = getDatesBC(F);
      return F;
    }
  }
};
// end of patch
SimileAjax.DateTime.roundDownToInterval=function(E,B,I,K,A){var F=I*SimileAjax.DateTime.gregorianUnitLengths[SimileAjax.DateTime.HOUR];
var J=new Date(E.getTime()+F);
var C=function(L){L.setUTCMilliseconds(0);
L.setUTCSeconds(0);
L.setUTCMinutes(0);
L.setUTCHours(0);
};

Apple and iPad 2

Apple is revolutionizing the computer industry. In many ways it’s the PC revolution all over again and Apple is very well poised to win it all this time around. Their iPad is amazing in many multiple small ways that add up. The one-click purchasing of app, songs, movies, and books is marvelous. I really like reading books and watching movies (with earphones) on the iPad. At the end of the day, it’s really the quality and quantity of apps that leave other other tablets in the dust right now and provide Apple’s total supremacy. And it’s only one simple tap to purchase/install/update and you only have to put in your password once up front. Compare this to PC apps: download it (step 1), where did it go? (step 2), click on the installer (step 3 what’s an installer? says grandma), review a bunch of incomprehensible questions and click next, next (step 4 and 5) and then run the app (step 6, but again, where did it go?). Purchasing a program online is another 6 steps or so (add it the shopping cart, enter your name, enter you CC number, enter the CCID, etc etc). Apple has got this mess solved. For usability, the iPad is a great device for grandma and grandpa on so many levels — they really can’t get themselves into any serious trouble with it. The iPad is also great with 2-3 year old kids. I know this from personal experience and a youtube search is convincing. The iPad apps are typically of great quality and so-o-o cheap, and they come with no chance of malware. My workplace requires anti-virus software on all network devices – except iOS — because malware doesn’t exist with iPads and iPhones and that’s a very very important revolution. Of course, there is no iPad malware because each and every app is pre-approved by Apple before it is made available on an Apple device. Techie’s hate this closed approach but for the general public now-days it’s really a gift — as the average PC Joe is sitting on a boatload of malware. I am writing this on  a dual core Win7 laptop PC. I run a lot of apps and get annoyed at the wait cursor which can sometimes come up for no good reason. No wait cursor on the iPad. In fact, no waiting — at least not perceptually — and no apps ever freeze (i.e. “not responding”) for any reason whatever. Finally, developers are making real money off the iPad through the app store. Just try selling your PC app! Some of the best stuff for the PC has to just be given away and very little money is made unless you are a big corporation that has cornered some part of a vertical market. Not so with the iPad — at least not yet. Computers are leaving the desktop and going mobile. The world is changing.

One another note: The iPad 2 is out in the US and being released internationally in 3 days. It appears to be a worthy successor. The look of the unit is very impressive and much of what I asked for in my last iPad post is now available. Here’s my take on it …

  1. Screen: I was looking forward to a higher res OLED screen. Maybe iPad 3. Meanwhile the display is still very, very good.
  2. Battery: Maintaining the same 10 hours is not an improvement, but still noteworthy considering the lighter, thinner design and additional hardware features (2 video cameras and an HDMI port). Ten hours is passable for now. Again, maybe iPad 3.
  3. Weight: Thinner and lighter is great for book reading. Look forward to trying it.
  4. OS: iOS has been upgraded twice since I got my iPad. Getting  better and better. Great!
  5. Camera: I was expecting iPad 2 with one camera but it comes with two!! Plus both cameras do video. Cool. They’re not high spec cameras by any means, however I am sure they’ll be fine for Facebook social media. Apple hit the target market and kept the overall price the same as iPad 1. Smart move actually.
  6. Memory: Ram memory doubled to 512k for iPad2. However, with the solid state drive on iPad1, plus a very modern OS, I’ve never actually encountered a real “memory problem” on iPad 1 in the traditional PC sense — and I use my iPad with many multiple apps a LOT.
  7. Micro-card reader: They didn’t provide it on iPad 2. I would still like this.
  8. Other: iPad 2 has a gyroscope and a plug and play HDMI video port that works with all apps. Very important features that I overlooked in my last post. I hope to get an iPad 2 ASAP but won’t be a super-early adopter. Nevertheless, I look forward to it!!

Me and My iPad

Yes, I have an iPad and love the thing. It’s a great addition but not a replacement to my regular computer. Still, I use it every day even when I am not on the go. I originally pre-ordered an iPad in March 2010 and was one of the first to receive one. Unfortunately my original iPad had a hardware problem with freezing video but Apple honored their 90 day warranty gracefully and replaced it. That was a confidence builder!

Technology is marching so fast, I already see a host of improvements Apple can make for version 2 of the iPad. Here’s my list:

  1. The iPad screen is a marvelous 160 dpi IPS beauty but it could be better. What I really want is the 360 dpi “retina display” that recently came out on the iPhone. Oh, and I want it to be OLED of course.
  2. 10 hours battery life is much better than I get with my laptop but still not quite good enough, especially since I don’t really get quite 10 hours in actual use. An OLED screen would help here. Also, it’s rumored that there are new battery saving technologies that Apple is considering. This includes capturing energy from Wi-Fi signals and solar energy capture. Great! I want it.
  3. I find the iPad to be a good book reader in many regards except for one thing. It’s too heavy. At 1.5 pounds it’s still too heavy for real convenience. Most of that weight is in the battery size, so that needs to be reduced. 12 ounces would be perfect. To do that Apple will have to lose the big battery and probably some of the aluminum.
  4. The iOS needs a upgrade to add a few convenience features. iOS 4.0 is coming out in November 2010. Great! This means multi-tasking support and ability to actually arrange/categorize desktop icons.
  5. The next iPad needs a camera. The same or better than the current iPhone 4 camera would be great! What’s really nice about the idea of an iPad camera is the size of the photo preview screen. iPhone 4 camera specs:
    • 5.0 Mpixels (2592 x 1936)
    • 1/3.2″ back-illuminated CMOS sensor
    • 4:3 aspect ratio
    • 35 mm film camera crop factor: 7.64
    • Low ISO 80 (or better)
    • 3.85 mm lens focal length
    • f/2.8 lens aperture
    • Autofocus: tap to focus
  6. The iPad also needs more memory. 256K just won’t cut it for long. Fortunately at present, the memory limitations do not slow it down, largely thanks to the solid state hard drive I imagine.
  7. Finally, the iPad needs a micro-card reader. Right now it’s just too difficult to get data in and and out over wi-fi networks. It’s not Apples fault but there is just too much security and inconvenience to worry about so everyone needs a quick and easy way to exchange and backup data.

Well, that’s all I want. I am not asking too much am I? I would like all this on the anniversary of the first iPad – April 1.

😉

Ripples

Ripples is what I call the brownish graphics pattern seen at the head of this blog. The Ripples graphic is based on the formula for a circle (z = y2 + x2). The actual formula is:

IntegerResult = (OffSetY + (Y * Param1)2) + (OffSetX + (X * Param1)2)

where X and Y are pixel coordinates. Param1 has a value of 1 so that we get a circular (not an oval) pattern. OffSetY and OffSetX are the number of pixels to shift the pattern (not the whole picture) horizontally and vertically. OffSetY and OffSetX are both zero in this case. Each pixel is calculated one at a time, left to right, top to bottom.  Given the default values for OffSetX, OffSetY, and Param1, we can rewrite the formula as:

IntegerResult = Y2 + X2

So the only purpose of the formula is to determine if a given X,Y pixel gets a color and, if so, what that color it will be. The modulo value of IntegerResult determines the color of the pixel at coordinates X,Y like this: Each pixel color is assigned for prime numbers 2, 3, 5 etc, whichever is first evenly divisible into the IntegerResult. In this case, if IntegerResult is divisible by 2 then the current X,Y pixel is assigned the color black. If the IntegerResult is divisible by 3 (but not 2)  then the current X,Y pixel is assigned the color brown. If the IntegerResult is divisible by 5 (but not 2 or 3) then the current X,Y pixel is assigned the color dark green. If the IntegerResult is not evenly divisible by 2, 3, or 5 then the X,Y pixel is assigned the background color, which in this case is a brick red.

Virus Scanner

One of my websites (not this one) was recently hit with a virus, which prompted me to write the following php virus scanner. The script is also useful for getting a directory structure listing or to search for strings other than virus signatures. The code comments below explain usage…

//
// Author: Jay Mackley. For public use with attribution.
// PHP version 5.2
// This program scans every file in every directory for potential virus strings
//
// Only valid 'fix' value is 'Y' otherwise just don't use it. Don't set this
// unless you KNOW what you are doing as this could destroy your
// website if there's a bug in your virus removal section...
$fix_mode = $_GET["fix"];
//
// 'list' triggers a scan results report and support one or more of these flags:
//   'N' for including files Not found by the scan (no virus string detected)
//   'E' for including Empty, zero length files detected
//   'F' for including files Found by the scan (virus string detected)
//   'C' for including files Changed by the scan (only possible if fix=Y)
//   'A' to print All directories and files, including indenting by directory structure.
// Multiple list flags can be combined in the URL. Example: list=FE
$list = $_GET["list"];
//
// 'type' is one or more substrings separate by a '|', that filters which filenames
// are considered for scanning. Usually the extension would be targeted
// but it could be any file substring. Example: .css|.js|.htm
// In the example, css, js, an .htm files would be scanned. html files are
// also scanned since .htm is a substring of .html
$type = $_GET["type"];
//
// 'find' is the substring of the file representing the signature of the
// virus being scanned for. Example: eval(escape
// The example would likely find the Gumbar web virus and variants
$find = $_GET["find"];
//
// The scan won't happen unless a password is passed in. Not very tight
// security but it keeps this scanning program from being run carelessly.
$pswd = $_GET["pswd"];
//
// Set default value to zero for counter variables.
$total_good = 0;
$total_bad = 0;
$total_links = 0;
$total_scanned = 0;
$total_files = 0;
$total_dirs = 0;
$total_links = 0;
$total_all = 0;
//
// This function checks for the virus and returns a string value indicating
// the results for each file path passed in.
function CheckVirus($my_path, $fix_mode, $my_virus_signature) {
  $my_text = file_get_contents($my_path);
  global $total_good;
  global $total_bad;
  global $total_scanned;
  global $missing_files;
  $total_scanned++;
  if ($my_text != false) {
    $signature_pos = strpos($my_text,$my_virus_signature);
    if ($signature_pos !== false) {
      $total_bad++;
      // Show the found virus signature string, plus 30 chars more - just a quick peek.
      $snippet = substr($my_text,$signature_pos,30);
      // make tags appear as literals so we don't mess up the listing
      $snippet = str_replace("<","<",$snippet);
      $snippet = str_replace(">",">",$snippet);
      if ($fix_mode === "Y") {
        // Here's where you need to be careful. The next line needs to be
        // customized for each specific type of virus occurrence. Here, I am
        // assuming the virus is always at the end of the file where it can
        // easily be stripped off.
        $my_text = rtrim(substr($my_text, 0, $signature_pos-120));
        //
        if (file_put_contents($my_path, $my_text)) {
          $output = " - <b>Changed</b>: <span style='font:orange'>$snippet</span>";
        }
      } else {
        $output = " - <b>Found</b>: <span style='color:red'>$snippet</span>";
      }
    } else {
      $total_good++;
      $output = " - <b>Not Found</b>: <span style='color:blue'>$my_virus_signature</span>";
    }
  } else {
    $missing_files++;
    $output = " - <b>Empty</b>: <span style='color:green'>Missing or zero length file</span></b>";
  }
  return $output;
}

function GetDirFiles($path, $level, $types, $find_string, $my_fix_mode, $list) {
  // Let's not scan the following, including ourself
  $ignore = array('.', '..','cgi-bin', $me_name);
  $dh = @opendir($path);
  global $total_all;
  global $total_dirs;
  global $total_files;
  global $total_links;
  global $my_types;
  global $me_name;
  while (($file = readdir($dh)) == true) {
    if (!in_array($file, $ignore)) {
      $total_all++;
      // We can easily tell how much to indent by counting the forward slashes
      if (strpos($list,"A") !== false) $spaces = str_repeat('&nbsp;', substr_count("$path/$file",'/')*4);
      if (is_dir("$path/$file")) {
        // Report on each directory path encountered
        $total_dirs++;
        if (strpos($list,"A") !== false) echo "<strong>$spaces $path/$file</strong><br />";
        // This routine is recursive. That's how it checks all the directories...
        GetDirFiles("$path/$file", ($level+1), $types, $find_string, $my_fix_mode, $list);
      } elseif (is_link("$path/$file")) {
        // Report on links encountered.
        $total_links++;
        if (strpos($list,"A") !== false) echo "$spaces@$path/$file<br />"; // Report links
      } else {
        if (strpos($list,"A") !== false) echo "$spaces$path/$file ";
        $output = "";
        $total_files++;
        for ($i=0; $i<count($types); $i++) {
          // We shouldn't encounter an empty string but check just in case
          if ($types[$i] !== "") {
            // If the file qualifies by type sub-string then continue
            if (strpos($file,$types[$i]) !== false) {
              // If we're looking for an empty string then skip but that shouldn't happen either...
              if ($find_string !== "") {
                // Set the path a call the CheckVirus function to see if we got a problem
                $my_path = "$path/$file";
                $my_path = substr($my_path,2,9999);
                $output = CheckVirus($my_path, $my_fix_mode, $find_string, $list);
                // A is a full directory listing
                if (strpos($list,"A") !== false) {
                  echo $output;
                } else {
                  // Just print out the results of the virus check. One of these cases should apply.
                  if (strpos($list,"C") !== false && strpos($output,"Changed") !== false) {
                    echo "$my_path - $output<br />";
                  } else if (strpos($list,"F") !== false && strpos($output,">Found") !== false) {
                    echo "$my_path - $output<br />";
                  } else if (strpos($list,"N") !== false && strpos($output,"Not Found") !== false) {
                    echo "$my_path - $output<br />";
                  } else if (strpos($list,"E") !== false && strpos($output,"Empty") !== false) {
                    echo "$my_path - $output<br />";
                  }
                }
              }
            }
          }
        }
        if (strpos($list,"A") !== false) echo "<br />";
      }
    }
  }
  closedir($dh);
}
// Main Program
if ($pswd === "your password here") {
  echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"';
  // Get my own name for later use.
  $me_path = $_SERVER["SCRIPT_NAME"];
  $pieces = explode('/', $me_path);
  $me_name = $pieces[count($pieces) - 1];
  //
  echo '<html><head></head><body><br />';
  $types = explode('|', $type);
  echo "Looking for the string: <b>".$find."</b><br />";
  echo "For file names containing: <b>".implode(" ", $types)."</b><br /><br />";
  GetDirFiles(".",$listing,$types,$find,$fix_mode,$list);
  echo "<br />";
  echo "Total empty files = $missing_files<br />";
  echo "Total non matching files = $total_good<br />";
  echo "Total matching files = $total_bad<br />";
  echo "Total scanned files = $total_scanned<br />";
  echo "<br />";
  echo "Total directories = $total_dirs<br />";
  echo "Total files = $total_files<br />";
  echo "Total links = $total_links<br />";
  echo "Total links, files, directories = $total_all<br />";
  echo "</body></html>";
}

Comparing Text

The code below is used by theholyscriptures.info to compare similar verses. For example, sections of Isaiah are quoted in the Book of Mormon (e.g. 2 Nephi 24:2 Δ Isaiah 14:2), and these verses can be shown side by side thanks to the compare function shown below. The compare function is designed to work on plain text and produce a result with minimal markup: only ins and del tags are added. To compare xml or html text, the < and > characters should first be converted to html entities (e.g. &lt; and &gt;).

The difficult part of this algorithm is determining how many words to “look ahead”. My solution was brute force, checking all possibilities and then choosing the lookahead number that produces the least markup. More information is given in the code comments.

I hope someone finds this useful in other applications. If you can suggest improvements to the code or algorithm, I am very interested!

// This compare routine takes two similar texts and returns
// the difference with <ins> and <del> markup.
String.prototype.compare = function(compareTo) {
  // Author: Jay Mackley - 2009, TheHolyScriptures.info. For public use with attribution.
  function Compare(mySource, myTarget, lookAhead) {
    // First clean up and normalize the text.
    //
    // Trim spaces front and back. Make sure it's all one line per verse.
    mySource = mySource.replace(/^\s*|\s*$|[\n\r]/g,"");
    myTarget = myTarget.replace(/^\s*|\s*$|[\n\r]/g,"");
    // Remove all markup tags
    mySource = mySource.replace(/<(.|\n)*?>/g,"");
    myTarget = myTarget.replace(/<(.|\n)*?>/g,"");
    // Remove extra spaces and treat long dashes as a word separator.
    // Include soft hyphen (00AD), figure dash (2012), em dash (2013), en dash (2014)
    mySource = mySource.replace(/\s+|(--)|[\u00AD\u2012\u2013\u2014]/g," ");
    myTarget = myTarget.replace(/\s+|(--)|[\u00AD\u2012\u2013\u2014]/g," ");
    // Move cleaned text into arrays that still contain word punctuation.
    // These arrays are used to build the final result but not for comparison.
    var cleanSourceArray = mySource.split(" ");
    var cleanTargetArray = myTarget.split(" ");
    //
    // Remove all other punctuation and caps so only plain words are left to compare.
    // Create these arrays for making the actual text comparisons word by word
    var sourceWords = mySource.replace(/[,:?!.;]/g,"").toLowerCase().split(" ");
    var targetWords = myTarget.replace(/[,:?!.;]/g,"").toLowerCase().split(" ");
    //
    var myDelta = "";   // Holds the comparison results text with markup
    var bDone;          // Set to true when all words have been examined
    var aMax;           // Maximum words in source array
    var bMax;           // Maximum words in target array
    var aLookAhead;     // How many source words to look ahead for comparison
    var bLookAhead;     // How many target words to look ahead for comparison
    var aFound;         // Indicates a word match in source
    var bFound;         // Indicates a word match in target
    var a = 0;          // Source array pointer
    var b = 0;          // Target array pointer
    var la;             // Source array pointer
    var lb;             // Target array pointer

    bDone = false;
    while (bDone === false) {
      if (sourceWords[b] === targetWords[a]) {
        myDelta = myDelta+cleanTargetArray[a]+" ";
        b++;
        a++;
      } else {
        if ((b + lookAhead) > cleanSourceArray.length-1) {
          bLookAhead = cleanSourceArray.length-1;
        } else {
          bLookAhead = b + lookAhead;
        }
        if ((a + lookAhead) > cleanTargetArray.length-1) {
          aLookAhead = cleanTargetArray.length-1;
        } else {
          aLookAhead = a + lookAhead;
        }
        bFound = false;
        aFound = false;
        for (lb = b; lb <= bLookAhead; lb++) {
          if (targetWords[a] === sourceWords[lb]) {
            bFound = true;
            break;
          }
        }
        for (la = a; la <= aLookAhead; la++) {
          if (sourceWords[b] === targetWords[la]) {
            aFound = true;
            break;
          }
        }
        if (bFound && aFound) {
          if ((lb - b) > (la - a)) {
            for (i = a; i < la; i++) {
              myDelta = myDelta+"<ins>"+cleanTargetArray[i]+"</ins> ";
            }
            a = la;
          } else {
            for (i = b; i < lb; i++) {
              myDelta = myDelta+"<del>"+cleanSourceArray[i]+"</del> ";
            }
            b = lb;
          }
        } else if (bFound === true) {
          for (i = b; i < lb; i++) {
            myDelta = myDelta+"<del>"+cleanSourceArray[i]+"</del> ";
          }
          b = lb;
        } else if (aFound === true) {
          for (i = a; i < la; i++) {
            myDelta = myDelta+"<ins>"+cleanTargetArray[i]+"</ins> ";
          }
          a = la;
        } else {
          if ((b >= cleanSourceArray.length-1) && (a < cleanTargetArray.length-1)) {
            myDelta = myDelta+"<ins>"+cleanTargetArray[a]+"</ins> ";
            b = cleanSourceArray.length-1;
            a++;
          } else {
            if ((a >= cleanTargetArray.length-1) && (b < cleanSourceArray.length-1)) {
              myDelta = myDelta+"<del>"+cleanSourceArray[b]+"</del> ";
              a = cleanTargetArray.length-1;
              b++;
            } else {
              myDelta = myDelta+"<ins>"+cleanTargetArray[a]+"</ins> ";
              myDelta = myDelta+"<del>"+cleanSourceArray[b]+"</del> ";
              a++;
              b++;
            }
          }
        }
      }
      if ((b > cleanSourceArray.length-1) || (a > cleanTargetArray.length-1)) bDone = true;
    }
    if (cleanSourceArray.length < cleanTargetArray.length) {
      // The target text has words remaining, so add them on the end as insertions
      for (i = a; i < cleanTargetArray.length; i++) {
        myDelta += "<ins>"+cleanTargetArray[i]+"</ins> ";
      }
    } else if (cleanTargetArray.length < cleanSourceArray.length) {
      // The source text has words remaining, so add them on the end as deletions.
      for (i = b; i < cleanSourceArray.length; i++) {
        myDelta += "<del>"+cleanSourceArray[i]+"</del> ";
      }
    }
    myDelta = RemoveRedundantTags(myDelta)
    return myDelta;
  } // end of Compare function
  //
  function RemoveRedundantTags(deltaMarkup) {
    var state = "";
    var prevState = "";
    var words = deltaMarkup.split(" ");
    for (var p = 1; p < words.length; p++) {
      prevState = state;
      if (words[p].left(4) === "<ins") {
          state = "ins";
      } else if (words[p].left(4) === "<del") {
        state = "del";
      } else {
        state = "";
      }
      if ((state === "ins" && prevState === "ins") || (state === "del" && prevState === "del")) {
        words[p-1] = words[p-1].substr(0,words[p-1].length-6);
        words[p] = words[p].substr(5,words[p].length-5);
      }
    }
    return words.join(" ");
  }
  var sourceMax = this.count(" ")+1;
  var targetMax = compareTo.count(" ")+1;
  var max;
  if (targetMax > sourceMax) {
    max = targetMax;
  } else {
    max = sourceMax;
  }
  var bestCnt = max;
  var currCnt;
  var bestLookAhead = 1;
  // Examine all possibilities to determine the best lookahead value
  for (var testLookAhead = 1; testLookAhead <= max; testLookAhead++) {
    currCnt = Compare(this, compareTo, testLookAhead).count("</");
    if (currCnt < bestCnt) {
      bestCnt = currCnt;
      bestLookAhead = testLookAhead;
    }
  }
  return Compare(this, compareTo, bestLookAhead);
}
// Include the left/count functions in this listing for completeness
String.prototype.count=function(myString) {
  var regexp = new RegExp(myString, "g");
  return this.replace(regexp, myString+"*").length - this.length;
}
String.prototype.left = function(p) {
  if (this.length < p) {
    return this;
  } else {
    return this.substr(0,p);
  }
}

XML Output

Below is actual XML output from the PHP of the previous post. XML is way cool. The XML shown can be generated directly from your browser with the following URL:

http://theholyscriptures.info/PHP/ZLookup_XML.php?vol=1&book=1&chapter=1&verse=1-3

<scriptures type="List" title="The Holy Scriptures" sql="">
  <volume num="1" title="Old Testament">
    <book num="1" title="Genesis" longtitle="The First Book of Moses called Genesis" chapters="50">
      <chapter num="1" title="Chapter 1 - God creates the Earth" comments="1">
        <verse id="1" num="1" mark="" score="">
          IN the beginning God created the heaven and the earth.
          <comment type="World" name="There is a God"/>
        </verse>
        <verse id="2" num="2" mark="" score="">
          And the earth was without form, and void; and darkness was upon the face of the deep.  And the Spirit of God moved upon the face of the waters.
        </verse>
        <verse id="3" num="3" mark="" score="">
          And God said, Let there be light: and there was light.
        </verse>
      </chapter>
    </book>
  </volume>
</scriptures>

PHP Usage

Sometimes, for a carpenter, every solution involves a hammer. PHP is like that. It’s every web developers hammer. My problem is that many PHP based apps, because of the way they are written, are hard to understand and maintain. Also since PHP apps runs on the server side, they are always going to be slower than running JavaScript on the client side. Finally, a PHP app doesn’t fit the AJAX paradigm that I think is the future of the web.

Therefore my PHP usage is primarily as a data bridge to bring the other pieces (JavaScript, xhtml, MySQL) together. Therefore TheHolyScriptures.info has no need for PHP frameworks because it’s just not that complicated. I want all my PHP to be straightforward, fast, short, simple to maintain. Every time I call the server (via AJAX) I execute PHP to fetch and format the data – and never for web page manipulation. TheHolyScriptures.info requires PHP 5.2 or greater for built in XML support. Here’s a short example of a PHP script I use and how it builds the XML (parts are obfuscated for security reasons…).:

$MyPswd = "real password goes here...";
$DB_Connection = mysql_connect("localhost", "mackle_lds", $MyPswd) or die ('Cannot connect to database: '.mysql_error());
mysql_select_db("mackle_LDS");

 // prevent SQL injection attacks with mysql_real_escape_string
$volume_id = mysql_real_escape_string($_GET["volume"]);
$book_id = mysql_real_escape_string($_GET["book"]);
$chapter = mysql_real_escape_string($_GET["chapter"]);

// Tell the browser the output is XML Unicode
header("Content-Type: text/xml; charset=utf-8");

// Query for volume and book
$sql  = "SELECT volume_title, num_chapters, book_title, book_title_long FROM scriptures_books WHERE ";
$sql .= "volume_id = $volume_id And book_id = $book_id";
$result = mysql_query($sql,$DB_Connection);

if ($row = mysql_fetch_object($result)) {
  
  //Create XML document using the DOM 
  $xmlDoc = new DomDocument('1.0', 'UTF-8');
  
  $scriptures->setAttribute('type','Fill');
  $scriptures->setAttribute('title','The Holy Scriptures');
  if ($SQL == 'Y') {$scriptures->setAttribute('sql',$sql2);} // Used for diagnostics in admin mode
  
  // Create volume sub-node                 
  $volume = $scriptures->appendChild($xmlDoc->createElement('volume')); 
  $volume->setAttribute('num','1');
  $volume->setAttribute('title',$row->volume_title);
  
  // Create book sub-node
  $book = $volume->appendChild($xmlDoc->createElement('book'));        
  $book->setAttribute('num','1');
  $book->setAttribute('title',$row->book_title);
  
  // Query for chapter and verses
  $sql2 = "SELECT chapter, chapter_name, verse_id, verse, verse_scripture, html_top, html_right, html_bottom,";
  $sql2 .= " html_left, html_replace, groupname FROM lds_scriptures_verses ";
  $sql2 .= " WHERE volume_id = $volume_id And book_id = $book_id And chapter = $chapter";
  $result2 = mysql_query($sql2,$DB_Connection);
  $FirstTime = true;
  while ($row2 = mysql_fetch_object($result2)) {
     if ($FirstTime === true) {
       // Create the chapter sub-node. We are only doing one chapter of verses.
       $chapter = $book->appendChild($xmlDoc->createElement('chapter'));
       $chapter->setAttribute('num',$row2->chapter);
       $chapter->setAttribute('title',$row2->chapter_name);
       $chapter->setAttribute('comments','');
       $FirstTime = false;
    }
    if ($row2->html_replace === '') {
      // Format the standard version with formatting as defined, if any
      $verse_text = str_replace('\r\n','<br>',$row2->verse_scripture);
      $verse_text = $row2->html_top.$row2->html_left.$verse_text.$row2->html_right.$row2->html_bottom;
    } else {
      // Assign the special formatted verse,  rather than the raw text verse used for searching.
      $verse_text = $row2->html_replace;
    }
    // Create the chapter verse sub-node and attributes
    $verse = $chapter->appendChild($xmlDoc->createElement('verse'));
    $verse->appendChild($xmlDoc->createTextNode($verse_text));
    $verse->setAttribute('id',$row2->verse_id);
    $verse->setAttribute('num',$row2->verse);
  }
  // Send results back to JavaScript Client
  echo $xmlDoc->saveXML();
  // Close the connection
  mysql_close($DB_Connection);
}

Despite my remarks on the role of PHP, I enjoy PHP programming a lot. It’s is a great and powerful language. My next post will discuss the script above…

The Key to Everything…

TheHolyScriptures.info uses the “AJAX” technique on the client side (xhtml, CSS, JavaScript) to communicate with the server side (PHP, MySQL) and is the key to everything that allows the page to interact with the database. The communication occurs by using the XMLHttpRequest object. Here is the function I use to create my XMLHttpRequest object:

function getNewXMLHttpRequest() {
  var Obj = false;
  try {
    // For Firefox, Safari, Chrome, Opera, IE7+
    Obj = new XMLHttpRequest();
  } catch (trymicrosoft) {
    try {
      Obj = new ActiveXObject('Msxml2.XMLHTTP');
    } catch (othermicrosoft) {
      try {
        Obj = new ActiveXObject('Microsoft.XMLHTTP');
      } catch (failed) {
        try {
          AJAX = window.createRequest();
        } catch (e) {
          Obj = false;
          alert('XMLHttpRequest Error! Your Browser is incompatible.');
        }
      }
    }
  }
  return Obj;
}

… and below is an example of actual usage – in this case a POST (update) to the server. If I tried to do this by tacking my params onto the url and using a GET, my commentaryText variable would be limited in size to about 4k and would get truncated. So I always use a POST when writing to the server and a GET when reading from the server, just like the names imply. Reasons for always doing it this way include: consistency, security with POST writes, no size limitations with POST writes, and client side caching with GET reads.

if (!AJAX) AJAX = getNewXMLHttpRequest();
var url = "PHP/SaveComment.php";
var params = "id=" + CVERSEID + "&type=" + CTYPE + "&name=" + CNAME;
params += "&cmd=" + sqlCommand + "&comment=" + commentaryText;
AJAX.open('POST', url, true);
AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
AJAX.setRequestHeader("Content-length", params.length);
AJAX.onreadystatechange = function() { ReShowCommentary(myCommands); };
AJAX.send(params);

For completeness a GET example is shown below. The GET is actually a bit simpler than the POST. Not much code really. Note that the param’s are part of the url. Also the callback routine (populateDisplay) is specified with no arguments, but just because it’s not needed in this case.

 if (!AJAX) AJAX = getNewXMLHttpRequest();
 var url = 'PHP/Lookup_XML.php?vol=' + escape(volume) + "&book=" + escape(book);
 AJAX.open('GET', url, true);
 AJAX.onreadystatechange = populateDisplay;
 AJAX.send(null);

The last AJAX step is to actually receive data. This involves a little dance step in the callback routine looking for the right events before proceeding. We just have to wait for the right readyState and status before grabbing the data string from AJAX.responseText

function populateDisplay() {
  if (AJAX.readyState == 4) {
    if (AJAX.status == 200) {
      var myXML = XMLparse(AJAX.responseText);
      // put code here to use the XML document
    }
  }
}

Well, to be complete I guess I should go a little further with this. The data is coming back as a XML string but it must be defined as such if we’re going to use it. The responseText is just a string. It could be any type of string but passing XML around makes a heck of a lot of sense if responseText contains anything complicated at all. In the case of scripture data there are volumes, books, chapters, verses – each with meta-data so using XML is a no brainer. By using XML we have a larger string being passed in but the increased size of the string is not a good reason to avoid XML. If the data string is small, then a few extra XML tags just aren’t significant and if the data string is large then the XML tags are just a small percentage of the total size but all the more important. The meta-data is never a waste and is always used or I wouldn’t be sending it.

function XMLparse(text) {
  if (typeof DOMParser != "undefined") {
    // Mozilla, Firefox, and related browsers
    return (new DOMParser()).parseFromString(text, "application/xml");
  } else if (typeof ActiveXObject != "undefined") {
    // Internet Explorer.
    var doc = XMLnewDocument( );    // Create an empty document
    doc.loadXML(text);              // Parse text into it
    return doc;                     // Return it
  }
}

Well, that’s it for the AJAX I use in JavaScript. In others posts, I’ll discuss the PHP server side of this AJAX communication and also how I access and use the client side XML.

About Browsers…

This blog is about state-of-the-art web development, specifically as it applies to my site: TheHolyScriptures.info.

Recently, some great web browsers have been introduced to compete with IE and Firefox: specifically Chrome, and Safari 4. These new browsers are more significant than many realize: they are more than web browsers, they are “platforms”. They have compiled Javascript! and support programs that run in their own process space. This means a big step forward: the era of the internet based applications. Eventually this means no more slow page refreshes, no more heavy reliance on HTML and no more over-use of server languages like PHP. Ok, I know, internet computer-like apps have been around for awhile but they could not compete because of speed and compatibility issues. Now they can compete because of the new breed of browsers. Safari 4 even passes the Acid 3 standards test! Firefox 3.5 has a compiled Javascript engine too. On the other hand, IE 8 just came out and, oops, it has slow Javascript, is buggy, and still non-standard, so it’s going to slow up progress — but only in the short term.

In any case, I think I see the web future and it is Linux / Apache / compiled JavaScript / AJAX / PHP / MySql-PostgreSQL. Time will tell. Meanwhile I’ve developed my most advanced website, TheHolyScriptures.info on the technologies mentioned. It works for me!