Ext.ns('MyApp'); MyApp.LazyTextLoader = Ext.extend(Ext.Panel, { url: '', chunkSize: 100 * 1024, // 100 KB autoScroll: true, border: true, layout: 'border', initComponent: function () { this.loadedSize = 0; this.fileSize = null; this.isLoading = false; this.isFullyLoaded = false; this.statusBar = new Ext.Toolbar.TextItem('Loading...'); this.spinner = new Ext.BoxComponent({ html: '
', style: 'float:right;' }); this.headerBar = new Ext.Toolbar({ region: 'north', height: 30, items: [ this.statusBar, '->', { text: 'Load All', handler: this.loadAllRemaining, scope: this }, this.spinner ] }); this.textBody = new Ext.BoxComponent({ autoEl: { tag: 'pre', cls: 'lazy-text-body', style: 'padding:10px; white-space:pre-wrap; font-family:monospace;', html: '' }, region: 'center' }); this.footerEl = new Ext.BoxComponent({ region: 'south', height: 30, autoEl: { tag: 'div', html: '...', style: 'padding:5px;' } }); this.items = [this.headerBar, this.textBody, this.footerEl]; MyApp.LazyTextLoader.superclass.initComponent.call(this); this.on('afterrender', this.onAfterRender, this); }, onAfterRender: function () { const el = this.textBody.getEl().dom.parentNode; Ext.fly(el).on('scroll', this.maybeLoadMore, this); this.fetchChunk(0, this.chunkSize); }, updateStatus: function () { let msg = `Loaded: ${this.loadedSize} bytes`; if (this.fileSize) { const pct = ((this.loadedSize / this.fileSize) * 100).toFixed(2); msg += ` / ${this.fileSize} bytes (${pct}%)`; } this.statusBar.setText(msg); }, showSpinner: function (show) { const spinner = Ext.fly(this.spinner.getEl().dom).child('.spinner'); if (spinner) spinner.setVisible(show); }, fetchChunk: function (start, end) { this.isLoading = true; this.showSpinner(true); Ext.Ajax.request({ url: this.url, method: 'GET', headers: { 'Range': `bytes=${start}-${end - 1}` }, callback: (options, success, response) => { if (success) { let data = response.responseText; if (!this.fileSize) { const rangeHeader = response.getResponseHeader('Content-Range'); const match = /\/(\d+)$/.exec(rangeHeader); if (match) { this.fileSize = parseInt(match[1], 10); } } const el = this.textBody.getEl(); el.dom.innerText += data; this.loadedSize += data.length; if (this.fileSize && this.loadedSize >= this.fileSize) { this.isFullyLoaded = true; this.footerEl.hide(); } this.updateStatus(); } this.isLoading = false; this.showSpinner(false); } }); }, maybeLoadMore: function () { if (this.isLoading || this.isFullyLoaded) return; const el = this.textBody.getEl().dom.parentNode; const scrollBottom = el.scrollHeight - el.scrollTop - el.clientHeight; if (scrollBottom < 20) { this.fetchChunk(this.loadedSize, this.loadedSize + this.chunkSize); } }, loadAllRemaining: function () { if (this.isLoading || this.isFullyLoaded) return; const remaining = this.fileSize ? this.fileSize - this.loadedSize : this.chunkSize * 10; const end = this.loadedSize + remaining; this.fetchChunk(this.loadedSize, end); } }); // You can use it like this: new MyApp.LazyTextLoader({ renderTo: 'text-loader-root', height: 500, width: 800, url: '/path/to/your/large-file.txt', title: 'Lazy Loaded Text File' });