From 580244e5cf8fcbae7febd4f89d788c22b7fc3b3b Mon Sep 17 00:00:00 2001 From: Adam R Claxton Date: Tue, 4 Apr 2017 14:34:06 -0400 Subject: [PATCH 1/4] Listing page internal filtering reorganized somewhat in preperation for search function Added fuzzy.js search library and empty search bar --- WebContent/html/javascript/lib/fuzzy.js | 143 +++++++++++++++++++++ WebContent/html/javascript/listing.jsp | 92 ++++++++------ WebContent/html/webpages/listingPage.jsp | 155 ++++++++++++----------- 3 files changed, 275 insertions(+), 115 deletions(-) create mode 100644 WebContent/html/javascript/lib/fuzzy.js diff --git a/WebContent/html/javascript/lib/fuzzy.js b/WebContent/html/javascript/lib/fuzzy.js new file mode 100644 index 0000000..d2131cb --- /dev/null +++ b/WebContent/html/javascript/lib/fuzzy.js @@ -0,0 +1,143 @@ +/* + * Fuzzy + * https://github.com/myork/fuzzy + * + * Copyright (c) 2012 Matt York + * Licensed under the MIT license. + */ + +(function() { + +var root = this; + +var fuzzy = {}; + +// Use in node or in browser +if (typeof exports !== 'undefined') { + module.exports = fuzzy; +} else { + root.fuzzy = fuzzy; +} + +// Return all elements of `array` that have a fuzzy +// match against `pattern`. +fuzzy.simpleFilter = function(pattern, array) { + return array.filter(function(str) { + return fuzzy.test(pattern, str); + }); +}; + +// Does `pattern` fuzzy match `str`? +fuzzy.test = function(pattern, str) { + return fuzzy.match(pattern, str) !== null; +}; + +// If `pattern` matches `str`, wrap each matching character +// in `opts.pre` and `opts.post`. If no match, return null +fuzzy.match = function(pattern, str, opts) { + opts = opts || {}; + var patternIdx = 0 + , result = [] + , len = str.length + , totalScore = 0 + , currScore = 0 + // prefix + , pre = opts.pre || '' + // suffix + , post = opts.post || '' + // String to compare against. This might be a lowercase version of the + // raw string + , compareString = opts.caseSensitive && str || str.toLowerCase() + , ch; + + pattern = opts.caseSensitive && pattern || pattern.toLowerCase(); + + // For each character in the string, either add it to the result + // or wrap in template if it's the next string in the pattern + for(var idx = 0; idx < len; idx++) { + ch = str[idx]; + if(compareString[idx] === pattern[patternIdx]) { + ch = pre + ch + post; + patternIdx += 1; + + // consecutive characters should increase the score more than linearly + currScore += 1 + currScore; + } else { + currScore = 0; + } + totalScore += currScore; + result[result.length] = ch; + } + + // return rendered string if we have a match for every char + if(patternIdx === pattern.length) { + // if the string is an exact match with pattern, totalScore should be maxed + totalScore = (compareString === pattern) ? Infinity : totalScore; + return {rendered: result.join(''), score: totalScore}; + } + + return null; +}; + +// The normal entry point. Filters `arr` for matches against `pattern`. +// It returns an array with matching values of the type: +// +// [{ +// string: 'lah' // The rendered string +// , index: 2 // The index of the element in `arr` +// , original: 'blah' // The original element in `arr` +// }] +// +// `opts` is an optional argument bag. Details: +// +// opts = { +// // string to put before a matching character +// pre: '' +// +// // string to put after matching character +// , post: '' +// +// // Optional function. Input is an entry in the given arr`, +// // output should be the string to test `pattern` against. +// // In this example, if `arr = [{crying: 'koala'}]` we would return +// // 'koala'. +// , extract: function(arg) { return arg.crying; } +// } +fuzzy.filter = function(pattern, arr, opts) { + if(!arr || arr.length === 0) { + return []; + } + if (typeof pattern !== 'string') { + return arr; + } + opts = opts || {}; + return arr + .reduce(function(prev, element, idx, arr) { + var str = element; + if(opts.extract) { + str = opts.extract(element); + } + var rendered = fuzzy.match(pattern, str, opts); + if(rendered != null) { + prev[prev.length] = { + string: rendered.rendered + , score: rendered.score + , index: idx + , original: element + }; + } + return prev; + }, []) + + // Sort by score. Browsers are inconsistent wrt stable/unstable + // sorting, so force stable by using the index in the case of tie. + // See http://ofb.net/~sethml/is-sort-stable.html + .sort(function(a,b) { + var compare = b.score - a.score; + if(compare) return compare; + return a.index - b.index; + }); +}; + + +}()); \ No newline at end of file diff --git a/WebContent/html/javascript/listing.jsp b/WebContent/html/javascript/listing.jsp index cdf42d7..2aaff0b 100644 --- a/WebContent/html/javascript/listing.jsp +++ b/WebContent/html/javascript/listing.jsp @@ -3,24 +3,24 @@ - -Insert title here + + Insert title here -<% -ListedDevice[] mydevices = DeviceQueries.getAllDevices(); + <% + ListedDevice[] mydevices = DeviceQueries.getAllDevices(); //string representation of array. -String deviceString = ListedDevice.arrayToString(mydevices); + String deviceString = ListedDevice.arrayToString(mydevices); //out.println(description); //out.println(hardware); -%> + %> - \ No newline at end of file diff --git a/WebContent/html/webpages/listingPage.jsp b/WebContent/html/webpages/listingPage.jsp index 4415d9c..3086dba 100644 --- a/WebContent/html/webpages/listingPage.jsp +++ b/WebContent/html/webpages/listingPage.jsp @@ -1,86 +1,91 @@ - - - - - - - + + + + + + + - Synchrony Financial + Synchrony Financial - - - - - - - + div.availableAnchor{ + width: 50%; + margin: auto; + border: solid; + border-width: thin; + } + + - - - + + + -
-

Device Dictionary

-
-
- - <%@ include file="../javascript/listing.jsp" %> - + +
+

Device Dictionary +
+ +
+

+
+
+ +<%@ include file="../javascript/listing.jsp" %> + \ No newline at end of file From 045598398b13ad347b9dec7a13e177c947a7e680 Mon Sep 17 00:00:00 2001 From: Adam R Claxton Date: Tue, 4 Apr 2017 17:49:05 -0400 Subject: [PATCH 2/4] Search works, horray! (only for device names right now) --- WebContent/html/javascript/listing.jsp | 35 +++++++++++++++++++++++- WebContent/html/webpages/listingPage.jsp | 7 +++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/WebContent/html/javascript/listing.jsp b/WebContent/html/javascript/listing.jsp index 2aaff0b..6aeb139 100644 --- a/WebContent/html/javascript/listing.jsp +++ b/WebContent/html/javascript/listing.jsp @@ -33,6 +33,10 @@ for(var a = 0; a < hardwareOptions.length; a++){ for(var a = 0; a < softwareOptions.length; a++){ softwareOptions[a].addEventListener('click', refresh); } +// searchbar listener +var searchbar = document.getElementsByName('searchBar'); +searchbar[0].onkeyup = refresh; + showAll(); function showAll(){ var html = ''; @@ -57,7 +61,7 @@ function showAll(){ } } function refresh() { - var filteredDevices = applyFilter(); + var filteredDevices = fuzzyFilter(applyFilter()); show(filteredDevices); } function show(deviceArray){ @@ -130,10 +134,39 @@ function applyFilter() { return filteredDevices; } +function fuzzyFilter(deviceList) { + var searchText = document.getElementsByName('searchBar')[0].value; + var options = { + pre: '', + post: '', + extract: function(arg) {return arg.name;} + } + var fuzzyResults = fuzzy.filter(searchText, deviceList, options); + // this returns a filtered array of objects with attributes 'index', 'original', 'score', and 'string' + // I am interested in the 'original' attribute, which is the relevant object exactly as it was submitted, + // and the 'string' attribute, which is the attribute that was compared against with matching characters conveniantly bolded + var results = new Array(fuzzyResults.length); + // so I'm going to rebuild a filtered device array in the same format as we've been using, + // substituting the 'name' attribute with the 'string' attribute returned by fuzzy + for (var i = 0; i < fuzzyResults.length; i++) { + results[i] = { + name:fuzzyResults[i].string, //This one is being replaced + id:fuzzyResults[i].original.id, + description:fuzzyResults[i].original.description, + hardware:fuzzyResults[i].original.hardware, + status:fuzzyResults[i].original.status, + model:fuzzyResults[i].original.model, + manufacturer:fuzzyResults[i].original.manufacturer, + }; + } + + return results; +} function makeDeviceArray(){ window.json = '<%=deviceString%>'; return JSON.parse(window.json); } + \ No newline at end of file diff --git a/WebContent/html/webpages/listingPage.jsp b/WebContent/html/webpages/listingPage.jsp index 3086dba..e481837 100644 --- a/WebContent/html/webpages/listingPage.jsp +++ b/WebContent/html/webpages/listingPage.jsp @@ -79,9 +79,10 @@

Device Dictionary -
- -
+ +
+ +

From 991fb38d07b6286cfd7729e3ef5c5960306672aa Mon Sep 17 00:00:00 2001 From: Adam R Claxton Date: Tue, 4 Apr 2017 18:25:02 -0400 Subject: [PATCH 3/4] Fixed 'other' filter bug on listing page --- WebContent/html/javascript/listing.jsp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/WebContent/html/javascript/listing.jsp b/WebContent/html/javascript/listing.jsp index 6aeb139..7da247d 100644 --- a/WebContent/html/javascript/listing.jsp +++ b/WebContent/html/javascript/listing.jsp @@ -111,6 +111,18 @@ function applyFilter() { if (activeHOptions[j] == devices[i].hardware){ hardwareMatch = true; } + // now we must handle the 'other' case + // there is no 'other' catagory, so if that is the option selected we must make sure + // that the device in question is not one of the categories we DO have options for + if(activeHOptions[j] === 'Other') + { + var othermatch = true; + for (var k = 0; k < hardwareOptions.length; k++) { + if(devices[i].hardware == hardwareOptions[k].getAttribute('data-type')) + othermatch = false; + } + if(othermatch) hardwareMatch = true; + } } if (activeHOptions.length == 0) { @@ -121,6 +133,18 @@ function applyFilter() { if (activeSOptions[j] == devices[i].manufacturer){ softwareMatch = true; } + // now we must handle the 'other' case + // there is no 'other' catagory, so if that is the option selected we must make sure + // that the device in question is not one of the categories we DO have options for + if(activeSOptions[j] === 'Other') + { + var othermatch = true; + for (var k = 0; k < softwareOptions.length; k++) { + if(devices[i].manufacturer == softwareOptions[k].getAttribute('data-type')) + othermatch = false; + } + if(othermatch) softwareMatch = true; + } } if (activeSOptions.length == 0) { From a588daa9d7c2eb55155d9dafbb8528c9432264b7 Mon Sep 17 00:00:00 2001 From: Adam R Claxton Date: Tue, 4 Apr 2017 19:10:39 -0400 Subject: [PATCH 4/4] Search feature introduced to request page Also applied 'other' bugfix to request page --- WebContent/html/javascript/request.jsp | 148 +++++++++++++++-------- WebContent/html/webpages/listingPage.jsp | 2 +- WebContent/html/webpages/requestPage.jsp | 9 +- 3 files changed, 106 insertions(+), 53 deletions(-) diff --git a/WebContent/html/javascript/request.jsp b/WebContent/html/javascript/request.jsp index 086c24f..0ad33c5 100644 --- a/WebContent/html/javascript/request.jsp +++ b/WebContent/html/javascript/request.jsp @@ -59,20 +59,44 @@ function ajaxFunction(){ //add event listeners to the options in the left sidebar for(var a = 0; a < hardwareOptions.length; a++){ - hardwareOptions[a].addEventListener('click', show); + hardwareOptions[a].addEventListener('click', refresh); } for(var a = 0; a < softwareOptions.length; a++){ - softwareOptions[a].addEventListener('click', show); + softwareOptions[a].addEventListener('click', refresh); } +// searchbar listener +var searchbar = document.getElementsByName('searchBar'); +searchbar[0].onkeyup = refresh; -//when an option is selected, show a new list of devices based on what the user asked for -function show(){ +function refresh() { + show(fuzzyFilter(filter(devices))); +} - //currentFilter = readFilter(); - //selectedDevices = getDevices(currentFilter); //when connor is done with the database library getDevices should be redirected there +//when an option is selected, show a new list of devices based on what the user asked for +function show(deviceArray){ - var type = this.getAttribute('data-type'); + // var type = this.getAttribute('data-type'); var html = ''; + for (var i = 0; i < deviceArray.length; i++) { + html += '
' + deviceArray[i].name + '

' + deviceArray[i].description + '



' + } + + if(html.localeCompare("")==0) + html += "

There are no devices with the search criteria: " + this.textContent + "

"; + + //add to HTML page + document.getElementById('devContainer').innerHTML = html; + + //now we need to add event listeners to all the request buttons + var requestbuttons = document.getElementsByClassName('requestbutton'); + for(var i = 0; i < requestbuttons.length; i++) { + requestbuttons[i].addEventListener('click',addToCart); + } +} + + +function filter(deviceArray) { + var filteredArray = new Array(); var activeHOptions = []; var activeSOptions = []; for(var i = 0; i < hardwareOptions.length; i++) { @@ -85,60 +109,83 @@ function show(){ activeSOptions.push(softwareOptions[i].getAttribute("data-type")); } } - if(activeHOptions.length == 0 && activeSOptions.length == 0){ - document.getElementById('devContainer').innerHTML = "

Choose an option to the left to begin requesting!

"; - } - else{ - //iterate through the hardcoded device DB and select all the ones that match the selected option - for(var i = 0; i < devices.length; i++){ - var hardwareMatch = false; - var softwareMatch = false; - - for(var j = 0; j < activeHOptions.length; j++) { - if (activeHOptions[j] == devices[i].hardware){ - hardwareMatch = true; - } - } - - if (activeHOptions.length == 0) { + //iterate through the hardcoded device DB and select all the ones that match the selected option + for(var i = 0; i < deviceArray.length; i++){ + var hardwareMatch = false; + var softwareMatch = false; + for(var j = 0; j < activeHOptions.length; j++) { + if (activeHOptions[j] == deviceArray[i].hardware){ hardwareMatch = true; } - - for(var j = 0; j < activeSOptions.length; j++) { - if (activeSOptions[j] == devices[i].manufacturer){ - softwareMatch = true; + // now we must handle the 'other' case + // there is no 'other' catagory, so if that is the option selected we must make sure + // that the device in question is not one of the categories we DO have options for + if(activeHOptions[j] === 'Other') + { + var othermatch = true; + for (var k = 0; k < hardwareOptions.length; k++) { + if(devices[i].hardware == hardwareOptions[k].getAttribute('data-type')) + othermatch = false; } + if(othermatch) hardwareMatch = true; } - - if (activeSOptions.length == 0) { - softwareMatch = true; + } + if (activeHOptions.length == 0) { + hardwareMatch = true; + } + // now we must handle the 'other' case + // there is no 'other' catagory, so if that is the option selected we must make sure + // that the device in question is not one of the categories we DO have options for + if(activeSOptions[j] === 'Other') + { + var othermatch = true; + for (var k = 0; k < softwareOptions.length; k++) { + if(devices[i].manufacturer == softwareOptions[k].getAttribute('data-type')) + othermatch = false; } - - - if(hardwareMatch == true && softwareMatch == true){ - //if((hw_type.localeCompare(devices[i].hardware) == 0 && sw_type.localeCompare(devices[i].software) == 0) && !isUnavailable(i)){ - html += '
' + devices[i].name + '

' + devices[i].description + '



' + if(othermatch) softwareMatch = true; + } + for(var j = 0; j < activeSOptions.length; j++) { + if (activeSOptions[j] == deviceArray[i].manufacturer){ + softwareMatch = true; } } - - if(html.localeCompare("")==0) - html += "

There are no devices with the search criteria: " + this.textContent + "

"; - - //add to HTML page - document.getElementById('devContainer').innerHTML = html; - - //now we need to add event listeners to all the request buttons - var requestbuttons = document.getElementsByClassName('requestbutton'); - for(var i = 0; i < requestbuttons.length; i++){ - requestbuttons[i].addEventListener('click',addToCart); + if (activeSOptions.length == 0) { + softwareMatch = true; } + if(hardwareMatch == true && softwareMatch == true) + filteredArray.push(deviceArray[i]); } + return filteredArray; } -function readFilter() { - //Constrcts a filter object for use in the database library that corresponds to the checked optionsin the sidebar - var hw_type; - var sw_type; +function fuzzyFilter(deviceArray) { + var searchText = document.getElementsByName('searchBar')[0].value; + var options = { + pre: '', + post: '', + extract: function(arg) {return arg.name;} + } + var fuzzyResults = fuzzy.filter(searchText, deviceArray, options); + // this returns a filtered array of objects with attributes 'index', 'original', 'score', and 'string' + // I am interested in the 'original' attribute, which is the relevant object exactly as it was submitted, + // and the 'string' attribute, which is the attribute that was compared against with matching characters conveniantly bolded + var results = new Array(fuzzyResults.length); + // so I'm going to rebuild a filtered device array in the same format as we've been using, + // substituting the 'name' attribute with the 'string' attribute returned by fuzzy + for (var i = 0; i < fuzzyResults.length; i++) { + results[i] = { + name:fuzzyResults[i].string, //This one is being replaced + id:fuzzyResults[i].original.id, + description:fuzzyResults[i].original.description, + hardware:fuzzyResults[i].original.hardware, + status:fuzzyResults[i].original.status, + model:fuzzyResults[i].original.model, + manufacturer:fuzzyResults[i].original.manufacturer, + }; + } + + return results; } function addToCart(){ @@ -190,5 +237,6 @@ function makeDeviceArray(){ } //this code allows a message to appear that indicates that the item was successfully placed in the shopping cart. + \ No newline at end of file diff --git a/WebContent/html/webpages/listingPage.jsp b/WebContent/html/webpages/listingPage.jsp index e481837..fa01508 100644 --- a/WebContent/html/webpages/listingPage.jsp +++ b/WebContent/html/webpages/listingPage.jsp @@ -81,7 +81,7 @@

Device Dictionary
- +

diff --git a/WebContent/html/webpages/requestPage.jsp b/WebContent/html/webpages/requestPage.jsp index e6d30e3..ebf9e87 100644 --- a/WebContent/html/webpages/requestPage.jsp +++ b/WebContent/html/webpages/requestPage.jsp @@ -87,8 +87,13 @@
-

Available Devices

-

Choose an option to the left to begin requesting!

+

Available Devices + +
+ +
+

+

Or choose an option to the left to begin requesting!