├── .gitignore ├── loading.gif ├── search_data_sample.mtml ├── flexibleSearch-config.js ├── sample.html ├── flexibleSearch.min.js ├── mustache.js ├── README.md └── flexibleSearch.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | -------------------------------------------------------------------------------- /loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinybeans/jq-plugin-flexibleSearch/HEAD/loading.gif -------------------------------------------------------------------------------- /search_data_sample.mtml: -------------------------------------------------------------------------------- 1 | {"items":[ 2 | 3 | 4 | 5 | 6 | 7 | ,, 8 | 9 | , 10 | 11 | 12 | 13 | 14 | 15 | 16 | ,, 17 | 18 | , 19 | 20 | ]} -------------------------------------------------------------------------------- /flexibleSearch-config.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $("#search").flexibleSearch({ 3 | // // The limit parameter is overwritten and locked as this value. 4 | // limit: null, 5 | // // Path 6 | // searchDataPath: "/flexibleSearch/search.json", 7 | // searchDataPathPreload: null, 8 | 9 | // // Data API 10 | // dataApiDataIds: null, 11 | // dataApiParams: null, 12 | 13 | // // Performance 14 | // cache: true, // I recommend "true". 15 | 16 | // // Search Form 17 | // searchFormCreation: true, 18 | // searchFormHTML: null, 19 | // searchFormAction: "", 20 | // searchFormInputType: "search", 21 | // searchFormInputPlaceholder: "Search words", 22 | // searchFormSubmitBtnText: "Search", 23 | 24 | // // Advanced Search Form 25 | // advancedFormObj: null, 26 | // advancedSearchCond: 'OR', // 'AND' 27 | 28 | // // Result Block 29 | // loadingImgPath: "/flexibleSearch/loading.gif", 30 | // loadingImgHTML: null, 31 | 32 | // resultBlockId: "fs-result", 33 | // resultItemTmpl: null, 34 | 35 | // resultMsgId: null, 36 | // resultMsgClassName: "fs-result-msg", 37 | // resultMsgTmpl: null, 38 | 39 | // resultMetaTitleTmpl: null, 40 | 41 | // // You can set an array including plane object which has two properties, 42 | // // method property and selector property. 43 | // // e.g. 44 | // // resultMsgInsertMethods: [ 45 | // // { 46 | // // "selector": "foo", 47 | // // "method": "append" 48 | // // } 49 | // // ], 50 | // resultMsgInsertMethods: null, 51 | 52 | // // Paginate 53 | // paginateId: null, 54 | // paginateClassName: "fs-paginate", 55 | // paginateTmpl: null, 56 | // paginateCount: 10, 57 | // hidePageNumber: false, 58 | // showTurnPage: true, 59 | // prevPageText: 'Prev', 60 | // nextPageText: 'Next', 61 | // maxPageCount: 10, 62 | // // You can set an array including plane object which has two properties, 63 | // // method property and selector property. 64 | // // e.g. 65 | // // paginateInsertMethods: [ 66 | // // { 67 | // // "selector": "foo", 68 | // // "method": "append" 69 | // // } 70 | // // ], 71 | // paginateInsertMethods: null, 72 | 73 | // // Submit 74 | // submitAction: function (paramArray) { 75 | // return paramArray; 76 | // }, 77 | 78 | // // Ajax 79 | // ajaxError: function (jqXHR, textStatus, errorThrown) { 80 | // window.alert(textStatus); 81 | // }, 82 | 83 | // // Callbacks 84 | 85 | // // you can search in your logic. 86 | // // e.g. 87 | // // customSearch: function(item, paramObj){ 88 | // // // item : Each item in items 89 | // // // paramObj : Plane object of parameters 90 | // // // The item is removed when return false 91 | // // return true or false; 92 | // // }, 93 | // customSearch: null, 94 | 95 | // // You can modify the search result JSON. 96 | // // e.g. 97 | // // modifyResultJSON = function(json){ 98 | // // json["fullName"] = function(){ 99 | // // return this.firstName + " " + this.lastName; 100 | // // }; 101 | // // return json; 102 | // // }, 103 | // modifyResultJSON: null, 104 | // modifyResultMsgHTML: null, 105 | // modifyResultItemHTML: null, 106 | // modifyPaginateHTML: null, 107 | // resultComplete: null, 108 | 109 | // excludeParams: null, 110 | dummy: false 111 | }); 112 | })(jQuery); -------------------------------------------------------------------------------- /sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flexibleSearch.js 6 | 7 | 8 |

検索結果

9 | 10 |
11 | 12 | 13 | 14 | 15 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /flexibleSearch.min.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * flexibleSearch.js 3 | * 4 | * Copyright (c) Tomohiro Okuwaki / bit part LLC (http://bit-part.net/) 5 | * 6 | * Since : 2010-11-12 7 | * Update : 2020-02-12 8 | * Version: 2.5.0 9 | * Comment: Please use this with Movable Type :) 10 | * 11 | * You have to include "mustache.js" before "flexibleSearch.js". 12 | * Maybe... jQuery 1.7.x later 13 | */ 14 | !function(e){e.fn.flexibleSearch=function(a){var t=e.extend({},e.fn.flexibleSearch.defaults,a);if(t.searchFormCreation){var n=[],r="";if(null!==t.advancedFormObj&&("hidden"in t.advancedFormObj&&n.push(['"].join("")),"text"in t.advancedFormObj&&n.push(['
',"{{#text}}","{{#label}}",'',"{{/label}}",'',"{{#label}}","{{label}}","{{/label}}","{{/text}}","
"].join("")),"checkbox"in t.advancedFormObj&&n.push(['
',"{{#checkbox}}","{{#label}}",'',"{{/label}}",'',"{{#label}}","{{label}}","{{/label}}","{{/checkbox}}","
"].join("")),"radio"in t.advancedFormObj&&n.push(['
',"{{#radio}}","{{#label}}",'',"{{/label}}",'',"{{#label}}","{{label}}","{{/label}}","{{/radio}}","
"].join("")),r=Mustache.render(n.join(""),t.advancedFormObj),"select"in t.advancedFormObj)){var l={selects:t.advancedFormObj.select,options:function(){for(var e=this.option,a=[],t=0,n=e.length;t"+e[t].label+"")}return a.join("")}},s=['
',"{{#selects}}",'',"{{{options}}}","","{{/selects}}","
"].join("");r+=Mustache.render(s,l)}var i={action:t.searchFormAction,type:t.searchFormInputType,placeholder:t.searchFormInputPlaceholder,submitBtnText:t.searchFormSubmitBtnText},o="";if(null!==t.limit&&"number"==typeof t.limit&&(t.paginateCount=t.limit),null!==t.searchFormHTML)o=t.searchFormHTML;else{var c=['
','','','','',r,"
"].join("");o=Mustache.render(c,i)}this[0].innerHTML=o}var u="";null!==t.loadingImgHTML?u=t.loadingImgHTML:t.loadingImgPath&&(u='');var d="";d=null!==t.resultMsgTmpl?t.resultMsgTmpl:['',"

","{{#keywords}}「{{keywords}}」が {{/keywords}}","{{#count}}{{count}} 件見つかりました。{{/count}}","{{^count}}見つかりませんでした。{{/count}}","{{#count}}({{lastPage}} ページ中 {{currentPage}} ページ目を表示){{/count}}","

",""].join("");var m="";m=null!==t.resultItemTmpl?t.resultItemTmpl:['
',"
    ","{{#items}}","
  • {{&title}}
  • ","{{/items}}","
","
"].join("");var p="";p=null!==t.paginateTmpl?t.paginateTmpl:['',"
    ","{{#showTurnPage}}","{{#exceptFirst}}",'
  • {{prevPageText}}
  • ',"{{/exceptFirst}}","{{/showTurnPage}}","{{#page}}","{{#checkRange}}",'',"{{/checkRange}}","{{/page}}","{{#showTurnPage}}","{{#exceptLast}}",'
  • {{nextPageText}}
  • ',"{{/exceptLast}}","{{/showTurnPage}}","
",""].join(""),this.find("form").on("submit",(function(a){a.preventDefault();for(var n=e(this).attr("action")||location.href.replace(/\?.*/,""),r=e(this).serializeArray(),l=-1,s=r.length;++ls?-1*t:0}))}(r,I,j,N)),null!==t.customSort&&"function"==typeof t.customSort&&(r=t.customSort(r,y)),n.items=e.grep(r,(function(e,a){return!(a=l)}))}var s,i,o=Math.ceil(k/M)+1,c=Math.ceil(n.totalResults/M),u=t.maxPageCount?t.maxPageCount:100,h=u%2==0?u/2-1:Math.floor(u/2),f=u%2==0?u/2:Math.floor(u/2);c<=u?(s=1,i=c):(i=o+f,(s=o-h)<1?(s=1,i=u):i>c&&(i=c,s=o-(h=u-(f=c-o))));for(var g=[],v=0,P=c;++v<=P;)g.push({pageNumber:v});var T={id:t.paginateId,classname:t.paginateClassName,page:g,hidePageNumber:t.hidePageNumber,showTurnPage:t.showTurnPage,prevPageText:t.prevPageText,nextPageText:t.nextPageText,isFirst:function(){return 1===o},isLast:function(){return!g.length||o===g.length},exceptFirst:function(){return 1!==o},exceptLast:function(){return g.length>0&&o!==g.length},checkRange:function(){return this.pageNumber>=s&&this.pageNumber<=i},current:function(){return this.pageNumber===o?"fs-current":this.pageNumber===o-1?"fs-current-prev":this.pageNumber===o+1?"fs-current-next":""},isCurrent:function(){return this.pageNumber===o},lastPage:function(){return T.page.length},currentLink:function(){return this.pageNumber===o?"fs-current-link":this.pageNumber===o-1?"fs-current-prev-link":this.pageNumber===o+1?"fs-current-next-link":""},currentCountFrom:function(){return k-0+1},currentCountTo:function(){var e=k-0+M;return(e 18 | } 19 | } 20 | }(this, function (mustache) { 21 | 22 | var whiteRe = /\s*/; 23 | var spaceRe = /\s+/; 24 | var nonSpaceRe = /\S/; 25 | var eqRe = /\s*=/; 26 | var curlyRe = /\s*\}/; 27 | var tagRe = /#|\^|\/|>|\{|&|=|!/; 28 | 29 | // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 30 | // See https://github.com/janl/mustache.js/issues/189 31 | var RegExp_test = RegExp.prototype.test; 32 | function testRegExp(re, string) { 33 | return RegExp_test.call(re, string); 34 | } 35 | 36 | function isWhitespace(string) { 37 | return !testRegExp(nonSpaceRe, string); 38 | } 39 | 40 | var Object_toString = Object.prototype.toString; 41 | var isArray = Array.isArray || function (obj) { 42 | return Object_toString.call(obj) === '[object Array]'; 43 | }; 44 | 45 | function escapeRegExp(string) { 46 | return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); 47 | } 48 | 49 | var entityMap = { 50 | "&": "&", 51 | "<": "<", 52 | ">": ">", 53 | '"': '"', 54 | "'": ''', 55 | "/": '/' 56 | }; 57 | 58 | function escapeHtml(string) { 59 | return String(string).replace(/[&<>"'\/]/g, function (s) { 60 | return entityMap[s]; 61 | }); 62 | } 63 | 64 | function Scanner(string) { 65 | this.string = string; 66 | this.tail = string; 67 | this.pos = 0; 68 | } 69 | 70 | /** 71 | * Returns `true` if the tail is empty (end of string). 72 | */ 73 | Scanner.prototype.eos = function () { 74 | return this.tail === ""; 75 | }; 76 | 77 | /** 78 | * Tries to match the given regular expression at the current position. 79 | * Returns the matched text if it can match, the empty string otherwise. 80 | */ 81 | Scanner.prototype.scan = function (re) { 82 | var match = this.tail.match(re); 83 | 84 | if (match && match.index === 0) { 85 | this.tail = this.tail.substring(match[0].length); 86 | this.pos += match[0].length; 87 | return match[0]; 88 | } 89 | 90 | return ""; 91 | }; 92 | 93 | /** 94 | * Skips all text until the given regular expression can be matched. Returns 95 | * the skipped string, which is the entire tail if no match can be made. 96 | */ 97 | Scanner.prototype.scanUntil = function (re) { 98 | var match, pos = this.tail.search(re); 99 | 100 | switch (pos) { 101 | case -1: 102 | match = this.tail; 103 | this.pos += this.tail.length; 104 | this.tail = ""; 105 | break; 106 | case 0: 107 | match = ""; 108 | break; 109 | default: 110 | match = this.tail.substring(0, pos); 111 | this.tail = this.tail.substring(pos); 112 | this.pos += pos; 113 | } 114 | 115 | return match; 116 | }; 117 | 118 | function Context(view, parent) { 119 | this.view = view || {}; 120 | this.parent = parent; 121 | this._cache = {}; 122 | } 123 | 124 | Context.make = function (view) { 125 | return (view instanceof Context) ? view : new Context(view); 126 | }; 127 | 128 | Context.prototype.push = function (view) { 129 | return new Context(view, this); 130 | }; 131 | 132 | Context.prototype.lookup = function (name) { 133 | var value = this._cache[name]; 134 | 135 | if (!value) { 136 | if (name == '.') { 137 | value = this.view; 138 | } else { 139 | var context = this; 140 | 141 | while (context) { 142 | if (name.indexOf('.') > 0) { 143 | value = context.view; 144 | var names = name.split('.'), i = 0; 145 | while (value && i < names.length) { 146 | value = value[names[i++]]; 147 | } 148 | } else { 149 | value = context.view[name]; 150 | } 151 | 152 | if (value != null) break; 153 | 154 | context = context.parent; 155 | } 156 | } 157 | 158 | this._cache[name] = value; 159 | } 160 | 161 | if (typeof value === 'function') value = value.call(this.view); 162 | 163 | return value; 164 | }; 165 | 166 | function Writer() { 167 | this.clearCache(); 168 | } 169 | 170 | Writer.prototype.clearCache = function () { 171 | this._cache = {}; 172 | this._partialCache = {}; 173 | }; 174 | 175 | Writer.prototype.compile = function (template, tags) { 176 | var fn = this._cache[template]; 177 | 178 | if (!fn) { 179 | var tokens = mustache.parse(template, tags); 180 | fn = this._cache[template] = this.compileTokens(tokens, template); 181 | } 182 | 183 | return fn; 184 | }; 185 | 186 | Writer.prototype.compilePartial = function (name, template, tags) { 187 | var fn = this.compile(template, tags); 188 | this._partialCache[name] = fn; 189 | return fn; 190 | }; 191 | 192 | Writer.prototype.getPartial = function (name) { 193 | if (!(name in this._partialCache) && this._loadPartial) { 194 | this.compilePartial(name, this._loadPartial(name)); 195 | } 196 | 197 | return this._partialCache[name]; 198 | }; 199 | 200 | Writer.prototype.compileTokens = function (tokens, template) { 201 | var self = this; 202 | return function (view, partials) { 203 | if (partials) { 204 | if (typeof partials === 'function') { 205 | self._loadPartial = partials; 206 | } else { 207 | for (var name in partials) { 208 | self.compilePartial(name, partials[name]); 209 | } 210 | } 211 | } 212 | 213 | return renderTokens(tokens, self, Context.make(view), template); 214 | }; 215 | }; 216 | 217 | Writer.prototype.render = function (template, view, partials) { 218 | return this.compile(template)(view, partials); 219 | }; 220 | 221 | /** 222 | * Low-level function that renders the given `tokens` using the given `writer` 223 | * and `context`. The `template` string is only needed for templates that use 224 | * higher-order sections to extract the portion of the original template that 225 | * was contained in that section. 226 | */ 227 | function renderTokens(tokens, writer, context, template) { 228 | var buffer = ''; 229 | 230 | var token, tokenValue, value; 231 | for (var i = 0, len = tokens.length; i < len; ++i) { 232 | token = tokens[i]; 233 | tokenValue = token[1]; 234 | 235 | switch (token[0]) { 236 | case '#': 237 | value = context.lookup(tokenValue); 238 | 239 | if (typeof value === 'object') { 240 | if (isArray(value)) { 241 | for (var j = 0, jlen = value.length; j < jlen; ++j) { 242 | buffer += renderTokens(token[4], writer, context.push(value[j]), template); 243 | } 244 | } else if (value) { 245 | buffer += renderTokens(token[4], writer, context.push(value), template); 246 | } 247 | } else if (typeof value === 'function') { 248 | var text = template == null ? null : template.slice(token[3], token[5]); 249 | value = value.call(context.view, text, function (template) { 250 | return writer.render(template, context); 251 | }); 252 | if (value != null) buffer += value; 253 | } else if (value) { 254 | buffer += renderTokens(token[4], writer, context, template); 255 | } 256 | 257 | break; 258 | case '^': 259 | value = context.lookup(tokenValue); 260 | 261 | // Use JavaScript's definition of falsy. Include empty arrays. 262 | // See https://github.com/janl/mustache.js/issues/186 263 | if (!value || (isArray(value) && value.length === 0)) { 264 | buffer += renderTokens(token[4], writer, context, template); 265 | } 266 | 267 | break; 268 | case '>': 269 | value = writer.getPartial(tokenValue); 270 | if (typeof value === 'function') buffer += value(context); 271 | break; 272 | case '&': 273 | value = context.lookup(tokenValue); 274 | if (value != null) buffer += value; 275 | break; 276 | case 'name': 277 | value = context.lookup(tokenValue); 278 | if (value != null) buffer += mustache.escape(value); 279 | break; 280 | case 'text': 281 | buffer += tokenValue; 282 | break; 283 | } 284 | } 285 | 286 | return buffer; 287 | } 288 | 289 | /** 290 | * Forms the given array of `tokens` into a nested tree structure where 291 | * tokens that represent a section have two additional items: 1) an array of 292 | * all tokens that appear in that section and 2) the index in the original 293 | * template that represents the end of that section. 294 | */ 295 | function nestTokens(tokens) { 296 | var tree = []; 297 | var collector = tree; 298 | var sections = []; 299 | 300 | var token; 301 | for (var i = 0, len = tokens.length; i < len; ++i) { 302 | token = tokens[i]; 303 | switch (token[0]) { 304 | case '#': 305 | case '^': 306 | sections.push(token); 307 | collector.push(token); 308 | collector = token[4] = []; 309 | break; 310 | case '/': 311 | var section = sections.pop(); 312 | section[5] = token[2]; 313 | collector = sections.length > 0 ? sections[sections.length - 1][4] : tree; 314 | break; 315 | default: 316 | collector.push(token); 317 | } 318 | } 319 | 320 | return tree; 321 | } 322 | 323 | /** 324 | * Combines the values of consecutive text tokens in the given `tokens` array 325 | * to a single token. 326 | */ 327 | function squashTokens(tokens) { 328 | var squashedTokens = []; 329 | 330 | var token, lastToken; 331 | for (var i = 0, len = tokens.length; i < len; ++i) { 332 | token = tokens[i]; 333 | if (token) { 334 | if (token[0] === 'text' && lastToken && lastToken[0] === 'text') { 335 | lastToken[1] += token[1]; 336 | lastToken[3] = token[3]; 337 | } else { 338 | lastToken = token; 339 | squashedTokens.push(token); 340 | } 341 | } 342 | } 343 | 344 | return squashedTokens; 345 | } 346 | 347 | function escapeTags(tags) { 348 | return [ 349 | new RegExp(escapeRegExp(tags[0]) + "\\s*"), 350 | new RegExp("\\s*" + escapeRegExp(tags[1])) 351 | ]; 352 | } 353 | 354 | /** 355 | * Breaks up the given `template` string into a tree of token objects. If 356 | * `tags` is given here it must be an array with two string values: the 357 | * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of 358 | * course, the default is to use mustaches (i.e. Mustache.tags). 359 | */ 360 | function parseTemplate(template, tags) { 361 | template = template || ''; 362 | tags = tags || mustache.tags; 363 | 364 | if (typeof tags === 'string') tags = tags.split(spaceRe); 365 | if (tags.length !== 2) throw new Error('Invalid tags: ' + tags.join(', ')); 366 | 367 | var tagRes = escapeTags(tags); 368 | var scanner = new Scanner(template); 369 | 370 | var sections = []; // Stack to hold section tokens 371 | var tokens = []; // Buffer to hold the tokens 372 | var spaces = []; // Indices of whitespace tokens on the current line 373 | var hasTag = false; // Is there a {{tag}} on the current line? 374 | var nonSpace = false; // Is there a non-space char on the current line? 375 | 376 | // Strips all whitespace tokens array for the current line 377 | // if there was a {{#tag}} on it and otherwise only space. 378 | function stripSpace() { 379 | if (hasTag && !nonSpace) { 380 | while (spaces.length) { 381 | delete tokens[spaces.pop()]; 382 | } 383 | } else { 384 | spaces = []; 385 | } 386 | 387 | hasTag = false; 388 | nonSpace = false; 389 | } 390 | 391 | var start, type, value, chr, token; 392 | while (!scanner.eos()) { 393 | start = scanner.pos; 394 | 395 | // Match any text between tags. 396 | value = scanner.scanUntil(tagRes[0]); 397 | if (value) { 398 | for (var i = 0, len = value.length; i < len; ++i) { 399 | chr = value.charAt(i); 400 | 401 | if (isWhitespace(chr)) { 402 | spaces.push(tokens.length); 403 | } else { 404 | nonSpace = true; 405 | } 406 | 407 | tokens.push(['text', chr, start, start + 1]); 408 | start += 1; 409 | 410 | // Check for whitespace on the current line. 411 | if (chr == '\n') stripSpace(); 412 | } 413 | } 414 | 415 | // Match the opening tag. 416 | if (!scanner.scan(tagRes[0])) break; 417 | hasTag = true; 418 | 419 | // Get the tag type. 420 | type = scanner.scan(tagRe) || 'name'; 421 | scanner.scan(whiteRe); 422 | 423 | // Get the tag value. 424 | if (type === '=') { 425 | value = scanner.scanUntil(eqRe); 426 | scanner.scan(eqRe); 427 | scanner.scanUntil(tagRes[1]); 428 | } else if (type === '{') { 429 | value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1]))); 430 | scanner.scan(curlyRe); 431 | scanner.scanUntil(tagRes[1]); 432 | type = '&'; 433 | } else { 434 | value = scanner.scanUntil(tagRes[1]); 435 | } 436 | 437 | // Match the closing tag. 438 | if (!scanner.scan(tagRes[1])) throw new Error('Unclosed tag at ' + scanner.pos); 439 | 440 | token = [type, value, start, scanner.pos]; 441 | tokens.push(token); 442 | 443 | if (type === '#' || type === '^') { 444 | sections.push(token); 445 | } else if (type === '/') { 446 | // Check section nesting. 447 | if (sections.length === 0) throw new Error('Unopened section "' + value + '" at ' + start); 448 | var openSection = sections.pop(); 449 | if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); 450 | } else if (type === 'name' || type === '{' || type === '&') { 451 | nonSpace = true; 452 | } else if (type === '=') { 453 | // Set the tags for the next time around. 454 | tags = value.split(spaceRe); 455 | if (tags.length !== 2) throw new Error('Invalid tags at ' + start + ': ' + tags.join(', ')); 456 | tagRes = escapeTags(tags); 457 | } 458 | } 459 | 460 | // Make sure there are no open sections when we're done. 461 | var openSection = sections.pop(); 462 | if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); 463 | 464 | tokens = squashTokens(tokens); 465 | 466 | return nestTokens(tokens); 467 | } 468 | 469 | mustache.name = "mustache.js"; 470 | mustache.version = "0.7.2"; 471 | mustache.tags = ["{{", "}}"]; 472 | 473 | mustache.Scanner = Scanner; 474 | mustache.Context = Context; 475 | mustache.Writer = Writer; 476 | 477 | mustache.parse = parseTemplate; 478 | 479 | // Export the escaping function so that the user may override it. 480 | // See https://github.com/janl/mustache.js/issues/244 481 | mustache.escape = escapeHtml; 482 | 483 | // All Mustache.* functions use this writer. 484 | var defaultWriter = new Writer(); 485 | 486 | /** 487 | * Clears all cached templates and partials in the default writer. 488 | */ 489 | mustache.clearCache = function () { 490 | return defaultWriter.clearCache(); 491 | }; 492 | 493 | /** 494 | * Compiles the given `template` to a reusable function using the default 495 | * writer. 496 | */ 497 | mustache.compile = function (template, tags) { 498 | return defaultWriter.compile(template, tags); 499 | }; 500 | 501 | /** 502 | * Compiles the partial with the given `name` and `template` to a reusable 503 | * function using the default writer. 504 | */ 505 | mustache.compilePartial = function (name, template, tags) { 506 | return defaultWriter.compilePartial(name, template, tags); 507 | }; 508 | 509 | /** 510 | * Compiles the given array of tokens (the output of a parse) to a reusable 511 | * function using the default writer. 512 | */ 513 | mustache.compileTokens = function (tokens, template) { 514 | return defaultWriter.compileTokens(tokens, template); 515 | }; 516 | 517 | /** 518 | * Renders the `template` with the given `view` and `partials` using the 519 | * default writer. 520 | */ 521 | mustache.render = function (template, view, partials) { 522 | return defaultWriter.render(template, view, partials); 523 | }; 524 | 525 | // This is here for backwards compatibility with 0.4.x. 526 | mustache.to_html = function (template, view, partials, send) { 527 | var result = mustache.render(template, view, partials); 528 | 529 | if (typeof send === "function") { 530 | send(result); 531 | } else { 532 | return result; 533 | } 534 | }; 535 | 536 | })); 537 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | flexibleSearch.js - jQuery plugin 2 | ========================= 3 | 4 | あらかじめ用意したJSONファイルを検索することにより超高速JavaScript検索を実現するjQueryプラグインです。 5 | **JSONファイルさえ用意できればどのようなサイトにでも導入することができます。** 6 | 7 | CMSの種類、静的なHTMLサイト、動的なPHPサイト、スマホサイトなど、様々なシーンでご利用ください。 8 | 9 | なお、Movable Type 6 で使う場合は、Data API にパラメータを渡して検索することもできます。検索は Data API で行い、ページングは flexibleSearch.js の機能を使うという方法が可能になります。 10 | 11 | ## 前バージョンとの違い 12 | 13 | 前バージョン(v1系)はURLにハッシュを付け、その検索条件をcookieに保存するなど少し特殊な方法でしたが、本バージョン(v2系)からは通常の検索と同様にURLにパラメータを付けてGETリクエストするような方法で検索します。 14 | 15 | したがって、前バージョンではやりにくかった下記の運用が可能です。 16 | 17 | - search.html など検索結果表示ページを用意する通常検索 18 | - 検索結果ページをブックマークさせる 19 | - 特定の検索条件のページにリンクを貼る 20 | 21 | ## 使い方 22 | 23 | ### ファイルのアップロードとHTMLの準備 24 | 25 | サーバー上のドメインルートに下記のファイルをアップロードします。アップロード先を変更する場合は、適宜置き換えてください。 26 | 27 | * /flexibleSearch/flexibleSearch.js 28 | * /flexibleSearch/flexibleSearch-config.js 29 | * /flexibleSearch/mustache.js 30 | * /flexibleSearch/loading.gif 31 | 32 | mustache.jsはHTMLを簡単に書き出せるJavaScript用のテンプレートエンジンです。 33 | 34 | * [janl/mustache.js](https://github.com/janl/mustache.js) 35 | 36 | ### HTMLの用意 37 | 38 | 検索結果を表示するHTMLページを用意し、jQuery、mustache.js、flexibleSearch.js、flexibleSearch-config.jsの順番で読み込みます。 39 | 40 | ここでは以下の様に、サイトのトップページとしてindex.htmlを、検索結果表示ページとしてsearch.htmlを用意します。 41 | 42 | なお、flexibleSearch-config.jsはflexibleSearchを実行するための設定を書くファイルです。flexibleSearch.jsより後ろに位置していれば、scriptタグでHTMLファイルに直接書いても、他のJavaScriptファイルに書いても構いません。 43 | 44 | #### index.html 45 | 46 | ``` 47 | 48 | 49 | 50 | 51 | flexibleSearch.js 52 | 53 | 54 |

flexibleSearch.js

55 |

あらかじめ用意したJSONファイルを検索することにより超高速JavaScript検索を実現するjQueryプラグインです。

56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ``` 65 | 66 | #### search.html 67 | 68 | ``` 69 | 70 | 71 | 72 | 73 | flexibleSearch.js 74 | 75 | 76 |

検索結果

77 | 78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ``` 87 | 88 | ### JSONファイルの用意 89 | 90 | 検索用のJSONファイルを用意します。JSONの書式は以下の通りです。 91 | 92 | ``` 93 | {"items": [ 94 | {"title": "タイトル", "contents": "コンテンツ", ...(省略)... }, 95 | {"title": "タイトル", "contents": "コンテンツ", ...(省略)... } 96 | ]} 97 | ``` 98 | 99 | ルートのitemsは必須です。その値に、各記事ごとのオブジェクトが入った配列並べるスタイルになります。 100 | 101 | なお、Movable Typeで記事とウェブページに関するJSONを書き出すときのテンプレートは以下の様になります。カスタムフィールド等も検索対象にする場合は適宜加えてください。 102 | 103 | #### Movable TypeのインデックステンプレートでJSONを書き出すテンプレート 104 | 105 | ``` 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | ,, 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | ,, 123 | 124 | 125 | 126 | {"items":[ 127 | 128 | ]} 129 | 130 | ``` 131 | 132 | 検索対象とするブログを特定する場合は `mt:Entries` タグや `mt:Pages` タグの `include_blogs` モディファイアを調整してください( [include_blogs](http://www.movabletype.jp/documentation/mt5/design/multiblog/tags.html#blogids) )。 133 | 134 | ### flexibleSearchの実行 135 | 136 | flexibleSearch-config.jsにオプション等を記述してflexibleSearchを実行します。flexibleSearchはform要素を包含するdiv等のブロック要素に適用します。 137 | 138 | 必須のオプションはケースによって異なりますが、searchDataPath、searchFormAction、loadingImgPathは指定しておくと良いでしょう。 139 | 各オプションの説明は後述します。 140 | 141 | ``` 142 | jQuery(function($) { 143 | $('#search').flexibleSearch({ 144 | searchDataPath: "/flexibleSearch/search.json", 145 | searchFormAction: "/flexibleSearch/search.html", 146 | loadingImgPath: "/flexibleSearch/loading.gif", 147 | dummy: null 148 | }); 149 | }); 150 | ``` 151 | 152 | flexibleSearch-config.jsに書く内容は以下のとおりです。flexibleSearch-config.jsには使用できるオプションがコメントアウトされているので、必要に応じてコメントを外して設定を変更してください。 153 | 154 | dummy: null は不要ですが、最後のカンマを入れたり入れなかったり変更するのが面倒な場合は最終行に入れておくと良いかもしれません :-) 155 | 156 | 設定できるオプションは後述します。 157 | 158 | ## 検索結果のソート (version added: 2.2.0) 159 | 160 | 検索結果の並び順は、デフォルトの状態だとJSONの並び順に準拠します。 161 | 162 | この並び順を``sortBy``、``sortOrder``、``sortType``の3つのURLパラメータを使って並べ替えることができます。この機能はv2.2.0で追加されました。 163 | 164 | ### sortBy 165 | 166 | 並び替えの基準となるキーを指定します。並べ替える場合は必須です。 167 | 168 | ### sortOrder 169 | 170 | 並び順を``ascend``(昇順)または``descend``(降順)で指定します。初期値は``descend``です。 171 | 172 | ### sortType 173 | 174 | 数値として並び変える場合は``numeric``を、文字列として並び変える場合は``string``を指定します。初期値は``string``です。 175 | 176 | ## オプション 177 | 178 | 指定出来るオプションは以下の通りです。詳細は後述します。 179 | なお、下記の説明に出てくる「Data API」とは、Movable Type 6 から実装された [Data API](http://www.movabletype.jp/documentation/mt6/developer/movable-type-api.html) のことを指します。Movable Type 以外で flexibleSearch.js を利用する場合は読み飛ばしていただいて構いません。 180 | 181 | | オプション名 | 設定値 | 初期値 | 説明 | 182 | |:--|:--|:--|:--| 183 | | [limit](#limit) | Number | null | limit オプションを指定すると URL の limit パラメータは無視され、ここで指定した値が常に優先されます。この `limit` オプションも URL パラメータの `limit` パラメータもどちらも指定がない場合は `10` がセットされた状態となります。
(Add: v2.2.0) | 184 | | [searchDataPath](#searchDataPath) | String
Object | "/flexibleSearch/search.json" | flexibleSearchで検索対象とするJSONファイルのパスを指定します。文字列で1つ指定する方法と、オブジェクトで複数指定する方法があります。 | 185 | | [searchDataPathPreload](#searchDataPathPreload) | String
Array
Object | "/flexibleSearch/search.json" | 検索実行ページ以外で、検索対象とするJSONファイルをあらかじめ読み込んでおきキャッシュすることができます。文字列で1つ指定する方法と、配列またはオブジェクトで複数指定する方法があります。 | 186 | | [dataApiDataIds](#dataApiDataIds) | String | null | MTのData APIを利用するdataIdを指定します。複数ある場合はカンマ区切りで指定します。dataIdとは、searchDataPathオプションをオブジェクトで指定した場合のプロパティ名のことを指します。 | 187 | | [dataApiParams](#dataApiParams) | Object | null | Data APIを利用する場合に、検索フォームとは別にエンドポイントに渡すパラメータを設定できます。 | 188 | | [cache](#cache) | Boolean | true | JSONファイルをキャッシュするかどうかを指定します。 | 189 | | [searchFormCreation](#searchFormCreation) | Boolean | true | 検索フォームをJavaScriptで書き出すかどうかを設定します。ここでfalesを設定すれば、HTMLに書かれた静的なフォームを利用することができます。ただし、必須のname項目があります。 | 190 | | [searchFormHTML](#searchFormHTML) | String | null | JavaScriptで書き出す検索フォームをHTML文字列で設定する場合に使用します。 | 191 | | [searchFormAction](#searchFormAction) | String | (空文字) | form要素のaction属性を指定します。検索結果ページを用意する場合は必ず指定してください。 | 192 | | [searchFormInputType](#searchFormInputType) | String | "search" | form要素のキーワード入力欄のtype属性を指定します。 | 193 | | [searchFormInputPlaceholder](#searchFormInputPlaceholder) | String | "Search words" | form要素のキーワード入力欄に入れるplaceholder属性を指定します。 | 194 | | [searchFormSubmitBtnText](#searchFormSubmitBtnText) | String | "Search" | form要素の検索実行ボタンのテキストを指定します。 | 195 | | [advancedFormObj](#advancedFormObj) | String | null | advancedFormObjオプションにオブジェクトを設定することでキーワード入力欄以外のフォーム要素を簡単に作成できます。 | 196 | | [advancedSearchCond](#advancedSearchCond) | String | "OR" | searchパラメータ以外の検索項目の検索条件を"OR"検索か"AND"検索か指定します。 | 197 | | [loadingImgPath](#loadingImgPath) | String | "/flexibleSearch/loading.gif" | ローディング画像のパスを指定します。 | 198 | | [loadingImgHTML](#loadingImgHTML) | String | null | ローディング画像を直接HTMLで指定することができます。このオプションを指定した場合はloadingImgPathオプションの設定は無視されます。 | 199 | | [resultBlockId](#resultBlockId) | String | "fs-result" | 検索結果やローディング画像入れるブロック要素のIDを指定します。 | 200 | | [resultItemTmpl](#resultItemTmpl) | String | null | 検索結果を表示するMustacheテンプレートです。 | 201 | | [resultMsgId](#resultMsgId) | String | null | 検索結果のメッセージを表示する要素のidを指定します。
(Add: v2.2.0) | 202 | | [resultMsgClassName](#resultMsgClassName) | String | "fs-result-msg" | 検索結果のメッセージを表示する要素のclass名を指定します。
(Add: v2.2.0) | 203 | | [resultMsgTmpl](#resultMsgTmpl) | String | null | 検索結果のメッセージを表示するMustacheテンプレートです。 | 204 | | [resultMsgInsertMethods](#resultMsgInsertMethods) | Array | null | 検索結果のメッセージを表示する要素のセレクタと挿入方法を指定します。
(Add: v2.2.0) | 205 | | [resultMetaTitleTmpl](#resultMetaTitleTmpl) | String | null | 検索結果ページのmeta title用のMustacheテンプレートです。
(Add: v2.2.0) | 206 | | [paginateId](#paginateId) | String | null | 検索結果のページ送りを表示するブロックのIDを指定します。 | 207 | | [paginateClassName](#paginateClassName) | String | "fs-paginate" | 検索結果のページ送りを表示するブロックのclass名を指定します。
(Add: v2.2.0) | 208 | | [paginateTmpl](#paginateTmpl) | String | null | 検索結果が複数ページにわたる場合のページ送りを表示するMustacheテンプレートです。 | 209 | | [paginateCount](#paginateCount) | Number | 10 | 1ページに表示する件数をしていします。この値がlimitパラメータになります。 | 210 | | [hidePageNumber](#hidePageNumber) | Boolean | false | trueを設定するとページ分割のページ番号を非表示にします。
(Add: v2.2.0) | 211 | | [showTurnPage](#showTurnPage) | Boolean | true | falseを設定するとページ分割の「Prev」「Next」のページ送りを非表示にします。
(Add: v2.2.0) | 212 | | [prevPageText](#prevPageText) | String | "Prev" | ページ分割の前のページへ送るリンクのテキストを指定します。
(Add: v2.2.0) | 213 | | [nextPageText](#nextPageText) | String | "Next" | ページ分割の次のページへ送るリンクのテキストを指定します。
(Add: v2.2.0) | 214 | | [maxPageCount](#maxPageCount) | Number | 10 | ページ分割時に表示する最大ページ数を指定します。例えば、maxPageCountオプションを10に設定して、検索結果が全部で30ページになったとすると、そのうちの、現在のページを中心にして最大何ページ表示するか、という意味です。
(Add: v2.2.0) | 215 | | [paginateInsertMethods](#paginateInsertMethods) | Array | null | ページ分割ナビゲーションを表示する要素のセレクタと挿入方法を指定します。
(Add: v2.2.0) | 216 | | [initialParameter](#initialParameter) | String | null | flexibleSearch を適用しているページにパラメータが無い場合でも、flexibleSearch を動かすためのパラメータを設定することができます。
(Add: v2.2.3) | 217 | | [resultMetaTitleRewrite](#resultMetaTitleRewrite) | Boolean | true | メタタイトルの書き換えを無効ににできます。
(Add: v2.2.3) | 218 | | [submitAction](#submitAction) | Function | function (paramArray) { return paramArray; } | フォームがsubmitされ、ページが遷移する前に呼ばれる関数を設定できます。この関数にはシリアライズされたパラメータの配列paramArrayが渡されます。 | 219 | | [ajaxError](#ajaxError) | Function | function (jqXHR, textStatus, errorThrown) { window.alert(textStatus); } | jQuery.ajaxでエラーが起きたときに呼ばれる関数を設定できます。 | 220 | | [customSearch](#customSearch) | Function | null | 独自の検索ロジックを追加することができます。
(Add: v2.2.0) | 221 | | [customSort](#customSort) | Function | null | `sortBy` パラメータとは別に複雑なソート処理などを加えることができます。
(Add: v2.2.3) | 222 | | [modifyResultJSON](#modifyResultJSON) | Function | null | 検索結果をHTMLに出力する直前にJSONを加工することができます。
(Add: v2.2.0) | 223 | | [modifyResultMsgHTML](#modifyResultMsgHTML) | Function | null | 検索結果メッセージのHTMLを加工することができます。 | 224 | | [modifyResultItemHTML](#modifyResultItemHTML) | Function | null | 検索結果一覧のHTMLを加工することができます。 | 225 | | [modifyPaginateHTML](#modifyPaginateHTML) | Function | null | ページ分割のHTMLを加工することができます。 | 226 | | [resultComplete](#resultComplete) | Function | null | 検索結果をページのDOMに挿入した後に呼ばれる関数を設定します。 | 227 | | [excludeParams](#excludeParams) | String | null | パラメータのうち検索から除外する項目をカンマ区切りで指定します。 | 228 | | [excludeSearchParams](#excludeSearchParams) | String | null | JSON の項目のうちキーワード検索の対象から除外する項目をカンマ区切りで指定します。 | 229 | 230 | ### limit (version added: 2.2.0) 231 | 232 | limitオプションを指定するとURLのlimitパラメータは無視され、ここで指定した値が常に優先されます。このオプションはv2.2.0で追加されました。 233 | 234 | なお、この `limit` オプションと URL パラメータの `limit` パラメータのどちらも指定がない場合は `limit` は `10` がセットされた状態となります。 235 | 236 | searchFormCreationオプションがtrueのときに書き出されるフォームのlimit値、つまりlimitパラメータの値は [paginateCount](#paginateCount) で指定してください。 237 | 238 | **設定例** 239 | 240 | ``` 241 | limit: 20, 242 | ``` 243 | 244 | ### searchDataPath 245 | 246 | flexibleSearchで検索対象とするJSONファイルのパスを指定します。文字列で1つ指定する方法と、オブジェクトで複数指定する方法があります。 247 | 248 | **設定例** 249 | 250 | ``` 251 | loadingImgPath: "/flexibleSearch/loading.gif", 252 | 253 | または 254 | 255 | searchDataPath: { 256 | static: "/flexibleSearch/search_data.js", 257 | entries: "/mt/mt-data-api.cgi/v1/sites/1/entries" 258 | }, 259 | ``` 260 | 261 | searchDataPathオプションをオブジェクトで指定した場合は、どのJSONを検索対象とするのかをdataIdパラメータで指定します。 262 | 263 | 例えば、上記の例で言えば、下記のようなラジオボタンを設置して検索対象を切り替える方法があります。 264 | 265 | ``` 266 |

267 | 268 | 269 |

270 | ``` 271 | 272 | 例えば検索カテゴリを必ず選択させるといった検索方法の場合、カテゴリごとにJSONファイルを用意して、dataIdパラメータで検索対象のJSONを切り替えれば、1つのJSONファイルのファイルサイズを減らすことができます。 273 | 274 | ### searchDataPathPreload 275 | 276 | 検索実行ページ以外で、検索対象とするJSONファイルをあらかじめ読み込んでおきキャッシュすることができます。文字列で1つ指定する方法と、配列またはオブジェクトで複数指定する方法があります。 277 | 278 | **設定例** 279 | 280 | ``` 281 | loadingImgPath: "/flexibleSearch/loading.gif", 282 | 283 | または 284 | 285 | searchDataPath: [ 286 | "/flexibleSearch/search_data.js", 287 | "/mt/mt-data-api.cgi/v1/sites/1/entries" 288 | ], 289 | 290 | または 291 | 292 | searchDataPath: { 293 | static: "/flexibleSearch/search_data.js", 294 | entries: "/mt/mt-data-api.cgi/v1/sites/1/entries" 295 | }, 296 | ``` 297 | 298 | ### dataApiDataIds 299 | 300 | MTのData APIを利用するdataIdを指定します。複数ある場合はカンマ区切りで指定します。dataIdとは、searchDataPathオプションをオブジェクトで指定した場合のプロパティ名のことを指します。 301 | 302 | **設定例** 303 | 304 | ``` 305 | dataApiDataIds: "entries,categories", 306 | ``` 307 | 308 | ### dataApiParams 309 | 310 | Data APIを利用する場合に、検索フォームとは別にエンドポイントに渡すパラメータを設定できます。 311 | 312 | **設定例** 313 | 314 | ``` 315 | dataApiParams: { 316 | fields: "title,keywords", 317 | searchFields: "title,body,keywords" 318 | }, 319 | ``` 320 | 321 | ### cache 322 | 323 | JSONファイルをキャッシュするかどうかを指定します。 324 | 325 | **設定例** 326 | 327 | ``` 328 | cache: false, 329 | ``` 330 | 331 | ### searchFormCreation 332 | 333 | 検索フォームをJavaScriptで書き出すかどうかを設定します。ここでfalesを設定すれば、HTMLに書かれた静的なフォームを利用することができます。ただし、以下のname値を持つ要素は必須です。 334 | 335 | * search 336 | * offset 337 | * limit(limitオプションを指定した場合は不要) 338 | 339 | **設定例** 340 | 341 | ``` 342 | searchFormCreation: false, 343 | ``` 344 | 345 | ### searchFormHTML 346 | 347 | JavaScriptで書き出す検索フォームをHTML文字列で設定する場合に使用します。 348 | 349 | **設定例** 350 | 351 | ``` 352 | searchFormHTML: [ 353 | '
', 354 | '', 355 | '', 356 | '', 357 | '', 358 | '', 359 | '', 360 | '
' 361 | ].join(""), 362 | ``` 363 | 364 | ### searchFormAction 365 | 366 | form要素のaction属性を指定します。検索結果ページを用意する場合は必ず指定してください。 367 | 368 | **設定例** 369 | 370 | ``` 371 | searchFormAction: "search.html", 372 | ``` 373 | 374 | ### searchFormInputType 375 | 376 | form要素のキーワード入力欄のtype属性を指定します。 377 | 378 | **設定例** 379 | 380 | ``` 381 | searchFormInputType: "text", 382 | ``` 383 | 384 | ### searchFormInputPlaceholder 385 | 386 | form要素のキーワード入力欄に入れるplaceholder属性を指定します。 387 | 388 | **設定例** 389 | 390 | ``` 391 | searchFormInputPlaceholder: "キーワードを入力", 392 | ``` 393 | 394 | ### searchFormSubmitBtnText 395 | 396 | form要素の検索実行ボタンのテキストを指定します。 397 | 398 | **設定例** 399 | 400 | ``` 401 | searchFormSubmitBtnText: "検索", 402 | ``` 403 | 404 | ### advancedFormObj 405 | 406 | searchFormCreationオプションがtrueのとき、advancedFormObjオプションにオブジェクトを設定することでキーワード入力欄以外のフォーム要素を作成できます。 407 | 408 | このオプションでは以下の要素を書き出すことができます。 409 | 410 | * input:hidden 411 | * input:text 412 | * input:checkbox 413 | * input:radio 414 | * select 415 | 416 | 基本的な設定方法は以下の書式になります。 417 | 418 | ``` 419 | advancedFormObj: { 420 | 要素タイプ: [ 421 | {属性名: "属性値", 属性名: "属性値" ... }, 422 | ...(いくつでも設定できます) 423 | ] 424 | } 425 | ``` 426 | 427 | 属性値を空にするか、属性名の指定をしないものは、その属性自体が書き出されなくなります。 428 | 429 | 詳細は下記の個別項目を参照してください。なお、「HTML出力例」は実際には改行なしの1行になります。 430 | 431 | #### input:hidden要素 432 | 433 | **設定例** 434 | 435 | ``` 436 | advancedFormObj: { 437 | hidden: [ 438 | {id: "id値", name: "name値", value: "value値"}, 439 | ...(いくつでも設定できます) 440 | ] 441 | } 442 | ``` 443 | 444 | **HTML出力例** 445 | 446 | ``` 447 | 450 | ``` 451 | 452 | #### input:text要素 453 | 454 | **設定例** 455 | 456 | ``` 457 | advancedFormObj: { 458 | text: [ 459 | {id: "id値", name: "name値", value: "value値", placeholder: "placeholder値", label: "label値"}, 460 | {id: "id値", name: "name値", value: "value値", placeholder: "", label: ""}, 461 | ...(いくつでも設定できます) 462 | ] 463 | } 464 | ``` 465 | 466 | **HTML出力例** 467 | 468 | ``` 469 |
470 | 474 | 475 |
476 | ``` 477 | 478 | #### input:checkbox要素 479 | 480 | **設定例** 481 | 482 | ``` 483 | advancedFormObj: { 484 | checkbox: [ 485 | {id: "id値", name: "name値", value: "value値", label: "label値"}, 486 | ...(いくつでも設定できます) 487 | ] 488 | } 489 | ``` 490 | 491 | **HTML出力例** 492 | 493 | ``` 494 |
495 | 499 |
500 | ``` 501 | 502 | #### input:radio要素 503 | 504 | **設定例** 505 | 506 | ``` 507 | advancedFormObj: { 508 | radio: [ 509 | {id: "id値", name: "name値", value: "value値", label: "label値"}, 510 | ...(いくつでも設定できます) 511 | ] 512 | } 513 | ``` 514 | 515 | **HTML出力例** 516 | 517 | ``` 518 |
519 | 523 |
524 | ``` 525 | 526 | #### select要素 527 | 528 | **設定例** 529 | 530 | ``` 531 | advancedFormObj: { 532 | select: [ 533 | {id: "id値", name: "name値", size: "", multiple: "", option: [ 534 | {label: "選択してください", value: ""}, 535 | {label: "opt_label1", value: "opt_value1"}, 536 | {label: "opt_label2", value: "opt_value2"}, 537 | {label: "opt_label3", value: "opt_value3"} 538 | ]}, 539 | ...(いくつでも設定できます) 540 | ] 541 | } 542 | ``` 543 | 544 | **HTML出力例** 545 | 546 | ``` 547 |
548 | 554 |
555 | ``` 556 | 557 | **advancedFormObj全体の設定例** 558 | 559 | ``` 560 | advancedFormObj: { 561 | hidden: [ 562 | {id: "id値", name: "name値1", value: "value値"} 563 | ], 564 | text: [ 565 | {id: "id値1", name: "name値2", value: "value値1", placeholder: "placeholder値1", label: "label値1"}, 566 | {id: "id値2", name: "name値3", value: "value値2", placeholder: "placeholder値2", label: "label値2"} 567 | ], 568 | checkbox: [ 569 | {id: "id値1", name: "name値4", value: "value値1", label: "label値1"}, 570 | {id: "id値2", name: "name値5", value: "value値2", label: "label値2"} 571 | ], 572 | radio: [ 573 | {id: "id値1", name: "name値6", value: "value値1", label: "label値1"}, 574 | {id: "id値2", name: "name値6", value: "value値2", label: "label値2"} 575 | ], 576 | select: [ 577 | {id: "id値1", name: "name値7", size: "", multiple: "", option: [ 578 | {label: "選択してください", value: ""}, 579 | {label: "opt_label1", value: "opt_value1"}, 580 | {label: "opt_label2", value: "opt_value2"}, 581 | {label: "opt_label3", value: "opt_value3"} 582 | ]}, 583 | {id: "id値2", name: "name値8", size: "3", multiple: "multiple", option: [ 584 | {label: "opt_label1", value: "opt_value1"}, 585 | {label: "opt_label2", value: "opt_value2"}, 586 | {label: "opt_label3", value: "opt_value3"} 587 | ]} 588 | ] 589 | }, 590 | ``` 591 | 592 | ### advancedSearchCond 593 | 594 | searchパラメータ以外の検索項目の検索条件を"OR"検索か"AND"検索か指定します。 595 | 596 | **設定例** 597 | 598 | ``` 599 | advancedSearchCond: "AND", 600 | ``` 601 | 602 | ### loadingImgPath 603 | 604 | ローディング画像のパスを指定します。 605 | 606 | **設定例** 607 | 608 | ``` 609 | loadingImgPath: "/loading.gif", 610 | ``` 611 | 612 | loadingImgPathを指定すると、自動的に次のようなHTMLが検索結果表示ブロックの中に書き出されます。 613 | なお、検索結果表示ブロックの中身はappendやprependではなくinnerHTMLでまるごと書き換わるので注意してください。 614 | 615 | ``` 616 | 617 | ``` 618 | 619 | このHTMLを変更する場合は、次のloadingImgHTMLオプションを指定してください。 620 | 621 | ### loadingImgHTML 622 | 623 | ローディング画像を直接HTMLで指定することができます。このオプションを指定した場合はloadingImgPathオプションの設定は無視されます。 624 | 625 | **設定例** 626 | 627 | ``` 628 | loadingImgHTML: '読み込み中', 629 | ``` 630 | 631 | ### resultBlockId 632 | 633 | 検索結果やローディング画像入れるブロック要素のIDを指定します。 634 | 635 | **設定例** 636 | 637 | ``` 638 | resultBlockId: "contents-inner", 639 | ``` 640 | 641 | ### resultItemTmpl 642 | 643 | 検索結果を表示するMustacheテンプレートです。このオプションを指定しない場合は、次のテンプレートが使用されます。 644 | 645 | ``` 646 |
647 |
    648 | {{#items}} 649 |
  • {{&title}}
  • 650 | {{/items}} 651 |
652 |
653 | ``` 654 | 655 | {{#items}}〜{{/items}}で囲まれている部分が検索結果件の数だけループし、その中の{{項目名}}の部分はitemsのプロパティ名を指定します。 656 | 657 | Mustacheテンプレートの書き方は [janl/mustache.js](https://github.com/janl/mustache.js) を参照してください。 658 | 659 | **設定例** 660 | 661 | ``` 662 | resultItemTmpl: [ 663 | '
', 664 | '
    ', 665 | '{{#items}}', 666 | '
  • {{&title}}
  • ', 667 | '{{/items}}', 668 | '
', 669 | '
' 670 | ].join(""), 671 | ``` 672 | 673 | ### resultMsgId (version added: 2.2.0) 674 | 675 | 検索結果のメッセージをデフォルトのテンプレートで利用する場合のdiv要素のid名を指定します。このオプションはv2.2.0で追加されました。 676 | 677 | もしそれ以前のバージョンのデフォルトテンプレートと揃えたい場合は、このオプションに `fs-result-msg` を指定してください。 678 | 679 | **設定例** 680 | 681 | ``` 682 | resultMsgId: "fs-result-msg", 683 | ``` 684 | 685 | ### resultMsgClassName (version added: 2.2.0) 686 | 687 | 検索結果のメッセージをデフォルトのテンプレートで利用する場合のdiv要素のclass名を指定します。このオプションはv2.2.0で追加されました。 688 | 689 | **設定例** 690 | 691 | ``` 692 | resultMsgClassName: "fs-result-msg", 693 | ``` 694 | 695 | ### resultMsgTmpl 696 | 697 | 検索結果の上部に表示するメッセージのMustacheテンプレートです。このオプションを指定しない場合は、次のテンプレートが使用されます。 698 | 699 | ``` 700 | 701 |

702 | {{#keywords}}「{{keywords}}」が {{/keywords}} 703 | {{#count}}{{count}} 件見つかりました。{{/count}} 704 | {{^count}}見つかりませんでした。{{/count}} 705 | {{#count}}({{lastPage}} ページ中 {{currentPage}} ページ目を表示){{/count}} 706 |

707 | 708 | ``` 709 | 710 | {{項目名}}の部分は適宜該当する項目に置き換わりますので、resultMsgTmplオプションを指定する場合は、上記を参考に{{項目名}}を入れてください。 711 | 712 | **設定例** 713 | 714 | ``` 715 | resultMsgTmpl: [ 716 | '
', 717 | '

{{#keywords}}「{{keywords}}」が {{/keywords}}{{count}} 件見つかりました。', 718 | '({{firstPage}}〜{{lastPage}} ページ中 {{currentPage}} ページ目を表示)

', 719 | '
' 720 | ].join(""), 721 | ``` 722 | 723 | Mustacheテンプレートの書き方は[janl/mustache.js](https://github.com/janl/mustache.js)を参照してください。 724 | 725 | ### resultMsgInsertMethods (version added: 2.2.0) 726 | 727 | 検索結果のメッセージを表示する場所を指定します。このオプションはv2.2.0で追加されました。 728 | 729 | このオプションを指定しない場合は、検索結果一覧の上部にメッセージが表示されます。 730 | 731 | このオプションでは、下記の設定例のようにselecterとmethodの2つのキーを持つオブジェクトで指定します。methodに指定出来るのは、jQueryのappend、prependなど、DOMに挿入したりHTMLを書き換えたりするメソッドです。 732 | 733 | 下記のようにページの上部と下部など、異なるセレクタで複数箇所に挿入することもできます。 734 | 735 | **設定例** 736 | 737 | ``` 738 | resultMsgInsertMethods: [ 739 | { 740 | "selector": "#page-title", 741 | "method": "html" 742 | }, 743 | { 744 | "selector": "div.search-message", 745 | "method": "append" 746 | } 747 | ], 748 | ``` 749 | 750 | ### resultMetaTitleTmpl (version added: 2.2.0) 751 | 752 | 検索結果ページのmeta title用のMustacheテンプレートです。このオプションを指定しない場合は下記のテンプレートが使用されます(実際には1行になります)。このオプションはv2.2.0で追加されました。 753 | 754 | ``` 755 | {{#keywordArray}}{{.}} {{/keywordArray}} 756 | {{#count}} {{count}}件{{/count}} 757 | {{#count}} {{currentPage}}/{{lastPage}}{{/count}} 758 | {{#metaTitle}} | {{metaTitle}}{{/metaTitle}} 759 | ``` 760 | 761 | このテンプレートで利用している `{{#keywordArray}}{{.}} {{/keywordArray}}` については、 762 | キーワードが複数のときは `{{.}}` 部分にキーワードが入り、 `{{#keywordArray}}` と `{{/keywordArray}}` の内部がキーワード数分繰り返されます。 763 | 764 | また `{{#keywords}}{{keywords}}{{/keywords}}` とすると、キーワードが複数のときは `, ` でキーワードが区切られたテキストになります。 765 | 766 | **設定例** 767 | 768 | ``` 769 | resultMetaTitleTmpl: "{{#keywordArray}}{{.}} {{/keywordArray}}の検索結果", 770 | ``` 771 | 772 | ### paginateId 773 | 774 | 検索結果のページ送りを表示するブロックのIDを指定します。 775 | 776 | **設定例** 777 | 778 | ``` 779 | paginateId: "paginate", 780 | ``` 781 | 782 | ### paginateClassName (version added: 2.2.0) 783 | 784 | 検索結果のページ送りを表示するブロックのclass名を指定します。このオプションはv2.2.0で追加されました。 785 | 786 | **設定例** 787 | 788 | ``` 789 | paginateClassName: "fs-paginate", 790 | ``` 791 | 792 | ### paginateTmpl 793 | 794 | 検索結果が複数ページにわたる場合のページ送りを表示するMustacheテンプレートです。このオプションを指定しない場合は、次のテンプレートが使用されます。 795 | 796 | ``` 797 | 798 |
    799 | {{#showTurnPage}} 800 | {{#exceptFirst}} 801 |
  • {{prevPageText}}
  • 802 | {{/exceptFirst}} 803 | {{/showTurnPage}} 804 | 805 | {{#page}} 806 | {{#checkRange}} 807 | 808 | {{/checkRange}} 809 | {{/page}} 810 | 811 | {{#showTurnPage}} 812 | {{#exceptLast}} 813 |
  • {{nextPageText}}
  • 814 | {{/exceptLast}} 815 | {{/showTurnPage}} 816 |
817 | 818 | ``` 819 | 820 | {{#page}}〜{{/page}}で囲まれている内部がページ数分ループします。{{current}}はカレントページの時に``fs-current``が出力されます。{{pageNumber}}がページ番号です。paginateHTMLオプションでHTMLを指定する場合は、上記HTMLと同様に{{項目名}}の各項目を入れてください。 821 | 822 | テンプレートの書き方は[janl/mustache.js](https://github.com/janl/mustache.js)を参照してください。 823 | 824 | **設定例** 825 | 826 | ``` 827 | paginateTmpl: [ 828 | '
', 829 | '
    ', 830 | '{{#page}}', 831 | '{{.}}', 832 | '{{/page}}', 833 | '
', 834 | '
' 835 | ].join(""), 836 | ``` 837 | 838 | ### paginateCount 839 | 840 | 1ページに表示する件数をしていします。この値がlimitパラメータになります。 841 | 842 | 前述した [limit](#limit) オプションに値を設定した場合は、このpaginateCountオプションは無視されます。 843 | 844 | **設定例** 845 | 846 | ``` 847 | paginateCount: 20, 848 | ``` 849 | 850 | ### hidePageNumber (version added: 2.2.0) 851 | 852 | trueを設定するとページ分割のページ番号を非表示にします。このオプションはv2.2.0で追加されました。 853 | 854 | **設定例** 855 | 856 | ``` 857 | hidePageNumber: true, 858 | ``` 859 | 860 | ### showTurnPage (version added: 2.2.0) 861 | 862 | falseを設定するとページ分割の「前へ」「次へ」のページ送りを非表示にします。このオプションはv2.2.0で追加されました。 863 | 864 | **設定例** 865 | 866 | ``` 867 | showTurnPage: false, 868 | ``` 869 | 870 | ### prevPageText (version added: 2.2.0) 871 | 872 | ページ分割の前のページへ送るリンクのテキストを指定します。このオプションはv2.2.0で追加されました。 873 | 874 | **設定例** 875 | 876 | ``` 877 | prevPageText: "前のページへ", 878 | ``` 879 | 880 | ### nextPageText (version added: 2.2.0) 881 | 882 | ページ分割の次のページへ送るリンクのテキストを指定します。このオプションはv2.2.0で追加されました。 883 | 884 | **設定例** 885 | 886 | ``` 887 | nextPageText: "Next", 888 | ``` 889 | ### maxPageCount (version added: 2.2.0) 890 | 891 | ページ分割時に表示する最大ページ数を指定します。このオプションはv2.2.0で追加されました。 892 | 893 | 例えば、maxPageCountオプションを10に設定して、検索結果が全部で30ページになったとすると、そのうちの、現在のページを中心にして最大何ページ表示するか、という意味です。 894 | 895 | **設定例** 896 | 897 | ``` 898 | maxPageCount: 5, 899 | ``` 900 | 901 | ### paginateInsertMethods (version added: 2.2.0) 902 | 903 | ページ分割ナビゲーションを表示する要素のセレクタと挿入方法を指定します。このオプションはv2.2.0で追加されました。 904 | 905 | このオプションを指定しない場合は、検索結果一覧の下部にナビゲーションが表示されます。 906 | 907 | このオプションでは、下記の設定例のようにselecterとmethodの2つのキーを持つオブジェクトで指定します。methodに指定出来るのは、jQueryのappend、prependなど、DOMに挿入したりHTMLを書き換えたりするメソッドです。 908 | 909 | 下記のようにページの上部と下部など、異なるセレクタで複数箇所に挿入することもできます。 910 | 911 | **設定例** 912 | 913 | ``` 914 | paginateInsertMethods: [ 915 | { 916 | "selector": "#content-header", 917 | "method": "append" 918 | }, 919 | { 920 | "selector": "div.paginate", 921 | "method": "html" 922 | } 923 | ], 924 | ``` 925 | 926 | ### initialParameter (version added: 2.2.3) 927 | 928 | flexibleSearch を適用しているページにパラメータが無い場合でも、flexibleSearch を動かすためのパラメータを設定することができます。 929 | 930 | 例えば、通常は `search.html` のようにパラメータが付いていないと flexibleSearch.js は動きませんが、下記のように `initialParameter` オプションを設定すると、 `search.html` は `search.html?limit=10&offset=0` にアクセスしているのと同様に flexibleSearch を動かすことができます。 931 | 932 | **設定例** 933 | 934 | ``` 935 | initialParameter: 'limit=10&offset=0' 936 | ``` 937 | 938 | ### resultMetaTitleRewrite (version added: 2.2.3) 939 | 940 | `resultMetaTitleRewrite` オプションに `false` を設定すると、flexibleSearch.js によるメタタイトルの書き換えが無効になります。 941 | 942 | ### submitAction 943 | 944 | フォームがsubmitされ、ページが遷移する前に呼ばれる関数を設定できます。この関数にはシリアライズされたパラメータの配列paramArrayが渡されます。 945 | 946 | **設定例** 947 | 948 | ``` 949 | submitAction: function (paramArray) { 950 | var dataapi = false, l = paramArray.length; 951 | for (var i = 0; i < l; i++) { 952 | if (paramArray[i].name === "category" && paramArray[i].value === "movabletype") { 953 | dataapi = true; 954 | } 955 | } 956 | if (dataapi) { 957 | for (var i = 0; i < l; i++) { 958 | if (paramArray[i].name === "dataId") { 959 | paramArray[i].value = "entries"; 960 | } 961 | } 962 | } 963 | return paramArray; 964 | }, 965 | ``` 966 | 967 | ### ajaxError 968 | 969 | jQuery.ajaxでエラーが起きたときに呼ばれる関数を設定できます。 970 | 971 | **設定例** 972 | 973 | ``` 974 | ajaxError: function (jqXHR, textStatus, errorThrown) { 975 | window.alert(textStatus); 976 | }, 977 | ``` 978 | 979 | ### customSearch (version added: 2.2.0) 980 | 981 | 通常の検索で絞り込まれた検索結果JSONを、独自の検索ロジックでフィルターすることができます。このオプションはv2.2.0で追加されました。 982 | 983 | このオプションに設定した関数に検索結果JSONの1アイテムずつが渡され、この関数でfalseを返すと、そのアイテムは検索結果JSONから削除されます。 984 | 985 | このオプションに設定した関数には下記の2つの引数が渡されます。 986 | 987 | ``` 988 | function(item, paramObj){ 989 | // do something 990 | } 991 | ``` 992 | 993 | - item : 検索結果JSONから渡される1アイテムのプレーンオブジェクト 994 | - paramObj : パラメータオブジェクト。パラメータの値は``paramObj["キー"]``で取り出すことができます。 995 | 996 | **設定例** 997 | 998 | ``` 999 | customSearch: function(item, paramObj){ 1000 | // ageプロパティの値が20以上の場合は検索結果に残す 1001 | return item.age >= 20; 1002 | }, 1003 | ``` 1004 | 1005 | ### customSort (version added: 2.2.3) 1006 | 1007 | 通常の検索と独自の検索ロジックで絞り込まれた JSON に対して処理を加えることができます。 `sortBy` パラメータとは別に複数条件による複雑なソート処理などを加える場合はこのオプションを利用します。このオプションはv2.2.3で追加されました。 1008 | 1009 | このオプションで設定した関数には、通常の検索と独自の検索ロジックで絞り込まれた JSON が渡されます。この JSON に処理を加えて、 `return` で返却します。 1010 | 1011 | ``` 1012 | function(json){ 1013 | // do something 1014 | return json; 1015 | } 1016 | ``` 1017 | 1018 | - json : 通常の検索と独自の検索ロジックで絞り込まれた JSON 1019 | 1020 | 1021 | ### modifyResultJSON (version added: 2.2.0) 1022 | 1023 | 検索結果をHTMLに出力する直前に検索結果JSONを加工することができます。このオプションはv2.2.0で追加されました。 1024 | 1025 | このオプションに設定した関数に検索結果JSON全体が渡され、この関数で返したJSONが検索結果のHTML出力に使われます。 1026 | 1027 | このオプションに設定した関数には下記の1つの引数が渡されます。 1028 | 1029 | ``` 1030 | function(resultJSON){ 1031 | // do something 1032 | } 1033 | ``` 1034 | 1035 | - resultJSON : 検索結果のHTMLを出力する直前の検索結果JSON 1036 | 1037 | resultJSONはtotalResultsとitemsのプロパティを持つオブジェクトで、 1038 | ``resultJSON.totalResults``で検索結果総数に、 1039 | ``resultJSON.items``で検索結果のアイテムオブジェクトが並んだ配列にアクセスすることができます。 1040 | 1041 | **設定例** 1042 | 1043 | ``` 1044 | modifyResultJSON: function(resultJSON){ 1045 | for (var i = 0, l = resultJSON.items.length; i < l; i++) { 1046 | // do something 1047 | } 1048 | return resultJSON; 1049 | }, 1050 | ``` 1051 | 1052 | ### modifyResultMsgHTML 1053 | 1054 | 検索結果メッセージのHTMLを加工することができます。 1055 | 1056 | このオプションに設定した関数に検索結果メッセージのHTMLが渡され、この関数で返したHTMLが検索結果メッセージのHTML出力に使われます。 1057 | 1058 | このオプションに設定した関数には下記の1つの引数が渡されます。 1059 | 1060 | ``` 1061 | function(html){ 1062 | // do something 1063 | } 1064 | ``` 1065 | 1066 | - html : 検索結果メッセージのHTML 1067 | 1068 | **設定例** 1069 | 1070 | ``` 1071 | // 全角数字を半角数字に変換する 1072 | modifyResultMsgHTML: function(html){ 1073 | var zenkaku = { 1074 | "0":"0", 1075 | "1":"1", 1076 | "2":"2", 1077 | "3":"3", 1078 | "4":"4", 1079 | "5":"5", 1080 | "6":"6", 1081 | "7":"7", 1082 | "8":"8", 1083 | "9":"9" 1084 | }; 1085 | html = html.replace(/([0123456789])/g, function(match, p1, offset, string){ 1086 | return zenkaku[p1]; 1087 | }); 1088 | return html; 1089 | }, 1090 | ``` 1091 | 1092 | ### modifyResultItemHTML 1093 | 1094 | 検索結果一覧のHTMLを加工することができます。 1095 | 1096 | このオプションに設定した関数に検索結果一覧のHTMLが渡され、この関数で返したHTMLが検索結果一覧のHTML出力に使われます。 1097 | 1098 | このオプションに設定した関数には下記の1つの引数が渡されます。 1099 | 1100 | ``` 1101 | function(html){ 1102 | // do something 1103 | } 1104 | ``` 1105 | 1106 | - html : 検索結果一覧のHTML 1107 | 1108 | **設定例** 1109 | 1110 | ``` 1111 | // URL文字列をリンクにする 1112 | modifyResultItemHTML: function(html){ 1113 | return html.replace(/(https?:\/\/[\w\/:%#\$&\?\(\)~\.=\+\-]+)/gi, "$1"); 1114 | }, 1115 | ``` 1116 | 1117 | ### modifyPaginateHTML 1118 | 1119 | ページ分割のHTMLを加工することができます。 1120 | 1121 | このオプションに設定した関数にページ分割のHTMLが渡され、この関数で返したHTMLがページ分割のHTML出力に使われます。 1122 | 1123 | このオプションに設定した関数には下記の1つの引数が渡されます。 1124 | 1125 | ``` 1126 | function(html){ 1127 | // do something 1128 | } 1129 | ``` 1130 | 1131 | - html : ページ分割のHTML 1132 | 1133 | **設定例** 1134 | 1135 | ``` 1136 | // URL文字列をリンクにする 1137 | modifyPaginateHTML: function(html){ 1138 | // do something 1139 | return html; 1140 | }, 1141 | ``` 1142 | 1143 | ### resultComplete 1144 | 1145 | 検索結果をページのDOMに挿入した後に呼ばれる関数を設定します。このオプションに設定した関数の戻り値は特に影響を及ぼしません。直接DOMを操作するなどしてください。 1146 | 1147 | このオプションに設定した関数には下記の1つの引数が渡されます。 1148 | 1149 | ``` 1150 | function(totalResults){ 1151 | // do something 1152 | } 1153 | ``` 1154 | 1155 | - totalResults : 検索結果総数 1156 | 1157 | **設定例** 1158 | 1159 | ``` 1160 | // URL文字列をリンクにする 1161 | resultComplete: function(totalResults){ 1162 | // メタタイトルと同じ物をog:titleにセットする 1163 | $('meta[property="og:title"]').attr('content', document.title); 1164 | return; 1165 | }, 1166 | ``` 1167 | 1168 | ### excludeParams 1169 | 1170 | パラメータのうち検索から除外する項目をカンマ区切りで指定します。 1171 | 1172 | excludeParamsにはあらかじめ下記の項目が設定されています。つまり、下記の項目の値は検索には利用できません。 1173 | 1174 | - search 1175 | - dataId 1176 | - offset 1177 | - limit 1178 | - sortBy 1179 | - sortOrder 1180 | - sortType 1181 | 1182 | **設定例** 1183 | 1184 | ``` 1185 | excludeParams: "tags,prices", 1186 | ``` 1187 | 1188 | ### excludeSearchParams 1189 | 1190 | JSON の項目のうちキーワード検索の対象から除外する項目をカンマ区切りで指定します。 1191 | 1192 | **設定例** 1193 | 1194 | ``` 1195 | excludeSearchParams: "title,keywords", 1196 | ``` 1197 | -------------------------------------------------------------------------------- /flexibleSearch.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * flexibleSearch.js 3 | * 4 | * Copyright (c) Tomohiro Okuwaki / bit part LLC (http://bit-part.net/) 5 | * 6 | * Since : 2010-11-12 7 | * Update : 2020-02-12 8 | * Version: 2.5.0 9 | * Comment: Please use this with Movable Type :) 10 | * 11 | * You have to include "mustache.js" before "flexibleSearch.js". 12 | * Maybe... jQuery 1.7.x later 13 | */ 14 | (function ($) { 15 | $.fn.flexibleSearch = function (options) { 16 | var op = $.extend({}, $.fn.flexibleSearch.defaults, options); 17 | var $this = this; 18 | 19 | // ======================================================================= 20 | // Search Form HTML 21 | // 22 | 23 | if (op.searchFormCreation) { 24 | 25 | // Advanced Form HTML 26 | var advancedFormTmpl = []; 27 | var advancedFormHTML = ""; 28 | if (op.advancedFormObj !== null) { 29 | // input:hidden 30 | if ("hidden" in op.advancedFormObj) { 31 | advancedFormTmpl.push([ 32 | '' 40 | ].join("")); 41 | } 42 | 43 | // input:text 44 | if ("text" in op.advancedFormObj) { 45 | advancedFormTmpl.push([ 46 | '
', 47 | '{{#text}}', 48 | '{{#label}}', 49 | '', 50 | '{{/label}}', 51 | '', 56 | '{{#label}}', 57 | '{{label}}', 58 | '{{/label}}', 59 | '{{/text}}', 60 | '
' 61 | ].join("")); 62 | } 63 | 64 | // input:checkbox 65 | if ("checkbox" in op.advancedFormObj) { 66 | advancedFormTmpl.push([ 67 | '
', 68 | '{{#checkbox}}', 69 | '{{#label}}', 70 | '', 71 | '{{/label}}', 72 | '', 77 | '{{#label}}', 78 | '{{label}}', 79 | '{{/label}}', 80 | '{{/checkbox}}', 81 | '
' 82 | ].join("")); 83 | } 84 | 85 | // input:radio 86 | if ("radio" in op.advancedFormObj) { 87 | advancedFormTmpl.push([ 88 | '
', 89 | '{{#radio}}', 90 | '{{#label}}', 91 | '', 92 | '{{/label}}', 93 | '', 98 | '{{#label}}', 99 | '{{label}}', 100 | '{{/label}}', 101 | '{{/radio}}', 102 | '
' 103 | ].join("")); 104 | } 105 | 106 | // Make HTML of hidden, text, checkbox and radio 107 | advancedFormHTML = Mustache.render(advancedFormTmpl.join(""), op.advancedFormObj); 108 | 109 | // input:select 110 | if ("select" in op.advancedFormObj) { 111 | var advancedFormSelectObj = { 112 | "selects": op.advancedFormObj.select, 113 | "options": function () { 114 | var optionObj = this.option; 115 | var optionSet = []; 116 | for (var i = 0, l = optionObj.length; i < l; i++) { 117 | var slctd = optionObj[i].selected ? " selected": ""; 118 | optionSet.push(''); 119 | } 120 | return optionSet.join(""); 121 | } 122 | }; 123 | var advancedFormSelectTmpl = [ 124 | '
', 125 | '{{#selects}}', 126 | '', 127 | '{{{options}}}', 128 | '', 129 | '{{/selects}}', 130 | '
' 131 | ].join(""); 132 | 133 | // Make HTML of select 134 | advancedFormHTML += Mustache.render(advancedFormSelectTmpl, advancedFormSelectObj); 135 | } 136 | } 137 | // Advanced Form HTML 138 | 139 | // Search Form 140 | // 141 | // * {{action}} (= op.searchFormAction) 142 | // * {{type}} (= op.searchFormInputType) 143 | // * {{placeholder}} (= op.searchFormInputPlaceholder) 144 | // * {{submitBtnText}} (= op.searchFormSubmitBtnText) 145 | var searchFormObj = { 146 | action: op.searchFormAction, 147 | type: op.searchFormInputType, 148 | placeholder: op.searchFormInputPlaceholder, 149 | submitBtnText: op.searchFormSubmitBtnText 150 | }; 151 | var searchFormHTML = ""; 152 | if (op.limit !== null && typeof op.limit === 'number') { 153 | op.paginateCount = op.limit; 154 | } 155 | if (op.searchFormHTML !== null) { 156 | searchFormHTML = op.searchFormHTML; 157 | } 158 | else { 159 | var searchFormTmpl = [ 160 | '
', 161 | '', 162 | '', 163 | '', 164 | '', 165 | advancedFormHTML, // Advanced Form 166 | '
' 167 | ].join(""); 168 | 169 | // Make search form HTML 170 | searchFormHTML = Mustache.render(searchFormTmpl, searchFormObj); 171 | } 172 | // Search Form 173 | 174 | // Insert into DOM 175 | $this[0].innerHTML = searchFormHTML; 176 | } 177 | 178 | // 179 | // Search Form HTML 180 | // ----------------------------------------------------------------------- 181 | 182 | // ======================================================================= 183 | // Result Block Template 184 | // 185 | 186 | // Search Result Loading Image 187 | var resultLoadingHTML = ""; 188 | if (op.loadingImgHTML !== null) { 189 | resultLoadingHTML = op.loadingImgHTML; 190 | } 191 | else if (op.loadingImgPath) { 192 | resultLoadingHTML = ''; 193 | } 194 | // Search Result Loading Image 195 | 196 | // Search Result Message 197 | // 198 | // * {{keywords}} 199 | // * {{count}} 200 | // * {{firstPage}} 201 | // * {{lastPage}} 202 | // * {{currentPage}} 203 | var resultMsgTmpl = ""; 204 | if (op.resultMsgTmpl !== null) { 205 | resultMsgTmpl = op.resultMsgTmpl; 206 | } 207 | else { 208 | resultMsgTmpl = [ 209 | '', 210 | '

', 211 | '{{#keywords}}「{{keywords}}」が {{/keywords}}', 212 | '{{#count}}{{count}} 件見つかりました。{{/count}}', 213 | '{{^count}}見つかりませんでした。{{/count}}', 214 | '{{#count}}({{lastPage}} ページ中 {{currentPage}} ページ目を表示){{/count}}', 215 | // '({{firstPage}}〜{{lastPage}} ページ中 {{currentPage}} ページ目を表示)', 216 | '

', 217 | '' 218 | ].join(""); 219 | } 220 | // Search Result Message 221 | 222 | // Search Result Item 223 | // 224 | // * keys used in your JSON 225 | var resultItemTmpl = ""; 226 | if (op.resultItemTmpl !== null) { 227 | resultItemTmpl = op.resultItemTmpl; 228 | } 229 | else { 230 | resultItemTmpl = [ 231 | '
', 232 | '
    ', 233 | '{{#items}}', 234 | '
  • {{&title}}
  • ', 235 | '{{/items}}', 236 | '
', 237 | '
' 238 | ].join(""); 239 | } 240 | // Search Result Item 241 | 242 | // Paginate 243 | // 244 | // * {{paginateId}} 245 | // * {{¤t}} 246 | // * {{.}} 247 | var paginateTmpl = ""; 248 | if (op.paginateTmpl !== null) { 249 | paginateTmpl = op.paginateTmpl; 250 | } 251 | else { 252 | paginateTmpl = [ 253 | '', 254 | '
    ', 255 | 256 | '{{#showTurnPage}}', 257 | '{{#exceptFirst}}', 258 | '
  • {{prevPageText}}
  • ', 259 | '{{/exceptFirst}}', 260 | '{{/showTurnPage}}', 261 | 262 | '{{#page}}', 263 | '{{#checkRange}}', 264 | '', 265 | '{{/checkRange}}', 266 | '{{/page}}', 267 | 268 | '{{#showTurnPage}}', 269 | '{{#exceptLast}}', 270 | '
  • {{nextPageText}}
  • ', 271 | '{{/exceptLast}}', 272 | '{{/showTurnPage}}', 273 | 274 | '
', 275 | '' 276 | ].join(""); 277 | } 278 | // Paginate 279 | 280 | // 281 | // Result Block Template 282 | // ----------------------------------------------------------------------- 283 | 284 | // ======================================================================= 285 | // Get parameters and serialize parameters 286 | // 287 | 288 | // Serialize parameters 289 | $this.find("form").on("submit", function (e) { 290 | e.preventDefault(); 291 | var url = $(this).attr("action") || location.href.replace(/\?.*/, ""); 292 | var params = $(this).serializeArray(); 293 | for (var i = -1, n = params.length; ++i < n;) { 294 | if (params[i].name === "search") { 295 | params[i].value = $.trim(params[i].value.replace(/[ | ]+/g, " ")); 296 | } 297 | } 298 | params = op.submitAction(params); 299 | var serializeParams = $.param(params); 300 | if (serializeParams) { 301 | url = url + "?" + serializeParams; 302 | } 303 | location.href = url; 304 | return false; 305 | }); 306 | 307 | var paramStr = decodeURIComponent(location.search.replace(/^\?/, "")); 308 | 309 | // Set initial parameters 310 | if (paramStr === '' && op.initialParameter !== null && typeof op.initialParameter === 'string') { 311 | paramStr = decodeURIComponent(op.initialParameter.replace(/^\?/, "")); 312 | } 313 | 314 | // 315 | // Get parameters and serialize parameters 316 | // ----------------------------------------------------------------------- 317 | 318 | // ======================================================================= 319 | // Preload search data files 320 | // 321 | 322 | if (paramStr === "") { 323 | switch (typeof op.searchDataPathPreload) { 324 | case "string": 325 | $.ajax({ 326 | type: "GET", 327 | cache: true, 328 | dataType: "json", 329 | url: op.searchDataPathPreload 330 | }); 331 | break; 332 | case "object": 333 | if (op.searchDataPathPreload === null || op.searchDataPathPreload === "") { 334 | break; 335 | } 336 | if (op.searchDataPathPreload.length) { 337 | for (var i = -1, n = op.searchDataPathPreload.length; ++i < n;) { 338 | $.ajax({ 339 | type: "GET", 340 | cache: true, 341 | dataType: "json", 342 | url: op.searchDataPathPreload[i] 343 | }); 344 | } 345 | } 346 | else { 347 | for (var key in op.searchDataPathPreload) { 348 | $.ajax({ 349 | type: "GET", 350 | cache: true, 351 | dataType: "json", 352 | url: op.searchDataPathPreload[key] 353 | }); 354 | } 355 | } 356 | break; 357 | } 358 | return false; 359 | } 360 | 361 | // 362 | // Preload search data files 363 | // ----------------------------------------------------------------------- 364 | 365 | // Search Result Loading Image 366 | if (resultLoadingHTML) { 367 | document.getElementById(op.resultBlockId).innerHTML = resultLoadingHTML; 368 | } 369 | 370 | // Set values to Search Form 371 | var searchWords = []; 372 | var paramAry = paramStr.split(/&|%26/); 373 | var paramObj = {}; 374 | var paramExistArry = []; 375 | var advancedSearchObj = {}; 376 | var limit = (op.limit !== null && typeof op.limit === 'number') ? op.limit: 10; 377 | var offset = 0; 378 | var sortBy = ""; 379 | var sortOrder = ""; 380 | var sortType = ""; 381 | var jsonPath = ""; 382 | var dataId = ""; 383 | var api = false; 384 | var excludeParams = ["search", "dataId", "offset", "limit", "sortBy", "sortOrder", "sortType"]; 385 | var excludeSearchParams = op.excludeSearchParams ? op.excludeSearchParams.split(',') : []; 386 | if (op.simplePaginate === true) { 387 | excludeParams.push("page"); 388 | } 389 | if (op.excludeParams !== null) { 390 | $.merge(excludeParams, op.excludeParams.toLowerCase().split(",")); 391 | } 392 | 393 | for (var i = -1, n = paramAry.length; ++i < n;) { 394 | var param = paramAry[i].split("="); 395 | var key = param[0]; 396 | var value = param[1] || ""; 397 | paramObj[key] = value; 398 | // Set "advancedSearchObj" and "searchWords" 399 | switch (key) { 400 | case "search": 401 | value = (value === "+") ? "" : value; // If value is " ", it is "+" on URL. 402 | searchWords = value.split(/\+| |%20/); 403 | break; 404 | case "offset": 405 | offset = value; 406 | break; 407 | case "limit": 408 | limit = (op.limit !== null && typeof op.limit === 'number') ? op.limit: value; 409 | break; 410 | case "dataId": 411 | dataId = value; 412 | break; 413 | case "sortBy": 414 | sortBy = value.toLowerCase(); 415 | break; 416 | case "sortOrder": 417 | sortOrder = value.toLowerCase(); 418 | break; 419 | case "sortType": 420 | sortType = value.toLowerCase(); 421 | break; 422 | } 423 | 424 | // Restore search condition 425 | if (key === "offset" || key === "limit") { 426 | continue; 427 | } 428 | if (op.simplePaginate === true && key === "page") { 429 | continue; 430 | } 431 | $this.find("[name='" + key + "']").each(function () { 432 | var tagname = this.tagName.toLowerCase(); 433 | switch (tagname) { 434 | case "input": 435 | var type = $(this).attr('type'); 436 | if (type === "checkbox" && $(this).val() === value) { 437 | $(this).prop("checked", true); 438 | } 439 | else if (type === "radio" && $(this).val() === value) { 440 | $(this).prop("checked", true); 441 | } 442 | else if (type === "text" || type === "search" || type === "hidden") { 443 | if (key === "search") { 444 | $(this).val(value.replace(/\+/g," ")); 445 | } else { 446 | $(this).val(value); 447 | } 448 | } 449 | break; 450 | case "select": 451 | $(this).find("option").each(function () { 452 | if ($(this).val() === value) { 453 | $(this).prop("selected", true); 454 | } 455 | else if (value === "" && $(this).val() === " ") { 456 | $(this).prop("selected", true); 457 | } 458 | }); 459 | break; 460 | } 461 | }); 462 | 463 | // Set advancedSearchObj. This object is used in original search. 464 | if (value !== "") { 465 | value = value.replace(/\+/g, " "); 466 | if ($.inArray(key, excludeParams) !== -1) { 467 | continue; 468 | } 469 | if ($.inArray(key, paramExistArry) !== -1) { 470 | advancedSearchObj[key] += "," + value; 471 | } 472 | else { 473 | advancedSearchObj[key] = value; 474 | } 475 | } 476 | paramExistArry.push(key); 477 | } // for 478 | 479 | // For simple paginate option 480 | if (op.simplePaginate === true && typeof paramObj["page"] !== 'undefined') { 481 | if (/all/i.test(paramObj["page"])) { 482 | offset = 0; 483 | limit = 100000000; 484 | } 485 | else { 486 | offset = (paramObj["page"] - 1) * limit; 487 | } 488 | } 489 | 490 | // Set paramKeyCount 491 | var paramKeyCount = 0; 492 | for (var key in advancedSearchObj) { 493 | paramKeyCount++; 494 | } 495 | 496 | // ======================================================================= 497 | // Set JSON path and using Data API 498 | // 499 | 500 | // Set jsonPath 501 | switch (typeof op.searchDataPath) { 502 | case "string": 503 | jsonPath = op.searchDataPath; 504 | break; 505 | case "object": 506 | if (dataId === "") { 507 | window.alert("dataId is required."); 508 | return; 509 | } 510 | else { 511 | if (op.dataApiDataIds !== null && $.inArray(dataId, op.dataApiDataIds.split(",")) !== -1) { 512 | api = true; 513 | if (op.dataApiParams !== null) { 514 | paramStr += (paramStr !== "") ? "&" + $.param(op.dataApiParams): $.param(op.dataApiParams); 515 | } 516 | } 517 | jsonPath = op.searchDataPath[dataId]; 518 | } 519 | break; 520 | } 521 | 522 | // 523 | // Set JSON path and using Data API 524 | // ----------------------------------------------------------------------- 525 | 526 | // ======================================================================= 527 | // Search 528 | // 529 | if (api) { 530 | jsonPath += "?" + paramStr; 531 | } 532 | $.ajax({ 533 | type: "GET", 534 | cache: op.cache, 535 | dataType: "json", 536 | url: jsonPath, 537 | error: function (jqXHR, textStatus, errorThrown) { 538 | op.ajaxError(jqXHR, textStatus, errorThrown); 539 | }, 540 | success: function (response) { 541 | var resultJSON = {}; 542 | if (api) { 543 | // Data API 544 | resultJSON = response; 545 | } 546 | else { 547 | // Original JSON 548 | // Clone the items 549 | var cloneItems = $.grep(response.items, function () { 550 | return true; 551 | }); 552 | // Advanced Search 553 | cloneItems = $.grep(cloneItems, function (item, i) { 554 | return jsonAdvancedSearch (item, advancedSearchObj, paramKeyCount, "like", op.advancedSearchCond); 555 | }); 556 | 557 | // Search by keywords 558 | cloneItems = $.grep(cloneItems, function (item, i) { 559 | return jsonKeywordsSearch (item, searchWords); 560 | }); 561 | 562 | // Do custom search 563 | if (op.customSearch !== null && typeof op.customSearch === "function") { 564 | cloneItems = $.grep(cloneItems, function (item, i) { 565 | return op.customSearch(item, paramObj); 566 | }); 567 | } 568 | 569 | // Set resultJSON 570 | var limitIdx = Number(limit) + Number(offset); 571 | resultJSON.totalResults = cloneItems.length; 572 | 573 | // Sort 574 | if (resultJSON.totalResults !== 0 && sortBy !== "" && sortBy in cloneItems[0]) { 575 | if (sortOrder !== "ascend") { 576 | sortOrder = "descend"; 577 | } 578 | if (sortType !== "numeric") { 579 | sortType = "string"; 580 | } 581 | objectSort(cloneItems, sortBy, sortOrder, sortType); 582 | } 583 | 584 | // Custom Sort 585 | if (op.customSort !== null && typeof op.customSort === "function") { 586 | cloneItems = op.customSort(cloneItems, paramObj); 587 | } 588 | 589 | resultJSON.items = $.grep(cloneItems, function (item, i) { 590 | if (i < offset) { 591 | return false; 592 | } 593 | else if (i >= limitIdx) { 594 | return false; 595 | } 596 | else { 597 | return true; 598 | } 599 | }); 600 | } 601 | // Paginate 602 | var currentPage = Math.ceil(offset / limit) + 1; 603 | var realMaxPageCount = Math.ceil(resultJSON.totalResults / limit); 604 | var maxPageCount = op.maxPageCount ? op.maxPageCount : 100; 605 | var forwordRange = ((maxPageCount % 2) == 0) ? (maxPageCount / 2) - 1 : Math.floor(maxPageCount / 2); 606 | var backwardRange = ((maxPageCount % 2) == 0) ? maxPageCount / 2 : Math.floor(maxPageCount / 2); 607 | var startPage, lastPage; 608 | if (realMaxPageCount <= maxPageCount) { 609 | startPage = 1; 610 | lastPage = realMaxPageCount; 611 | } 612 | else { 613 | startPage = currentPage - forwordRange; 614 | lastPage = currentPage + backwardRange; 615 | if (startPage < 1) { 616 | startPage = 1; 617 | lastPage = maxPageCount; 618 | } 619 | else { 620 | if (lastPage > realMaxPageCount) { 621 | backwardRange = realMaxPageCount - currentPage; 622 | lastPage = realMaxPageCount; 623 | forwordRange = maxPageCount - backwardRange; 624 | startPage = currentPage - forwordRange; 625 | } 626 | } 627 | } 628 | var pageList = []; 629 | for (var i = 0, n = realMaxPageCount; ++i <= n;) { 630 | pageList.push({pageNumber: i}); 631 | } 632 | var paginateJSON = { 633 | id: op.paginateId, 634 | classname: op.paginateClassName, 635 | page: pageList, 636 | hidePageNumber: op.hidePageNumber, 637 | showTurnPage: op.showTurnPage, 638 | prevPageText: op.prevPageText, 639 | nextPageText: op.nextPageText, 640 | isFirst: function(){ 641 | return currentPage === 1; 642 | }, 643 | isLast: function(){ 644 | if (!pageList.length) { 645 | return true; 646 | } 647 | return currentPage === pageList.length; 648 | }, 649 | exceptFirst: function(){ 650 | return currentPage !== 1; 651 | }, 652 | exceptLast: function(){ 653 | return pageList.length > 0 && currentPage !== pageList.length; 654 | }, 655 | checkRange: function(){ 656 | return this.pageNumber >= startPage && this.pageNumber <= lastPage; 657 | }, 658 | current: function () { 659 | if (this.pageNumber === currentPage) { 660 | return 'fs-current'; 661 | } 662 | else if (this.pageNumber === (currentPage - 1)) { 663 | return "fs-current-prev"; 664 | } 665 | else if (this.pageNumber === (currentPage + 1)) { 666 | return "fs-current-next"; 667 | } 668 | else { 669 | return ""; 670 | } 671 | }, 672 | isCurrent: function(){ 673 | return this.pageNumber === currentPage; 674 | }, 675 | lastPage: function () { 676 | return paginateJSON.page.length; 677 | }, 678 | currentLink: function () { 679 | if (this.pageNumber === currentPage) { 680 | return 'fs-current-link'; 681 | } 682 | else if (this.pageNumber === (currentPage - 1)) { 683 | return "fs-current-prev-link"; 684 | } 685 | else if (this.pageNumber === (currentPage + 1)) { 686 | return "fs-current-next-link"; 687 | } 688 | else { 689 | return ""; 690 | } 691 | }, 692 | currentCountFrom: function () { 693 | return offset - 0 + 1; 694 | }, 695 | currentCountTo: function () { 696 | var num = offset - 0 + limit; 697 | var res = num < resultJSON.totalResults ? num : resultJSON.totalResults; 698 | return res - 0; 699 | }, 700 | totalResults: resultJSON.totalResults, 701 | count: resultJSON.totalResults, 702 | currentPage: currentPage 703 | }; 704 | var paginateHTML = Mustache.render(paginateTmpl, paginateJSON); 705 | 706 | // Result message 707 | var resultMsgObj = { 708 | id: op.resultMsgId ? op.resultMsgId : '', 709 | classname: op.resultMsgClassName ? op.resultMsgClassName : '', 710 | keywords: searchWords.join(', '), 711 | keywordArray: searchWords.join('') !== '' ? searchWords : null, 712 | count: resultJSON.totalResults, 713 | metaTitle: document.title, 714 | firstPage: function () { 715 | return paginateJSON.page[0].pageNumber; 716 | }, 717 | lastPage: function () { 718 | return paginateJSON.page.length; 719 | }, 720 | currentPage: currentPage, 721 | param: paramObj 722 | }; 723 | var resultMsgHTML = Mustache.render(resultMsgTmpl, resultMsgObj); 724 | 725 | // Set the meta title 726 | if (op.resultMetaTitleRewrite) { 727 | var resultMetaTitleTmpl = (op.resultMetaTitleTmpl) ? op.resultMetaTitleTmpl : [ 728 | // '{{#keywords}}{{keywords}}{{/keywords}}', 729 | '{{#keywordArray}}{{.}} {{/keywordArray}}', 730 | '{{#count}} {{count}}件{{/count}}', 731 | '{{#count}} {{currentPage}}/{{lastPage}}{{/count}}', 732 | '{{#metaTitle}} | {{metaTitle}}{{/metaTitle}}' 733 | ].join(""); 734 | var resultMetaTitleHTML = Mustache.render(resultMetaTitleTmpl, resultMsgObj); 735 | document.title = resultMetaTitleHTML; 736 | } 737 | 738 | // Show result 739 | if (op.modifyResultJSON !== null && typeof op.modifyResultJSON === "function") { 740 | resultJSON = op.modifyResultJSON(resultJSON); 741 | } 742 | var resultItemHTML = Mustache.render(resultItemTmpl, resultJSON); 743 | 744 | // Callback 745 | if (op.modifyResultMsgHTML !== null && typeof op.modifyResultMsgHTML === "function") { 746 | resultMsgHTML = op.modifyResultMsgHTML(resultMsgHTML); 747 | } 748 | if (op.modifyResultItemHTML !== null && typeof op.modifyResultItemHTML === "function") { 749 | resultItemHTML = op.modifyResultItemHTML(resultItemHTML); 750 | } 751 | if (op.modifyPaginateHTML !== null && typeof op.modifyPaginateHTML === "function") { 752 | paginateHTML = op.modifyPaginateHTML(paginateHTML); 753 | } 754 | 755 | // Search Result Block HTML 756 | var resultAllHTML = ''; 757 | 758 | // Add result message HTML 759 | if (op.resultMsgInsertMethods === null) { 760 | resultAllHTML += resultMsgHTML; 761 | } 762 | // Add result items HTML 763 | resultAllHTML += resultItemHTML; 764 | // Add paginate HTML 765 | if (realMaxPageCount === 1 && op.hideOnePagePaginate === true) { 766 | // nothing to do 767 | } 768 | else if (op.paginateInsertMethods === null) { 769 | resultAllHTML += paginateHTML; 770 | } 771 | 772 | // Add all of result HTML to DOM 773 | document.getElementById(op.resultBlockId).innerHTML = resultAllHTML; 774 | 775 | // Add result message HTML to DOM 776 | if (op.resultMsgInsertMethods !== null) { 777 | for (var i = 0, l = op.resultMsgInsertMethods.length; i < l; i++) { 778 | $(op.resultMsgInsertMethods[i].selector)[op.resultMsgInsertMethods[i].method](resultMsgHTML); 779 | } 780 | } 781 | // Add paginate HTML to DOM 782 | if (realMaxPageCount === 1 && op.hideOnePagePaginate === true) { 783 | // nothing to do 784 | } 785 | else if (op.paginateInsertMethods !== null) { 786 | for (var i = 0, l = op.paginateInsertMethods.length; i < l; i++) { 787 | $(op.paginateInsertMethods[i].selector)[op.paginateInsertMethods[i].method](paginateHTML); 788 | } 789 | } 790 | 791 | // Callback 792 | if (op.resultComplete !== null && typeof op.resultComplete === "function") { 793 | op.resultComplete(resultJSON.totalResults); 794 | } 795 | 796 | // Bind pageLink() to paginate link 797 | var paginateSelector = op.paginateId ? "#" + op.paginateId : "." + op.paginateClassName; 798 | $(paginateSelector + " a.fs-page-link").on("click", function (e) { 799 | e.preventDefault(); 800 | var page = $(this).attr("title"); 801 | var offset = (Number(page) - 1) * Number(limit); 802 | var url = location.href.replace(/\?.*/g, ''); 803 | var query = location.search ? location.search.replace(/^\?/, '') : op.initialParameter; 804 | if (op.simplePaginate === true) { 805 | if (query.indexOf('page=') === -1) { 806 | query += query ? '&page=' + page : 'page=' + page; 807 | } 808 | else { 809 | query = query.replace(/page=[0-9]+/, 'page=' + page); 810 | } 811 | query = query.replace(/&?offset=[0-9]+/g, ''); 812 | } 813 | else { 814 | if (query.indexOf('offset=') === -1) { 815 | query += query ? '&offset=' + offset : 'offset=' + offset; 816 | } 817 | else { 818 | query = query.replace(/offset=[0-9]+/, 'offset=' + offset); 819 | } 820 | query = query.replace(/&?offset=0/, ''); 821 | } 822 | if (query) { 823 | url += "?" + query.replace(/^&+/, ''); 824 | } 825 | location.href = url; 826 | }); 827 | $(paginateSelector + " a.fs-turn-page-link").on("click", function (e) { 828 | e.preventDefault(); 829 | if ($(this).hasClass('fs-prev-link')) { 830 | $(paginateSelector).find('.fs-current-prev-link').trigger('click'); 831 | } 832 | else if ($(this).hasClass('fs-next-link')) { 833 | $(paginateSelector).find('.fs-current-next-link').trigger('click'); 834 | } 835 | }); 836 | } // success 837 | }); // ajax 838 | 839 | // 840 | // Search 841 | // ----------------------------------------------------------------------- 842 | 843 | // ======================================================================= 844 | // Functions 845 | // 846 | 847 | function jsonAdvancedSearch (obj, advancedSearchObj, paramKeyCount, matchType, cond) { 848 | var matched = 0; 849 | if (matchType === "like") { 850 | for (var key in advancedSearchObj) { 851 | var valueArray = advancedSearchObj[key].split(","); 852 | var valueArrayLength = valueArray.length; 853 | var _matched = 0; 854 | for (var i = -1, n = valueArrayLength; ++i < n;) { 855 | var reg = new RegExp(valueArray[i].replace(/([.*+?^=!:${}()|[\]\/\\])/g, "\\$1"), "i"); 856 | if (typeof obj[key] === "undefined" || typeof obj[key] === "string" && reg.test(obj[key])) { 857 | if (cond === 'AND' || cond === 'and') { 858 | _matched++; 859 | } 860 | else { 861 | matched++; 862 | break; 863 | } 864 | } 865 | // else if (obj[key] && typeof obj[key] === "object" && obj[key].length) { 866 | // for (var i = -1, n = obj[key].length; ++i < n;) { 867 | // if (reg.test(obj[key])) return true; 868 | // } 869 | // } 870 | } 871 | if (cond === 'AND' || cond === 'and') { 872 | if (_matched === valueArrayLength) { 873 | matched++; 874 | } 875 | } 876 | } 877 | return matched === paramKeyCount; 878 | } 879 | else { 880 | for (var key in advancedSearchObj) { 881 | if (obj[key] && typeof obj[key] === "string" && obj[key] !== advancedSearchObj[key]) { 882 | return false; 883 | } 884 | // else if (obj[key] && typeof obj[key] === "object" && obj[key].length) { 885 | // for (var i = -1, n = obj[key].length; ++i < n;) { 886 | // return advancedSearchObj[key] === obj[key][i]; 887 | // } 888 | // } 889 | } 890 | } 891 | return matched; 892 | } 893 | 894 | function jsonKeywordsSearch (obj, keywordsArray) { 895 | var keywordsCount = keywordsArray.length; 896 | var keywordsMutchCount = 0; 897 | for (var i = -1; ++i < keywordsCount;) { 898 | var reg = new RegExp(keywordsArray[i].replace(/([.*+?^=!:${}()|[\]\/\\])/g, "\\$1"), "i"); 899 | // if (reg.test(obj[key])) keyMatch++; 900 | for (var key in obj) { 901 | if ($.inArray(key, excludeSearchParams) !== -1) { 902 | continue; 903 | } 904 | if (reg.test(obj[key])) { 905 | keywordsMutchCount++; 906 | break; 907 | } 908 | } 909 | } 910 | return (keywordsCount === keywordsMutchCount); 911 | } 912 | 913 | // Sort object. 914 | // @paran {Array} array JSON 915 | // @paran {String} sortBy Sort by this property's value. 916 | // @paran {String} sortOrder ascend or descend. 917 | // @paran {String} sortType numeric or string. 918 | function objectSort (array, sortBy, sortOrder, sortType) { 919 | if (!sortBy && typeof sortBy !== "string") { 920 | return; 921 | } 922 | sortOrder = (sortOrder === 'ascend') ? -1 : 1; 923 | array.sort(function(obj1, obj2){ 924 | var v1 = obj1[sortBy]; 925 | var v2 = obj2[sortBy]; 926 | if (sortType === 'numeric') { 927 | v1 = v1 - 0; 928 | v2 = v2 - 0; 929 | } 930 | else if (sortType === 'string') { 931 | v1 = '' + v1; 932 | v2 = '' + v2; 933 | } 934 | if (v1 < v2) { 935 | return 1 * sortOrder; 936 | } 937 | if (v1 > v2) { 938 | return -1 * sortOrder; 939 | } 940 | return 0; 941 | }); 942 | } 943 | 944 | // 945 | // Functions 946 | // ----------------------------------------------------------------------- 947 | }; 948 | $.fn.flexibleSearch.defaults = { 949 | initialParameter: null, 950 | // The limit parameter is overwritten and locked as this value. 951 | limit: null, 952 | // If you want to use 'page' parameter without 'limit' and 'offset', set true to this option. 953 | simplePaginate: false, 954 | // Path 955 | searchDataPath: "/flexibleSearch/search.json", 956 | searchDataPathPreload: null, 957 | 958 | // Data API 959 | dataApiDataIds: null, 960 | dataApiParams: null, 961 | 962 | // Performance 963 | cache: true, // I recommend "true". 964 | 965 | // Search Form 966 | searchFormCreation: true, 967 | searchFormHTML: null, 968 | searchFormAction: "", 969 | searchFormInputType: "search", 970 | searchFormInputPlaceholder: "Search words", 971 | searchFormSubmitBtnText: "Search", 972 | 973 | // Advanced Search Form 974 | advancedFormObj: null, 975 | advancedSearchCond: 'OR', // 'AND' 976 | 977 | // Result Block 978 | loadingImgPath: "/flexibleSearch/loading.gif", 979 | loadingImgHTML: null, 980 | 981 | resultBlockId: "fs-result", 982 | resultItemTmpl: null, 983 | 984 | resultMsgId: null, 985 | resultMsgClassName: "fs-result-msg", 986 | resultMsgTmpl: null, 987 | 988 | resultMetaTitleRewrite: true, 989 | resultMetaTitleTmpl: null, 990 | 991 | // You can set an array including plane object which has two properties, 992 | // method property and selector property. 993 | // e.g. 994 | // resultMsgInsertMethods: [ 995 | // { 996 | // "selector": "foo", 997 | // "method": "append" 998 | // } 999 | // ], 1000 | resultMsgInsertMethods: null, 1001 | 1002 | // Paginate 1003 | paginateId: null, 1004 | paginateClassName: "fs-paginate", 1005 | paginateTmpl: null, 1006 | paginateCount: 10, 1007 | hidePageNumber: false, 1008 | showTurnPage: true, 1009 | prevPageText: 'Prev', 1010 | nextPageText: 'Next', 1011 | maxPageCount: 10, 1012 | // If you want to hide one page pagination, set true. 1013 | hideOnePagePaginate: false, 1014 | // You can set an array including plane object which has two properties, 1015 | // method property and selector property. 1016 | // e.g. 1017 | // paginateInsertMethods: [ 1018 | // { 1019 | // "selector": "foo", 1020 | // "method": "append" 1021 | // } 1022 | // ], 1023 | paginateInsertMethods: null, 1024 | 1025 | // Submit 1026 | submitAction: function (paramArray) { 1027 | return paramArray; 1028 | }, 1029 | 1030 | // Ajax 1031 | ajaxError: function (jqXHR, textStatus, errorThrown) { 1032 | window.alert(textStatus); 1033 | }, 1034 | 1035 | // Callbacks 1036 | 1037 | // you can search in your logic. 1038 | // e.g. 1039 | // customSearch: function(item, paramObj){ 1040 | // // item : Each item in items 1041 | // // paramObj : Plane object of parameters 1042 | // // The item is removed when return false 1043 | // return true or false; 1044 | // }, 1045 | customSearch: null, 1046 | 1047 | // You can modify the search result JSON. 1048 | // e.g. 1049 | // modifyResultJSON = function(json){ 1050 | // json["fullName"] = function(){ 1051 | // return this.firstName + " " + this.lastName; 1052 | // }; 1053 | // return json; 1054 | // }, 1055 | modifyResultJSON: null, 1056 | modifyResultMsgHTML: null, 1057 | modifyResultItemHTML: null, 1058 | modifyPaginateHTML: null, 1059 | resultComplete: null, 1060 | 1061 | excludeParams: null, 1062 | excludeSearchParams: null, 1063 | dummy: false 1064 | }; 1065 | })(jQuery); 1066 | --------------------------------------------------------------------------------