Home Articles FAQs XREF Games Software Instant Books BBS About FOLDOC RFCs Feedback Sitemap
irt.Org

Related items

Arrays, Object Arrays and Sorting

Text Strings and String Objects

Searching

Seek, and ye shall find

You are here: irt.org | Articles | JavaScript | Object | Seek, and ye shall find [ previous next ]

Published on: Sunday 21st September 1997 By: Martin Webb

Introduction

This article describes how to create a multiple keyword search facility. It builds on previous articles:

Storing Data in Objects

The first part creates an articleArray array of articleObject objects. Each of which has five properties, href, text, desc, tech and date:

<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript"><!--
function articleObject(href,text,desc,tech,date) {
     this.href = href; this.text = text; this.desc = desc; this.tech = tech; this.date = date;
}

function setArticle(href,text,desc,tech,date) {
    articleArray[articleIndex++] = new articleObject(href,text,desc,tech,date);
}

var articleIndex = 0;
var articleArray = new Array();

setArticle('js038/index.htm',
    'Split Ends',
    'Describes how to split strings, and how to split strings in JavaScript 1.0. Also describes how the two methods can be combined to provide a script suitable for all browsers.',
    'split, length, array, while, break, string, substring, indexOf, function overridding, JavaScript1.1',
    '19970906');
setArticle('js037/index.htm',
    'How long is a piece of string?',
    'Describes methods associated with the String object, how to use them to find characters or strings within strings, to replace characters and portions of text, to change the case and to alter the style and formatting of text.',
    'length, charAt, indexOf, lastIndexOf, split, substring, replace, iteration, toLowerCase, toUpperCase, upperFirst, upperInitial, arguments, big, blink, bold, italics, small, strike, sub, sup, fixed, fontsize, fontcolor',
    '19970830');
//...

//...
setArticle('js003/index.htm',
    'Searching',
    'Explanation of JavaScript Searching.',
    'Frames, JavaScript, Arrays, Objects, Forms, Frames, Tables',
    '19970209');
setArticle('js002/index.htm',
    'Array-Tours',
    'Demonstration of navigation tools.',
    'Frames, JavaScript, Arrays, Objects, Forms',
    '19970101');
setArticle('js001/index.htm',
    'Calendars',
    'Demonstrations of different calendar displays.',
    'JavaScript, Functions, Arrays, Tables, Floating Frames, Windows',
    '19961201');

Nested Splitting

The second part contains the necessary splitting functions, described in Nested Splitting:

var splitIndex = 0;
var splitArray = new Array();

var splitUpIndex = 0;
var splitUpArray = new Array();

function splitUp(string,separator1,separator2,by) {
    splitIndex = 0, splitUpIndex = 0; 
    string = separator2 + replace(string,separator1+separator1,separator1+separator2+separator1);
    splits(string,separator1,1);

    for (var i=0; i < splitIndex; i++) {
        if ((i/2) != (Math.floor(i/2)))
            setArray(replace(splitArray[i],separator2,by),2);
        else
            splits(splitArray[i],separator2,2);
    }
}

function splits(string,text,no) {
    var strLength = string.length, txtLength = text.length;
    if ((strLength == 0) || (txtLength == 0)) return;

    var i = string.indexOf(text);
    if ((!i) && (text != string.substring(0,txtLength))) return;
    if (i == -1) {
        setArray(string,no);
        return;
    }

    setArray(string.substring(0,i),no);
    
    if (i+txtLength < strLength)
        splits(string.substring(i+txtLength,strLength),text,no);

    return;
}

function setArray(text,no) {
    if (text != '') {
        if (no == 1) splitArray[splitIndex++]     = text.toUpperCase();
        else         splitUpArray[splitUpIndex++] = text.toUpperCase();
    }
}

How long is a piece of string?

The third part contains the replace() function, described in How long is a piece of string?:

