Coder Social home page Coder Social logo

codelv / enaml-web Goto Github PK

View Code? Open in Web Editor NEW
99.0 15.0 17.0 42.4 MB

Build interactive websites with enaml

Home Page: https://codelv.com/projects/enaml-web/

License: MIT License

Python 96.16% Makefile 0.25% C 3.59%
enaml lxml python web-components web

enaml-web's Introduction

Enaml Web

status codecov Downloads

A web component toolkit for enaml that let's you build websites in python declaratively.

You can use enaml-web to build "interactive" websites using python, enaml, and a few lines of simple javascript (see the simple pandas dataframe viewer example). The view state (dom) is stored on the server as an enaml view and interaction works by syncing changes between between the client(s) and server using websockets (or polling).

To demonstrate, the following interaction is all handled with enaml-web

interactive-websites-in-python-with-enaml

Examples

See the examples folder

Have a site? Feel free to share it!

Why?

It makes it easy to build websites without a lot of javascript.

Short intro

To use enaml web, you simply replace html tags with the enaml component (the capitalized tag name). For example:

from web.components.api import *

enamldef Index(Html):
    Head:
        Title:
            text = "Hello world"
    Body:
        H1:
            text = "Render a list!"
        Ul:
            Looper:
                iterable = range(3)
                Li:
                    style = 'color: blue' if loop.index & 1 else ''
                    text = loop.item

Calling render() on an instance of this enaml view then generates the html from the view. This is shown in the simple case of a static site generator:

import enaml
from web.core.app import WebApplication

# Create an enaml Application that supports web components
app = WebApplication()

# Import Index from index.enaml
with enaml.imports():
    from index import Index

# Render the Index.enaml to index.html
view = Index()
with open('index.html', 'w') as f:
    f.write(view.render())

You can also use it in a request handler with your favorite web framework. For example with tornado web you can do something like this:

import enaml
import tornado.web
import tornado.ioloop
from web.core.app import WebApplication

# Import Index from index.enaml
with enaml.imports():
    from index import Index

class IndexHandler(tornado.web.RequestHandler):
    view = Index()
    def get(self, request):
        return self.view.render(request=request)

class Application(tornado.web.Application):
    def __init__(self):
        super(Application, self).__init__([
                (r'/',IndexHandler)
           ],
        )

if __name__ == "__main__":
    web_app = WebApplication()
    app = Application()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

So what's the advantage over plain html?

It's as simple as html but it's python so you can, loop over lists, render conditionally, format variables, etc...

Also, it's not just formatting a template, the server maintains the page state so you can interact with the page after it's rendered. This is something that no other python template frameworks can do (to my knowledge).

How it works

It simply generates a tree of lxml elements.

Advantages

  1. Inherently secure

Since it's using lxml elements instead of text, your template code is inherently secure from injection as lxml automatically escapes all attributes. A closing tag cannot be accidentally missed.

The atom framework provides additional security by enforcing runtime type checking and optional validation.

  1. Minified by default

Other templates engines often render a lot of useless whitespace. This does not. The response is always minified.

  1. No template tags needed

Some template engines require the use of "template tags" wrapped in {% %} or similar to allow the use of python code to transform variables.

Since enaml is python, you can use any python code directly in your enaml components and templates. You don't need any template tags.

  1. Templates can be modified

The tree can be modified after it's rendered to react to events or data changes. These changes can be propogated out to clients (see the data binding section).

  1. Component based

Since enaml views are like python classes, you can "subclass" and extend any component and extend it's functionality. This enables you to quickly build reusable components. This is like "web components" but it's rendered server side so it's not slow. See materialize-ui for an example.

Disadvantages

  1. Memory usage

Even though lxml is written in c and enaml uses atom objects, the memory usage may still be more than plain string templates.

  1. HTML only

It only works with html.

Data binding

Because enaml-web is generating a dom, you can use websockets and some js to manipulate the dom to do data binding between the client to server.

The dom can be shared per user or per session making it easy to create collaborative pages or they can be unique to each page.

Data binding

Each node has a unique identifier and can be modified using change events. An example of this is in the examples folder.

You can also have the client trigger events on the server and have the server trigger JS events on the client.

To use:

  1. Include a client side script in your page to process modified events
  2. Observe the modified event of an Html node and pass these changes to the client via websockets.
  3. Make a handlers on the server side to update dom accordingly.

See app.js for an example client side handler and app.py for an example server side handler.

Modified events

The modified events will be a dict. The keys depend on the event type but the general format is:

{
  'id': 'id-of-node', # ID of node where the event originated
  'type': 'update', # Type of event, eg, update, added, removed, etc..
  'name': 'attr-modified', # Attr name that was modified, eg `cls` or `children`
  'value': object, # Depends on the event type
  # May have other events
}

