├── .gitignore ├── README.TXT ├── exampledata └── ac-example.csv ├── feed-ac.sh ├── run-solr.sh └── solr-home ├── ac └── conf │ ├── mapping-ISOLatin1Accent.txt │ ├── schema.xml │ ├── solrconfig.xml │ └── velocity │ ├── VM_global_library.vm │ ├── browse.vm │ ├── cluster.vm │ ├── clusterResults.vm │ ├── doc.vm │ ├── facet_fields.vm │ ├── facet_queries.vm │ ├── facet_ranges.vm │ ├── facets.vm │ ├── footer.vm │ ├── head.vm │ ├── header.vm │ ├── hit.vm │ ├── jquery.autocomplete.css │ ├── jquery.autocomplete.js │ ├── layout.vm │ ├── main.css │ ├── query.vm │ ├── querySpatial.vm │ ├── suggest.vm │ └── tabs.vm ├── lib ├── apache-solr-velocity-3.5.0.jar ├── commons-beanutils-1.7.0.jar ├── commons-beanutils-LICENSE-ASL.txt ├── commons-beanutils-NOTICE.txt ├── commons-collections-3.2.1.jar ├── commons-collections-LICENSE-ASL.txt ├── commons-collections-NOTICE.txt ├── velocity-1.6.4.jar ├── velocity-LICENSE-ASL.txt ├── velocity-NOTICE.txt ├── velocity-tools-2.0.jar ├── velocity-tools-LICENSE-ASL.txt └── velocity-tools-NOTICE.txt └── solr.xml /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | solr-home/ac/data 3 | -------------------------------------------------------------------------------- /README.TXT: -------------------------------------------------------------------------------- 1 | #### 2 | #### Licensed to the Apache Software Foundation (ASF) 3 | #### http://www.apache.org/licenses/LICENSE-2.0 4 | #### See http://www.cominvent.com/?p=576 for instructions 5 | #### 6 | 7 | AutoComplete example core 8 | ========================= 9 | This is an example of how to do advanced super-flexible AutoComplete using a 10 | dedicated Solr core. A blog post detailing the autocomplete technique is found 11 | at http://www.cominvent.com/?p=576 12 | 13 | This code is intended as a template for you to get started. Normally you will copy 14 | the "ac" folder, holding the configuration of the autocomplete core, into your own 15 | multi core SolrHome, but for this simple example we assume that you run it 16 | stand-alone in the provided solr-home. 17 | 18 | We assume Linux/Mac/Cygwin, feel free to contribute start script for Windows :) 19 | 20 | Quick start 21 | =========== 22 | 1. Set environment variable JETTY_HOME to your solr/example folder (solr3.x) 23 | export JETTY_HOME=/home/john/solr-3.5.0/solr/example 24 | 2. Start Solr using solr-home 25 | ./run-solr.sh 26 | 3. In another SHELL window, feed the example data (you need curl) 27 | ./feed-ac.sh 28 | 4. Goto http://localhost:8983/solr/ac/browse and start typing. 29 | The example data is country names and names of large cities from Geonames, and 30 | they are boosted by population so if you type 'c' you'll see China on top.. 31 | 32 | Using your own data 33 | =================== 34 | Of course you want suggestions for your own data. The easiest is to start with the 35 | exampledata/ac-example.csv file, keep the first line and enter your own data below. 36 | Here is a short description of the columns and how they are configured to work in 37 | Solr's schema.xml: 38 | id : Unique id 39 | textsuggest : Main text for matching and display 40 | subtext : Extra text not being searched but displayed in () 41 | extrasearch : Extra text not being displayed but searched (full words only) 42 | extradisplay : Extra text not being searched, for additional display 43 | type : You may choose your own types, e.g. Country, City. 44 | Each type will be suggested in its own section in the result 45 | action : Optional field to give a hint to Ajax comp about what to do 46 | when suggestion is clicked, e.g. search, jump-to-url etc 47 | value : Optional field to give details about how to execute action 48 | popularity : This number gets boosted into the score for relevancy tuning 49 | thumbnail_url : Example of extra data field to be able to display an image 50 | phonetic : If filled, this suggestion will also be matched phonetically 51 | 52 | To learn more about how the autoSuggester is built and how to tune further, read 53 | the blog post at http://www.cominvent.com/?p=576 54 | 55 | 56 | 57 | About the data set 58 | ================== 59 | We've used data from Geonames dataset, adapted for this example 60 | Licensed under Creative Commons http://creativecommons.org/licenses/by/3.0/ 61 | 62 | Country names: 63 | Deducted from http://download.geonames.org/export/dump/countryInfo.txt 64 | We use country ISO code for extra search, Capital city as sub-text, population as 65 | popularity 66 | 67 | City names: 68 | Deducted from http://download.geonames.org/export/dump/cities15000.zip, filtered 69 | to cities with population above 100.000 70 | We use the country as sub-text and 71 | population as popularity 72 | 73 | Flags: 74 | Flags used in /browse GUI links to http://flags.blogpotato.de/ and are CC-by-sa 75 | licensed -------------------------------------------------------------------------------- /feed-ac.sh: -------------------------------------------------------------------------------- 1 | #### 2 | #### Licensed to the Apache Software Foundation (ASF) http://www.apache.org/licenses/LICENSE-2.0 3 | #### See http://www.cominvent.com/?p=576 for instructions 4 | #### 5 | #!/bin/sh 6 | curl "http://localhost:8983/solr/ac/update/csv?separator=;&commit=true" -H "Content-Type: text/plain; charset=utf-8" --data-binary @exampledata/ac-example.csv -------------------------------------------------------------------------------- /run-solr.sh: -------------------------------------------------------------------------------- 1 | #### 2 | #### Licensed to the Apache Software Foundation (ASF) http://www.apache.org/licenses/LICENSE-2.0 3 | #### See http://www.cominvent.com/?p=576 for instructions 4 | #### 5 | #!/bin/sh 6 | echo "Running Solr AutoComplete example in Jetty." 7 | echo "Syntax: ./run-solr.sh []" 8 | if [ X$JETTY_HOME = X ] ; then 9 | echo "Please set environment variable JETTY_HOME to point to your Solr example directory" 10 | echo "Example: export JETTY_HOME=/home/john/solr-3.5.0/solr/example" 11 | exit 12 | fi 13 | if [ X$1 != X ] ; then 14 | PORT=$1 15 | else 16 | PORT=8983 17 | fi 18 | 19 | SOLR_HOME=`pwd`/solr-home 20 | 21 | export JAVA_OPTIONS="-server \ 22 | -XX:+UseConcMarkSweepGC \ 23 | -XX:+CMSClassUnloadingEnabled \ 24 | -XX:-CMSParallelRemarkEnabled \ 25 | -XX:+UseCMSCompactAtFullCollection \ 26 | -XX:+UseParNewGC \ 27 | -XX:+PrintGCDetails \ 28 | -Xms512m -Xmx1024m \ 29 | -Dsolr.solr.home=$SOLR_HOME \ 30 | -Djetty.port=$PORT \ 31 | $JAVA_OPTIONS" 32 | echo JAVA_OPTIONS is $JAVA_OPTIONS 33 | 34 | cd $JETTY_HOME 35 | java $JAVA_OPTIONS -jar start.jar 36 | -------------------------------------------------------------------------------- /solr-home/ac/conf/mapping-ISOLatin1Accent.txt: -------------------------------------------------------------------------------- 1 | # The ASF licenses this file to You under the Apache License, Version 2.0 2 | # (the "License"); you may not use this file except in compliance with 3 | # the License. You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | 13 | # Syntax: 14 | # "source" => "target" 15 | # "source".length() > 0 (source cannot be empty.) 16 | # "target".length() >= 0 (target can be empty.) 17 | 18 | # example: 19 | # "À" => "A" 20 | # "\u00C0" => "A" 21 | # "\u00C0" => "\u0041" 22 | # "ß" => "ss" 23 | # "\t" => " " 24 | # "\n" => "" 25 | 26 | # Norwegian mappings 27 | # 28 | # 2012-01-02 janhoy modified mappings of norwegian,swedish characters to ø,ö->ø, å->å, æ,ä->æ 29 | 30 | # À => A 31 | "\u00C0" => "A" 32 | 33 | # Á => A 34 | "\u00C1" => "A" 35 | 36 | #  => A 37 | "\u00C2" => "A" 38 | 39 | # à => A 40 | "\u00C3" => "A" 41 | 42 | # Ä => AE 43 | "\u00C4" => "AE" 44 | 45 | # Å => AA 46 | "\u00C5" => "Å" 47 | 48 | # Æ => AE 49 | "\u00C6" => "Æ" 50 | 51 | # Ç => C 52 | "\u00C7" => "C" 53 | 54 | # È => E 55 | "\u00C8" => "E" 56 | 57 | # É => E 58 | "\u00C9" => "E" 59 | 60 | # Ê => E 61 | "\u00CA" => "E" 62 | 63 | # Ë => E 64 | "\u00CB" => "E" 65 | 66 | # Ì => I 67 | "\u00CC" => "I" 68 | 69 | # Í => I 70 | "\u00CD" => "I" 71 | 72 | # Î => I 73 | "\u00CE" => "I" 74 | 75 | # Ï => I 76 | "\u00CF" => "I" 77 | 78 | # IJ => IJ 79 | "\u0132" => "IJ" 80 | 81 | # Ð => D 82 | "\u00D0" => "D" 83 | 84 | # Ñ => N 85 | "\u00D1" => "N" 86 | 87 | # Ò => O 88 | "\u00D2" => "O" 89 | 90 | # Ó => O 91 | "\u00D3" => "O" 92 | 93 | # Ô => O 94 | "\u00D4" => "O" 95 | 96 | # Õ => O 97 | "\u00D5" => "O" 98 | 99 | # Ö => OE 100 | "\u00D6" => "OE" 101 | 102 | # Ø => OE 103 | "\u00D8" => "Ø" 104 | 105 | # Œ => OE 106 | "\u0152" => "OE" 107 | 108 | # Þ 109 | "\u00DE" => "TH" 110 | 111 | # Ù => U 112 | "\u00D9" => "U" 113 | 114 | # Ú => U 115 | "\u00DA" => "U" 116 | 117 | # Û => U 118 | "\u00DB" => "U" 119 | 120 | # Ü => U 121 | "\u00DC" => "U" 122 | 123 | # Ý => Y 124 | "\u00DD" => "Y" 125 | 126 | # Ÿ => Y 127 | "\u0178" => "Y" 128 | 129 | # à => a 130 | "\u00E0" => "a" 131 | 132 | # á => a 133 | "\u00E1" => "a" 134 | 135 | # â => a 136 | "\u00E2" => "a" 137 | 138 | # ã => a 139 | "\u00E3" => "a" 140 | 141 | # ä => ae 142 | "\u00E4" => "æ" 143 | 144 | # å => aa 145 | "\u00E5" => "å" 146 | 147 | # æ => ae 148 | "\u00E6" => "æ" 149 | 150 | # ç => c 151 | "\u00E7" => "c" 152 | 153 | # è => e 154 | "\u00E8" => "e" 155 | 156 | # é => e 157 | "\u00E9" => "e" 158 | 159 | # ê => e 160 | "\u00EA" => "e" 161 | 162 | # ë => e 163 | "\u00EB" => "e" 164 | 165 | # ì => i 166 | "\u00EC" => "i" 167 | 168 | # í => i 169 | "\u00ED" => "i" 170 | 171 | # î => i 172 | "\u00EE" => "i" 173 | 174 | # ï => i 175 | "\u00EF" => "i" 176 | 177 | # ij => ij 178 | "\u0133" => "ij" 179 | 180 | # ð => d 181 | "\u00F0" => "d" 182 | 183 | # ñ => n 184 | "\u00F1" => "n" 185 | 186 | # ò => o 187 | "\u00F2" => "o" 188 | 189 | # ó => o 190 | "\u00F3" => "o" 191 | 192 | # ô => o 193 | "\u00F4" => "o" 194 | 195 | # õ => o 196 | "\u00F5" => "o" 197 | 198 | # ö => oe 199 | "\u00F6" => "oe" 200 | 201 | # ø => oe 202 | "\u00F8" => "ø" 203 | 204 | # œ => oe 205 | "\u0153" => "oe" 206 | 207 | # ß => ss 208 | "\u00DF" => "ss" 209 | 210 | # þ => th 211 | "\u00FE" => "th" 212 | 213 | # ù => u 214 | "\u00F9" => "u" 215 | 216 | # ú => u 217 | "\u00FA" => "u" 218 | 219 | # û => u 220 | "\u00FB" => "u" 221 | 222 | # ü => u 223 | "\u00FC" => "u" 224 | 225 | # ý => y 226 | "\u00FD" => "y" 227 | 228 | # ÿ => y 229 | "\u00FF" => "y" 230 | 231 | # ff => ff 232 | "\uFB00" => "ff" 233 | 234 | # fi => fi 235 | "\uFB01" => "fi" 236 | 237 | # fl => fl 238 | "\uFB02" => "fl" 239 | 240 | # ffi => ffi 241 | "\uFB03" => "ffi" 242 | 243 | # ffl => ffl 244 | "\uFB04" => "ffl" 245 | 246 | # ſt => ft 247 | "\uFB05" => "ft" 248 | 249 | # st => st 250 | "\uFB06" => "st" 251 | -------------------------------------------------------------------------------- /solr-home/ac/conf/schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 54 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | id 166 | textng 167 | 168 | 169 | -------------------------------------------------------------------------------- /solr-home/ac/conf/solrconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 22 | LUCENE_35 23 | 24 | 27 | 28 | 29 | edismax 30 | 10 31 | *,score 32 | textsuggest^30 extrasearch^30.0 textng^50.0 phonetic^10 33 | textnge^50.0 34 | product(log(sum(popularity,1)),100)^20 35 | 36 | product(map(query($type1query),0,0,1,$type1boost),map(query($type2query),0,0,1,$type2boost),map(query($type3query),0,0,1,$type3boost),map(query($type4query),0,0,1,$type4boost),$typeboost) 37 | 1.0 38 | 39 | type:"Countries" 40 | 0.9 41 | type:"Cities" 42 | 0.5 43 | type:"NA" 44 | 0.0 45 | type:"NA" 46 | 0.0 47 | 48 | false 49 | 50 | 51 | 52 | 53 | 54 | 55 | all 56 | velocity 57 | browse 58 | layout 59 | Autocomplete demo 60 | *:* 61 | on 62 | type 63 | 64 | 65 | edismax 66 | 10 67 | *,score 68 | textsuggest^30 extrasearch^30.0 textng^50.0 phonetic^10 69 | textnge^50.0 70 | product(log(sum(popularity,1)),100)^20 71 | 72 | product(map(query($type1query),0,0,1,$type1boost),map(query($type2query),0,0,1,$type2boost),map(query($type3query),0,0,1,$type3boost),map(query($type4query),0,0,1,$type4boost),$typeboost) 73 | 1.0 74 | 75 | type:"Countries" 76 | 0.9 77 | type:"Cities" 78 | 0.5 79 | type:"NA" 80 | 0.0 81 | type:"NA" 82 | 0.0 83 | 84 | false 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | ${solr.abortOnConfigurationError:true} 96 | 97 | 98 | 99 | *:* 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/VM_global_library.vm: -------------------------------------------------------------------------------- 1 | 2 | #macro(param $key)$request.params.get($key)#end 3 | 4 | #macro(url_for_solr)/solr#if($request.core.name != "")/$request.core.name#end#end 5 | #macro(url_for_home)#url_for_solr/browse#end 6 | 7 | #macro(q)&q=$!{esc.url($params.get('q'))}#end 8 | 9 | #macro(fqs $p)#foreach($fq in $p)#if($velocityCount>1)&#{end}fq=$esc.url($fq)#end#end 10 | 11 | #macro(debug)#if($request.params.get('debugQuery'))&debugQuery=true#end#end 12 | 13 | #macro(boostPrice)#if($request.params.get('bf') == 'price')&bf=price#end#end 14 | 15 | #macro(annotate)#if($request.params.get('annotateBrowse'))&annotateBrowse=true#end#end 16 | 17 | #macro(annTitle $msg)#if($annotate == true)title="$msg"#end#end 18 | 19 | #macro(spatial)#if($request.params.get('sfield'))&sfield=store#end#if($request.params.get('pt'))&pt=$request.params.get('pt')#end#if($request.params.get('d'))&d=$request.params.get('d')#end#end 20 | 21 | #macro(qOpts)#set($queryOpts = $request.params.get("queryOpts"))#if($queryOpts && $queryOpts != "")&queryOpts=$queryOpts#end#end 22 | 23 | #macro(group)#if($request.params.getBool("group") == true)&group=true#end#if($request.params.get("group.field"))#foreach($grp in $request.params.getParams('group.field'))&group.field=$grp#end#end#end 24 | 25 | #macro(lensNoQ)?#if($request.params.getParams('fq') and $list.size($request.params.getParams('fq')) > 0)&#fqs($request.params.getParams('fq'))#end#debug#boostPrice#annotate#spatial#qOpts#group#end 26 | #macro(lens)#lensNoQ#q#end 27 | 28 | 29 | #macro(url_for_lens)#{url_for_home}#lens#end 30 | 31 | #macro(url_for_start $start)#url_for_home#lens&start=$start#end 32 | 33 | #macro(url_for_filters $p)#url_for_home?#q#boostPrice#spatial#qOpts#if($list.size($p) > 0)&#fqs($p)#end#debug#end 34 | 35 | 36 | #macro(url_for_nested_facet_query $field)#url_for_home#lens&fq=$esc.url($field)#end 37 | 38 | ## TODO: convert to use {!raw f=$field}$value (with escaping of course) 39 | #macro(url_for_facet_filter $field $value)#url_for_home#lens&fq=$esc.url($field):%22$esc.url($value)%22#end 40 | 41 | #macro(url_for_facet_date_filter $field $value)#url_for_home#lens&fq=$esc.url($field):$esc.url($value)#end 42 | 43 | #macro(url_for_facet_range_filter $field $value)#url_for_home#lens&fq=$esc.url($field):$esc.url($value)#end 44 | 45 | 46 | #macro(link_to_previous_page $text) 47 | #if($page.current_page_number > 1) 48 | #set($prev_start = $page.start - $page.results_per_page) 49 | $text 50 | #end 51 | #end 52 | 53 | #macro(link_to_next_page $text) 54 | #if($page.current_page_number < $page.page_count) 55 | #set($next_start = $page.start + $page.results_per_page) 56 | $text 57 | #end 58 | #end 59 | 60 | #macro(link_to_page $page_number $text) 61 | #if($page_number == $page.current_page_number) 62 | $text 63 | #else 64 | #if($page_number <= $page.page_count) 65 | #set($page_start = $page_number * $page.results_per_page - $page.results_per_page) 66 | $text 67 | #end 68 | #end 69 | #end 70 | 71 | #macro(display_facet_query $field, $display, $fieldName) 72 | #if($field.size() > 0) 73 | $display 74 | 84 | #end 85 | #end 86 | 87 | 88 | #macro(display_facet_range $field, $display, $fieldName, $start, $end, $gap, $before, $after) 89 | $display 90 | 110 | #end 111 | 112 | ## $pivots is a list of facet_pivot 113 | #macro(display_facet_pivot $pivots, $display) 114 | #if($pivots.size() > 0) 115 | $display 116 | 128 | #end 129 | #end 130 | 131 | #macro(field $f) 132 | #if($response.response.highlighting.get($docId).get($f).get(0)) 133 | $!response.response.highlighting.get($docId).get($f).get(0) 134 | #else 135 | #foreach($v in $doc.getFieldValues($f)) 136 | $v 137 | #end 138 | #end 139 | #end 140 | 141 | #macro(utc_date $theDate) 142 | $date.format("yyyy-MM-dd'T'HH:mm:ss'Z'",$theDate,$date.getLocale(),$date.getTimeZone().getTimeZone("UTC"))## 143 | #end 144 | 145 | #macro(format_value $val) 146 | #if(${val.class.name} == "java.util.Date") 147 | #utc_date($val)## 148 | #else 149 | $val## 150 | #end 151 | #end 152 | 153 | #macro(range_get_to_value $inval, $gapval) 154 | #if(${gapval.class.name} == "java.lang.String") 155 | $inval$gapval## 156 | #elseif(${gapval.class.name} == "java.lang.Float" || ${inval.class.name} == "java.lang.Float") 157 | $math.toDouble($math.add($inval,$gapval))## 158 | #else 159 | $math.add($inval,$gapval)## 160 | #end 161 | #end -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/browse.vm: -------------------------------------------------------------------------------- 1 | #set($searcher=$request.searcher) 2 | #set($params=$request.params) 3 | #set($clusters = $response.response.clusters) 4 | #set($mltResults = $response.response.get("moreLikeThis")) 5 | #set($annotate = $params.get("annotateBrowse")) 6 | #parse('query.vm') 7 | #if($response.response.spellcheck.suggestions and $response.response.spellcheck.suggestions.size() > 0) 8 | Did you mean $response.response.spellcheck.suggestions.collation? 9 | #end 10 | 11 | 14 | 15 | 22 | 23 |
24 | #if($response.response.get('grouped')) 25 | #foreach($grouping in $response.response.get('grouped')) 26 | #parse("hitGrouped.vm") 27 | #end 28 | #else 29 | #foreach($doc in $response.results) 30 | #parse("hit.vm") 31 | #end 32 | #end 33 |
34 | 35 | 46 | -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/cluster.vm: -------------------------------------------------------------------------------- 1 | 17 | 18 |

