├── .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(['',"{{#hidden}}",'',"{{/hidden}}","
"].join("")),"text"in t.advancedFormObj&&n.push(['',"{{#text}}","{{#label}}",'","{{/label}}","{{/text}}","
"].join("")),"checkbox"in t.advancedFormObj&&n.push(['',"{{#checkbox}}","{{#label}}",'","{{/label}}","{{/checkbox}}","
"].join("")),"radio"in t.advancedFormObj&&n.push(['',"{{#radio}}","{{#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}}",'","{{/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=['"].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}}",'- {{pageNumber}}
',"{{/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 | ''
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 |
448 |
449 |
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 | - {{pageNumber}}
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 | '',
33 | '{{#hidden}}',
34 | '',
38 | '{{/hidden}}',
39 | '
'
40 | ].join(""));
41 | }
42 |
43 | // input:text
44 | if ("text" in op.advancedFormObj) {
45 | advancedFormTmpl.push([
46 | '',
47 | '{{#text}}',
48 | '{{#label}}',
49 | '',
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 | '',
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 | '',
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 | '',
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 | ''
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 | '- {{pageNumber}}
',
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 |
--------------------------------------------------------------------------------