From 2fbafa76044ecbe27ca8b963cceb91c2e1f64f9e Mon Sep 17 00:00:00 2001 From: dkrasner Date: Fri, 13 May 2022 19:40:48 +0200 Subject: [PATCH 01/12] fixing typo in docs/examples/consumer.py --- docs/examples/consumer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/examples/consumer.py b/docs/examples/consumer.py index bb1a58e75..98c7ab568 100644 --- a/docs/examples/consumer.py +++ b/docs/examples/consumer.py @@ -3,19 +3,20 @@ schema = Schema("hello_world") + @schema.define class Message: timestamp = float message = str -db = connect('localhost', 8000, 'TOKEN') + +db = connect("localhost", 8000, "TOKEN") db.subscribeToSchema(schema) ts = 0 while True: - with db.view(): - Message(timestamp=time.time(), - message='message_5') + with db.transaction(): + Message(timestamp=time.time(), message="message_5") messages = Message.lookupAll() for m in messages: if m.timestamp > ts: From ee3855e55e23952af512b75247b6b294dd9a97f9 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Fri, 13 May 2022 21:41:10 +0200 Subject: [PATCH 02/12] adding a cells.md introduction to cells file --- docs/cells.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/cells.md diff --git a/docs/cells.md b/docs/cells.md new file mode 100644 index 000000000..6ef7bbd55 --- /dev/null +++ b/docs/cells.md @@ -0,0 +1,70 @@ +### Cells ### + +Cells represents an abstraction of the web, tightly coupled with object database, which allows you to create "reactive" interface in python.... + + +### Preamble ### + +In order to get ourselves up and running we'll need a few things: + +* Object Database installed (see the [installation docs](../INSTALLATION.md) for more details) +* an object_database engine service instance (see [here](./object_engine.md) for more information) +* a configured and installed web service instance, which will be responsible for building and serving the web application +* and an installed instance of the service we'd like to run + +After this we can start up our web server and service. + +Lets walk through it (as in the other examples we'll use "TOKEN" for our special token): + +In a python virtual environment boot up Object Database engine like so: +``` + object_database_service_manager \ + localhost \ + localhost \ + 8000 \ + Master \ + --run_db \ + --service-token TOKEN \ + --source ./odb/source \ + --storage ./odb/storage +``` + +In another python virtual environment instance run the following: +``` +# export our special Object Database token +export ODB_AUTH_TOKEN=TOKEN + +# install the ActiveWebService +object_database_service_config install \ +--class object_database.web.ActiveWebService.ActiveWebService \ +--placement Master + +# configure ActiveWebService +object_database_service_config configure ActiveWebService \ +--port 8080 --hostname localhost --internal-port 8081 + +# check to make sure it is listed +object_database_service_config list + +# start it up +object_database_service_config start ActiveWebService + +# check to see that it is running +object_database_service_config instances +``` + +NOTE: you can always open `http://localhost:8080/` in your browser to see the running services and click to see what they are. + +#### Running a boring web app ### + +Run the following in your virtual environment: + +``` +object_database_service_config install --class object_database.service_manager.ServiceBase.ServiceBase --placement Master + +object_database_service_config instances +``` + +(NOTE: you might need to change the paths to the [ServiceBase](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/service_manager/ServiceBase.py) file depending on the directory you are running this from.) + +If you head to `http://localhost:8080/services/ServiceBase` you will see our really boring service. The simple text holder card is what [ServiceBase.serviceDisplay](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/service_manager/ServiceBase.py#L67) returns. In the next example, we'll subclass `ServiceBase` and change this method to do something more interesting. From 5f10af88fdcfe025eea787ff5dc3034630b1bcf1 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Sat, 21 May 2022 11:56:06 +0200 Subject: [PATCH 03/12] linting --- docs/cells.md | 9 ++++++--- docs/examples/aws_start.sh | 22 ++++++++++++++++++++++ docs/examples/cells.py | 16 ++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 docs/examples/aws_start.sh create mode 100644 docs/examples/cells.py diff --git a/docs/cells.md b/docs/cells.md index 6ef7bbd55..6c2f1947d 100644 --- a/docs/cells.md +++ b/docs/cells.md @@ -53,9 +53,9 @@ object_database_service_config start ActiveWebService object_database_service_config instances ``` -NOTE: you can always open `http://localhost:8080/` in your browser to see the running services and click to see what they are. +NOTE: you can always open [http://localhost:8080/](http://localhost:8080/) in your browser to see the running services and click to see what they are. Also, if you get tired of running the above commands, there is a small bash script found [here](./examples/aws_start.sh). -#### Running a boring web app ### +#### Running a boring web app #### Run the following in your virtual environment: @@ -67,4 +67,7 @@ object_database_service_config instances (NOTE: you might need to change the paths to the [ServiceBase](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/service_manager/ServiceBase.py) file depending on the directory you are running this from.) -If you head to `http://localhost:8080/services/ServiceBase` you will see our really boring service. The simple text holder card is what [ServiceBase.serviceDisplay](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/service_manager/ServiceBase.py#L67) returns. In the next example, we'll subclass `ServiceBase` and change this method to do something more interesting. +If you head to [http://localhost:8080/services/ServiceBase](http://localhost:8080/services/ServiceBase) you will see our really boring service. The simple text holder card is what [ServiceBase.serviceDisplay](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/service_manager/ServiceBase.py#L67) returns. In the next example, we'll subclass `ServiceBase` and change this method to do something more interesting. + + +#### Running a more interesting web app #### diff --git a/docs/examples/aws_start.sh b/docs/examples/aws_start.sh new file mode 100644 index 000000000..e17134a7f --- /dev/null +++ b/docs/examples/aws_start.sh @@ -0,0 +1,22 @@ +## Active Web Service startup script + +# export our special Object Database token +export ODB_AUTH_TOKEN=TOKEN + +# install the ActiveWebService +object_database_service_config install \ +--class object_database.web.ActiveWebService.ActiveWebService \ +--placement Master + +# configure ActiveWebService +object_database_service_config configure ActiveWebService \ +--port 8080 --hostname localhost --internal-port 8081 + +# check to make sure it is listed +object_database_service_config list + +# start it up +object_database_service_config start ActiveWebService + +# check to see that it is running +object_database_service_config instances diff --git a/docs/examples/cells.py b/docs/examples/cells.py new file mode 100644 index 000000000..5d9254d47 --- /dev/null +++ b/docs/examples/cells.py @@ -0,0 +1,16 @@ +import object_database.web.cells as cells +from object_database import ServiceBase + + +class SomethingMoreInteresting(ServiceBase): + def initialize(self): + self.buttonName = "click me" + return + + @staticmethod + def serviceDisplay(serviceObject, instance=None, objType=None, queryArgs=None): + return cells.Card(cells.button("Click me", onClick)) + + +def onClick(): + return "thanks" From ed629c2c1f5150f1642094c814e1fabab7bcdac8 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Thu, 2 Jun 2022 13:52:03 +0200 Subject: [PATCH 04/12] Update cells.md updating cells.md docs npm install/bundle build instructions minor correction to basic service install --- docs/cells.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/cells.md b/docs/cells.md index 6c2f1947d..f63a8915d 100644 --- a/docs/cells.md +++ b/docs/cells.md @@ -9,6 +9,10 @@ In order to get ourselves up and running we'll need a few things: * Object Database installed (see the [installation docs](../INSTALLATION.md) for more details) * an object_database engine service instance (see [here](./object_engine.md) for more information) +* build the bundle for the services landing page + * `cd object_database/object_database/web/content` + * create a node environment and source it + * run `npm install && npm run build` * a configured and installed web service instance, which will be responsible for building and serving the web application * and an installed instance of the service we'd like to run @@ -61,7 +65,11 @@ Run the following in your virtual environment: ``` object_database_service_config install --class object_database.service_manager.ServiceBase.ServiceBase --placement Master - +# check to see it is installed +object_database_service_config list +# start +object_database_service_config start ServiceBase +# check to see it is running object_database_service_config instances ``` From 989f679894bcf325667a3213625a4757368a8c68 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Thu, 2 Jun 2022 13:21:17 +0000 Subject: [PATCH 05/12] adding a cells.py and cells_odb.py examples --- docs/examples/__init__.py | 0 docs/examples/cells.py | 14 ++++--- docs/examples/cells_odb.py | 81 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 docs/examples/__init__.py create mode 100644 docs/examples/cells_odb.py diff --git a/docs/examples/__init__.py b/docs/examples/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/examples/cells.py b/docs/examples/cells.py index 5d9254d47..3cb393d8f 100644 --- a/docs/examples/cells.py +++ b/docs/examples/cells.py @@ -9,8 +9,12 @@ def initialize(self): @staticmethod def serviceDisplay(serviceObject, instance=None, objType=None, queryArgs=None): - return cells.Card(cells.button("Click me", onClick)) - - -def onClick(): - return "thanks" + return cells.Card( + cells.Panel( + cells.Button("Reload", lambda: "") + + cells.Button("Service Base", lambda: "ServiceBase") + + cells.Button("Active Web Service", lambda: "ActiveWebService") + ), + header="This is a 'card' cell with some buttons", + padding="10px" + ) diff --git a/docs/examples/cells_odb.py b/docs/examples/cells_odb.py new file mode 100644 index 000000000..8512d90a6 --- /dev/null +++ b/docs/examples/cells_odb.py @@ -0,0 +1,81 @@ +from object_database import Schema, ServiceBase +import object_database.web.cells as cells +import time +import random + +# define a 'schema', which is a collection of classes we can subscribe to as a group +schema = Schema("cells_obd") + +# define a type of entry in odb. We'll have one instance of this class for each +# message in the database +@schema.define +class Message: + timestamp = float + message = str + lifetime = float + + +class AnODBService(ServiceBase): + def initialize(self): + # make sure we're subscribed to all objects in our schema. + self.db.subscribeToSchema(schema) + + def doWork(self, shouldStop): + # this is the main entrypoint for the service - it gets to do work here. + while not shouldStop.is_set(): + #wake up every 100ms and look at the objects in the ODB. + time.sleep(.1) + + # delete any messages more than 10 seconds old + with self.db.transaction(): + # get all the messages + messages = Message.lookupAll() + + for m in messages: + if m.timestamp < time.time() - m.lifetime: + # this will actually delete the object from the ODB. + m.delete() + + @staticmethod + def serviceDisplay(serviceObject, instance=None, objType=None, queryArgs=None): + # make sure cells has loaded these classes in the database and subscribed + # to all the objects. + cells.ensureSubscribedSchema(schema) + + def newMessage(): + # calling the constructor creates a new message object. Even though we + # orphan it immediately, we can always get it back by calling + # Message.lookupAll() + # because ODB objects have an explicit lifetime (they have to be destroyed) + Message(timestamp=time.time(), message=editBox.currentText.get(), lifetime=20) + + # reset our edit box so we can type again + editBox.currentText.set("") + + # define an 'edit box' cell. The user can type into this. + editBox = cells.SingleLineTextBox(onEnter=lambda newText: newMessage()) + + return cells.Panel( + editBox >> cells.Button( + "New Message", + newMessage + ) + ) + ( + cells.Table( + colFun=lambda: ['timestamp', 'lifetime', 'message'], + rowFun=lambda: sorted(Message.lookupAll(), key=lambda m: -m.timestamp), + headerFun=lambda x: x, + rendererFun=lambda m, col: cells.Subscribed( + lambda: + cells.Timestamp(m.timestamp) if col == 'timestamp' else + m.message if col == 'message' else + cells.Dropdown( + m.lifetime, + [1, 2, 5, 10, 20, 60, 300], + lambda val: setattr(m, 'lifetime', val) + ) + ), + maxRowsPerPage=100, + fillHeight=True + ) + ) From d63e4c09cd6fc9ea182000b1a493e0305dc5b41a Mon Sep 17 00:00:00 2001 From: dkrasner Date: Thu, 2 Jun 2022 15:26:58 +0200 Subject: [PATCH 06/12] wrapping up examples/cells.py and adding examples/cells_odb.py --- docs/examples/__init__.py | 0 docs/examples/cells.py | 14 ++++--- docs/examples/cells_odb.py | 81 ++++++++++++++++++++++++++++++++++++++ quickstart/Dockerfile | 11 ++++-- 4 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 docs/examples/__init__.py create mode 100644 docs/examples/cells_odb.py diff --git a/docs/examples/__init__.py b/docs/examples/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/examples/cells.py b/docs/examples/cells.py index 5d9254d47..3cb393d8f 100644 --- a/docs/examples/cells.py +++ b/docs/examples/cells.py @@ -9,8 +9,12 @@ def initialize(self): @staticmethod def serviceDisplay(serviceObject, instance=None, objType=None, queryArgs=None): - return cells.Card(cells.button("Click me", onClick)) - - -def onClick(): - return "thanks" + return cells.Card( + cells.Panel( + cells.Button("Reload", lambda: "") + + cells.Button("Service Base", lambda: "ServiceBase") + + cells.Button("Active Web Service", lambda: "ActiveWebService") + ), + header="This is a 'card' cell with some buttons", + padding="10px" + ) diff --git a/docs/examples/cells_odb.py b/docs/examples/cells_odb.py new file mode 100644 index 000000000..8512d90a6 --- /dev/null +++ b/docs/examples/cells_odb.py @@ -0,0 +1,81 @@ +from object_database import Schema, ServiceBase +import object_database.web.cells as cells +import time +import random + +# define a 'schema', which is a collection of classes we can subscribe to as a group +schema = Schema("cells_obd") + +# define a type of entry in odb. We'll have one instance of this class for each +# message in the database +@schema.define +class Message: + timestamp = float + message = str + lifetime = float + + +class AnODBService(ServiceBase): + def initialize(self): + # make sure we're subscribed to all objects in our schema. + self.db.subscribeToSchema(schema) + + def doWork(self, shouldStop): + # this is the main entrypoint for the service - it gets to do work here. + while not shouldStop.is_set(): + #wake up every 100ms and look at the objects in the ODB. + time.sleep(.1) + + # delete any messages more than 10 seconds old + with self.db.transaction(): + # get all the messages + messages = Message.lookupAll() + + for m in messages: + if m.timestamp < time.time() - m.lifetime: + # this will actually delete the object from the ODB. + m.delete() + + @staticmethod + def serviceDisplay(serviceObject, instance=None, objType=None, queryArgs=None): + # make sure cells has loaded these classes in the database and subscribed + # to all the objects. + cells.ensureSubscribedSchema(schema) + + def newMessage(): + # calling the constructor creates a new message object. Even though we + # orphan it immediately, we can always get it back by calling + # Message.lookupAll() + # because ODB objects have an explicit lifetime (they have to be destroyed) + Message(timestamp=time.time(), message=editBox.currentText.get(), lifetime=20) + + # reset our edit box so we can type again + editBox.currentText.set("") + + # define an 'edit box' cell. The user can type into this. + editBox = cells.SingleLineTextBox(onEnter=lambda newText: newMessage()) + + return cells.Panel( + editBox >> cells.Button( + "New Message", + newMessage + ) + ) + ( + cells.Table( + colFun=lambda: ['timestamp', 'lifetime', 'message'], + rowFun=lambda: sorted(Message.lookupAll(), key=lambda m: -m.timestamp), + headerFun=lambda x: x, + rendererFun=lambda m, col: cells.Subscribed( + lambda: + cells.Timestamp(m.timestamp) if col == 'timestamp' else + m.message if col == 'message' else + cells.Dropdown( + m.lifetime, + [1, 2, 5, 10, 20, 60, 300], + lambda val: setattr(m, 'lifetime', val) + ) + ), + maxRowsPerPage=100, + fillHeight=True + ) + ) diff --git a/quickstart/Dockerfile b/quickstart/Dockerfile index 5116d5ba5..46e0d3bd8 100644 --- a/quickstart/Dockerfile +++ b/quickstart/Dockerfile @@ -1,17 +1,20 @@ # Using 3.8 because typed_python doesn't seem to # compile with 3.9 (as of 2022-04-27) -FROM python:3.8-slim as builder +FROM python:3.9-slim as builder -ADD . /opt/object_database +ADD ./object_database /opt/object_database +ADD ./typed_python /opt/typed_python RUN apt update -y -qq \ && apt upgrade -y -qq \ + && apt-get update \ && apt install -y -qq curl libssl-dev build-essential git npm \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN pip install --upgrade pip \ - && pip install typed_python \ + && cd /opt/typed_python \ + && pip install -e . \ && cd /opt/object_database \ && pip install -e . @@ -19,4 +22,4 @@ RUN cd /opt/object_database/object_database/web/content \ && npm install \ && npm run build -CMD ["object_database_webtest"] \ No newline at end of file +CMD ["object_database_webtest"] From 4f7279e8c95f0c478bec338a550f83d377604aea Mon Sep 17 00:00:00 2001 From: dkrasner Date: Thu, 2 Jun 2022 15:41:18 +0200 Subject: [PATCH 07/12] Update cells.md Wrapping up cells.md more examples and ODB example --- docs/cells.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/cells.md b/docs/cells.md index f63a8915d..2def0e57e 100644 --- a/docs/cells.md +++ b/docs/cells.md @@ -79,3 +79,38 @@ If you head to [http://localhost:8080/services/ServiceBase](http://localhost:808 #### Running a more interesting web app #### + +Take a look at [cells.py]('./examples/cells.py'). You'll see we made a subclass of `ServiceBase` and overrode its `.serviceDisplay` method. There is card with a header and some buttons which all route you to the corresponding URI (in our case the list of services we have running), plus a little bit of styling. + +Lets install our more interesting app like above: +``` +object_database_service_config install --class docs.examples.cells.SomethingMoreInteresting --placement Master +object_database_service_config start SomethingMoreInteresting +``` + +Note: when you make changes to `SomethingMoreInteresting` you need to reinstall it, with the above command. If you see a message like `Cannot set codebase of locked service 'SomethingMoreInteresting'` then click the "Locked" icon in [http://localhost:8080/services]([http://localhost:8080/services) to unlock it, then reinstall. + + +You can learn more about cells by perusing the [cells](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/cells) directory or taking a look at the [cells test example](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/CellsTestService.py#L63) + +#### Running an ODB web app #### + +But before we wrap up, we should really build a cells examples which works with Object Database, since that's the main point here. + +I am going to skip over details of how ODB works. Please here [here](https://github.com/APrioriInvestments/object_database/blob/dev/docs/object_engine.md) for an introduction. + +We'll be building an `AnODBService` which you can find [here](https://github.com/APrioriInvestments/object_database/blob/daniel-examples/docs/examples/cells_odb.py). You'll see we need to define +* a [schema](https://github.com/APrioriInvestments/object_database/blob/daniel-examples/docs/examples/cells_odb.py#L12) +* how our app will [interact with ODB](https://github.com/APrioriInvestments/object_database/blob/daniel-examples/docs/examples/cells_odb.py#L23) +* and the [UI](https://github.com/APrioriInvestments/object_database/blob/daniel-examples/docs/examples/cells_odb.py#L40) for the app itself. + +The app will send and recieve messages from the database, and update the UI which consists largely of a Panel and Table cell. The key departure here from the previous examples is the lambda functions passed to the cells. Instead of returning something like a string (which then tells the service to route to the correponding URI), these interact with the DB via the `Message` class. + +As before we'll need to install and start the service: +Lets install our more interesting app like above: +``` +object_database_service_config install --class docs.examples.cells_db.AnODBService --placement Master +object_database_service_config start AnODBService +``` + +We'll learn more about cells and how to develop them in the upcoming `cells_dev.md` doc. From b12c63f3a29402ce43e3724be599f8f31e5fb76d Mon Sep 17 00:00:00 2001 From: dkrasner Date: Fri, 3 Jun 2022 11:02:41 +0000 Subject: [PATCH 08/12] adding first part of cells_dev.md --- docs/cells_dev.md | 99 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/cells_dev.md diff --git a/docs/cells_dev.md b/docs/cells_dev.md new file mode 100644 index 000000000..e0c2bab58 --- /dev/null +++ b/docs/cells_dev.md @@ -0,0 +1,99 @@ +## Cells Development ## + +This document is intended for those interested in developing cells. We'll see how to customize currently available cells, as well as develop ones to add to the object database ecosystem. + +We'll assume you have a decent idea about how things fit together (cells, object database, typed python and so on). But if you need a refresher on specifics take a look at the corresponding docs [here](https://github.com/APrioriInvestments/object_database/tree/docs). We'll also assume that you have gone through the [cells.md](./cells.md) doc and will be using the services developed there as a starting point. + + +### Basic Overview ### + +Leaving the server and related backend infrastructure aside, cells consist of two main components. The [python classes](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/cells) which are the cells themselves and the corresponding [JS classes](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/content/src/components) which are responsible for generated the html/js/css etc. Every python cell has a corresponding JS cell, but not all of these strictly generate DOM elements. Some are utilities for styling, layouts, events etc. + +For example, +* border: [python](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/cells/border.py) and [JS](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/content/src/components/Border.js) +* layout/flex: [python](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/cells/flex.py) and [JS](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/content/src/components/Flex.js) +* key events: [JS](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/content/src/components/KeyAction.js) + + +### Making changes ### + +In our [cells.md](./cells.md) doc we made a [__SlightlyMoreInteresting__ service](./examples/cells.py) where we strung together a number of button cells which linked to the different installed services. + +Take a look at the [Button.build()](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/content/src/components/Button.js#L32). You'll see that we use 'hyperscript' h() notation to build the actual DOM elements. + +Suppose you decide that all buttons need to have a padding, as a inline style, you can add `style: "padding: 5px"` argument to h(), then rebuild the bundle (`npm run build` in the [content](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/content) directory) and refresh. You should see the button names padded with 5px. + +Of course this is a hardcoded change, which you cannot control from the python side, so lets try to make this a bit more configurable. + +Lets go to our [python Button]() class and a `padding="5px"` keyword argument and export the data to the JS side which happens in the `Button.recalculate()` method. + +Your python Button classshould now look like this: +``` +class Button(Clickable): + def __init__(self, *args, small=False, active=True, style="primary", + padding="5px", **kwargs): + Clickable.__init__(self, *args, **kwargs) + self.small = small + self.active = active + self.style = style + self.padding = padding + + def recalculate(self): + super().recalculate() + + self.exportData["small"] = bool(self.small) + self.exportData["active"] = bool(self.active) + self.exportData["style"] = self.style + self.exportData["padding"] = self.padding +``` + +The `padding` argument will now be sent alogn with props and you could handle it on the JS side as you needed. For example, your JS Button class could now look like this: +``` +class Button extends ConcreteCell { + constructor(props, ...args){ + super(props, ...args); + + // Bind context to methods + this.onClick = this.onClick.bind(this); + this._getHTMLClasses = this._getHTMLClasses.bind(this); + + this.buttonDiv = null; + // Our new padding arg! + self.padding = props.padding; + } + +... + + build() { + this.buttonDiv = h('div', { + id: this.getElementId(), + "data-cell-id": this.identity, + "data-cell-type": "Button", + class: this._getHTMLClasses(), + style: `padding: ${self.padding}`, // using padding here! + onclick: this.onClick, + tabindex: -1, + onmousedown: (event) => { + // prevent the event from causing us to focus since we just want a + // click + event.preventDefault(); + } + }, [this.renderChildNamed('content')]); + + let res = h( + 'div', + {'class': 'allow-child-to-fill-space button-holder'}, + [this.buttonDiv] + ); + + this.applySpacePreferencesToClassList(this.buttonDiv); + this.applySpacePreferencesToClassList(res); + + return res; + } +... +``` + +Of course you could add more logic, as well as change things beoynd styling (the DOM element itself, event handling and so on). + + From c15bca2f090f33e87c376e48429450cf3073ab5b Mon Sep 17 00:00:00 2001 From: dkrasner Date: Fri, 3 Jun 2022 14:10:39 +0000 Subject: [PATCH 09/12] wrapping up a new cell dev documentation and examples --- docs/cells_dev.md | 91 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/docs/cells_dev.md b/docs/cells_dev.md index e0c2bab58..075f5cbee 100644 --- a/docs/cells_dev.md +++ b/docs/cells_dev.md @@ -96,4 +96,95 @@ class Button extends ConcreteCell { Of course you could add more logic, as well as change things beoynd styling (the DOM element itself, event handling and so on). +### Making a new cell ### +If the current collections of cells is not sufficient for you can make a new one. + +There are three steps here: +* create a Python cells class +* create the corresponding JS cells class +* register the classes at the object database system and module levels, as well as with the web bundle + +Lets create an `OurNewCell` cell that displays some text with a few basic options. + +Create a file `our_new_cell.py` in the [cells directory](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/cells) with the following code: + +``` +from object_database.web.cells.cell import Cell + +class OurNewCell(Cell): + def __init__(self, text, makeBold=False): + super().__init__() + self.text = test + self.bold = makeBold + + def recalculate(self): + self.exportData["text"] = self.text + self.exportData["bold"] = self.bold +``` + +Create a file `OurNewCell.js` in the js [components directory](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/content/src/components) with the following code: + +``` +import {ConcreteCell} from './ConcreteCell'; +import {makeDomElt as h} from './Cell'; + +class OurNewCell extends ConcreteCell { + constructor(props, ...args){ + super(props, ...args); + this.bold = props.bold; + this.text = props.text; + } + + build() { + let style = "text-align: center"; + if(this.bold){ + style += "; font-weight: bold"; + }; + let res = h( + 'div', + {style: style}, + [this.text] + ); + return res; + } +} + +export {OurNewCell, OurNewCell as default}; +``` + +Update the various registers with your new cells class: +* [JS components registry](https://github.com/APrioriInvestments/object_database/blob/b20b6c280b09f7381c9ac9900945a33e234eb621/object_database/web/content/ComponentRegistry.js) +* [object database module init](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/cells/__init__.py) + +Now lets update our [cells.py](./examples/cells.py) examples we used in [cells.md](./cells.md) to use our new cell: +``` +import object_database.web.cells as cells +from object_database import ServiceBase + + +class SomethingMoreInteresting(ServiceBase): + def initialize(self): + self.buttonName = "click me" + return + + @staticmethod + def serviceDisplay(serviceObject, instance=None, objType=None, queryArgs=None): + return cells.Card( + cells.Panel( + cells.OurNewCell("This is our new cell", makeBold=True) + + cells.Button("Reload", lambda: "") + + cells.Button("Service Base", lambda: "ServiceBase") + + cells.Button("Active Web Service", lambda: "ActiveWebService") + ), + header="This is a 'card' cell with some buttons", + padding="10px" + ) +``` + +The last step is to rebuild the bundle `npm run build` in the [componentsi](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/content/src/components) directory and then reinstall our service like so: +``` +object_database_service_config install --class docs.examples.cells.SomethingMoreInteresting --placement Master +``` + +You should see your new cell appear when you click on the `SomethingMoreInteresting` service. From a281b1e436c1de22b73fde390200e294d86270f8 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Fri, 3 Jun 2022 17:56:13 +0200 Subject: [PATCH 10/12] Update cells_dev.md fixing typos and cleaning up language in cells_dev.md --- docs/cells_dev.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/cells_dev.md b/docs/cells_dev.md index 075f5cbee..e9d4a5677 100644 --- a/docs/cells_dev.md +++ b/docs/cells_dev.md @@ -1,6 +1,6 @@ ## Cells Development ## -This document is intended for those interested in developing cells. We'll see how to customize currently available cells, as well as develop ones to add to the object database ecosystem. +This document is intended for those interested in developing cells. We'll see how to customize currently available cells, as well as develop new ones to add to the object database ecosystem. We'll assume you have a decent idea about how things fit together (cells, object database, typed python and so on). But if you need a refresher on specifics take a look at the corresponding docs [here](https://github.com/APrioriInvestments/object_database/tree/docs). We'll also assume that you have gone through the [cells.md](./cells.md) doc and will be using the services developed there as a starting point. @@ -9,7 +9,7 @@ We'll assume you have a decent idea about how things fit together (cells, object Leaving the server and related backend infrastructure aside, cells consist of two main components. The [python classes](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/cells) which are the cells themselves and the corresponding [JS classes](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/content/src/components) which are responsible for generated the html/js/css etc. Every python cell has a corresponding JS cell, but not all of these strictly generate DOM elements. Some are utilities for styling, layouts, events etc. -For example, +For some starter examples take a look at the following: * border: [python](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/cells/border.py) and [JS](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/content/src/components/Border.js) * layout/flex: [python](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/cells/flex.py) and [JS](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/content/src/components/Flex.js) * key events: [JS](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/content/src/components/KeyAction.js) @@ -19,13 +19,13 @@ For example, In our [cells.md](./cells.md) doc we made a [__SlightlyMoreInteresting__ service](./examples/cells.py) where we strung together a number of button cells which linked to the different installed services. -Take a look at the [Button.build()](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/content/src/components/Button.js#L32). You'll see that we use 'hyperscript' h() notation to build the actual DOM elements. +The first task will be to modify the button. Lets take a look at the [Button.build()](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/content/src/components/Button.js#L32). You'll see that we use 'hyperscript' h() notation to build the actual DOM elements. -Suppose you decide that all buttons need to have a padding, as a inline style, you can add `style: "padding: 5px"` argument to h(), then rebuild the bundle (`npm run build` in the [content](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/content) directory) and refresh. You should see the button names padded with 5px. +Suppose you decide that all buttons need to have a padding, as an inline style. For this simply add `style: "padding: 5px"` argument to h(), then rebuild the bundle (`npm run build` in the [content](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/content) directory) and refresh. You should see the button names padded with 5px. Of course this is a hardcoded change, which you cannot control from the python side, so lets try to make this a bit more configurable. -Lets go to our [python Button]() class and a `padding="5px"` keyword argument and export the data to the JS side which happens in the `Button.recalculate()` method. +Lets go to our [python Button]() class and a `padding="5px"` keyword argument. Then we'll make sure that to export the data to the JS side which happens in the `Button.recalculate()` method. Your python Button classshould now look like this: ``` @@ -37,7 +37,9 @@ class Button(Clickable): self.active = active self.style = style self.padding = padding - + + ... + def recalculate(self): super().recalculate() @@ -45,9 +47,10 @@ class Button(Clickable): self.exportData["active"] = bool(self.active) self.exportData["style"] = self.style self.exportData["padding"] = self.padding + ... ``` -The `padding` argument will now be sent alogn with props and you could handle it on the JS side as you needed. For example, your JS Button class could now look like this: +The `padding` argument will now be sent along with props, which subsequently can be handled on the JS side as needed. For example, the JS Button class could now look like this: ``` class Button extends ConcreteCell { constructor(props, ...args){ @@ -98,7 +101,7 @@ Of course you could add more logic, as well as change things beoynd styling (the ### Making a new cell ### -If the current collections of cells is not sufficient for you can make a new one. +If the current collections of cells is not sufficient, you can always make a new one. There are three steps here: * create a Python cells class @@ -182,7 +185,7 @@ class SomethingMoreInteresting(ServiceBase): ) ``` -The last step is to rebuild the bundle `npm run build` in the [componentsi](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/content/src/components) directory and then reinstall our service like so: +The last step is to rebuild the bundle `npm run build` in the [components](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/content/src/components) directory and then reinstall our service as before: ``` object_database_service_config install --class docs.examples.cells.SomethingMoreInteresting --placement Master ``` From 6f188f7bd1be9f546572357c1078ebcd4da14728 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Fri, 22 Jul 2022 20:29:53 +0200 Subject: [PATCH 11/12] Update cells.md adding odb webtest decription the cells.md readme --- docs/cells.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/cells.md b/docs/cells.md index 2def0e57e..821725b28 100644 --- a/docs/cells.md +++ b/docs/cells.md @@ -114,3 +114,8 @@ object_database_service_config start AnODBService ``` We'll learn more about cells and how to develop them in the upcoming `cells_dev.md` doc. + + +#### ODB Cells Playground #### + +ODB provides a playground where you can explore and see examples of various cells in action. Running `object_database_webtest` and then heading to [http://localhost:8000/services](http://localhost:8000) you will see a cells test service. If you update the code in the editor and press ctrl-n-enter the cell will refresh in the browser. This is one of the better way to explore cells. From 7647e6856c55cba7efdcf2b59006eb1ad0f6a935 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Fri, 29 Jul 2022 15:01:44 +0200 Subject: [PATCH 12/12] Update cells.md --- docs/cells.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/cells.md b/docs/cells.md index 821725b28..c668994f8 100644 --- a/docs/cells.md +++ b/docs/cells.md @@ -119,3 +119,26 @@ We'll learn more about cells and how to develop them in the upcoming `cells_dev. #### ODB Cells Playground #### ODB provides a playground where you can explore and see examples of various cells in action. Running `object_database_webtest` and then heading to [http://localhost:8000/services](http://localhost:8000) you will see a cells test service. If you update the code in the editor and press ctrl-n-enter the cell will refresh in the browser. This is one of the better way to explore cells. + +#### A Note About slots #### + +In the ODB playground (mentioned above) you will see a number of examples with `cells.Slot()`'s and `cells.Subscribed()`'s. A cell which is subscribed to a slot will automatically update when the contents of the slot change. This can happen from within the UI itselt on the client side, or from the ODB on the server side. Slots allow you to build reactive web applications. + +The slot API is very simple `slot.set([value])` sets the value and `slot.get([value])` retrieves it. + +Here is an example of a dropdown cell element which displays the contents of a slot but also changes those contents based on the user selection. + +```python +slot = cells.Slot("you haven't picked anything yet") + +dropdown = cells.Subscribed( + lambda: + cells.Dropdown( + str(slot.get()), + ["option 1", "option 2", "option 3"], + lambda i: slot.set(i) + ) + ) +``` + +The dropdown selection calls `lambda i: slot(i)` which takes the selected value and sets the slot, while the display value is defined by `slot.get()`. The dropdown is notified when the slot value changes, calls .`get()` on it and udpates the display.