New to typescript and have been using it for 2 days now mainly to build a custom component for my streamlit app. I have created the below navigation bar and the custom component is a tab that can be clicked on the sidebar though it is behaving strangely. When I click on the component tab, sometimes it loads the corresponding page and at times it does not as demonstrated below:
custom navigation bar gif
I have a hunch it might be how I wrote the typescript code, perhaps the onClick function. Is there a better way to do this so as to:
return the value input labelName via Streamlit.setComponentValue()
make sure that upon clicking on the component tab, it actually loads the page behind it? I have placed the python code for how it should behave below:
Typescript component
import {
Streamlit,
StreamlitComponentBase,
withStreamlitConnection,
} from "streamlit-component-lib"
import React, { ReactNode } from "react"
interface State {
navLabel: string|number
}
class MyComponent extends StreamlitComponentBase<State> {
public state = {navLabel: ""}
public render = (): ReactNode => {
const labelName = this.props.args["name"]
const iconName = this.props.args["iconName"]
const { theme } = this.props
const styles: React.CSSProperties = {}
return (
<div className="navtab" onClick={this.onClicked}>
<div className="content">
<input type="radio" name="indicator" id="input-data"/>
<div className="list">
<label htmlFor="input-data" className="input-data">
<span><i className="material-icons">{iconName}</i></span>
<span className="text-display">{labelName}</span>
</label>
</div>
</div>
</div>
)
}
private onClicked = (): void => {
this.setState(
prevState => ({navLabel: this.props.args["name"]}),
() => Streamlit.setComponentValue(this.state.navLabel)
)
}
Python execution code
import streamlit as st
import streamlit.components.v1 as components
def my_component(name, iconName, tabIndex, key=None):
component_value = _component_func(name=name, iconName=iconName, tabIndex=tabIndex, key=key, default='Option')
# We could modify the value returned from the component if we wanted.
# There's no need to do this in our simple example - but it's an option.
return component_value
with st.sidebar:
test = my_component(name='Dashboard', iconName='dashboard', tabIndex=1, key="1")
test_2 = my_component(name='Data Analysis', iconName='insights', tabIndex=2, key="2")
test_3 = my_component(name='Testing', iconName='business', tabIndex=3, key="3")
if test == 'Dashboard':
st.title("Dashboard")
st.write('Name of option is {}'.format(test))
elif test_2 == 'Data Analysis':
st.title("Data Analysis")
st.write('Name of option is {}'.format(test_2))
elif test_3 == "Testing":
st.title("Third one")
I was able to sort this out using the li and ul element:
import {
Streamlit,
StreamlitComponentBase,
withStreamlitConnection,
} from "streamlit-component-lib"
import React, { ReactNode } from "react"
interface State {
label: string,
icon: string
}
class MyComponent extends StreamlitComponentBase<State> {
public render = (): ReactNode => {
const labelName:string[] = this.props.args["name"]
const iconName:string[] = this.props.args["iconName"]
let data:any[] = [];
iconName.forEach((v,i) =>
data= [...data, {"id":i+1, "label": labelName[i], "icon":v}]
)
this.state = {
icon:data[0].icon,
label:data[0].label
}
const res = data.map(({id, icon, label}) => (
<span>
<li className="tab"
key={id}
onClick={() => this.setState(
prevState => ({icon:icon, label:label}),
() => Streamlit.setComponentValue(label)
)}><i className="material-icons">{icon}</i><span className="labelName">{label}</span></li></span>
))
return (
<div className="navtab">
<ul className="tab-options">
{res}
</ul>
</div>
)
}
}
Related
I created a table in Django where upon doing row-click, the records for the corresponding row should be POSTED to the function "medicineRequestSelect" in view_doctor.py. However, it is unable to extract that row of values as shown in Image 1. It returns me None values.
I am relatively new to web development and any help or advice will be greatly appreciated!
<doctor_emr.html>
{% block mainbody%}
{% verbatim %}
<div id="app2" class="container">
<div class="emr-table">
<el-table
ref="multipleTable"
:data="list"
stripe
style="width: 50%"
#row-click="handle"
#selection-change="handleSelectionChange">
<el-table-column
prop="id"
label="Index">
</el-table-column>
<el-table-column
prop="order_id"
label="Order ID">
</el-table-column>
<el-table-column
prop="ward_number"
label="Ward No.">
</el-table-column>
<el-table-column
prop="prop"
label="Scan QR"
width="width">
<template slot-scope="{row$index}">
<el-button #click="onScanQR(row)" type="warning" icon="el-icon-camera" size="mini">Scan</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
{% endverbatim %}
<script>
new Vue({
el: '#app2',
data() {
return {
list: []
}
},
mounted() {
this.getemrList()
},
methods: {
getemrList() {
// Obtain EMR list
axios.post(ToDJ('emrList'), new URLSearchParams()).then(res => {
if (res.data.code === 0) {
console.log(res.data.data)
this.list = res.data.data
} else {
this.NotifyFail(res.data.data)
}
})
},
// Purpose: For the row click
handle(row, event, column) {
console.log(row, event, column)
axios.post(ToDJ('medicineRequestSelect'), new URLSearchParams()).then(res => {
if (res.data.code === 0) {
console.log(res.data.data)
this.list = res.data.data
let index = this.list.findIndex(item => {
return item.id == row.id
})
if (index == -1) {
this.$refs.multipleTable.toggleRowSelection(row, true);
this.list.push(row)
} else {
this.$refs.multipleTable.toggleRowSelection(row, false);
this.list.splice(index, 1)
}
} else {
this.NotifyFail(res.data.data)
}
})
},
onScanQR(row) {
this.dialogFormVisible = true;
this.tmForm={...row}
},
// Success notification
NotifySuc(str) {
this.$message({
message: str,
type: 'success'
})
},
// Error notification
NotifyFail(str) {
this.$message({
message: str,
type: 'warning'
})
}
}
})
</script>
{% endblock %}
<view_doctor.py>
#api_view(['GET',"POST"])
def medicineRequestSelect(request):
id = request.POST.get('id')
order_id = request.POST.get('order_id')
ward_number = request.POST.get('ward_number')
print("Values: ", id, order_id, ward_number)
return Action.success()
Image 1: Output Obtained
Image 2: Table I created
I'm also learning, and I try to progress by trying to help other's problem.
First where are you getting your data's from? From your Models?
So if I understood correctly you should import the model you want your data from and then make queries to it using your model.
model = TheNameOfYourModel.objects.all()
and then you can make your queries using q
PS: I'm still learning, i'm curious to see the answer of someone experienced
I'm working on a streamlit app, where i have some datas to manage with streamlit-aggrid, and specialy a date column.
I would like to edit this date column, with a date picker as a popup.
I tried to use a custom cell editor :
DatePicker = JsCode(
"""
class DatePicker {
init(params) {
const template = '
<input type="date" data-input style="width: 100%" />
<a class="fa fa-times">Cliquez pour modifier la date</a>
';
this.params = params;
this.eGui = document.createElement('div');
this.eGui.classList.add('ag-input');
this.eGui.style.height = '100%';
this.eGui.innerHTML = template;
this.eInput = this.eGui.querySelector('input');
this.picker = flatpickr(this.eGui, {
onChange: this.onDateChanged.bind(this),
dateFormat: 'd-m-Y',
wrap: true,
defaultDate: this.params.value,
});
this.picker.calendarContainer.classList.add('ag-custom-component-popup');
this.date = this.params.value;
}
getGui() {
return this.eGui;
}
onDateChanged(selectedDates) {
this.date = selectedDates[0] || this.params.value;
this.params.onDateChanged();
}
getValue() {
return this.eInput.value;
}
}
"""
)
gb.configure_column(
"Terminé le",
cellEditor=DatePicker2,
cellEditorPopup=True,
editable=True,
)
It’s working actualy. But 3 clicks are needed instead of a double, because of the inner html raw.
I am creating a live chart that dynamically updates itself. I am taking a tuple of (current time, random integer) for my chart from a dummy database, sender.py. Here is the code for sender.py-
import time
import random
import json
from datetime import datetime
def get_coordinates():
while True:
json_data = json.dumps(
{
'time' : datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'value' : random.random() * 1000
})
yield "data:{}\n\n".format(json_data)
time.sleep(1)
I share this data to the route, /chart-data, made in app.py -
import os
from flask import Flask, render_template, Response
from sender import get_coordinates
app = Flask(__name__)
#app.route('/chart-data', methods = ['POST'])
def chart_data():
get_coordinates()
return Response(get_coordinates())
#app.route("/")
def index():
return render_template('data.html')
I upload json_data from sender.py to the route /chart-data and want to get this in data.html. Here is the .html file (I am trying to retrieve this data in $.ajax() method ) -
...
<body>
<canvas id = 'myChart' width = "900" height = "400"></canvas>
<script>
var intervalID = setInterval(update_values, 1000);
var x_axis = 0;
var y_axis;
function update_values() {
$.ajax({
url: '/data',
type: 'GET',
success: function(result) {
//want to get x-axis and y-axis from sender.py
},
})
x_axis = x_axis + 1;
myChart.data.labels.push(x_axis);
myChart.data.datasets.forEach((datasets) => {
datasets.data.push(y_axis);
});
myChart.update();
};
...
</script>
</body>
Using a generator to stream data continuously does not make sense in this context. A possible solution is to get the current data with regular AJAX requests.
The following simple example shows you how to create a dynamically updating chart.
The application consists of two traditional endpoints. The former serves the actual page while the latter provides the data. With each request, additional data is generated and delivered in JSON format.
from flask import (
Flask,
jsonify,
render_template
)
from datetime import datetime
import random
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/data')
def data():
return jsonify({
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'value': random.randint(0, 1000)
})
Within the page, data is obtained from the server at regular intervals using the Fetch API. The data obtained is added to the chart. If there are already more than 10 data records, the oldest ones will be removed.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
</head>
<body>
<div>
<canvas id="myChart" width="900" height="400"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script type="text/javascript">
(function(url) {
const limit = 9;
const canvas = document.getElementById('myChart');
const chart = new Chart(canvas, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'MyDataset',
data: []
}]
},
options: {
scales: {
yAxis: {
max: 1000,
min: 0
}
}
}
});
setInterval(async () => {
const data = await fetch(url).then(resp => resp.ok && resp.json());
if (data) {
const { time, value } = data;
const overflow = chart.data.labels.length - limit;
if (overflow > 0) {
chart.data.labels.splice(0, overflow);
chart.data.datasets[0].data.splice(0, overflow);
}
chart.data.labels.push(time);
chart.data.datasets[0].data.push(value);
chart.update();
}
}, 1000);
})({{ url_for('data') | tojson }});
</script>
</body>
</html>
I'm confused about how to do it via Ajax or Json, but how can I send the selection array (curCheck) on-click to Django views and receive it as a python array
javascript
document.getElementById('results').addEventListener('click', function(){
html_table = '<thead><tr><th>Currency</th><th>Amount</th><th>Symbol</th>><tr/><thead/>'
var checkElements = document.getElementsByClassName('ch');
for(var i =0; i< curname.length; i++){
if (checkElements[i].checked) {
var curChecked = curname[i];
var JsonArr = JSON.stringify(curChecked);
postcurChecked(JsonArr)
html_table += '<tr><td>' + curname[i] + '</td>';
}
}
document.getElementById('result_table').innerHTML = html_table;
},false;
ajax
function postsubChecked(curChecked) {
$.ajax({
"url": "http://127.0.0.1:8000/results/",
"type": "POST",
"data": {"checkbox": curChecked},
"headers": { 'X-CSRFToken': getCookie('csrftoken')}
})
}
in django
def currencyChecked(request):
body_unicode = request.body.decode('utf-8')
body_unicode = body_unicode.replace('%22','')
print(body_unicode) json_data = json.loads(body_unicode.read())
I would like to see the python array print to see it is passed to the back
but I keep getting this error:
json_data = json.loads(body_unicode.read()) AttributeError: 'str' object has no attribute 'read'
For getting the selected checkbox values and sending as an array using ajax you can use jquery like this:
consider you have multiple checkboxes and a button.
<input type="checkbox" name="imageName">
<input type="checkbox" name="imageName">
.......
<button id="deletePhoto">Delete</button>
after selecting multiple checkbox values click on this button. On clicking the below jquery will be triggered to make an arrya of selected checkbox values.
//jquery for getting the selelcted checkbox values
$(document).on("click","#deletePhoto",function(){
var favorite = [];//define array
$.each($("input[name='imageName']:checked"), function(){
favorite.push($(this).val());
});
alert("Photo Selected: " + favorite.join(", "));
if(favorite.length == 0){
alert("Select Photo to delete");
return false;
}
//ajax for deleting the multiple selelcted photos
$.ajax({type: "GET",
url: "/olx/deletePhotoFromEdit",
data:{
favorite:favorite
},
success: function(){
// put more stuff here as per your requirement
});
}
});
});
In the view you can get the array like this:
selected_photo = request.GET.getlist('favorite[]')
Is there a way to change how selection field looks? I need to change it to look like a bunch of boolean fields (technically it would be one field, not multiple boolean fields. Only look would change)?
It should look something like this (it looks like there are multiple fields, but technically it should be only one):
And it should function like selection field - that you can only select one value. Is it possible to do it?
Update:
Found this - http://help.openerp.com/question/29061/how-to-add-radio-button-widget/
It seems it is possible to do with widget on OpenERP 8 (using radio widget for selection field). So I think it might be possible to move such functionality in OpenERP 7.
I managed to move radio widget from OpenERP 8 to OpenERP 7. So I will post it how I've done. Maybe some one will need it too.
Basically you only need two main files, one js and one xml (also empty __init__.py is needed, because OpenERP will throw error that it didn't find that module).
in __openerp__.py:
'js': ['static/src/js/widget_radio.js'],
'qweb': ['static/src/xml/widget_radio.xml'],
widget_radio.js (web_widget_radio is addon name):
openerp.web_widget_radio = function (instance)
{
instance.web.form.widgets.add('radio', 'instance.web_widget_radio.FieldRadio');
instance.web_widget_radio.FieldRadio = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
template: 'FieldRadio',
events: {
'click input': 'click_change_value'
},
init: function(field_manager, node) {
/* Radio button widget: Attributes options:
* - "horizontal" to display in column
* - "no_radiolabel" don't display text values
*/
this._super(field_manager, node);
this.selection = _.clone(this.field.selection) || [];
this.domain = false;
},
initialize_content: function () {
this.uniqueId = _.uniqueId("radio");
this.on("change:effective_readonly", this, this.render_value);
this.field_manager.on("view_content_has_changed", this, this.get_selection);
this.get_selection();
},
click_change_value: function (event) {
var val = $(event.target).val();
val = this.field.type == "selection" ? val : +val;
if (val == this.get_value()) {
this.set_value(false);
} else {
this.set_value(val);
}
},
/** Get the selection and render it
* selection: [[identifier, value_to_display], ...]
* For selection fields: this is directly given by this.field.selection
* For many2one fields: perform a search on the relation of the many2one field
*/
get_selection: function() {
var self = this;
var selection = [];
var def = $.Deferred();
if (self.field.type == "many2one") {
var domain = instance.web.pyeval.eval('domain', this.build_domain()) || [];
if (! _.isEqual(self.domain, domain)) {
self.domain = domain;
var ds = new instance.web.DataSetStatic(self, self.field.relation, self.build_context());
ds.call('search', [self.domain])
.then(function (records) {
ds.name_get(records).then(function (records) {
selection = records;
def.resolve();
});
});
} else {
selection = self.selection;
def.resolve();
}
}
else if (self.field.type == "selection") {
selection = self.field.selection || [];
def.resolve();
}
return def.then(function () {
if (! _.isEqual(selection, self.selection)) {
self.selection = _.clone(selection);
self.renderElement();
self.render_value();
}
});
},
set_value: function (value_) {
if (value_) {
if (this.field.type == "selection") {
value_ = _.find(this.field.selection, function (sel) { return sel[0] == value_;});
}
else if (!this.selection.length) {
this.selection = [value_];
}
}
this._super(value_);
},
get_value: function () {
var value = this.get('value');
return value instanceof Array ? value[0] : value;
},
render_value: function () {
var self = this;
this.$el.toggleClass("oe_readonly", this.get('effective_readonly'));
this.$("input:checked").prop("checked", false);
if (this.get_value()) {
this.$("input").filter(function () {return this.value == self.get_value();}).prop("checked", true);
this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : "");
}
}
});
};
widget_radio.xml:
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="FieldRadio">
<span t-attf-class="oe_form_field oe_form_field_radio #{widget.options.horizontal ? 'oe_horizontal' : 'oe_vertical'}" t-att-style="widget.node.attrs.style">
<span t-if="!widget.get('effective_readonly')">
<t t-if="widget.options.horizontal">
<t t-set="width" t-value="Math.floor(100 / widget.selection.length)"/>
<t t-if="!widget.options.no_radiolabel">
<t t-foreach="widget.selection" t-as="selection">
<label t-att-for="widget.uniqueId + '_' + selection[0]" t-att-style="'width: ' + width + '%;'"><t t-esc="selection[1]"/></label>
</t>
<br/>
</t>
<t t-foreach="widget.selection" t-as="selection">
<div t-att-style="'width: ' + width + '%;'">
<span class="oe_radio_input"><input type="radio" t-att-name="widget.uniqueId" t-att-id="widget.uniqueId + '_' + selection[0]" t-att-value="selection[0]"/></span>
</div>
</t>
</t>
<t t-if="!widget.options.horizontal">
<t t-foreach="widget.selection" t-as="selection">
<div>
<span class="oe_radio_input"><input type="radio" t-att-id="widget.uniqueId + '_' + selection[0]" t-att-name="widget.uniqueId" t-att-value="selection[0]"/></span><label t-if="!widget.options.no_radiolabel" t-att-for="widget.uniqueId + '_' + selection[0]"><t t-esc="selection[1]"/></label>
</div>
</t>
</t>
</span>
<span t-if="widget.get('effective_readonly')" class="oe_radio_readonly"><t t-esc="widget.get('value')[1]"/></span>
</span>
</t>
</templates>
P.S. You can find original code in OpenERP trunk version.
Yes. its possible. You can see a similar example in openerp itself. Go to settings / users /access rights tab. There you can see all the list of boolean and selection fields for adding the groups. Actually it is a many2many field related to res.groups and its view is modified in such a way that all the groups that are inherited and under the same category will be viewed as a selection list and all others will be in boolean. Please check the code in the base/ res/res_users.py file. Hope it will be helpful for you.