function replace(string,text,by) {
    var strLength = string.length, txtLength = text.length;
    if ((strLength == 0) || (txtLength == 0)) return string;

    var i = string.indexOf(text);
    if ((!i) && (text != string.substring(0,txtLength))) return string;
    if (i == -1) return string;

    var newstr = string.substring(0,i) + by;

    if (i+txtLength < strLength)
        newstr += replace(string.substring(i+txtLength,strLength),text,by);

    return newstr;
}

The remaining script is new and will therefore be described in more detail.

Highlighting the text

The highlight() function is almost identical to the previous replace() function. It is subtly different, in that, instead of replacing the text found in string with by, it wraps the text found in string with before and after.

To ensure that lower and upper case characters are treated as the same, the searching of text within string is performed after converting string using toUpperCase.

function highlight(string,text,before,after) {
    var strLength = string.length, txtLength = text.length;
    if ((strLength == 0) || (txtLength == 0)) return string;
    
    var i = string.toUpperCase().indexOf(text);
    if ((!i) && (text != string.toUpperCase().substring(0,txtLength))) return string;
    if (i == -1) return string;

    var newstr = string.substring(0,i) + before + string.substring(i,i+txtLength) + after;

    if (i+txtLength < strLength)
        newstr += highlight(string.substring(i+txtLength,strLength),text,before,after);

    return newstr;
}

Outputing and Displaying the text

The output() function is used to display each and any article found with the required search string. The function is passed the details to be displayed. It maintains an articlesoutput count. The URL's used within the anchor tags, are retrieved directly from the href property of the appropriate articleArray[] element.

function output(href,desc,text,tech,date) {
    articlesoutput++;

     document.write('<TABLE WIDTH=595 CELLPADDING=5 CELLSPACING=0><TR><TD VALIGN=TOP WIDTH=230>');
     document.write('<A HREF="' + articleArray[i].href + '">' + text + '<\/A>');
     document.write('<BR>' + date);
     document.write('<\/TD><TD VALIGN=TOP WIDTH=365 BGCOLOR="#FFFFCC">');
     document.write('<A HREF="' + articleArray[i].href + '">' + href + '<\/A>');
     document.write('<BR>' + desc);
     document.write('<BR><B>Techniques:<\/B> ' + tech);
     document.write('<\/TD><\/TR><\/TABLE>');
}

The display() function cycles around each of the search keywords held within the splitUpArray[] array, by adapting the contents of the global variables href, desc, text and tech, with the result of the highlight() function, using before1 and after1.

Once all the search keywords have been highlighted, the replace() function is used to replace the occurences of before1 with before2, and after1 with after2.

Finally the output() function is used to display the article.

function display() {
    for (var j=0; j < splitUpIndex; j++) {
        href = highlight(href,splitUpArray[j],before1,after1);
        desc = highlight(desc,splitUpArray[j],before1,after1);
        text = highlight(text,splitUpArray[j],before1,after1);
        tech = highlight(tech,splitUpArray[j],before1,after1);
    }

    href = replace(replace(href,before1,before2),after1,after2);
    desc = replace(replace(desc,before1,before2),after1,after2);
    text = replace(replace(text,before1,before2),after1,after2);
    tech = replace(replace(tech,before1,before2),after1,after2);

    output(href,desc,text,tech,date);
}

Finding the text

The found() function cycles around each of the search keywords held within the splitUpArray[] array, to search for the any occurences. If any of the search keywords are not found, then the Found flag is set to false, i.e. all of the search keywords MUST be found in the entry for it to be displayed. The Found flag is returned to the caller of the found() function.

function found() {
    var Found = true;
    for (var j=0; j < splitUpIndex; j++) {
        if ((href.toUpperCase().indexOf(splitUpArray[j]) != -1) ||
            (desc.toUpperCase().indexOf(splitUpArray[j]) != -1) ||
            (text.toUpperCase().indexOf(splitUpArray[j]) != -1) ||
            (tech.toUpperCase().indexOf(splitUpArray[j]) != -1))
            continue;
        else
            Found = false;
    }

    return Found;
}                           

