Skip to content

Commit 3196d09

Browse files
committed
move js code to a separate file
1 parent 8117898 commit 3196d09

File tree

3 files changed

+106
-62
lines changed

3 files changed

+106
-62
lines changed

bigframes/display/anywidget.py

Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from importlib import resources
1516
from typing import Iterator
1617
import uuid
1718

@@ -27,68 +28,10 @@ class TableWidget(anywidget.AnyWidget):
2728
An interactive, paginated table widget for BigFrames DataFrames.
2829
"""
2930

30-
# The _esm variable contains the JavaScript source code for the frontend
31-
# component of the widget.
32-
_esm = """
33-
function render({ model, el }) {
34-
const container = document.createElement('div');
35-
container.innerHTML = model.get('table_html');
36-
37-
const buttonContainer = document.createElement('div');
38-
const prevPage = document.createElement('button');
39-
const label = document.createElement('span');
40-
const nextPage = document.createElement('button');
41-
prevPage.type = 'button';
42-
nextPage.type = 'button';
43-
prevPage.textContent = 'Prev';
44-
nextPage.textContent = 'Next';
45-
46-
// update button states and label
47-
function updateButtonStates() {
48-
const totalPages = Math.ceil(model.get('row_count') / model.get('page_size'));
49-
const currentPage = model.get('page');
50-
51-
// Update label
52-
label.textContent = `Page ${currentPage + 1} of ${totalPages}`;
53-
54-
// Update button states
55-
prevPage.disabled = currentPage === 0;
56-
nextPage.disabled = currentPage >= totalPages - 1;
57-
}
58-
59-
// Initial button state setup
60-
updateButtonStates();
61-
62-
prevPage.addEventListener('click', () => {
63-
let newPage = model.get('page') - 1;
64-
if (newPage < 0) {
65-
newPage = 0;
66-
}
67-
console.log(`Setting page to ${newPage}`)
68-
model.set('page', newPage);
69-
model.save_changes();
70-
});
71-
72-
nextPage.addEventListener('click', () => {
73-
const newPage = model.get('page') + 1;
74-
console.log(`Setting page to ${newPage}`)
75-
model.set('page', newPage);
76-
model.save_changes();
77-
});
78-
79-
model.on('change:table_html', () => {
80-
container.innerHTML = model.get('table_html');
81-
updateButtonStates(); // Update button states when table changes
82-
});
83-
84-
buttonContainer.appendChild(prevPage);
85-
buttonContainer.appendChild(label);
86-
buttonContainer.appendChild(nextPage);
87-
el.appendChild(container);
88-
el.appendChild(buttonContainer);
89-
}
90-
export default { render };
91-
"""
31+
@property
32+
def _esm(self):
33+
"""Load JavaScript code from external file."""
34+
return resources.read_text(bigframes.display, "table_widget.js")
9235

9336
page = traitlets.Int(0).tag(sync=True)
9437
page_size = traitlets.Int(25).tag(sync=True)

bigframes/display/table_widget.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const ModelProperty = {
18+
TABLE_HTML: 'table_html',
19+
ROW_COUNT: 'row_count',
20+
PAGE_SIZE: 'page_size',
21+
PAGE: 'page',
22+
};
23+
24+
const Event = {
25+
CHANGE_TABLE_HTML: `change:${ModelProperty.TABLE_HTML}`,
26+
CLICK: 'click',
27+
};
28+
29+
/**
30+
* Renders a paginated table and its controls into a given element.
31+
* @param {{
32+
* model: !Backbone.Model,
33+
* el: !HTMLElement
34+
* }} options
35+
*/
36+
function render({model, el}) {
37+
const container = document.createElement('div');
38+
container.innerHTML = model.get(ModelProperty.TABLE_HTML);
39+
40+
const buttonContainer = document.createElement('div');
41+
const prevPage = document.createElement('button');
42+
const label = document.createElement('span');
43+
const nextPage = document.createElement('button');
44+
45+
prevPage.type = 'button';
46+
nextPage.type = 'button';
47+
prevPage.textContent = 'Prev';
48+
nextPage.textContent = 'Next';
49+
50+
/** Updates the button states and page label based on the model. */
51+
function updateButtonStates() {
52+
const totalPages = Math.ceil(
53+
model.get(ModelProperty.ROW_COUNT) / model.get(ModelProperty.PAGE_SIZE));
54+
const currentPage = model.get(ModelProperty.PAGE);
55+
56+
label.textContent = `Page ${currentPage + 1} of ${totalPages}`;
57+
prevPage.disabled = currentPage === 0;
58+
nextPage.disabled = currentPage >= totalPages - 1;
59+
}
60+
61+
/**
62+
* Updates the page in the model.
63+
* @param {number} direction -1 for previous, 1 for next.
64+
*/
65+
function handlePageChange(direction) {
66+
const currentPage = model.get(ModelProperty.PAGE);
67+
const newPage = Math.max(0, currentPage + direction);
68+
if (newPage !== currentPage) {
69+
model.set(ModelProperty.PAGE, newPage);
70+
model.save_changes();
71+
}
72+
}
73+
74+
prevPage.addEventListener(Event.CLICK, () => handlePageChange(-1));
75+
nextPage.addEventListener(Event.CLICK, () => handlePageChange(1));
76+
77+
model.on(Event.CHANGE_TABLE_HTML, () => {
78+
// Note: Using innerHTML can be a security risk if the content is
79+
// user-generated. Ensure 'table_html' is properly sanitized.
80+
container.innerHTML = model.get(ModelProperty.TABLE_HTML);
81+
updateButtonStates();
82+
});
83+
84+
// Initial setup
85+
updateButtonStates();
86+
87+
buttonContainer.appendChild(prevPage);
88+
buttonContainer.appendChild(label);
89+
buttonContainer.appendChild(nextPage);
90+
el.appendChild(container);
91+
el.appendChild(buttonContainer);
92+
}
93+
94+
export default {render};

owlbot.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@
107107
"BigQuery DataFrames provides DataFrame APIs on the BigQuery engine",
108108
)
109109

110+
# Include JavaScript files for anywidget
111+
assert 1 == s.replace( # MANIFEST.in
112+
["MANIFEST.in"],
113+
"recursive-include bigframes *.json *.proto py.typed",
114+
"recursive-include bigframes *.json *.proto *.js py.typed",
115+
)
116+
110117
# Don't omit `*/core/*.py` when counting test coverages
111118
assert 1 == s.replace( # .coveragerc
112119
[".coveragerc"],

0 commit comments

Comments
 (0)