@cable8mm/jquery-infinite-with-template 1.0.3 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +14 -0
- package/.github/workflows/publish.yml +24 -0
- package/.github/workflows/static.yml +49 -0
- package/.github/workflows/test.yml +33 -0
- package/.vscode/settings.json +4 -0
- package/AGENTS.md +216 -0
- package/README.md +281 -166
- package/examples/data_sources.json +84 -0
- package/examples/generate +55 -11
- package/examples/index.html +3 -3
- package/jest.config.js +8 -0
- package/jquery.infiniteScrollWithTemplate.d.ts +65 -0
- package/jquery.infiniteScrollWithTemplate.js +96 -11
- package/jquery.infiniteScrollWithTemplate.min.js +1 -1
- package/package.json +20 -2
- package/scripts/prepare-dist.js +72 -0
- package/tests/jquery.infiniteScrollWithTemplate.test.js +310 -0
- package/tests/setup.js +16 -0
- package/examples/data_sources.ajax +0 -84
- /package/{LICENSE → LICENSE.md} +0 -0
package/examples/index.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/1.0.10/jsrender.min.js"></script>
|
|
11
11
|
|
|
12
12
|
<!-- Load InfiniteScrollWithTemplate -->
|
|
13
|
-
<script src="
|
|
13
|
+
<script src="../jquery.infiniteScrollWithTemplate.js"></script>
|
|
14
14
|
</head>
|
|
15
15
|
|
|
16
16
|
<body>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
$(function () {
|
|
19
19
|
$("#result").infiniteTemplate({
|
|
20
20
|
templateSelector: "#test-tmpl",
|
|
21
|
-
dataPath: "data_sources.
|
|
21
|
+
dataPath: "./data_sources.json",
|
|
22
22
|
query: "word=ajax",
|
|
23
23
|
zeroCallback: function () {
|
|
24
24
|
alert("zero alert");
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
declare namespace JQuery {
|
|
2
|
+
interface InfiniteTemplateOptions {
|
|
3
|
+
/** jsRender template selector (required) */
|
|
4
|
+
templateSelector: string;
|
|
5
|
+
|
|
6
|
+
/** URL to load more data via AJAX (required) */
|
|
7
|
+
dataPath: string;
|
|
8
|
+
|
|
9
|
+
/** Additional query parameters (optional) */
|
|
10
|
+
query?: string;
|
|
11
|
+
|
|
12
|
+
/** Key for data array in JSON response (default: "data") */
|
|
13
|
+
key?: string;
|
|
14
|
+
|
|
15
|
+
/** HTTP method (default: "GET") */
|
|
16
|
+
method?: string;
|
|
17
|
+
|
|
18
|
+
/** Helper data to merge with each item (optional) */
|
|
19
|
+
templateHelpers?: Record<string, any>;
|
|
20
|
+
|
|
21
|
+
/** Load data on initialization (default: true) */
|
|
22
|
+
loadAtStart?: boolean;
|
|
23
|
+
|
|
24
|
+
/** Selector for click-to-load button (optional) */
|
|
25
|
+
loadSelector?: string;
|
|
26
|
+
|
|
27
|
+
/** Initial page number (default: 1) */
|
|
28
|
+
initialPage?: number;
|
|
29
|
+
|
|
30
|
+
/** Add timestamp to prevent caching (default: false) */
|
|
31
|
+
preventCache?: boolean;
|
|
32
|
+
|
|
33
|
+
/** Callback when no data returned (optional) */
|
|
34
|
+
zeroCallback?: () => void;
|
|
35
|
+
|
|
36
|
+
/** Callback on AJAX/JSON error (optional) */
|
|
37
|
+
errorCallback?: (error: any) => void;
|
|
38
|
+
|
|
39
|
+
/** Callback when loading starts (optional) */
|
|
40
|
+
loadingCallback?: () => void;
|
|
41
|
+
|
|
42
|
+
/** Callback when loading completes (optional) */
|
|
43
|
+
loadedCallback?: () => void;
|
|
44
|
+
|
|
45
|
+
/** Scroll threshold multiplier (default: 2) */
|
|
46
|
+
scrollThreshold?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface InfiniteTemplateStatic {
|
|
50
|
+
(options: InfiniteTemplateOptions): JQuery;
|
|
51
|
+
defaults: InfiniteTemplateOptions;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface JQuery {
|
|
55
|
+
infiniteTemplate: InfiniteTemplateStatic;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface JQueryStatic {
|
|
60
|
+
fn: {
|
|
61
|
+
infiniteTemplate: JQuery.InfiniteTemplateStatic;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export {};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* JQuery plugin for ajax-enabled infinite page scroll with template.
|
|
4
4
|
*
|
|
5
5
|
* Author: Sam Lee (https://github.com/cable8mm)
|
|
6
|
-
* Version: 1.0.
|
|
6
|
+
* Version: 1.0.4
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
(function ($) {
|
|
@@ -15,19 +15,33 @@
|
|
|
15
15
|
|
|
16
16
|
var opts = $.extend({}, $.fn.infiniteTemplate.defaults, settings);
|
|
17
17
|
|
|
18
|
+
// Validate required options
|
|
19
|
+
if (!opts.templateSelector) {
|
|
20
|
+
console.error("infiniteTemplate: templateSelector is required");
|
|
21
|
+
return $this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!opts.dataPath) {
|
|
25
|
+
console.error("infiniteTemplate: dataPath is required");
|
|
26
|
+
return $this;
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
var currentScrollPage = opts.initialPage;
|
|
19
30
|
var scrollTriggered = false;
|
|
20
31
|
var isFinished = false;
|
|
32
|
+
var namespace =
|
|
33
|
+
".infiniteTemplate_" + Math.random().toString(36).substr(2, 9);
|
|
21
34
|
|
|
22
35
|
if (opts.loadSelector) {
|
|
23
|
-
$(document).on("click", opts.loadSelector, function () {
|
|
36
|
+
$(document).on("click" + namespace, opts.loadSelector, function () {
|
|
24
37
|
triggerDataLoad();
|
|
25
38
|
});
|
|
26
39
|
} else {
|
|
27
|
-
$(window).on("scroll", function () {
|
|
40
|
+
$(window).on("scroll" + namespace, function () {
|
|
28
41
|
if (
|
|
29
42
|
$(this).scrollTop() >
|
|
30
|
-
$(document.body).height() -
|
|
43
|
+
$(document.body).height() -
|
|
44
|
+
$(window).height() * (opts.scrollThreshold || 2) &&
|
|
31
45
|
!isFinished
|
|
32
46
|
) {
|
|
33
47
|
triggerDataLoad();
|
|
@@ -42,18 +56,29 @@
|
|
|
42
56
|
|
|
43
57
|
scrollTriggered = true;
|
|
44
58
|
|
|
45
|
-
|
|
59
|
+
if (typeof opts.loadingCallback === "function") {
|
|
60
|
+
opts.loadingCallback();
|
|
61
|
+
}
|
|
46
62
|
|
|
47
|
-
var
|
|
63
|
+
var tmpl = $.templates(opts.templateSelector);
|
|
48
64
|
|
|
49
|
-
var
|
|
65
|
+
var url = buildUrl(opts.dataPath, currentScrollPage);
|
|
50
66
|
|
|
51
67
|
$.ajax({
|
|
52
|
-
url:
|
|
68
|
+
url: url,
|
|
53
69
|
method: opts.method,
|
|
54
70
|
success: function (result) {
|
|
55
71
|
if (typeof result === "string") {
|
|
56
|
-
|
|
72
|
+
try {
|
|
73
|
+
result = JSON.parse(result);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.error("JSON parse error:", e);
|
|
76
|
+
scrollTriggered = false;
|
|
77
|
+
if (typeof opts.errorCallback === "function") {
|
|
78
|
+
opts.errorCallback(e);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
57
82
|
}
|
|
58
83
|
|
|
59
84
|
if (result) {
|
|
@@ -61,7 +86,6 @@
|
|
|
61
86
|
isFinished = true;
|
|
62
87
|
|
|
63
88
|
// if zero, call zeroCallback
|
|
64
|
-
console.log(currentScrollPage + " " + opts.zeroCallback);
|
|
65
89
|
if (
|
|
66
90
|
currentScrollPage == 1 &&
|
|
67
91
|
typeof opts.zeroCallback === "function"
|
|
@@ -69,24 +93,81 @@
|
|
|
69
93
|
opts.zeroCallback();
|
|
70
94
|
}
|
|
71
95
|
} else {
|
|
96
|
+
var htmlContent = "";
|
|
97
|
+
|
|
72
98
|
$.each(result[opts.key], function (_, item) {
|
|
73
99
|
if (opts.templateHelpers) {
|
|
74
100
|
item = Object.assign(item, opts.templateHelpers);
|
|
75
101
|
}
|
|
76
102
|
|
|
77
103
|
var html = tmpl.render(item);
|
|
78
|
-
|
|
104
|
+
htmlContent += html;
|
|
79
105
|
});
|
|
80
106
|
|
|
107
|
+
$this.append(htmlContent);
|
|
108
|
+
|
|
81
109
|
currentScrollPage += 1;
|
|
82
110
|
}
|
|
83
111
|
}
|
|
84
112
|
|
|
85
113
|
scrollTriggered = false;
|
|
86
114
|
},
|
|
115
|
+
error: function (xhr, status, error) {
|
|
116
|
+
scrollTriggered = false;
|
|
117
|
+
console.error("AJAX error:", status, error);
|
|
118
|
+
if (typeof opts.errorCallback === "function") {
|
|
119
|
+
opts.errorCallback(error);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
complete: function () {
|
|
123
|
+
if (typeof opts.loadedCallback === "function") {
|
|
124
|
+
opts.loadedCallback();
|
|
125
|
+
}
|
|
126
|
+
},
|
|
87
127
|
});
|
|
88
128
|
}
|
|
89
129
|
|
|
130
|
+
function buildUrl(baseUrl, page) {
|
|
131
|
+
var url;
|
|
132
|
+
|
|
133
|
+
// Check if baseUrl is absolute or relative
|
|
134
|
+
if (
|
|
135
|
+
baseUrl.indexOf("http://") === 0 ||
|
|
136
|
+
baseUrl.indexOf("https://") === 0
|
|
137
|
+
) {
|
|
138
|
+
// Absolute URL
|
|
139
|
+
url = new URL(baseUrl);
|
|
140
|
+
} else if (window.location && window.location.href) {
|
|
141
|
+
// Resolve relative URLs from the current page's directory
|
|
142
|
+
var basePath = window.location.href.substring(
|
|
143
|
+
0,
|
|
144
|
+
window.location.href.lastIndexOf("/") + 1,
|
|
145
|
+
);
|
|
146
|
+
url = new URL(baseUrl, basePath);
|
|
147
|
+
} else {
|
|
148
|
+
// Fallback: treat as relative to root
|
|
149
|
+
url = new URL(baseUrl, "http://localhost/");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
url.searchParams.set("page", page);
|
|
153
|
+
|
|
154
|
+
if (opts.query) {
|
|
155
|
+
var queryParams = opts.query.split("&");
|
|
156
|
+
$.each(queryParams, function (_, param) {
|
|
157
|
+
var keyValue = param.split("=");
|
|
158
|
+
if (keyValue.length === 2) {
|
|
159
|
+
url.searchParams.set(keyValue[0], keyValue[1]);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (opts.preventCache) {
|
|
165
|
+
url.searchParams.set("t", new Date().getTime());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return url.toString();
|
|
169
|
+
}
|
|
170
|
+
|
|
90
171
|
if (opts.loadAtStart) {
|
|
91
172
|
triggerDataLoad();
|
|
92
173
|
}
|
|
@@ -107,5 +188,9 @@
|
|
|
107
188
|
initialPage: 1,
|
|
108
189
|
preventCache: false,
|
|
109
190
|
zeroCallback: null,
|
|
191
|
+
errorCallback: null,
|
|
192
|
+
loadingCallback: null,
|
|
193
|
+
loadedCallback: null,
|
|
194
|
+
scrollThreshold: 2,
|
|
110
195
|
};
|
|
111
196
|
})(jQuery);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e){e.fn.infiniteTemplate=function(t){var l=e(this);if(!l.length)return l;var a=e.extend({},e.fn.infiniteTemplate.defaults,t),
|
|
1
|
+
!function(e){e.fn.infiniteTemplate=function(t){var l=e(this);if(!l.length)return l;var a=e.extend({},e.fn.infiniteTemplate.defaults,t);if(!a.templateSelector)return console.error("infiniteTemplate: templateSelector is required"),l;if(!a.dataPath)return console.error("infiniteTemplate: dataPath is required"),l;var r=a.initialPage,n=!1,o=!1,i=".infiniteTemplate_"+Math.random().toString(36).substr(2,9);function c(){if(!n&&!o){n=!0,"function"==typeof a.loadingCallback&&a.loadingCallback();var t=e.templates(a.templateSelector),i=function(t,l){var r;if(0===t.indexOf("http://")||0===t.indexOf("https://"))r=new URL(t);else if(window.location&&window.location.href){var n=window.location.href.substring(0,window.location.href.lastIndexOf("/")+1);r=new URL(t,n)}else r=new URL(t,"http://localhost/");if(r.searchParams.set("page",l),a.query){var o=a.query.split("&");e.each(o,function(e,t){var l=t.split("=");2===l.length&&r.searchParams.set(l[0],l[1])})}a.preventCache&&r.searchParams.set("t",(new Date).getTime());return r.toString()}(a.dataPath,r);e.ajax({url:i,method:a.method,success:function(i){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){return console.error("JSON parse error:",e),n=!1,void("function"==typeof a.errorCallback&&a.errorCallback(e))}if(i)if(0==i[a.key].length)o=!0,1==r&&"function"==typeof a.zeroCallback&&a.zeroCallback();else{var c="";e.each(i[a.key],function(e,l){a.templateHelpers&&(l=Object.assign(l,a.templateHelpers));var r=t.render(l);c+=r}),l.append(c),r+=1}n=!1},error:function(e,t,l){n=!1,console.error("AJAX error:",t,l),"function"==typeof a.errorCallback&&a.errorCallback(l)},complete:function(){"function"==typeof a.loadedCallback&&a.loadedCallback()}})}}return a.loadSelector?e(document).on("click"+i,a.loadSelector,function(){c()}):e(window).on("scroll"+i,function(){e(this).scrollTop()>e(document.body).height()-e(window).height()*(a.scrollThreshold||2)&&!o&&c()}),a.loadAtStart&&c(),this},e.fn.infiniteTemplate.defaults={dataPath:null,templateSelector:null,query:null,key:"data",method:"GET",templateHelpers:null,loadAtStart:!0,loadSelector:null,initialPage:1,preventCache:!1,zeroCallback:null,errorCallback:null,loadingCallback:null,loadedCallback:null,scrollThreshold:2}}(jQuery);
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cable8mm/jquery-infinite-with-template",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"title": "jQuery Infinite With Template",
|
|
5
5
|
"description": "JQuery plugin for ajax-enabled infinite page scroll with template. If you like jQuery until now, this little library will help.",
|
|
6
6
|
"main": "jquery.infiniteScrollWithTemplate.js",
|
|
7
|
+
"types": "jquery.infiniteScrollWithTemplate.d.ts",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
10
|
"url": "git+https://github.com/cable8mm/jquery-infinite-with-template.git"
|
|
@@ -21,9 +22,26 @@
|
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"jsrender": ">= 1.0.0"
|
|
23
24
|
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/jest": "^29.5.0",
|
|
27
|
+
"http-server": "^14.1.1",
|
|
28
|
+
"jest": "^29.7.0",
|
|
29
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
30
|
+
"jquery": "^3.7.0",
|
|
31
|
+
"jsrender": "^1.0.10",
|
|
32
|
+
"terser": "^5.19.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "jest",
|
|
36
|
+
"minify": "terser jquery.infiniteScrollWithTemplate.js -o jquery.infiniteScrollWithTemplate.min.js -c -m",
|
|
37
|
+
"build": "npm run minify && node scripts/prepare-dist.js",
|
|
38
|
+
"prepare-dist": "node scripts/prepare-dist.js",
|
|
39
|
+
"dev": "http-server . -p 8080 -o /examples/index.html",
|
|
40
|
+
"serve": "http-server . -p 8080"
|
|
41
|
+
},
|
|
24
42
|
"license": "MIT",
|
|
25
43
|
"bugs": {
|
|
26
44
|
"url": "https://github.com/cable8mm/jquery-infinite-with-template/issues"
|
|
27
45
|
},
|
|
28
46
|
"homepage": "https://github.com/cable8mm/jquery-infinite-with-template#readme"
|
|
29
|
-
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const rootDir = path.resolve(__dirname, "..");
|
|
5
|
+
const distDir = path.join(rootDir, "dist");
|
|
6
|
+
|
|
7
|
+
// Clean and create dist directory
|
|
8
|
+
if (fs.existsSync(distDir)) {
|
|
9
|
+
fs.rmSync(distDir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
12
|
+
|
|
13
|
+
// Copy examples folder to dist
|
|
14
|
+
const examplesDir = path.join(rootDir, "examples");
|
|
15
|
+
const distExamplesDir = path.join(distDir, "examples");
|
|
16
|
+
|
|
17
|
+
copyFolder(examplesDir, distExamplesDir);
|
|
18
|
+
|
|
19
|
+
// Copy main plugin file to dist
|
|
20
|
+
const pluginSource = path.join(rootDir, "jquery.infiniteScrollWithTemplate.js");
|
|
21
|
+
const pluginDest = path.join(distDir, "jquery.infiniteScrollWithTemplate.js");
|
|
22
|
+
|
|
23
|
+
fs.copyFileSync(pluginSource, pluginDest);
|
|
24
|
+
|
|
25
|
+
// Copy minified plugin file if exists
|
|
26
|
+
const minifiedSource = path.join(
|
|
27
|
+
rootDir,
|
|
28
|
+
"jquery.infiniteScrollWithTemplate.min.js",
|
|
29
|
+
);
|
|
30
|
+
if (fs.existsSync(minifiedSource)) {
|
|
31
|
+
const minifiedDest = path.join(
|
|
32
|
+
distDir,
|
|
33
|
+
"jquery.infiniteScrollWithTemplate.min.js",
|
|
34
|
+
);
|
|
35
|
+
fs.copyFileSync(minifiedSource, minifiedDest);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Copy TypeScript definitions if exists
|
|
39
|
+
const typesSource = path.join(
|
|
40
|
+
rootDir,
|
|
41
|
+
"jquery.infiniteScrollWithTemplate.d.ts",
|
|
42
|
+
);
|
|
43
|
+
if (fs.existsSync(typesSource)) {
|
|
44
|
+
const typesDest = path.join(
|
|
45
|
+
distDir,
|
|
46
|
+
"jquery.infiniteScrollWithTemplate.d.ts",
|
|
47
|
+
);
|
|
48
|
+
fs.copyFileSync(typesSource, typesDest);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log("✓ Dist folder prepared successfully");
|
|
52
|
+
console.log(" - examples/ folder copied");
|
|
53
|
+
console.log(" - jquery.infiniteScrollWithTemplate.js copied");
|
|
54
|
+
console.log(" - jquery.infiniteScrollWithTemplate.min.js copied");
|
|
55
|
+
console.log(" - jquery.infiniteScrollWithTemplate.d.ts copied");
|
|
56
|
+
|
|
57
|
+
function copyFolder(src, dest) {
|
|
58
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
59
|
+
|
|
60
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
61
|
+
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const srcPath = path.join(src, entry.name);
|
|
64
|
+
const destPath = path.join(dest, entry.name);
|
|
65
|
+
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
copyFolder(srcPath, destPath);
|
|
68
|
+
} else {
|
|
69
|
+
fs.copyFileSync(srcPath, destPath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|