Entering the search criteria

The input() function is used to display an input search form, containing one field holding the value of the search string, plus an ACTION set to the current documents location.

The form is created this way, because, I wanted to be able to use the facility offline as well as online in Netscape, my preferred browser. When online the ful URL of the search page is required, yet when used offline this causes problems. The solution is to analyse the href property of the current location, and then create the form using JavaScript.

Note, to avoid having too much information being passed across in the locations search property, we name the input form using NAME="". Therefore the search property holds '?=xxx' where 'xxx' is the search string.

function input() {
    var endOfRef = window.location.href.length;
    if (window.location.search.length > 0)
        if (window.location.href.indexOf(window.location.search) > 0)
            endOfRef = window.location.href.indexOf(window.location.search);

    var loc = window.location.href.substring(0,endOfRef);

    document.write('<FORM ACTION="' + loc + '" METHOD="GET">');
    document.write('<TABLE WIDTH=595 BORDER=0 CELLSPACING=0 CELLPADDING=10 BGCOLOR="#AAAAFF"><TR><TD>');
    document.write('<CENTER><P>This is a searchable index. Enter search keywords: ');
    document.write('<INPUT TYPE="TEXT" NAME="" VALUE="' + searchFor + '">');
    document.write('<\/CENTER><\/TD><\/TR><\/TABLE><\/FORM>');
}

Excluding out highlighting from the search

If, for example, we were to use '<B>' and '<\/B>' as the values of before1 and after1, and we were then to search for 'a b' then each of the highlighted 'a' characters would be first of all wrapped as: '<B>a<\/B>'. Then when the 'b' characters are highlighted they would effect each of the 'a' characters thus: '<<B>B<\/B>>a<\/<B>B<\/B>>'. As you can see a bit of a mess.

Therefore, the before1 and after1 variables are set to characters not likely to be found within any of the articleObject objects. We then use the before2 and after2 variables to replace the before1 and after1 ones with something more meaningful.

The searchFor variable holds the contents of the search property of the current location, stripped of the first two characters (i.e. '?='), and with any '+' characters changed to spaces using the replace() function. Finally the unescape() function is used to convert any encoded characters passed by the search form to their ASCII values.

The splitUp() function is then used to break up the searchFor variable into the separate search keywords.

var before1 = unescape('%01'), before2 = '<FONT COLOR=maroon><B><EM>';
var after1 = unescape('%02'), after2 = '<\/EM><\/B><\/FONT>';

var searchFor = unescape(replace(window.location.search.substring(2),'+',' '));
splitUp(searchFor.toUpperCase(),'"',' ',' ');

var articlesoutput = 0;
//--></SCRIPT>

Almost the End

All the above JavaScript is placed with the <HEAD> and </HEAD> tags.

</HEAD>
<BODY>
<CENTER>

The small script below first displays a search input() form.

If splitUpIndex is greater than zero, i.e. there were search keywords entered, then an appropriate heading is displayed.

Then each of the articleObject objects within the articleArray[] array is cycled through. Note, we create the global variables href, desc, text and tech here using the properties of the current articleObject object.

If there were search keywords entered and they are found() then we display() the article with all its relevant highlighting.

Otherwise, if there were not any search keywords entered, we simply output() each of the articles without any highlighting.

If articles were found we display the articlesoutput count.

<SCRIPT LANGUAGE="JavaScript"><!--
input();

if (splitUpIndex > 0)
    document.write('<H2>Searching Index for: ' + before2 + searchFor + after2 + '<\/H2>');

for (var i=0; i < articleIndex; i++) {
    var href = articleArray[i].href;
    var desc = articleArray[i].desc;
    var text = articleArray[i].text;
    var tech = articleArray[i].tech;
    var date = articleArray[i].date;
    
    if (splitUpIndex > 0) { if (found()) display(); }
    else output(href,desc,text,tech,date);
}