Clusters

19 |
20 | Run Solr with java -Dsolr.clustering.enabled=true -jar start.jar to see results 21 |
22 | 27 | -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/clusterResults.vm: -------------------------------------------------------------------------------- 1 | #foreach ($clusters in $response.response.clusters) 2 | #set($labels = $clusters.get('labels')) 3 | #set($docs = $clusters.get('docs')) 4 | 20 | 21 |

#foreach ($label in $labels)$label#if( $foreach.hasNext ),#end#end

22 |
    23 | #foreach ($cluDoc in $docs) 24 |
  1. $cluDoc
  2. 25 | #end 26 |
27 | 28 | 29 | #end -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/doc.vm: -------------------------------------------------------------------------------- 1 |
#field('textsuggest')#if($params.getBool('mlt', false) == false)More Like This#end
2 | ##do we have a physical store for this product 3 | #set($store = $doc.getFieldValue('store')) 4 | #if($store)
Larger Map
#end 5 | 6 | 7 | 13 | 18 | 19 |
8 |
Subtext: #field('subtext')
9 |
Type: #field('type')
10 |
Popularity (population): #field('popularity')
11 |
Score: #field('score')
12 |
14 | #if($doc.getFieldValue('thumbnail_url') != "") 15 | 16 | #end 17 |
20 |
21 | #set($mlt = $mltResults.get($docId)) 22 | #set($mltOn = $params.getBool('mlt')) 23 | #if($mltOn == true)
Similar Items
#end 24 | #if ($mltOn && $mlt && $mlt.size() > 0) 25 | 34 | #elseif($mltOn && $mlt.size() == 0) 35 |
No Similar Items Found
36 | #end 37 |
38 | #if($params.getBool("debugQuery",false)) 39 | toggle explain 40 |
$response.getExplainMap().get($doc.getFirstValue('id'))
41 | toggle all fields 42 | 43 | #foreach($fieldname in $doc.fieldNames) 44 |
45 | $fieldname : 46 | 47 | #foreach($value in $doc.getFieldValues($fieldname)) 48 | $value 49 | #end 50 | 51 | #end 52 |
53 |
54 | #end -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/facet_fields.vm: -------------------------------------------------------------------------------- 1 | #if($response.facetFields) 2 |

