VB.NET

There are two main syntax “paradigms” in the programming world: BASIC and C. Most programming languages today are derivatives of these. My first programming experiences in school were with BASIC. Later my career started with what is now called a No SQL type database language called “Data Basic”. Later I came to know and love Microsoft’s VB6. BASIC was designed to be “friendly”. On the other hand the C syntax was a little more difficult but more powerful. This is because originally BASIC was an interpreted language and C was compiled down to the native runtime machine code. With BASIC you could build business apps. With C you could build operating systems.

With the introduction of object oriented programming the distinctions between C languages and BASIC languages began to blur. One could use either one for business apps (example: JavaScript is an interpreted C like language) but C languages were still the core standard. So it looked like C syntax languages were destined to rule both worlds. Nope.

One reason is that Visual BASIC was wildly popular and so when the Microsoft .NET framework was introduced they supported C# but also VB.NET for those who liked the BASIC syntax. I have a lot of experience with C# but I am using VB.NET for my current project and finding it easier and friendlier. I think that in the end “ease of use” will win the day. For example, it turns out there are many ease of use advantages to VB.NET over C#.

There is an excellent write-up on the advantages of VB.NET over C# that I recommend: 10 Reasons Why Visual Basic is Better Than C#

OpenQM on my desktop

Even before I graduated from BYU in Computer Science, my first computer related job involved the PICK operating system. Nowadays, it’s called a “NoSQL” “MultiValue” database system. The first PICK system came alive in 1967-68. My first work experience with it was in 1979, even before I received my Computer Science degree in 1980. This of course, pre-dates the personal computer era. It was the time of the refrigerator sized “mini-computer”.

I know of no other computing system of this vintage that is still alive and relevant in the market place. There must be something to it…

In the course of my computer career I have worked with Unix, Linux, and Windows systems. I have programmed with many versions of SQL and relational databases. I have been heavily involved with HTML5 and JavaScript, mainly with LAMP web tools (Linux, Apache, MySql, PHP) but also with Window web servers, Visual Basic, Visual Studio C++ and related technologies.

All these tools are good at what they do but none of them can replace what a good Multi-Value database can do. Most people are surprised at how many large companies quietly rely on a Multi-Value database on the back end to keep their business grounded. The reason is a small memory footprint, scalability, blazing speed of the database and a programming language that excels above all others in data and text manipulation. I am not kidding.

Originally, the PICK system ran on mini-computers and companies like ADP Dealer Services (now SDK Global) made lots of money selling the “big iron”. Later it became a hosted multi-user sub-system residing within Unix, Linux, or Windows and the system and apps pricing highly priced to make up for the loss of the hardware sales revenue.

Recent trends are very positive, with many MultiValue vendors effectively competing on both price and compatibility among the various implementation flavors. The result has been increased standardization and a reduction in the cost to license a MultiValue system. I was recently pleased to obtain my own personal Windows version called “OpenQM“. It’s a complete and robust package, yet takes only 17MB to install. Cost is $158. I’ll be using it for education, personal projects, and for fun.

iPad Pro (updated 11/25/21)

Everything below the line is January 18, 2017. My how things have changed. I now have an iPad Pro 4 Gen. My old iPad was great but the iOS software is growing in maturity and therefore over tasking the CPU. Keynote would get so slow I could hardly type sometimes. Other apps were fine but clearly I needed to upgrade, so I did. Glad I did. No Pencil this time though. Expensive. Just don’t need it for what I do on the iPad. Except for Keynote, Windows 10 is my real productivity OS. iPads are mostly just a convenient media device. I even got a mouse and keyboard for my iPad Pro but never use it.

I did upgrade also to a Surface Pro 6. Great machine. It has 8 GB RAM which is okay 99% of the time but next time it will be 16 GB.


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.

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.

ZCHM.TheHolyScriptures.info

This site is massive in size, all hand-coded, highly interactive with advanced CSS5 for the accordion interface and features instantaneous response times. To try a sample of the responsive and interactive features go to the Entity Relationship table at https://zchm.theholyscriptures.info/#Aaron

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).

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…

[php collapse=”true”]
//
// 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>";
}
[/php]

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!

[js collapse=”true”]// 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);
}
}

[/js]

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

[xml light=”true” wraplines=”false”]
<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>
[/xml]

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…).:

[php collapse=”true”]
$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);
}
[/php]

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:

[js]
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;
}
[/js]

… 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.

[js]
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);
[/js]

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.

[js]
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);
[/js]

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

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

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.

[js]
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
}
}
[/js]

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!