Closed JeanLucPons closed 3 years ago
:) I hope it will fit your needs! I've used it in a couple of my own projects by now, and I quite enjoy it. It may however be a little confusing at first, so reading this short intro may help: https://dev.to/tarvk/clean-mvc-architecture-with-model-react-5fk7.
Feel free to open any question issues or bug issues if you find any. I am hoping this library may get some more traction in the near future when I release one of my other projects, but currently it's rather unknown and therefor not extensively tested or documented (in the form of questions).
I also hope this lib will also get some success in the future. I plan to use this to replace ATK (https://github.com/tango-controls/atk) we use for building java application for our accelerator control system. I'm just starting but it looks very good and works very well. When i will commit something, i let you inform.
Here is a part my code: The AppDataLoader is an extension of yours where i added a progress item. I modified a bit your Loader to change field names and include directly the DataLoader source. As in our ATK, the attribute list refresh the values by calling refresh() on each model (In our case, a model is typically an attribute coming from our Tango servers that I fetch using GraphQL https://gitlab.com/MaxIV/web-maxiv-tangogql ). If you have any tips (especially for the AttributeList.stopRefresher), don't hesitate to tell me
Keep up the good work :)
import WheelSwitch from './WebATK/widget/WheelSwitch';
import { AttributeList } from './WebATK/core/AttributeList';
import { NumberScalar } from './WebATK/core/NumberScalar';
import { createAttribute } from './WebATK/core/AttributeFactory';
import { AppLoader,AppDataLoader } from './WebATK/core/AppLoader';
import { sleep } from './WebATK/core/Utils';
const gqlURL = "http://vm-web-lab:5004/db";
// Declaration of models
var attList = new AttributeList();
var myNumber1:NumberScalar;
var myNumber2:NumberScalar;
// Create synchronously the models inside a DataLoader
const source = new AppDataLoader(async (loader) => {
myNumber1 = NumberScalar.cast(await createAttribute(gqlURL,"simu/powersupply/1/Current"))
await sleep(1000);
loader.setProgress("50%");
myNumber2 = NumberScalar.cast(await createAttribute(gqlURL,"simu/powersupply/1/Voltage"))
await sleep(1000);
loader.setProgress("100%");
attList.addAttribute(myNumber1);
attList.addAttribute(myNumber2);
attList.setRefreshInterval(3000);
attList.startRefresher();
return "Complete"
}, "Loading");
function App() {
return (
<div>
<AppLoader
source={source}
// The application is loading
onLoading={ (loadingStatus,progress) => {
return(
<div>{loadingStatus}:{progress}</div>
)
}}
// The application loading failed
onError={ e => {
let items = []
items.push(<div>Error(s) occured:</div>);
for(let i=0;i<e.length;i++)
items.push(<div>{e[i].message}</div>);
return(<>{items}</>)
}}
// The application is loaded
whenLoaded={(loadingStatus)=>{
console.log("loadingStatus:"+loadingStatus)
return(
<>
<WheelSwitch model={myNumber1}/>
<WheelSwitch model={myNumber2}/>
<WheelSwitch model={myNumber1}/>
</>
)
}}
/>
</div>
);
}
export default App;
NumberScalar
import { Field, IDataHook } from 'model-react';
import { Attribute } from './Attribute'
class NumberScalar extends Attribute {
// Note: Use Field() when the field is suppossed to trig a dynamic rendering
// of a react component
protected value = new Field(NaN);
protected wValue = new Field(NaN);
setValue(value: number) {
this.value.set(value);
}
getValue(h: IDataHook) {
return this.value.get(h);
}
setWValue(value: number) {
this.wValue.set(value);
}
getWValue(h: IDataHook) {
return this.wValue.get(h);
}
/**
* Runtime casting
* Throws an exception if the given attribute has not the expected type
* @param a Attribute to cast
*/
static cast(a: Attribute): NumberScalar {
if (a instanceof NumberScalar)
return a;
else
throw new TypeError(a.getName() + " is not a number scalar");
}
}
export { NumberScalar };
Attribute class
import {Field, IDataHook} from 'model-react';
import { DevError } from './DevError'
import { toNumber, fetchGQL } from './Utils';
// ---------------------------------------------------------------------
// Tango Attribute super class
// ---------------------------------------------------------------------
class Attribute {
// Note: Use Field() when the field is suppossed to trig a dynamic rendering
// of a react component
protected attName = "";
protected gqlUrl = "";
protected dataFormat = "";
protected dataType = "";
protected isWritable = false;
protected dispLevel = "OPERATOR";
protected description = "";
protected quality = new Field("");
protected label = new Field("");
protected format = new Field("%4.3f");
protected unit = new Field("");
protected min = new Field(NaN);
protected max = new Field(NaN);
protected alarmMin = new Field(NaN);
protected alarmMax = new Field(NaN);
/**
* Construct an empty data source not connected to Tango
*/
constructor() {
}
/**
* Apply default properties to the attribute
* @param gqlUrl GraphQL URL
* @param attName fully qualifed attribute name
* @param obj Attribute properties
*/
setDefaultProperties(gqlUrl:string, attName: string,obj: any) {
this.gqlUrl = gqlUrl;
this.attName = attName;
this.dataType = obj.datatype;
this.dataFormat = obj.dataformat;
this.isWritable = (obj.writable == "READ_WRITE");
this.dispLevel = obj.displevel;
this.description = obj.description;
this.setLabel(obj.label);
this.setUnit(obj.unit);
//this.setFormat(obj.format);
this.setMin(toNumber(obj.minvalue));
this.setMax(toNumber(obj.maxvalue));
this.setAlarmMin(toNumber(obj.minalarm));
this.setAlarmMax(toNumber(obj.maxalarm));
}
setName(name: string) {
this.attName = name;
}
getName() {
return this.attName;
}
setLabel(label: string) {
this.label.set(label);
}
getLabel(h: IDataHook) {
return this.label.get(h);
}
setFormat(format: string) {
this.format.set(format);
}
getFormat(h: IDataHook) {
return this.format.get(h);
}
setUnit(unit: string) {
this.unit.set(unit);
}
getUnit(h: IDataHook) {
return this.unit.get(h);
}
setQuality(quality: string) {
this.quality.set(quality);
}
getQaulity(h: IDataHook) {
return this.quality.get(h);
}
setMin(min: number) {
this.min.set(min);
}
getMin(h: IDataHook) {
return this.min.get(h);
}
setAlarmMin(min: number) {
this.alarmMin.set(min);
}
getAlarmMin(h: IDataHook) {
return this.alarmMin.get(h);
}
setMax(max: number) {
this.max.set(max);
}
getMax(h: IDataHook) {
return this.max.get(h);
}
setAlarmMax(max: number) {
this.alarmMax.set(max);
}
getAlarmMax(h: IDataHook) {
return this.alarmMax.get(h);
}
setValue(value: any) {
throw new Error("Attribute.setValue() cannot be called");
}
setWValue(value: any) {
throw new Error("Attribute.setWValue() cannot be called");
}
async refresh() {
let queryFields: string = "value quality" + (this.isWritable ? " writevalue" : "")
let query: string = "{ attributes(fullNames: [\"" + this.attName + "\"]) { " + queryFields + " } }";
try {
let response = await fetchGQL(this.gqlUrl, query);
let props: any = response.data.attributes[0];
//console.log(this.getName()+"\n"+JSON.stringify(props));
this.setValue(toNumber(props.value));
this.setQuality(props.quality);
if (this.isWritable) {
this.setWValue(toNumber(props.writevalue));
}
} catch (error) {
console.log(error);
this.setValue(NaN);
this.setQuality("ERROR");
if (this.isWritable)
this.setWValue(NaN);
}
}
}
export { Attribute };
AttributeList
import { Attribute } from './Attribute';
import { sleep } from './Utils';
class AttributeList {
private list: Array<Attribute> = []; // List of attribute to refresh
private refreshPeriod: number = 1000; // Polling period in ms
private isRunning = false;
private innerLoopRunning = false;
private stopRequest = false;
private timerId: number = 0;
addAttribute(att: Attribute) {
if(this.isRunning) {
console.log("Cannot add attribute while refresher is running");
return;
}
this.list.push(att)
}
setRefreshInterval(millis: number) {
this.refreshPeriod = millis;
}
async startRefresher() {
if(this.isRunning) {
console.log("Refresher is already running");
return;
}
// Start the refresher loop
this.isRunning = true;
this.stopRequest = false;
this.refresh();
}
async stopRefresher() {
this.stopRequest = true;
window.clearTimeout(this.timerId);
// Fixme: Do busy waiting there and timeout after 3 sec
let timeout = 3000;
if(this.innerLoopRunning) {
while(this.innerLoopRunning && timeout>0) {
await sleep(100);
timeout -= 100;
}
}
if( timeout<=0 ) {
this.isRunning = false;
} else {
console.log("Refresher cannot be stopped");
}
}
private async refresh() {
console.log("Refresh: " + this.list.length + " attributes");
this.innerLoopRunning = true;
let lgth:number = this.list.length
for(let i=0;i<lgth && !this.stopRequest;i++)
await this.list[i].refresh()
this.innerLoopRunning = false;
if(!this.stopRequest)
this.timerId = window.setTimeout(this.refresh.bind(this),this.refreshPeriod);
else
this.isRunning = false;
}
}
export { AttributeList };
Hello, I started a react project and I'm very familiar with the MVC model that I use with java. I'm glad to find your very good quality MVC lib for React. Jean-Luc Thx