For example, changing the style attribute on a node will generate an event like

{
  'id': 'id-of-a-node',
  'type': 'update',
  'name': 'style',
  'value': 'color: blue',
  'oldvalue': 'color: red'
}

Inserting a new list item node will generate an event like

{
  'id': 'id-of-my-list',
  'type': 'added',
  'name': 'children',
  'value': '<li>New item</li>',
  'before': 'id-of-node-to-insert-before', 
}

The full list of events can be found in the base Tag by searching for _notify_modified calls. You can also generate your own custom events as needed.

Data models

Forms can automatically be generated and populated using enaml's DynamicTemplate nodes. An implementation of the AutoForm using the materalize css framework is available on my personal repo. With this, we can take a model like:

from atom.api import Atom, Unicode, Bool, Enum

class Message(Atom):
    name = Unicode()
    email = Unicode()
    message = Unicode()
    options = Enum("Email","Phone","Text")
    sign_up = Bool(True)

Then use the AutoForm node and pass in either a new or populated instance of the model to render the form.

from templates import Base
from web.components.api import *
from web.core.api import Block


enamldef AddMessageView(Base): page:
    attr message
    Block:
        block = page.content
        AutoForm:
            model << message

Database ORM with Atom

For working with a database using atom see atom-db

Raw, Markdown, and Code nodes

