4 |
5 |
6 |
7 | VueEternalLoading Serve
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/guide/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Yarn
4 | ```
5 | yarn add @ts-pro/vue-eternal-loading
6 | ```
7 |
8 | ## Npm
9 | ```
10 | npm install @ts-pro/vue-eternal-loading
11 | ```
12 |
13 | ## Browser
14 | ```html
15 |
16 |
17 |
18 |
19 | ```
20 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import vue from '@vitejs/plugin-vue';
3 | import { resolve } from 'path';
4 |
5 | export default defineConfig({
6 | plugins: [vue()],
7 | build: {
8 | lib: {
9 | entry: resolve(__dirname, 'src/main.ts'),
10 | name: 'TSPro',
11 | formats: ['cjs', 'umd', 'es'],
12 | },
13 | rollupOptions: {
14 | external: ['vue'],
15 | output: {
16 | globals: {
17 | vue: 'Vue',
18 | },
19 | },
20 | },
21 | },
22 | });
23 |
--------------------------------------------------------------------------------
/src/components/VueEternalLoading/helpers/scroll/scroll.ts:
--------------------------------------------------------------------------------
1 | export function getScrollHeightFromEl($el: HTMLElement): number {
2 | return $el.scrollHeight;
3 | }
4 |
5 | export function getScrollWidthFromEl($el: HTMLElement): number {
6 | return $el.scrollWidth;
7 | }
8 |
9 | export function restoreScrollVerticalPosition(
10 | $el: HTMLElement,
11 | scrollHeight: number
12 | ): void {
13 | $el.scrollTop = $el.scrollHeight - scrollHeight + $el.scrollTop;
14 | }
15 |
16 | export function restoreScrollHorizontalPosition(
17 | $el: HTMLElement,
18 | scrollWidth: number
19 | ): void {
20 | $el.scrollLeft = $el.scrollWidth - scrollWidth + $el.scrollLeft;
21 | }
22 |
--------------------------------------------------------------------------------
/docs/api/types.md:
--------------------------------------------------------------------------------
1 | # Types
2 |
3 | ## LoadAction
4 | ```ts
5 | type LoadAction = {
6 | loaded(count?: number, pageSize?: number): State;
7 | noMore(): void;
8 | noResults(): void;
9 | error(): void;
10 | };
11 | ```
12 | Describes possible actions in `loaded` prop callback.
13 |
14 | ## LoadPayload
15 | ```ts
16 | type LoadPayload = {
17 | isFirstLoad: boolean;
18 | };
19 | ```
20 | Describes payload what we get in `loaded` prop callback.
21 |
22 | ## Position
23 | ```ts
24 | type Position = 'top' | 'left' | 'default';
25 | ```
26 | Describes possible loader positions.
27 |
28 | ## State
29 | Added in `v1.2.0`
30 | ```ts
31 | type State = 'loading' | 'error' | 'no-more' | 'no-results';
32 | ```
33 | Describes possible state for loader.
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": [
15 | "webpack-env",
16 | "jest",
17 | "node",
18 | ],
19 | "paths": {
20 | "@/*": [
21 | "src/*"
22 | ]
23 | },
24 | "lib": [
25 | "esnext",
26 | "dom",
27 | "dom.iterable",
28 | "scripthost"
29 | ]
30 | },
31 | "include": [
32 | "src/**/*.ts",
33 | "src/**/*.tsx",
34 | "src/**/*.vue",
35 | ],
36 | "exclude": [
37 | "node_modules",
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | browser: true,
6 | },
7 | extends: [
8 | 'plugin:vue/vue3-essential',
9 | 'eslint:recommended',
10 | '@vue/typescript/recommended',
11 | '@vue/prettier',
12 | '@vue/prettier/@typescript-eslint',
13 | ],
14 | parserOptions: {
15 | ecmaVersion: 2020,
16 | },
17 | rules: {
18 | 'prettier/prettier': [
19 | 'error',
20 | {
21 | singleQuote: true,
22 | },
23 | ],
24 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
26 | },
27 | overrides: [
28 | {
29 | files: [
30 | '**/__tests__/*.{j,t}s?(x)',
31 | '**/tests/unit/**/*.spec.{j,t}s?(x)',
32 | ],
33 | env: {
34 | jest: true,
35 | },
36 | },
37 | ],
38 | globals: {
39 | defineProps: "readonly",
40 | defineEmits: "readonly",
41 | defineExpose: "readonly",
42 | withDefaults: "readonly"
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/docs/guide/retry-loading.md:
--------------------------------------------------------------------------------
1 | # Retry loading
2 |
3 | Typically, when you encounter the **no-more**, **no-results**, or **error** states in **vue-eternal-loading**, your only option to restart the component is by using the `isInitial` prop. However, it's important to note that using the `isInitial` prop will also reset the `isFirstLoad` state, which may not always be desired. In scenarios where you have caught an error or reached the end and want to retry the loading process, you can utilize the `retry` method available within the `#no-more`, `#no-results`, and `#error` slots.
4 |
5 | Here's an example demonstrating how you can implement a retry button when an error is caught:
6 |
7 | ```html
8 |
9 |
10 | Oops. We've got an error.
11 |
12 |
13 |
14 | ```
15 |
16 |
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Oleksandr Havrashenko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/guide/preloaded-data.md:
--------------------------------------------------------------------------------
1 | # Preloaded data
2 |
3 | Normally, **vue-eternal-loading** calls the `load` prop when you need to fetch data from the server. However, there may be cases where you already have preloaded data from cache or other requests. In such cases, if you render your preloaded data and place **vue-eternal-loading** next to it as usual, the component is unaware of the preloaded data and behaves as if it's the first loading. This can lead to undesired results, such as getting the **no-results** state even when you have data, or having an incorrect value for the `isFirstLoad` prop in the `#loading` slot.
4 |
5 | To prevent this behavior, you can pass a `false` to the `isInitial` prop. This informs **vue-eternal-loading** that it is not the initial loading, and it will adjust its behavior accordingly.
6 |
7 | ```html
8 |
9 | ```
10 |
11 | Or you can pass it using v-model.
12 |
13 | ```html
14 |
15 |
16 | ```
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/api/slots.md:
--------------------------------------------------------------------------------
1 | # Slots
2 |
3 | ## #loading
4 | - Default: `
Loading...
`
5 |
6 | Renders normal loading state when try to load more content.
7 |
8 | - Available scoped slot data:
9 | - `isFirstLoad` - indicates whether it was first load ` ... `
10 |
11 | ## #noMore
12 | - Default: `
No more.
`
13 |
14 | Renders state when we don't have more new content and there is no need to make further loading.
15 |
16 | - Available scoped slot data (added in `v1.1.0`):
17 | - `retry` - activates `loading` state again
18 | ` ... `
19 |
20 | ## #noResults
21 | - Default: `
No results.
`
22 |
23 | Renders case when we don't have even 1 item loaded.
24 |
25 | - Available scoped slot data (added in `v1.1.0`):
26 | - `retry` - activates `loading` state again
27 | ` ... `
28 |
29 | ## #error
30 | - Default: `
Error.
`
31 |
32 | Renders case when we caught an error.
33 |
34 | - Available scoped slot data (added in `v1.1.0`):
35 | - `retry` - activates `loading` state again
36 | ` ... `
37 |
38 |
--------------------------------------------------------------------------------
/docs/guide/reset-state.md:
--------------------------------------------------------------------------------
1 | # Reset state
2 |
3 | Sometimes it is important to reset the state of **vue-eternal-loading** and the `isFirstLoad` flag to their defaults. For instance, when implementing filters on your website, you may want to reset the pagination if any of the filters have been changed. You have the flexibility to reset the component whenever needed, and we can implement a special reset button to demonstrate how it can be done.
4 |
5 | To enable the reset functionality, you need to pass an `isInitial` prop via `v-model` and initialize it with a value of `true`. When the `load` prop is called, the `isInitial` prop will be automatically changed to `false`. If you set the isInitial prop back to true, it will reset the component to its initial state.
6 |
7 | By managing the value of the `isInitial` prop, you can control the reset behavior of the component and trigger a reset whenever necessary.
8 |
9 | ```html
10 |
11 |
12 |
13 |
14 | ```
15 |
16 | ```js
17 | setup() {
18 | // Other props
19 | // ...
20 | const isInitial = ref(true);
21 |
22 | // Other methods
23 | // ...
24 | async function load({ loaded }) {
25 | // Process response logic
26 | }
27 |
28 | function reset() {
29 | // Reset all data
30 | // ...
31 | // This will reset `vue-eternal-loading`
32 | isInitial.value = true;
33 | }
34 |
35 | return {
36 | load,
37 | reset,
38 | isInitial,
39 | // Other data...
40 | };
41 | }
42 | ```
43 |
44 |
45 |
--------------------------------------------------------------------------------
/docs/guide/loader-margin.md:
--------------------------------------------------------------------------------
1 | # Loader margin
2 |
3 | In the previous examples, the `load` prop was triggered when the loader became visible on the screen or within the specified container element (depending on the `container` prop). However, there may be situations where you want to start the loading process a bit earlier to provide a more seamless experience for the user. This can be achieved using the `margin` prop, which accepts parameters in pixels or percentages as a string.
4 |
5 | The `margin` prop is passed as the `rootMargin` parameter to the underlying `IntersectionObserver`. You can refer to the [Mozilla Developer Network (MDN) documentation](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) for more information on the different formats and options for `rootMargin`.
6 |
7 | In most cases, specifying the `margin` as a single value in pixels (e.g., `200px`) will cover the most common scenarios, so there is no need to delve into the details of other format options unless required for specific use cases.
8 |
9 | ```html
10 |
11 |
12 | ```
13 |
14 |
15 |
16 | In the example mentioned above, we created an invisible bounding box with a margin of `200px` around our `VueEternalLoading` component markup. This means that the load prop will be triggered `200px` earlier, as if the bounding box were `200px` larger in all directions.
17 |
18 | The significant aspect to note is that the `margin` prop does not affect your layout in any way, unlike the **css margin** property, which pushes content if specified. The `margin` prop purely influences the triggering of the `load` prop and the timing of when the loading process starts, ensuring a smoother user experience.
19 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
54 |
55 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/docs/guide/manually-change-states.md:
--------------------------------------------------------------------------------
1 | # Manually Change States
2 |
3 |
4 | In **vue-eternal-loading**, you can manually change the state, which can be useful when you have more complex or custom logic that goes beyond what the `loaded` callback offers.
5 |
6 | Let's write a logic where we don't rely on the current page size, but instead, use another field in the response (e.g., **total_pages**). With this field, we can determine that if the current page number equals the total pages count, the state should be changed to **no-more**.
7 |
8 | ```js
9 | function load({ loaded, noMore }) {
10 | // Load data from server
11 | // ...
12 | if (page < response.total_pages) {
13 | loaded();
14 | } else {
15 | noMore();
16 | }
17 | }
18 | ```
19 |
20 |
21 | ---
22 |
23 | To set the **no-results** state if we receive an empty response for the first loading, we can utilize the second argument in the `load` prop method. This argument is a payload that contains the `isFirstLoad` flag. By checking the value of the `isFirstLoad` flag, we can determine whether it is the first load or subsequent loads. In the case of the first load and an empty response, we can manually set the state to **no-results**.
24 |
25 | ```js
26 | function load({ loaded, noMore, noResults }, { isFirstLoad }) {
27 | // Load data from server
28 | // ...
29 | if (items.length === 0) {
30 | if (isFirstLoad) {
31 | noResults();
32 | } else {
33 | noMore();
34 | }
35 | } else {
36 | loaded();
37 | }
38 | }
39 | ```
40 |
41 |
42 |
43 | ---
44 |
45 | It is indeed a good practice to handle errors in an application and provide clear feedback to the user. That's why the **error** state is offered in **vue-eternal-loading**. If something unfavorable occurs and you need to display an error message to the user, you can manually set the state to **error**. This can be done if you receive an error response from the server. Additionally, you can utilize the `#error` slot to show a custom error message to the user. This allows you to have more control over the error handling and display relevant information to the user when an error occurs.
46 |
47 | ```html
48 |
49 |
50 |
51 | Oops! Looks like we've got an error.
52 |
53 |
54 |
55 | ```
56 | ```js
57 | load({ loaded, error }) {
58 | // Load users from server
59 | this.loadUsers(this.page).then((users) => {
60 | // Add users to an array
61 | // ...
62 | loaded();
63 | }).catch(() => {
64 | error();
65 | })
66 | }
67 | ```
68 |
69 |
--------------------------------------------------------------------------------
/docs/guide/styling-with-slots.md:
--------------------------------------------------------------------------------
1 | # Styling with slots
2 |
3 | **vue-eternal-loading** provides 4 slots for each state, allowing you to set custom templates for each of them. The default templates for each state were described in the previous section. You can style the templates using regular CSS. If you require different markup, text, or more complex customization, you can pass your own template to the appropriate slot.
4 |
5 | ```html
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Trying to load content...
16 |
17 |
18 |
19 |
20 |
21 | There is no more content.
22 |
23 |
24 |
25 |
26 |
27 |
28 | It's empty here.
29 |
30 |
31 |
32 |
33 |
34 | Oops. We've got an error.
35 |
36 |
37 |
38 |
39 |
40 | ```
41 |
42 |
43 |
44 | ---
45 |
46 | Alternatively, you can use a fancy Bootstrap spinner or any other spinner component by providing your own custom template in the loading slot:
47 |
48 |
49 |
50 | ---
51 |
52 | In the loading slot, you have access to the `isFirstLoad` data through the scoped slot. This data can be utilized when you want to display a different loader for the first time, such as a skeleton loading animation or any other specific design or content.
53 |
54 | ```html
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | First loading attempt
67 |
68 |
69 |
70 |
71 |
72 | Loading...
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | ```
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/guide/loader-positions.md:
--------------------------------------------------------------------------------
1 | # Loader positions
2 |
3 | **vue-eternal-loading** offers four loader positions: `top`, `left`, `right`, and `bottom`. In the examples provided earlier, we used the `bottom` position, which means that the **vue-eternal-loading** component was positioned below the loaded items. In such cases, we do not need to pass any specific props to indicate the component's position.
4 |
5 | However, for the `top` and `left` positions, special props need to be passed. This is because when loading new content that is appended to the top or left of a container, it does not affect the scroll position towards our content. In other words, if you have scrolled down by 100 pixels and then load 200 pixels of new content at the top, it will be fine because the top content and scroll position remain in the same position. The same principle applies to the `right` loader position.
6 |
7 | By providing the necessary props for the `top` and `left` positions, you can ensure that the loading behavior and positioning work correctly, maintaining the desired scroll and content alignment.
8 |
9 |
10 |
11 | As you can see, we simply position the loader to the right using CSS without requiring any additional steps, and the functionality works seamlessly.
12 |
13 | If we simply position the loader at the top or left, it won't function in the same manner. This is because when new content is added to the top or left, the browser retains the scroll position. However, the content displayed at that scroll position will be different. As a result, we need to adjust the scroll position by the difference between the sizes of the old and new content.
14 |
15 | Fortunately, **vue-eternal-loading** takes care of these calculations for us. It automatically handles the necessary adjustments to the scroll position, accounting for the changes in content size. With **vue-eternal-loading**, we don't have to manually manage these complex calculations; the component handles them seamlessly behind the scenes.
16 |
17 | To specify the loader's position, we need to provide the `position` prop with one of the following values:
18 | - **default**: This is the default value for the bottom or right positions, and it does not need to be specified manually.
19 | - **top**: Use this value if the loader is positioned above the items.
20 | - **left**: Use this value if the loader is positioned to the left of the items.
21 |
22 | If your scroll area encompasses the entire document, specifying the `container` prop is not necessary. However, if your scroll area is a custom element, you must pass this element as a `ref` value to the `container` prop. It's required because we need to change scroll position and it's important to know where the scroll is:
23 |
24 | ```html
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ```
34 |
35 | ```js
36 | setup() {
37 | // Other props
38 | // ...
39 | const containerRef = ref();
40 |
41 | // Other methods
42 | // ...
43 | function load({ loaded }) {
44 | // Load logic
45 | }
46 |
47 | return {
48 | load,
49 | containerRef,
50 | // Other data
51 | };
52 | }
53 | ```
54 |
55 | Top scroll example:
56 |
57 |
58 |
59 | Left scroll example:
60 |
61 |
62 |
--------------------------------------------------------------------------------
/dist/vue-eternal-loading.mjs:
--------------------------------------------------------------------------------
1 | import { defineComponent as H, ref as _, watchEffect as N, watch as I, openBlock as O, createElementBlock as P, unref as u, renderSlot as v, normalizeProps as p, mergeProps as g, createCommentVNode as w, createElementVNode as h, nextTick as B } from "vue";
2 | function R(o) {
3 | return o.scrollHeight;
4 | }
5 | function T(o) {
6 | return o.scrollWidth;
7 | }
8 | function W(o, i) {
9 | o.scrollTop = o.scrollHeight - i + o.scrollTop;
10 | }
11 | function z(o, i) {
12 | o.scrollLeft = o.scrollWidth - i + o.scrollLeft;
13 | }
14 | const C = /* @__PURE__ */ h("div", { class: "loading" }, "Loading...", -1), M = /* @__PURE__ */ h("div", { class: "no-more" }, "No more.", -1), j = /* @__PURE__ */ h("div", { class: "no-results" }, "No results.", -1), A = /* @__PURE__ */ h("div", { class: "error" }, "Error.", -1), G = /* @__PURE__ */ H({
15 | __name: "VueEternalLoading",
16 | props: {
17 | load: {
18 | required: !0,
19 | type: Function
20 | },
21 | isInitial: {
22 | required: !1,
23 | type: Boolean,
24 | default: !0
25 | },
26 | position: {
27 | required: !1,
28 | type: String,
29 | default: "default"
30 | },
31 | container: {
32 | required: !1,
33 | type: Object,
34 | default: null
35 | },
36 | margin: {
37 | required: !1,
38 | type: String,
39 | default: void 0
40 | }
41 | },
42 | emits: ["update:isInitial"],
43 | setup(o, { emit: i }) {
44 | const t = o, l = _();
45 | let s = _("loading"), r = _(t.isInitial), c = 0;
46 | function f() {
47 | B(() => {
48 | var e, n;
49 | t.position === "top" ? W(
50 | (e = t.container) != null ? e : document.documentElement,
51 | c
52 | ) : t.position === "left" && z(
53 | (n = t.container) != null ? n : document.documentElement,
54 | c
55 | );
56 | });
57 | }
58 | function L(e, n) {
59 | return e === 0 ? r.value ? (b(), "no-results") : (y(), "no-more") : e !== void 0 && n !== void 0 && e < n ? (y(), "no-more") : (r.value = !1, f(), d(), "loading");
60 | }
61 | function y() {
62 | r.value = !1, a("no-more"), f();
63 | }
64 | function b() {
65 | r.value = !1, a("no-results"), f();
66 | }
67 | function F() {
68 | r.value = !1, a("error"), f();
69 | }
70 | function q() {
71 | r.value = !0, a("loading"), d();
72 | }
73 | function E() {
74 | a("loading"), d();
75 | }
76 | function a(e) {
77 | s.value = e;
78 | }
79 | function S() {
80 | l.value && m.unobserve(l.value);
81 | }
82 | function d() {
83 | l.value && m.observe(l.value);
84 | }
85 | function V() {
86 | return new IntersectionObserver(
87 | ([e]) => {
88 | var n, k;
89 | e.isIntersecting && (t.position === "top" ? c = R(
90 | (n = t.container) != null ? n : document.documentElement
91 | ) : t.position === "left" && (c = T(
92 | (k = t.container) != null ? k : document.documentElement
93 | )), S(), t.load(
94 | {
95 | loaded: L,
96 | noMore: y,
97 | noResults: b,
98 | error: F
99 | },
100 | {
101 | isFirstLoad: r.value
102 | }
103 | ));
104 | },
105 | {
106 | root: t.container,
107 | threshold: 0,
108 | rootMargin: t.margin
109 | }
110 | );
111 | }
112 | let m;
113 | return typeof IntersectionObserver < "u" && N(
114 | () => {
115 | m && S(), m = V(), d();
116 | },
117 | {
118 | flush: "post"
119 | }
120 | ), I(
121 | () => t.isInitial,
122 | (e) => {
123 | e && q();
124 | }
125 | ), I(r, (e) => {
126 | e || i("update:isInitial", !1);
127 | }), (e, n) => (O(), P("div", {
128 | class: "vue-eternal-loading",
129 | ref_key: "rootRef",
130 | ref: l
131 | }, [
132 | u(s) === "loading" ? v(e.$slots, "loading", p(g({ key: 0 }, { isFirstLoad: u(r) })), () => [
133 | C
134 | ]) : u(s) === "no-more" ? v(e.$slots, "no-more", p(g({ key: 1 }, { retry: E })), () => [
135 | M
136 | ]) : u(s) === "no-results" ? v(e.$slots, "no-results", p(g({ key: 2 }, { retry: E })), () => [
137 | j
138 | ]) : u(s) === "error" ? v(e.$slots, "error", p(g({ key: 3 }, { retry: E })), () => [
139 | A
140 | ]) : w("", !0)
141 | ], 512));
142 | }
143 | });
144 | export {
145 | G as VueEternalLoading
146 | };
147 |
--------------------------------------------------------------------------------
/docs/guide/simple-usage.md:
--------------------------------------------------------------------------------
1 | # Simple usage
2 |
3 | Here is a basic example of how to use **vue-eternal-loading**.
4 |
5 | When using **vue-eternal-loading**, the only required prop you need to implement is `load`. This callback method will be automatically called when it's time to load more data. `load` takes two arguments, which will be described later. For now, we only need the first one.
6 |
7 | ```html
8 |
9 | ```
10 | ```ts
11 | const PAGE_SIZE = 5;
12 |
13 | // We must pass this method to the component,
14 | // and it will be called automatically when needed.
15 | async function load({ loaded }) {
16 | // Load your data from the server or any other source.
17 | const loadedItems = await loadItems(page);
18 | items.value.push(...loadedItems);
19 | page += 1;
20 | // Call the `loaded` callback with 2 arguments:
21 | // 1. The number of items we have loaded
22 | // 2. Our page size to know when we have reached the end
23 | loaded(loadedItems.length, PAGE_SIZE);
24 | }
25 | ```
26 |
27 | > **_NOTE:_** You can find a detailed explanation and explore other possibilities of the component in the following sections.
28 |
29 | ## Example
30 |
31 | Here, you can scroll down to view more content. When you reach the end, you will see the message "No more." All the texts in this example are set to their default values.
32 |
33 |
34 |
35 | ## TypeScript
36 | ```vue
37 |
38 |
125 |
126 |
159 | ```
160 |
161 |
--------------------------------------------------------------------------------
/docs/guide/loading-states.md:
--------------------------------------------------------------------------------
1 | # Loading states
2 |
3 | The **vue-eternal-loading** component has four different states that can render different templates and influence the behavior of the component:
4 |
5 | - **loading**: This is the default state when we are trying to load new content. In this state, the `load` prop triggers automatically when needed. Default template: `
Loading...
`
6 |
7 |
8 | - **no-more**: This state indicates that we have reached the end of the available content. It occurs when the server responds with empty content or content that is less than a full page. In this state, the `load` prop is not called anymore. Default template: `
No more.
`
9 |
10 |
11 | - **no-results**: This state means that we have no content at all. It can occur when we attempt to load content from the server, but the initial request returns zero items. In such cases, we may want to display a "No results" message. In this state, the `load` prop is not called anymore. Default template: `
No results.
`
12 |
13 |
14 | - **error** - This state indicates that we encountered an error from the server or any other source. In this state, the `load` prop is not called anymore. Default template: `
Error.
`
15 |
16 | We can automatically switch between states by using the `loaded` callback within the `load` prop method, which will be described below. Alternatively, we can manually set any state, and we will explain this in a later section.
17 |
18 | ---
19 |
20 | In some cases, we may not want to have a state other than **loading**. For example, when implementing a loading feature that should never stop, such as loading logs, real-time news, or continuously attempting to load something. To achieve this behavior, we can call the `loaded` callback without any parameters.
21 |
22 | ```js
23 | function load({ loaded }) {
24 | // Load data from server
25 | // ...
26 | loaded();
27 | }
28 | ```
29 |
30 |
31 | ---
32 |
33 | If we use the `loaded` callback with one parameter (items count), we can reach two states: **no-more** and **no-results**. We might want to have these states in order to render the corresponding templates. If we call `loaded(0)` during our first load, we will enter the **no-results** state.
34 | ```js
35 | function load({ loaded }) {
36 | // Load data from server
37 | // ...
38 | // And items.length === 0
39 | loaded(items.length);
40 | }
41 | ```
42 |
43 |
44 | If we call `loaded(0)` during our second or subsequent load, we will enter the **no-more** state. This indicates that we have previously loaded content, but we have now reached the end and there is no more content available to load.
45 | ```js
46 | function load({ loaded }) {
47 | // Load data from server
48 | // ...
49 | // items.length === 0 and this is 2+ try
50 | loaded(items.length);
51 | }
52 | ```
53 |
54 |
55 | In the example mentioned above, we encountered an extra request before reaching the **no-more** state. This occurs because we don't know the exact page size, and we can only set the **no-more** state if we receive an empty response. This situation is acceptable when the page size is unknown or when the number of items per request may vary. However, if you expect a specific item count per page, it is good practice to pass a second parameter to the `loaded` callback, where you can specify your page size. This helps prevent unnecessary extra requests to the server and allows us to set the **no-more** state when we receive an items count less than the page size.
56 | ```js
57 | const PAGE_SIZE = 5;
58 |
59 | function load({ loaded }) {
60 | // Load data from server
61 | // ...
62 | loaded(items.length, PAGE_SIZE);
63 | }
64 | ```
65 |
66 |
67 | ---
68 |
69 | We have one more state called **error**, but it cannot be reached automatically just by using the `loaded` callback. This is because **vue-eternal-loading** is not aware of loading errors, and it switches states based on the information you pass to the `loaded` callback. The information provided is not sufficient to set the **error** state. In the upcoming sections, we will learn how to manually set the **error** state.
70 |
71 | ---
72 |
73 | If you want to know the current state inside the `load` function, the `loaded()` callback can return it for you.
74 | ```js
75 | function load({ loaded }) {
76 | // Load data from server
77 | // ...
78 | const state = loaded();
79 | if (state === 'no-more') {
80 | alert('Boom! You have reached the end.')
81 | }
82 | }
83 | ```
84 |
--------------------------------------------------------------------------------
/src/components/VueEternalLoading/VueEternalLoading.vue:
--------------------------------------------------------------------------------
1 |
2 |