if (splitUpIndex > 0) document.write('<P>' + articlesoutput + ' article/s found');
//--></SCRIPT>

</CENTER>
</BODY>
</HTML>

Thats all the script required to create multi keyword search page.

The following HTML can be used on other pages to search your database of articles:

<FORM ACTION="articles.htm" METHOD="GET">
This is a searchable index. Enter search keywords: <INPUT TYPE="TEXT" NAME="" VALUE=""> 
</FORM>

The search facility can be used in three ways:

Source code

<HTML>

<HEAD>
<SCRIPT TYPE="text/javascript" LANGUAGE="JavaScript"><!--
function articleObject(href,text,desc,tech,date) {
     this.href = href; this.text = text; this.desc = desc; this.tech = tech; this.date = date;
}

function setArticle(href,text,desc,tech,date) {
    articleArray[articleIndex++] = new articleObject(href,text,desc,tech,date);
}

var articleIndex = 0;
var articleArray = new Array();

setArticle('js003/index.htm',
	'Searching',
	'Explanation of JavaScript Searching.',
	'Frames, JavaScript, Arrays, Objects, Forms, Frames, Tables',
	'19970209');
setArticle('js002/index.htm',
	'Array-Tours',
	'Demonstration of navigation tools.',
	'Frames, JavaScript, Arrays, Objects, Forms',
	'19970101');
setArticle('js001/index.htm',
	'Calendars',
	'Demonstrations of different calendar displays.',
	'JavaScript, Functions, Arrays, Tables, Floating Frames, Windows',
	'19961201');


/* search routines */

var splitIndex = 0;
var splitArray = new Array();

var splitUpIndex = 0;
var splitUpArray = new Array();

function splitUp(string,separator1,separator2,by) {
    splitIndex = 0, splitUpIndex = 0; 
    string = separator2 + replace(string,separator1+separator1,separator1+separator2+separator1);
    splits(string,separator1,1);

    for (var i=0; i < splitIndex; i++) {
        if ((i/2) != (Math.floor(i/2)))
            setArray(replace(splitArray[i],separator2,by),2);
        else
            splits(splitArray[i],separator2,2);
    }
}

function splits(string,text,no) {
    var strLength = string.length, txtLength = text.length;
    if ((strLength == 0) || (txtLength == 0)) return;

    var i = string.indexOf(text);
    if ((!i) && (text != string.substring(0,txtLength))) return;
    if (i == -1) {
        setArray(string,no);
        return;
    }

    setArray(string.substring(0,i),no);
    
    if (i+txtLength < strLength)
        splits(string.substring(i+txtLength,strLength),text,no);

    return;
}

function setArray(text,no) {
    if (text != '') {
        if (no == 1) splitArray[splitIndex++]     = text.toUpperCase();
        else         splitUpArray[splitUpIndex++] = text.toUpperCase();
    }
}

function replace(string,text,by) {
    var strLength = string.length, txtLength = text.length;
    if ((strLength == 0) || (txtLength == 0)) return string;

    var i = string.indexOf(text);
    if ((!i) && (text != string.substring(0,txtLength))) return string;
    if (i == -1) return string;

    var newstr = string.substring(0,i) + by;

    if (i+txtLength < strLength)
        newstr += replace(string.substring(i+txtLength,strLength),text,by);

    return newstr;
}

function highlight(string,text,before,after) {
// Highlights text within string using before and after, includes upper and lower case
    var strLength = string.length, txtLength = text.length;
    if ((strLength == 0) || (txtLength == 0)) return string;
    
    var i = string.toUpperCase().indexOf(text);
    if ((!i) && (text != string.toUpperCase().substring(0,txtLength))) return string;
    if (i == -1) return string;

    var newstr = string.substring(0,i) + before + string.substring(i,i+txtLength) + after;

    if (i+txtLength < strLength)
        newstr += highlight(string.substring(i+txtLength,strLength),text,before,after);

    return newstr;
}