Field Facets

3 | #foreach($field in $response.facetFields) 4 | $field.name 5 | 6 | 11 | #end 12 | #end -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/facet_queries.vm: -------------------------------------------------------------------------------- 1 | #set($field = $response.response.facet_counts.facet_queries) 2 |

Query Facets

3 | #display_facet_query($field, "", "") -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/facet_ranges.vm: -------------------------------------------------------------------------------- 1 |

Range Facets

2 | #foreach ($field in $response.response.facet_counts.facet_ranges) 3 | #set($name = $field.key) 4 | #set($display = "$name") 5 | #set($f = $field.value.counts) 6 | #set($start = $field.value.start) 7 | #set($end = $field.value.end) 8 | #set($gap = $field.value.gap) 9 | #set($before = $field.value.before) 10 | #set($after = $field.value.after) 11 | #display_facet_range($f, $display, $name, $start, $end, $gap, $before, $after) 12 | #end -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/facets.vm: -------------------------------------------------------------------------------- 1 | #parse('facet_fields.vm') 2 | #parse('facet_queries.vm') 3 | #parse('facet_ranges.vm') 4 | #parse('cluster.vm') 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/footer.vm: -------------------------------------------------------------------------------- 1 |
2 |
3 | Options: 4 | #if($request.params.get('debugQuery')) 5 | disable debug 6 | #else 7 | enable debug 8 | #end 9 | #if($annotate) 10 | disable annotation 11 | #else 12 | enable annotation 13 | #end 14 | XML
15 |
Generated by VelocityResponseWriter
16 |
Documentation: Solr Home Page, Solr Wiki
17 |
Disclaimer: The locations displayed in this demonstration are purely fictional. It is more than likely that no store with the items listed actually exists at that location!
-------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/head.vm: -------------------------------------------------------------------------------- 1 | 2 | ## An example of using an arbitrary request parameter 3 | 19 | 20 | #param('title') 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/header.vm: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/hit.vm: -------------------------------------------------------------------------------- 1 | #set($docId = $doc.getFieldValue('id')) 2 | 3 |
4 | #parse("doc.vm") 5 |
6 | -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/jquery.autocomplete.css: -------------------------------------------------------------------------------- 1 | .ac_results { 2 | padding: 0px; 3 | border: 1px solid black; 4 | background-color: white; 5 | overflow: hidden; 6 | z-index: 99999; 7 | } 8 | 9 | .ac_results ul { 10 | width: 100%; 11 | list-style-position: outside; 12 | list-style: none; 13 | padding: 0; 14 | margin: 0; 15 | } 16 | 17 | .ac_results li { 18 | margin: 0px; 19 | padding: 2px 5px; 20 | cursor: default; 21 | display: block; 22 | /* 23 | if width will be 100% horizontal scrollbar will apear 24 | when scroll mode will be used 25 | */ 26 | /*width: 100%;*/ 27 | font: menu; 28 | font-size: 12px; 29 | /* 30 | it is very important, if line-height not setted or setted 31 | in relative units scroll will be broken in firefox 32 | */ 33 | line-height: 16px; 34 | overflow: hidden; 35 | } 36 | 37 | .ac_loading { 38 | background: white url('indicator.gif') right center no-repeat; 39 | } 40 | 41 | .ac_odd { 42 | background-color: #eee; 43 | } 44 | 45 | .ac_over { 46 | background-color: #0A246A; 47 | color: white; 48 | } 49 | -------------------------------------------------------------------------------- /solr-home/ac/conf/velocity/jquery.autocomplete.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Autocomplete plugin 1.2.2 3 | * 4 | * Copyright (c) 2009 Jörn Zaefferer 5 | * 6 | * Dual licensed under the MIT and GPL licenses: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.gnu.org/licenses/gpl.html 9 | * 10 | * With small modifications by Alfonso Gómez-Arzola. 11 | * See changelog for details. 12 | * 13 | */ 14 | 15 | ;(function($) { 16 | 17 | $.fn.extend({ 18 | autocomplete: function(urlOrData, options) { 19 | var isUrl = typeof urlOrData == "string"; 20 | options = $.extend({}, $.Autocompleter.defaults, { 21 | url: isUrl ? urlOrData : null, 22 | data: isUrl ? null : urlOrData, 23 | delay: isUrl ? $.Autocompleter.defaults.delay : 10, 24 | max: options && !options.scroll ? 25 : 250 25 | }, options); 26 | 27 | // if highlight is set to false, replace it with a do-nothing function 28 | options.highlight = options.highlight || function(value) { return value; }; 29 | 30 | // if the formatMatch option is not specified, then use formatItem for backwards compatibility 31 | options.formatMatch = options.formatMatch || options.formatItem; 32 | 33 | return this.each(function() { 34 | new $.Autocompleter(this, options); 35 | }); 36 | }, 37 | result: function(handler) { 38 | return this.bind("result", handler); 39 | }, 40 | search: function(handler) { 41 | return this.trigger("search", [handler]); 42 | }, 43 | flushCache: function() { 44 | return this.trigger("flushCache"); 45 | }, 46 | setOptions: function(options){ 47 | return this.trigger("setOptions", [options]); 48 | }, 49 | unautocomplete: function() { 50 | return this.trigger("unautocomplete"); 51 | } 52 | }); 53 | 54 | $.Autocompleter = function(input, options) { 55 | 56 | var KEY = { 57 | UP: 38, 58 | DOWN: 40, 59 | DEL: 46, 60 | TAB: 9, 61 | RETURN: 13, 62 | ESC: 27, 63 | COMMA: 188, 64 | PAGEUP: 33, 65 | PAGEDOWN: 34, 66 | BACKSPACE: 8 67 | }; 68 | 69 | var globalFailure = null; 70 | if(options.failure != null && typeof options.failure == "function") { 71 | globalFailure = options.failure; 72 | } 73 | 74 | // Create $ object for input element 75 | var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); 76 | 77 | var timeout; 78 | var previousValue = ""; 79 | var cache = $.Autocompleter.Cache(options); 80 | var hasFocus = 0; 81 | var lastKeyPressCode; 82 | var config = { 83 | mouseDownOnSelect: false 84 | }; 85 | var select = $.Autocompleter.Select(options, input, selectCurrent, config); 86 | 87 | var blockSubmit; 88 | 89 | // prevent form submit in opera when selecting with return key 90 | $.browser.opera && $(input.form).bind("submit.autocomplete", function() { 91 | if (blockSubmit) { 92 | blockSubmit = false; 93 | return false; 94 | } 95 | }); 96 | 97 | // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all 98 | $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { 99 | // a keypress means the input has focus 100 | // avoids issue where input had focus before the autocomplete was applied 101 | hasFocus = 1; 102 | // track last key pressed 103 | lastKeyPressCode = event.keyCode; 104 | switch(event.keyCode) { 105 | 106 | case KEY.UP: 107 | if ( select.visible() ) { 108 | event.preventDefault(); 109 | select.prev(); 110 | } else { 111 | onChange(0, true); 112 | } 113 | break; 114 | 115 | case KEY.DOWN: 116 | if ( select.visible() ) { 117 | event.preventDefault(); 118 | select.next(); 119 | } else { 120 | onChange(0, true); 121 | } 122 | break; 123 | 124 | case KEY.PAGEUP: 125 | if ( select.visible() ) { 126 | event.preventDefault(); 127 | select.pageUp(); 128 | } else { 129 | onChange(0, true); 130 | } 131 | break; 132 | 133 | case KEY.PAGEDOWN: 134 | if ( select.visible() ) { 135 | event.preventDefault(); 136 | select.pageDown(); 137 | } else { 138 | onChange(0, true); 139 | } 140 | break; 141 | 142 | // matches also semicolon 143 | case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: 144 | case KEY.TAB: 145 | case KEY.RETURN: 146 | if( selectCurrent() ) { 147 | // stop default to prevent a form submit, Opera needs special handling 148 | event.preventDefault(); 149 | blockSubmit = true; 150 | return false; 151 | } 152 | break; 153 | 154 | case KEY.ESC: 155 | select.hide(); 156 | break; 157 | 158 | default: 159 | clearTimeout(timeout); 160 | timeout = setTimeout(onChange, options.delay); 161 | break; 162 | } 163 | }).focus(function(){ 164 | // track whether the field has focus, we shouldn't process any 165 | // results if the field no longer has focus 166 | hasFocus++; 167 | }).blur(function() { 168 | hasFocus = 0; 169 | if (!config.mouseDownOnSelect) { 170 | hideResults(); 171 | } 172 | }).click(function() { 173 | // show select when clicking in a focused field 174 | // but if clickFire is true, don't require field 175 | // to be focused to begin with; just show select 176 | if( options.clickFire ) { 177 | if ( !select.visible() ) { 178 | onChange(0, true); 179 | } 180 | } else { 181 | if ( hasFocus++ > 1 && !select.visible() ) { 182 | onChange(0, true); 183 | } 184 | } 185 | }).bind("search", function() { 186 | // TODO why not just specifying both arguments? 187 | var fn = (arguments.length > 1) ? arguments[1] : null; 188 | function findValueCallback(q, data) { 189 | var result; 190 | if( data && data.length ) { 191 | for (var i=0; i < data.length; i++) { 192 | if( data[i].result.toLowerCase() == q.toLowerCase() ) { 193 | result = data[i]; 194 | break; 195 | } 196 | } 197 | } 198 | if( typeof fn == "function" ) fn(result); 199 | else $input.trigger("result", result && [result.data, result.value]); 200 | } 201 | $.each(trimWords($input.val()), function(i, value) { 202 | request(value, findValueCallback, findValueCallback); 203 | }); 204 | }).bind("flushCache", function() { 205 | cache.flush(); 206 | }).bind("setOptions", function() { 207 | $.extend(true, options, arguments[1]); 208 | // if we've updated the data, repopulate 209 | if ( "data" in arguments[1] ) 210 | cache.populate(); 211 | }).bind("unautocomplete", function() { 212 | select.unbind(); 213 | $input.unbind(); 214 | $(input.form).unbind(".autocomplete"); 215 | }); 216 | 217 | 218 | function selectCurrent() { 219 | var selected = select.selected(); 220 | if( !selected ) 221 | return false; 222 | 223 | var v = selected.result; 224 | previousValue = v; 225 | 226 | if ( options.multiple ) { 227 | var words = trimWords($input.val()); 228 | if ( words.length > 1 ) { 229 | var seperator = options.multipleSeparator.length; 230 | var cursorAt = $(input).selection().start; 231 | var wordAt, progress = 0; 232 | $.each(words, function(i, word) { 233 | progress += word.length; 234 | if (cursorAt <= progress) { 235 | wordAt = i; 236 | return false; 237 | } 238 | progress += seperator; 239 | }); 240 | words[wordAt] = v; 241 | // TODO this should set the cursor to the right position, but it gets overriden somewhere 242 | //$.Autocompleter.Selection(input, progress + seperator, progress + seperator); 243 | v = words.join( options.multipleSeparator ); 244 | } 245 | v += options.multipleSeparator; 246 | } 247 | 248 | $input.val(v); 249 | hideResultsNow(); 250 | $input.trigger("result", [selected.data, selected.value]); 251 | return true; 252 | } 253 | 254 | function onChange(crap, skipPrevCheck) { 255 | if( lastKeyPressCode == KEY.DEL ) { 256 | select.hide(); 257 | return; 258 | } 259 | 260 | var currentValue = $input.val(); 261 | 262 | if ( !skipPrevCheck && currentValue == previousValue ) 263 | return; 264 | 265 | previousValue = currentValue; 266 | 267 | currentValue = lastWord(currentValue); 268 | if ( currentValue.length >= options.minChars) { 269 | $input.addClass(options.loadingClass); 270 | if (!options.matchCase) 271 | currentValue = currentValue.toLowerCase(); 272 | request(currentValue, receiveData, hideResultsNow); 273 | } else { 274 | stopLoading(); 275 | select.hide(); 276 | } 277 | }; 278 | 279 | function trimWords(value) { 280 | if (!value) 281 | return [""]; 282 | if (!options.multiple) 283 | return [$.trim(value)]; 284 | return $.map(value.split(options.multipleSeparator), function(word) { 285 | return $.trim(value).length ? $.trim(word) : null; 286 | }); 287 | } 288 | 289 | function lastWord(value) { 290 | if ( !options.multiple ) 291 | return value; 292 | var words = trimWords(value); 293 | if (words.length == 1) 294 | return words[0]; 295 | var cursorAt = $(input).selection().start; 296 | if (cursorAt == value.length) { 297 | words = trimWords(value) 298 | } else { 299 | words = trimWords(value.replace(value.substring(cursorAt), "")); 300 | } 301 | return words[words.length - 1]; 302 | } 303 | 304 | // fills in the input box w/the first match (assumed to be the best match) 305 | // q: the term entered 306 | // sValue: the first matching result 307 | function autoFill(q, sValue){ 308 | // autofill in the complete box w/the first match as long as the user hasn't entered in more data 309 | // if the last user key pressed was backspace, don't autofill 310 | if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { 311 | // fill in the value (keep the case the user has typed) 312 | $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); 313 | // select the portion of the value not typed by the user (so the next character will erase) 314 | $(input).selection(previousValue.length, previousValue.length + sValue.length); 315 | } 316 | }; 317 | 318 | function hideResults() { 319 | clearTimeout(timeout); 320 | timeout = setTimeout(hideResultsNow, 200); 321 | }; 322 | 323 | function hideResultsNow() { 324 | var wasVisible = select.visible(); 325 | select.hide(); 326 | clearTimeout(timeout); 327 | stopLoading(); 328 | if (options.mustMatch) { 329 | // call search and run callback 330 | $input.search( 331 | function (result){ 332 | // if no value found, clear the input box 333 | if( !result ) { 334 | if (options.multiple) { 335 | var words = trimWords($input.val()).slice(0, -1); 336 | $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); 337 | } 338 | else { 339 | $input.val( "" ); 340 | $input.trigger("result", null); 341 | } 342 | } 343 | } 344 | ); 345 | } 346 | }; 347 | 348 | function receiveData(q, data) { 349 | if ( data && data.length && hasFocus ) { 350 | stopLoading(); 351 | select.display(data, q); 352 | autoFill(q, data[0].value); 353 | select.show(); 354 | } else { 355 | hideResultsNow(); 356 | } 357 | }; 358 | 359 | function request(term, success, failure) { 360 | if (!options.matchCase) 361 | term = term.toLowerCase(); 362 | var data = null // cache.load(term); 363 | // recieve the cached data 364 | if (data && data.length) { 365 | success(term, data); 366 | // if an AJAX url has been supplied, try loading the data now 367 | } else if( (typeof options.url == "string") && (options.url.length > 0) ){ 368 | 369 | var extraParams = { 370 | timestamp: +new Date() 371 | }; 372 | $.each(options.extraParams, function(key, param) { 373 | extraParams[key] = typeof param == "function" ? param() : param; 374 | }); 375 | 376 | $.ajax({ 377 | // try to leverage ajaxQueue plugin to abort previous requests 378 | mode: "abort", 379 | // limit abortion to this input 380 | port: "autocomplete" + input.name, 381 | dataType: options.dataType, 382 | url: options.url, 383 | data: $.extend({ 384 | q: lastWord(term), 385 | limit: options.max 386 | }, extraParams), 387 | success: function(data) { 388 | var parsed = options.parse && options.parse(data) || parse(data); 389 | cache.add(term, parsed); 390 | success(term, parsed); 391 | } 392 | }); 393 | } else { 394 | // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match 395 | select.emptyList(); 396 | if(globalFailure != null) { 397 | globalFailure(); 398 | } 399 | else { 400 | failure(term); 401 | } 402 | } 403 | }; 404 | 405 | function parse(data) { 406 | var parsed = []; 407 | var rows = data.split("\n"); 408 | for (var i=0; i < rows.length; i++) { 409 | var row = $.trim(rows[i]); 410 | if (row) { 411 | row = row.split("|"); 412 | parsed[parsed.length] = { 413 | data: row, 414 | value: row[0], 415 | result: options.formatResult && options.formatResult(row, row[0]) || row[0] 416 | }; 417 | } 418 | } 419 | return parsed; 420 | }; 421 | 422 | function stopLoading() { 423 | $input.removeClass(options.loadingClass); 424 | }; 425 | 426 | }; 427 | 428 | $.Autocompleter.defaults = { 429 | inputClass: "ac_input", 430 | resultsClass: "ac_results", 431 | loadingClass: "ac_loading", 432 | minChars: 1, 433 | delay: 400, 434 | matchCase: false, 435 | matchSubset: true, 436 | matchContains: false, 437 | cacheLength: 100, 438 | max: 1000, 439 | mustMatch: false, 440 | extraParams: {}, 441 | selectFirst: false, 442 | formatItem: function(row) { return row[0]; }, 443 | formatMatch: null, 444 | autoFill: false, 445 | width: 0, 446 | multiple: false, 447 | multipleSeparator: " ", 448 | inputFocus: true, 449 | clickFire: false, 450 | highlight: function(value, term) { 451 | return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); 452 | }, 453 | scroll: false, 454 | scrollHeight: 400, 455 | scrollJumpPosition: true 456 | }; 457 | 458 | $.Autocompleter.Cache = function(options) { 459 | 460 | var data = {}; 461 | var length = 0; 462 | 463 | function matchSubset(s, sub) { 464 | if (!options.matchCase) 465 | s = s.toLowerCase(); 466 | var i = s.indexOf(sub); 467 | if (options.matchContains == "word"){ 468 | i = s.toLowerCase().search("\\b" + sub.toLowerCase()); 469 | } 470 | if (i == -1) return false; 471 | return i == 0 || options.matchContains; 472 | }; 473 | 474 | function add(q, value) { 475 | if (length > options.cacheLength){ 476 | flush(); 477 | } 478 | if (!data[q]){ 479 | length++; 480 | } 481 | data[q] = value; 482 | } 483 | 484 | function populate(){ 485 | if( !options.data ) return false; 486 | // track the matches 487 | var stMatchSets = {}, 488 | nullData = 0; 489 | 490 | // no url was specified, we need to adjust the cache length to make sure it fits the local data store 491 | if( !options.url ) options.cacheLength = 1; 492 | 493 | // track all options for minChars = 0 494 | stMatchSets[""] = []; 495 | 496 | // loop through the array and create a lookup structure 497 | for ( var i = 0, ol = options.data.length; i < ol; i++ ) { 498 | var rawValue = options.data[i]; 499 | // if rawValue is a string, make an array otherwise just reference the array 500 | rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; 501 | 502 | var value = options.formatMatch(rawValue, i+1, options.data.length); 503 | if ( value === false ) 504 | continue; 505 | 506 | var firstChar = value.charAt(0).toLowerCase(); 507 | // if no lookup array for this character exists, look it up now 508 | if( !stMatchSets[firstChar] ) 509 | stMatchSets[firstChar] = []; 510 | 511 | // if the match is a string 512 | var row = { 513 | value: value, 514 | data: rawValue, 515 | result: options.formatResult && options.formatResult(rawValue) || value 516 | }; 517 | 518 | // push the current match into the set list 519 | stMatchSets[firstChar].push(row); 520 | 521 | // keep track of minChars zero items 522 | if ( nullData++ < options.max ) { 523 | stMatchSets[""].push(row); 524 | } 525 | }; 526 | 527 | // add the data items to the cache 528 | $.each(stMatchSets, function(i, value) { 529 | // increase the cache size 530 | options.cacheLength++; 531 | // add to the cache 532 | add(i, value); 533 | }); 534 | } 535 | 536 | // populate any existing data 537 | setTimeout(populate, 25); 538 | 539 | function flush(){ 540 | data = {}; 541 | length = 0; 542 | } 543 | 544 | return { 545 | flush: flush, 546 | add: add, 547 | populate: populate, 548 | load: function(q) { 549 | if (!options.cacheLength || !length) 550 | return null; 551 | /* 552 | * if dealing w/local data and matchContains than we must make sure 553 | * to loop through all the data collections looking for matches 554 | */ 555 | if( !options.url && options.matchContains ){ 556 | // track all matches 557 | var csub = []; 558 | // loop through all the data grids for matches 559 | for( var k in data ){ 560 | // don't search through the stMatchSets[""] (minChars: 0) cache 561 | // this prevents duplicates 562 | if( k.length > 0 ){ 563 | var c = data[k]; 564 | $.each(c, function(i, x) { 565 | // if we've got a match, add it to the array 566 | if (matchSubset(x.value, q)) { 567 | csub.push(x); 568 | } 569 | }); 570 | } 571 | } 572 | return csub; 573 | } else 574 | // if the exact item exists, use it 575 | if (data[q]){ 576 | return data[q]; 577 | } else 578 | if (options.matchSubset) { 579 | for (var i = q.length - 1; i >= options.minChars; i--) { 580 | var c = data[q.substr(0, i)]; 581 | if (c) { 582 | var csub = []; 583 | $.each(c, function(i, x) { 584 | if (matchSubset(x.value, q)) { 585 | csub[csub.length] = x; 586 | } 587 | }); 588 | return csub; 589 | } 590 | } 591 | } 592 | return null; 593 | } 594 | }; 595 | }; 596 | 597 | $.Autocompleter.Select = function (options, input, select, config) { 598 | var CLASSES = { 599 | ACTIVE: "ac_over" 600 | }; 601 | 602 | var listItems, 603 | active = -1, 604 | data, 605 | term = "", 606 | needsInit = true, 607 | element, 608 | list; 609 | 610 | // Create results 611 | function init() { 612 | if (!needsInit) 613 | return; 614 | element = $("
") 615 | .hide() 616 | .addClass(options.resultsClass) 617 | .css("position", "absolute") 618 | .appendTo(document.body) 619 | .hover(function(event) { 620 | // Browsers except FF do not fire mouseup event on scrollbars, resulting in mouseDownOnSelect remaining true, and results list not always hiding. 621 | if($(this).is(":visible")) { 622 | input.focus(); 623 | } 624 | config.mouseDownOnSelect = false; 625 | }); 626 | 627 | list = $("