TheRaw node parses text into dom nodes (using lxml's html parser). Similarly Markdown and Code nodes parse markdown and highlight code respectively.

For example, you can show content from a database like tihs:

from web.components.api import *
from web.core.api import *
from myapp.views.base import Page

enamldef BlogPage(Page):
    attr page_model: SomeModel # Page model
    body.cls = 'template-blogpage'
    Block:
        block = parent.content
        Raw:
            # Render source from database
            source << page_model.body

This let's you use web wysiwyg editors to insert content into the dom. If the content is not valid it will not mess up the rest of the page.

Block nodes

You can define a base template, then overwrite parts using the Block node.

In one file put:

from web.components.api import *
from web.core.api import Block

enamldef Base(Html):
    attr user
    attr site
    attr request
    alias content
    Head:
        Title:
            text << site.title
    Body:
        Header:
            text = "Header"
        Block: content:
            pass
        Footer:
            text = "Footer"

Then you can import that view and extend the template and override the block's content.

from templates import Base
from web.components.api import *
from web.core.api import Block

enamldef Page(Base): page:
    Block:
        block = page.content
        P:
            text = "Content inserted between Header and Footer"

Blocks let you either replace, append, or prepend to the content.

Gotchas

Text and tail nodes

Lxml uses text and tail properties to set text before and after child nodes, which can be confusing.

For instance in html you can do

<p>This is a sentence <a href="#">click here</a> then keep going</p>

To make this with enaml you need to do this:

P:
    text = "This is a sentence"
    A:
        href = "#"
        text = "click here"
        tail = "then keep going"

Notice how tail is set on the A NOT the P. See lxml etree documentation for more details.

Tag attribute

When creating a custom Tag, the tag attribute must be set to change what html tag is used for a node. For example:

enamldef Svg(Tag):
    tag = 'svg' # Force tag to be 'svg'

This will then render a <svg>...</svg> tag.

Note: In previous versions (0.8.8 and below) the tag name defaulted to the lowercase class name. This is no longer done to eliminate a function call per node and to avoid having to explicitly redefine the tag when subclassing.

Generic attributes

The html definitions only expose the commonly used attributes of each node, such as cls, style, and those specific to the tag (such as or href for a link).

Custom attributes or attributes which can't be set as a name in python (such as data-tooltip) can defined by assigning attrs to a dict of attr value pairs.

enamldef Tooltip(Span):
    attrs = {'data-tooltip': 'Tooltip text'}

This will create a node like:

<span data-tooltip="Tooltip text"></span>

enaml-web's People

Contributors

devxpy avatar dvska avatar frmdstryr avatar vahndi avatar youpsla avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

enaml-web's Issues

Idea: Streamed HTML

Streamed HTML as discussed here: The weirdly obscure art of Streamed HTML looks nice for performance enhancement.

I got an idea to have something similar in enaml-web but don't know if realistic:
I think we can add a "compute_order"property to Tag (default to 1). At the first pass for render, the engine will only compute Nodes (Tags) with compute_order=1 and send result to the browser. A second pass is done for rendering nodes with "compute_order=2". New nodes and updates are send to the client with websocket.

Do you thing it can be a good approach ?

Do you think it can be achieved without rewriting all enaml ?

Enaml-web and Django

Hello,
I'll have to create a project. I'm hesitating using enaml-web with Django. Enaml-web is so fun !!

I have seen the question has already been asked #8.
You have answered

Yes. You can create a LxmlApplication which has no server component and then render pages for any application (django, flask, etc..) or save them to a file to make a static site.

You talked about LxmlApplication. Haven't see any code relating to that in master.

Could you give me some hints and how to do this coupling ?

Regards

Support for Python 3?

I noticed that the examples all use cyclone, which is Python2 only. Is it possible to use enaml-web with any Python3 web servers?

Meta tag property ?

class Meta(Tag):

Trying to add open graph data but I cant use property I assume it needs to be added to the above link ?

Or perhaps not as property does not appear to be a standard tag, so would I need to create a new element type ? and if so any docs on how to go about doing that ?

NotImplementedError with file observer

app.deferred_call(self.queue_reload)

from web.impl.lxml_app import LxmlApplication
import signal

def main():
    app = LxmlApplication()
    app.init_reloader()
    signal.pause()

results in

Detected changes in ./index.enaml, scheduling reload...
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/home/dev/.pyenv/versions/3.6.5/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/home/dev/.local/share/virtualenvs/quiz_backend-RJ5HRaNa/lib/python3.6/site-packages/watchdog/observers/api.py", line 199, in run
    self.dispatch_events(self.event_queue, self.timeout)
  File "/home/dev/.local/share/virtualenvs/quiz_backend-RJ5HRaNa/lib/python3.6/site-packages/watchdog/observers/api.py", line 368, in dispatch_events
    handler.dispatch(event)
  File "/home/dev/.local/share/virtualenvs/quiz_backend-RJ5HRaNa/lib/python3.6/site-packages/watchdog/events.py", line 322, in dispatch
    self.on_any_event(event)
  File "/home/dev/.local/share/virtualenvs/quiz_backend-RJ5HRaNa/lib/python3.6/site-packages/web/impl/lxml_app.py", line 83, in on_any_event
    app.deferred_call(self.queue_reload)
  File "/home/dev/.local/share/virtualenvs/quiz_backend-RJ5HRaNa/lib/python3.6/site-packages/enaml/application.py", line 278, in deferred_call
    raise NotImplementedError
NotImplementedError

Insertion does not retain order

Ex this does not retain the order of 'Home', *menu_items, 'Contact

Ul:
  Li:
    text = 'Home'
  Looper:
    iterable << menu_items
    Li:
      text = loop_item
  Li:
     text = 'Contact'

When a new item is added to the menu_items it is appended after last item ('Contact' in the example) when it should be inserted in proper order. To support this the child_added event must include the index where it should be inserted.

error using tornado for data-binding demo

Hi, I'm getting this error in the browser console when running the data binding demo using tornado. Any ideas where to start looking? Shall I check the javascript or do you think it's tornado?

VM47:164 WebSocket connection to 'ws://localhost:8888/' failed: Error during WebSocket handshake: Unexpected response code: 200
WrappedWebSocket @ VM47:164

Thanks

How to implement a file upload button?

I'm interested in a very simple page that just provided an upload button.

If this was desktop Enaml I guess I would follow these steps:

  1. Window with a button
  2. On button click open a FileDialog widget
  3. Get the local file path when the widget closes
  4. Use Python's file to get the bytes and then "do stuff" with bytes.

Are there similar widgets in Enable-Web? How might you go about doing this?

Django integration 2: How to persist Atom object between user request

Hello !!!
I'm trying to integrate Enaml-web and Django using the CACHE system used ind dataframe example here:

CACHE[viewer.id] = viewer

I don't achieve to persist this object between users interactions, in other works, after the answer has been sent back to the user browser.

Any idea to achieve that ?

Here is my simple view:

from django.http import HttpResponse

from web.core.app import WebApplication
import enaml

with enaml.imports():
    from .index import Index
enaml_web_app = WebApplication()



def testview(request):
    # I would like to cache index to be able to retrieve it later.
    index = Index()
    return HttpResponse(index.render())

Docs

Better docs than the readme would be nice.

Launching a func when rendering component for the time and without end user action

Hello,
I would like to launch a a func for collecting datas from the DB and assigning to component attribute at widget initialization.
Currently, I have this working by launching my function in my main.py file in the tornado RequestHandler. I would like to make my component containing the maximum code it use.

enamldef MyComponent(Html):
    attr datas

    async func get_datas_from_db(**parameters):
        return datas

    IOLoop.current().add_callback(get_datas_from_db, parameters) # Here is the syntax error

   Conditionnal:
        condition << datas is not None
       Looper:
            ...........

I haven't seen something like on_prepare which could be usefull (prepare)

Any idea ?

Regards

Adding observe event on a variable inside enaml file

Edit: The above question solution will be useless in my case because I want an async function to be run when observed value change and I think I'll not able to call such a function inside enaml block.


I would like to create a component with business logic embedded.

    Select: 
        self.observe('value', test)
        func test(self, change):
            pass
        Option:
            value = "0"
            text = "Zero"
        Option:
            value = "1"
            text = "One"

A syntax error is raised.

Any idea if it's possible ?

SVG node

Better support for svg would be nice

How to alternate classes ?

Been trying out enaml I can see it has conditionals is this the best way to do this something like this ?

conditional << 2 % index
conditional:
    Div:
        cls = 'one'
conditional:
    Div:
        cls = 'two'

or is there a way to do this in a single line ?

running multiple instances

Hi @frmdstryr could you give some comments on how to serve a site with a different instance of the model for each browser window? When I was testing enaml-web a couple of months ago, every new browser window would show the same view when changes were made in one view. Perhaps this is just my lack of understanding of tornado?

Plotly example: How to update the graph in an enaml-web way ?

Do you think it's possible to replace a plotly graph with websocket by just modifyng the traces and display attribute of PlotlyGraph component ?
It doesn't works for me. Plotly.js doesn't seems to react when data-traces are modified in the DOM.

Any idea to do that without writing js for collecting all datas from Plotly div attribute and lauching a Plotly.react command ?

Error on pip install

pip install git+https://github.com/codelv/enaml-web.git
Collecting git+https://github.com/codelv/enaml-web.git
  Cloning https://github.com/codelv/enaml-web.git to /private/var/folders/mn/wzflzlvn1ngg1vtyzd0s4sgc0000gn/T/pip-f734ms7a-build
Collecting distribute (from enaml-web==0.1.0)
  Using cached distribute-0.7.3.zip
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/mn/wzflzlvn1ngg1vtyzd0s4sgc0000gn/T/pip-build-4lkpjyuv/distribute/setuptools/__init__.py", line 2, in <module>
        from setuptools.extension import Extension, Library
      File "/private/var/folders/mn/wzflzlvn1ngg1vtyzd0s4sgc0000gn/T/pip-build-4lkpjyuv/distribute/setuptools/extension.py", line 5, in <module>
        from setuptools.dist import _get_unpatched
      File "/private/var/folders/mn/wzflzlvn1ngg1vtyzd0s4sgc0000gn/T/pip-build-4lkpjyuv/distribute/setuptools/dist.py", line 7, in <module>
        from setuptools.command.install import install
      File "/private/var/folders/mn/wzflzlvn1ngg1vtyzd0s4sgc0000gn/T/pip-build-4lkpjyuv/distribute/setuptools/command/__init__.py", line 8, in <module>
        from setuptools.command import install_scripts
      File "/private/var/folders/mn/wzflzlvn1ngg1vtyzd0s4sgc0000gn/T/pip-build-4lkpjyuv/distribute/setuptools/command/install_scripts.py", line 3, in <module>
        from pkg_resources import Distribution, PathMetadata, ensure_directory
      File "/private/var/folders/mn/wzflzlvn1ngg1vtyzd0s4sgc0000gn/T/pip-build-4lkpjyuv/distribute/pkg_resources.py", line 1518, in <module>
        register_loader_type(importlib_bootstrap.SourceFileLoader, DefaultProvider)
    AttributeError: module 'importlib._bootstrap' has no attribute 'SourceFileLoader'
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/mn/wzflzlvn1ngg1vtyzd0s4sgc0000gn/T/pip-build-4lkpjyuv/distribute/

django integration

Is there any way to integrate this with django currently?

If not, can I use this to spit out static html?

Use enaml-web as markup language

THIS might sound a little annoying,

I'm trying to use it as a markup language, because i'm sick of writing HTML.

I don't want to deal with the hassle of running python on web browser or using web-sockets to communicate with server.

I just want a better way to write UI than HTML

Related SO question

Multi-pages website: Best pattern

Hello.

I'm trying to setup a muti-pages website (was websocket only before).

My Base page is structured like this:

# index.enaml

enamldef Item(Div):
    attrs item_ref

enamldef Statistics(Div):
    attrs datas

enamldef Home(Div):
    attrs datas

enamldef Index(MaterializePage): index:

    Block:
        block = parent.header
       .........
    Block: main_content:
        block = parent.content
        Home:
            pass
# main.py

class IndexHandler(RequestHandler):
    async def get(self):
        path = self.request.path
        page = path.split('/)[1]

        if page == 'home':
                datas = await Model_A().get_datas()
        if page == 'statistics':
                datas = await Model_B().get_datas()
        
        index = Index()
       #
       # Here, how to change the content of index.main_content with datas set to the choosed child datas attribute
       #    
        self.write(index.render())

I was wondering if there is a solution for changing the main_content child widget with specific attributes datas from python file. This file handle request.

Do you have any suggestion about other patterns to accomplish that ?

Regards

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.