function output(href,desc,text,tech,date) {
    articlesOutput++;

     document.write('<TABLE WIDTH=595 CELLPADDING=5 CELLSPACING=0><TR><TD VALIGN=TOP WIDTH=230>');
     document.write('<A HREF="' + articleArray[i].href + '">' + text + '</A>');
     document.write('<BR>' + date);
     document.write('</TD><TD VALIGN=TOP WIDTH=365 BGCOLOR="#FFFFCC">');
     document.write('<A HREF="' + articleArray[i].href + '">' + href + '</A>');
     document.write('<BR>' + desc);
     document.write('<BR><B>Techniques:</B> ' + tech);
     document.write('</TD></TR></TABLE>');
}

function display() {
    for (var j=0; j < splitUpIndex; j++) {
        href = highlight(href,splitUpArray[j],before1,after1);
        desc = highlight(desc,splitUpArray[j],before1,after1);
        text = highlight(text,splitUpArray[j],before1,after1);
        tech = highlight(tech,splitUpArray[j],before1,after1);
    }

    href = replace(replace(href,before1,before2),after1,after2);
    desc = replace(replace(desc,before1,before2),after1,after2);
    text = replace(replace(text,before1,before2),after1,after2);
    tech = replace(replace(tech,before1,before2),after1,after2);

    output(href,desc,text,tech,date);
}

function found() {
    var Found = true;
    for (var j=0; j < splitUpIndex; j++) {
        if ((href.toUpperCase().indexOf(splitUpArray[j]) != -1) ||
            (desc.toUpperCase().indexOf(splitUpArray[j]) != -1) ||
            (text.toUpperCase().indexOf(splitUpArray[j]) != -1) ||
            (tech.toUpperCase().indexOf(splitUpArray[j]) != -1))
            continue;
        else
            Found = false;
    }

    return Found;
}                           

function input() {
    var endOfRef = window.location.href.length;
    if (window.location.search.length > 0)
        if (window.location.href.indexOf(window.location.search) > 0)
            endOfRef = window.location.href.indexOf(window.location.search);

    var loc = window.location.href.substring(0,endOfRef);

    document.write('<FORM ACTION="' + loc + '" METHOD="GET">');
    document.write('<TABLE WIDTH=595 BORDER=0 CELLSPACING=0 CELLPADDING=10 BGCOLOR="#AAAAFF"><TR><TD>');
    document.write('<CENTER><P>This is a searchable index. Enter search keywords: ');
    document.write('<INPUT TYPE="TEXT" NAME="" VALUE="' + searchFor + '">');
    document.write('</CENTER></TD></TR></TABLE></FORM>');
}

var before1 = unescape('%01'), before2 = '<EM>';
var after1 = unescape('%02'), after2 = '</EM>';

var searchFor = unescape(replace(window.location.search.substring(2),'+',' '));
splitUp(searchFor.toUpperCase(),'"',' ',' ');

var articlesOutput = 0;
//--></SCRIPT>

</HEAD>
<BODY>
<CENTER>

<SCRIPT TYPE="text/javascript" LANGUAGE="JavaScript"><!--
input();

if (splitUpIndex > 0)
    document.write('<H2>Searching Index for: ' + before2 + searchFor + after2 + '</H2>');

for (var i=0; i < articleIndex; i++) {
    var href = articleArray[i].href, desc = articleArray[i].desc, text = articleArray[i].text, tech = articleArray[i].tech, date = articleArray[i].date;
    
    if (splitUpIndex > 0) { if (found()) display(); }
    else output(href,desc,text,tech,date);
}

if (splitUpIndex > 0) document.write('<P>' + articlesOutput + ' article/s found');
//--></SCRIPT>

<P>

</CENTER>
</BODY>
</HTML>

Related items

Arrays, Object Arrays and Sorting

Text Strings and String Objects

Searching

Feedback on 'Seek, and ye shall find'

©2018 Martin Webb