├── .gitignore ├── README.md ├── config.txt ├── static-search.ie.js └── static-search.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StaticSearch 2 | 3 | StaticSearch is object, It provide methods to client call to server to get data. The technology used is ajax. 4 | 5 | **Platform** 6 | - Jquery 7 | 8 | **Usage** 9 | 10 | Import file static-search.js (using ES6) or static-search.ie.js (for old browsers) to your html page. Then create new object and run it. 11 | 12 | ``` 13 | var search = new StaticSearch({ 14 | url: "Default get from attribute action of form", 15 | method: "Default get from attribute method of form", 16 | dataType: "html", 17 | autoload: true, 18 | 19 | // Setting elements in your html page 20 | elements: { 21 | form: ".ss-form", 22 | pagination: ".ss-pagination a", 23 | order: ".ss-order span", 24 | record: ".ss-record a", 25 | success: ".ss-success", 26 | error: ".ss-error" 27 | }, 28 | 29 | // Setting custom method 30 | methods: { 31 | render: { 32 | error: function(){}, 33 | success: function(){}, 34 | } 35 | }, 36 | 37 | // Setting custom events 38 | events: { 39 | submit: function(){}, 40 | loading: function(){}, 41 | loaded: function(){}, 42 | render: function(){}, 43 | rendered: function(){} 44 | } 45 | }); 46 | ``` 47 | 48 | Then call run to apply to. 49 | 50 | ``` 51 | search.run(); 52 | ``` 53 | 54 | OK, now it working! 55 | -------------------------------------------------------------------------------- /config.txt: -------------------------------------------------------------------------------- 1 | { 2 | url: "Default get from attribute action of form", 3 | method: "Default get from attribute method of form", 4 | dataType: "html", 5 | autoload: true, 6 | 7 | // Setting elements in your html page 8 | elements: { 9 | form: ".search-engine-form", 10 | pagination: ".search-engine-pagination a", 11 | record: ".search-engine-record a", 12 | success: ".search-engine-success", 13 | error: ".search-engine-error" 14 | }, 15 | 16 | // Setting custom method 17 | methods: { 18 | render: { 19 | error: function(){}, 20 | success: function(){}, 21 | } 22 | }, 23 | 24 | // Setting custom events 25 | events: { 26 | submit: function(){}, 27 | loading: function(){}, 28 | loaded: function(){}, 29 | render: function(){} 30 | } 31 | } -------------------------------------------------------------------------------- /static-search.ie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SearchData class 3 | */ 4 | function SearchData(app) { 5 | this.app = app; 6 | this.form = $(this.app.config.elements.form); 7 | this.data = false; 8 | 9 | /** 10 | * Get data from form 11 | * @return {Object} 12 | */ 13 | this.getData = function () { 14 | // Check if is first time load data 15 | // Then reset data to get new data in form 16 | if (this.data === false) { 17 | this.resetData(); 18 | } 19 | 20 | return this.data; 21 | }; 22 | 23 | /** 24 | * Reset data 25 | * @return {Data} 26 | */ 27 | this.resetData = function () { 28 | var list = this.form.serializeArray(); 29 | var data = {}; 30 | 31 | for (var index in list) { 32 | data[list[index].name] = list[index].value; 33 | } 34 | 35 | this.setData(data, null); 36 | 37 | return this; 38 | }; 39 | 40 | /** 41 | * Set data 42 | * @param objectOrKey 43 | * @param value 44 | * @return {Data} 45 | */ 46 | this.setData = function (objectOrKey, value) { 47 | if (objectOrKey instanceof Object) { 48 | this.data = objectOrKey; 49 | } else { 50 | this.data[objectOrKey] = value; 51 | } 52 | 53 | return this; 54 | }; 55 | } 56 | 57 | /** 58 | * SearchLoader class 59 | */ 60 | function SearchLoader(app) { 61 | this.app = app; 62 | 63 | /** 64 | * Load data 65 | * @param parameters 66 | * @return {*} 67 | */ 68 | this.load = function (parameters, callback) { 69 | $.ajax({ 70 | url: this.app.config.loader.url, 71 | method: this.app.config.loader.method, 72 | dataType: this.app.config.loader.dataType, 73 | data: parameters 74 | }).then(function (response) { 75 | callback({status: true, response: response}); 76 | }).catch(function (error) { 77 | callback({status: false, error: error}); 78 | }); 79 | }; 80 | } 81 | 82 | /** 83 | * SearchRender class 84 | */ 85 | function SearchRender(app) { 86 | this.app = app; 87 | 88 | /** 89 | * Success 90 | * @param result 91 | */ 92 | this.success = function (result) { 93 | if (this.app.config.render.methods.success != undefined) { 94 | this.app.config.render.methods.success(result); 95 | } else { 96 | $(this.app.config.render.success).html(result); 97 | } 98 | 99 | this.app.events.rendered('success', result); 100 | 101 | return this; 102 | }; 103 | 104 | /** 105 | * Error 106 | * @param error 107 | */ 108 | this.error = function (error) { 109 | if (this.app.config.render.methods.error != undefined) { 110 | this.app.config.render.methods.error(error); 111 | } else { 112 | if (error.responseText) { 113 | var errors = typeof error.responseText == 'object' ? error.responseText : JSON.parse(error.responseText); 114 | 115 | for (var name in errors) { 116 | $('[name="' + name + '"]').addClass('is-invalid'); 117 | $('[data-bind="error-' + name + '"]').text(errors[name]); 118 | } 119 | } 120 | } 121 | 122 | this.app.events.rendered('error', error); 123 | 124 | return this; 125 | }; 126 | 127 | /** 128 | * Run 129 | * @param result 130 | * @return {Render} 131 | */ 132 | this.run = function (result) { 133 | $('[data-bind]').text(""); 134 | $('[name]').removeClass('is-invalid'); 135 | if (result.status) { 136 | return this.success(result.response); 137 | } 138 | 139 | return this.error(result.error); 140 | }; 141 | } 142 | 143 | /** 144 | * StaticSearch class 145 | */ 146 | function StaticSearch(config) { 147 | var instance = this; 148 | 149 | /** 150 | * Passing data or default data 151 | * @param data 152 | * @param defaultData 153 | * @return {*} 154 | */ 155 | this.or = function (data, defaultData) { 156 | return data == undefined ? defaultData : data; 157 | }; 158 | 159 | /** 160 | * Config event 161 | * 162 | * @param key 163 | * @param method 164 | * @return {SearchEngine} 165 | */ 166 | this.on = function (key, method) { 167 | if (this.events[key] != undefined) { 168 | this.events[key] = method; 169 | } 170 | 171 | return this; 172 | }; 173 | 174 | /** 175 | * Submit 176 | * @return {SearchEngine} 177 | */ 178 | this.submit = function () { 179 | var instance = this; 180 | 181 | // Event when on submit 182 | // Get form data and call server to load data 183 | $(document).on('submit', this.config.elements.form, function (e) { 184 | e.preventDefault(); 185 | instance.formData.resetData(); 186 | 187 | // Event submit 188 | // If return false then stop 189 | var status = instance.events.submit(instance.formData, e); 190 | 191 | if (status === false) { 192 | return; 193 | } 194 | 195 | var parameters = instance.formData.getData(); 196 | instance.loadContent(parameters); 197 | }); 198 | 199 | return this; 200 | }; 201 | 202 | /** 203 | * Pagination 204 | * @return {StaticSearch} 205 | */ 206 | this.pagination = function () { 207 | var instance = this; 208 | 209 | // Event when click on pagination 210 | $(document).on('click', this.config.elements.pagination, function (e) { 211 | e.preventDefault(); 212 | var page = $(this).attr('data-page'); 213 | 214 | if (isNaN(page)) { 215 | var url = $(this).attr('href').split('?')[1]; 216 | var params = url.split('&'); 217 | for (var i = 0; i < params.length; i++) { 218 | var name = params[i].split('='); 219 | if (name[0] == 'page') { 220 | page = name[1]; 221 | break; 222 | } 223 | } 224 | } 225 | 226 | var parameters = instance.formData.setData('page', page).getData(); 227 | instance.loadContent(parameters) 228 | }); 229 | 230 | return this; 231 | }; 232 | 233 | /** 234 | * Order 235 | */ 236 | this.order = function () { 237 | $(document).on('click', this.config.elements.order, function (e) { 238 | e.preventDefault(); 239 | 240 | var field = $(this).closest('th').attr('data-sort'); 241 | var old_data = instance.formData.getData(); 242 | var type = $(this).attr('data-type'); 243 | 244 | if (field == undefined) { 245 | return; 246 | } 247 | 248 | if (old_data.sort_column === field) { 249 | if (old_data.sort_type === type) { 250 | return; 251 | } 252 | 253 | instance.formData.setData('sort_type', type); 254 | } else { 255 | instance.formData.setData('sort_column', field); 256 | instance.formData.setData('sort_type', type); 257 | } 258 | 259 | instance.formData.setData('page', 1); 260 | var new_data = instance.formData.getData(); 261 | instance.loadContent(new_data); 262 | }); 263 | 264 | return this; 265 | }; 266 | 267 | /** 268 | * Load record 269 | * @return {SearchEngine} 270 | */ 271 | this.record = function () { 272 | var instance = this; 273 | 274 | // Event for pagination 275 | $(document).on('click', this.config.elements.record, function (e) { 276 | e.preventDefault(); 277 | var parameters = instance.formData.setData('record', $(this).attr('data-record')).getData(); 278 | instance.loadContent(parameters); 279 | }); 280 | 281 | return this; 282 | }; 283 | 284 | /** 285 | * Load content 286 | * @param parameters 287 | * @return {Promise<*>} 288 | */ 289 | this.loadContent = function (parameters) { 290 | var instance = this; 291 | 292 | // Load data 293 | var status = instance.events.loading(instance.loader, parameters); 294 | 295 | if (status === false) { 296 | return false; 297 | } 298 | 299 | instance.loader.load(parameters, function (result) { 300 | var content = { 301 | status: result.status 302 | }; 303 | 304 | if (result.status) { 305 | content.response = instance.or( 306 | instance.events.loaded(result.status, result.response), 307 | result.response 308 | ); 309 | 310 | if (content.response === false) { 311 | return; 312 | } 313 | } else { 314 | content.error = instance.or( 315 | instance.events.loaded(result.status, result.error), 316 | result.error 317 | ); 318 | 319 | if (content.error === false) { 320 | return; 321 | } 322 | } 323 | 324 | status = instance.events.render(content); 325 | 326 | if (status === false) { 327 | return; 328 | } 329 | 330 | instance.render.run(content); 331 | }); 332 | 333 | return; 334 | }; 335 | 336 | /** 337 | * Reload content with current condition 338 | * @return {SearchEngine} 339 | */ 340 | this.reload = function () { 341 | var parameters = this.formData.getData(); 342 | this.loadContent(parameters); 343 | 344 | return this; 345 | }; 346 | 347 | /** 348 | * Init object 349 | * @return {SearchEngine} 350 | */ 351 | this.init = function () { 352 | // Init autoload 353 | if (this.config.autoload === true) { 354 | $(this.config.elements.form).submit(); 355 | } 356 | 357 | return this; 358 | }; 359 | 360 | /** 361 | * Run search engine 362 | * @return {SearchEngine} 363 | */ 364 | this.run = function () { 365 | return this.submit() 366 | .pagination() 367 | .record() 368 | .order() 369 | .init(); 370 | }; 371 | 372 | if (config == undefined) { 373 | config = {}; 374 | } 375 | 376 | if (config.elements == undefined) { 377 | config.elements = {}; 378 | } 379 | 380 | if (config.methods == undefined) { 381 | config.methods = {}; 382 | } 383 | 384 | if (config.events == undefined) { 385 | config.events = {}; 386 | } 387 | 388 | // Set config 389 | this.config = {}; 390 | this.config.elements = { 391 | form: instance.or(config.elements.form, '.ss-form'), 392 | pagination: instance.or(config.elements.pagination, '.ss-pagination a'), 393 | order: instance.or(config.elements.order, '.ss-order span'), 394 | record: instance.or(config.elements.record, '.ss-record a') 395 | }; 396 | 397 | // Config for loader 398 | this.config.loader = { 399 | url: instance.or(config.url, $(this.config.elements.form).attr('action')), 400 | method: instance.or(config.method, $(this.config.elements.form).attr('method')), 401 | dataType: instance.or(config.dataType, 'html') 402 | }; 403 | 404 | // Config for render 405 | this.config.render = { 406 | success: instance.or(config.elements.success, '.ss-success'), 407 | error: instance.or(config.elements.error, '.ss-error'), 408 | methods: instance.or(config.methods.render, {}) 409 | }; 410 | 411 | // Config event 412 | this.events = { 413 | submit: instance.or(config.events.submit, function (formData) { 414 | }), 415 | loading: instance.or(config.events.loading, function (loader, parameters) { 416 | }), 417 | loaded: instance.or(config.events.loaded, function (result) { 418 | }), 419 | render: instance.or(config.events.render, function (result) { 420 | }), 421 | rendered: instance.or(config.events.rendered, function (result) { 422 | }) 423 | }; 424 | 425 | // Config init 426 | this.config.autoload = instance.or(config.autoload, true); 427 | 428 | // Create object 429 | this.formData = new SearchData(this); 430 | this.loader = new SearchLoader(this); 431 | this.render = new SearchRender(this); 432 | } 433 | -------------------------------------------------------------------------------- /static-search.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SearchData class 3 | */ 4 | class SearchData { 5 | constructor(app) { 6 | this.app = app; 7 | this.form = $(this.app.config.elements.form); 8 | this.data = false; 9 | } 10 | 11 | /** 12 | * Get data from form 13 | * @return {Object} 14 | */ 15 | getData() { 16 | // Check if is first time load data 17 | // Then reset data to get new data in form 18 | if (this.data === false) { 19 | this.resetData(); 20 | } 21 | 22 | return this.data; 23 | } 24 | 25 | /** 26 | * Reset data 27 | * @return {Data} 28 | */ 29 | resetData() { 30 | var list = this.form.serializeArray(); 31 | var data = {}; 32 | 33 | for (var index in list) { 34 | data[list[index].name] = list[index].value; 35 | } 36 | 37 | this.setData(data, null); 38 | 39 | return this; 40 | } 41 | 42 | /** 43 | * Set data 44 | * @param objectOrKey 45 | * @param value 46 | * @return {Data} 47 | */ 48 | setData(objectOrKey, value) { 49 | if (objectOrKey instanceof Object) { 50 | this.data = objectOrKey; 51 | } else { 52 | this.data[objectOrKey] = value; 53 | } 54 | 55 | return this; 56 | } 57 | } 58 | 59 | /** 60 | * SearchLoader class 61 | */ 62 | class SearchLoader { 63 | constructor(app) { 64 | this.app = app; 65 | } 66 | 67 | /** 68 | * Load data 69 | * @param parameters 70 | * @return {*} 71 | */ 72 | async load(parameters) { 73 | return await $.ajax({ 74 | url: this.app.config.loader.url, 75 | method: this.app.config.loader.method, 76 | dataType: this.app.config.loader.dataType, 77 | data: parameters 78 | }).then(async function (response) { 79 | return {status: true, response: response}; 80 | }).catch(function (error) { 81 | return {status: false, error: error}; 82 | }); 83 | } 84 | } 85 | 86 | /** 87 | * SearchRender class 88 | */ 89 | class SearchRender { 90 | /** 91 | * constructor 92 | * @param app 93 | */ 94 | constructor(app) { 95 | this.app = app; 96 | } 97 | 98 | /** 99 | * Success 100 | * @param result 101 | */ 102 | success(result) { 103 | if (this.app.config.render.methods.success != undefined) { 104 | this.app.config.render.methods.success(result); 105 | } else { 106 | $(this.app.config.render.success).html(result); 107 | } 108 | 109 | this.app.events.rendered('success', result); 110 | 111 | return this; 112 | } 113 | 114 | /** 115 | * Error 116 | * @param error 117 | */ 118 | error(error) { 119 | if (this.app.config.render.methods.error != undefined) { 120 | this.app.config.render.methods.error(error); 121 | } else { 122 | if (error.responseText) { 123 | var errors = typeof error.responseText == 'object' ? error.responseText : JSON.parse(error.responseText); 124 | 125 | for (var name in errors) { 126 | $('[name="' + name + '"]').addClass('is-invalid'); 127 | $('[data-bind="error-' + name + '"]').text(errors[name]); 128 | } 129 | } 130 | } 131 | 132 | this.app.events.rendered('error', error); 133 | 134 | return this; 135 | } 136 | 137 | /** 138 | * Run 139 | * @param result 140 | * @return {Render} 141 | */ 142 | run(result) { 143 | $('[data-bind]').text(""); 144 | $('[name]').removeClass('is-invalid'); 145 | 146 | if (result.status) { 147 | return this.success(result.response); 148 | } 149 | 150 | return this.error(result.error); 151 | } 152 | } 153 | 154 | /** 155 | * StaticSearch class 156 | */ 157 | class StaticSearch { 158 | /** 159 | * Constructor 160 | * @param config 161 | */ 162 | constructor(config) { 163 | if (config == undefined) { 164 | config = {}; 165 | } 166 | 167 | if (config.elements == undefined) { 168 | config.elements = {}; 169 | } 170 | 171 | if (config.methods == undefined) { 172 | config.methods = {}; 173 | } 174 | 175 | if (config.events == undefined) { 176 | config.events = {}; 177 | } 178 | 179 | // Set config 180 | this.config = {}; 181 | this.config.elements = { 182 | form: this.or(config.elements.form, '.ss-form'), 183 | pagination: this.or(config.elements.pagination, '.ss-pagination a'), 184 | order: this.or(config.elements.pagination, '.ss-order span'), 185 | record: this.or(config.elements.record, '.ss-record a') 186 | }; 187 | 188 | // Config for loader 189 | this.config.loader = { 190 | url: this.or(config.url, $(this.config.elements.form).attr('action')), 191 | method: this.or(config.method, $(this.config.elements.form).attr('method')), 192 | dataType: this.or(config.dataType, 'html') 193 | }; 194 | 195 | // Config for render 196 | this.config.render = { 197 | success: this.or(config.elements.success, '.ss-success'), 198 | error: this.or(config.elements.error, '.ss-error'), 199 | methods: this.or(config.methods.render, {}) 200 | }; 201 | 202 | // Config event 203 | this.events = { 204 | submit: this.or(config.events.submit, function (formData) { 205 | }), 206 | loading: this.or(config.events.loading, function (loader, parameters) { 207 | }), 208 | loaded: this.or(config.events.loaded, function (result) { 209 | }), 210 | render: this.or(config.events.render, function (result) { 211 | }), 212 | rendered: this.or(config.events.rendered, function (result) { 213 | }) 214 | }; 215 | 216 | // Config init 217 | this.config.autoload = this.or(config.autoload, true); 218 | 219 | // Create object 220 | this.formData = new SearchData(this); 221 | this.loader = new SearchLoader(this); 222 | this.render = new SearchRender(this); 223 | } 224 | 225 | /** 226 | * Passing data or default data 227 | * @param data 228 | * @param defaultData 229 | * @return {*} 230 | */ 231 | or(data, defaultData) { 232 | return data == undefined ? defaultData : data; 233 | } 234 | 235 | /** 236 | * Config event 237 | * 238 | * @param key 239 | * @param method 240 | * @return {SearchEngine} 241 | */ 242 | on(key, method) { 243 | if (this.events[key] != undefined) { 244 | this.events[key] = method; 245 | } 246 | 247 | return this; 248 | } 249 | 250 | /** 251 | * Submit 252 | * @return {SearchEngine} 253 | */ 254 | submit() { 255 | let instance = this; 256 | 257 | // Event when on submit 258 | // Get form data and call server to load data 259 | $(document).on('submit', this.config.elements.form, function (e) { 260 | e.preventDefault(); 261 | instance.formData.resetData(); 262 | 263 | // Event submit 264 | // If return false then stop 265 | var status = instance.events.submit(instance.formData, e); 266 | 267 | if (status === false) { 268 | return; 269 | } 270 | 271 | var parameters = instance.formData.getData(); 272 | instance.loadContent(parameters); 273 | }); 274 | 275 | return this; 276 | } 277 | 278 | /** 279 | * Paginate 280 | * @return {SearchEngine} 281 | */ 282 | pagination() { 283 | let instance = this; 284 | 285 | // Event when click on pagination 286 | $(document).on('click', this.config.elements.pagination, function (e) { 287 | e.preventDefault(); 288 | var page = $(this).attr('data-page'); 289 | 290 | if (isNaN(page)) { 291 | var url = $(this).attr('href').split('?')[1]; 292 | var params = url.split('&'); 293 | for (var i = 0; i < params.length; i++) { 294 | var name = params[i].split('='); 295 | if (name[0] == 'page') { 296 | page = name[1]; 297 | break; 298 | } 299 | } 300 | } 301 | 302 | var parameters = instance.formData.setData('page', page).getData(); 303 | instance.loadContent(parameters) 304 | }); 305 | 306 | return this; 307 | } 308 | 309 | /** 310 | * Order 311 | * @return {SearchEngine} 312 | */ 313 | order() { 314 | $(document).on('click', this.config.elements.order, function (e) { 315 | e.preventDefault(); 316 | 317 | var field = $(this).closest('th').attr('data-sort'); 318 | var old_data = instance.formData.getData(); 319 | var type = $(this).attr('data-type'); 320 | 321 | if (field == undefined) { 322 | return; 323 | } 324 | 325 | if (old_data.sort_column === field) { 326 | if (old_data.sort_type === type) { 327 | return; 328 | } 329 | 330 | instance.formData.setData('sort_type', type); 331 | } else { 332 | instance.formData.setData('sort_column', field); 333 | instance.formData.setData('sort_type', type); 334 | } 335 | 336 | instance.formData.setData('page', 1); 337 | var new_data = instance.formData.getData(); 338 | instance.loadContent(new_data); 339 | }); 340 | 341 | return this; 342 | } 343 | 344 | /** 345 | * Load record 346 | * @return {SearchEngine} 347 | */ 348 | record() { 349 | let instance = this; 350 | 351 | // Event for pagination 352 | $(document).on('click', this.config.elements.record, function (e) { 353 | e.preventDefault(); 354 | var parameters = instance.formData.setData('record', $(this).attr('data-record')).getData(); 355 | instance.loadContent(parameters); 356 | }); 357 | 358 | return this; 359 | } 360 | 361 | /** 362 | * Load content 363 | * @param parameters 364 | * @return {Promise<*>} 365 | */ 366 | async loadContent(parameters) { 367 | let instance = this; 368 | 369 | // Load data 370 | var status = instance.events.loading(instance.loader, parameters); 371 | 372 | if (status === false) { 373 | return false; 374 | } 375 | 376 | var result = await instance.loader.load(parameters); 377 | var content = { 378 | status: result.status 379 | }; 380 | 381 | if (result.status) { 382 | content.response = this.or( 383 | instance.events.loaded(result.status, result.response), 384 | result.response 385 | ); 386 | 387 | if (content.response === false) { 388 | return; 389 | } 390 | } else { 391 | content.error = this.or( 392 | instance.events.loaded(result.status, result.error), 393 | result.error 394 | ); 395 | 396 | if (content.error === false) { 397 | return; 398 | } 399 | } 400 | 401 | status = instance.events.render(content); 402 | 403 | if (status === false) { 404 | return; 405 | } 406 | 407 | instance.render.run(content); 408 | return; 409 | } 410 | 411 | /** 412 | * Reload content with current condition 413 | * @return {SearchEngine} 414 | */ 415 | reload() { 416 | let parameters = this.formData.getData(); 417 | this.loadContent(parameters); 418 | 419 | return this; 420 | } 421 | 422 | /** 423 | * Init object 424 | * @return {SearchEngine} 425 | */ 426 | init() { 427 | // Init autoload 428 | if (this.config.autoload === true) { 429 | $(this.config.elements.form).submit(); 430 | } 431 | 432 | return this; 433 | } 434 | 435 | /** 436 | * Run search engine 437 | * @return {SearchEngine} 438 | */ 439 | run() { 440 | return this.submit() 441 | .pagination() 442 | .record() 443 | .order() 444 | .init(); 445 | } 446 | } 447 | --------------------------------------------------------------------------------