Skip to content
Sven edited this page May 1, 2018 · 32 revisions

Feature discussion

jsonredis sequence and mapping data structure

Next to Redis list we could also support Redis set (for mapping) and sorted set (for sequence and mapping). Experimental branch: https://github.com/noyainrain/micro/tree/seq-map-data-structure

Auto-Disabled button

If the button is triggered, it will suspend (disable), then after the promise resume (enable). If the button disabled property is modified inbetween (e.g. by the promise itself), we lose this change.

// Suspend:
this.suspended = true;
this.disabledResume = false;
// Resume:
this.suspended = false;
this.disabled = this.disabledResume;

class Button {
    set disabled(value) {
        if (this.suspended) {
            this.disabledResume = value;
        } else {
            super.disabled = value;
        }
    }
}

Generalized data binding transforms

At the moment transforms are special functions that receive ctx as first argument. We could rather make ctx available as special variable, which can be passed to transforms that really need it.

let stack = [null].concat(data, micro.bind.transforms);
// ...
stack[0] = {ctx};
// data-content="list ctx posts 'post'"

Collection with meta data

class Collection:
    """
    .. describe:: count

       Number of items in the collection (that are not trashed).
    """

    def __init__(self, host, meta):
        # ...
        self.count = meta['count']
        self.author_ids = meta['author_ids']

    @property
    def authors(self):
        return self.app.r.omget(self._author_ids)

    def add(self, item):
        """Subclass API."""
        self.app.r.rpush(self.map_key, item.id)
        self._update()

    def json(self, restricted=False, include=False):
        return {
            'count': self.count,
            **{'author_ids': self._author_ids},
            **({'authors': [a.json(restricted, include) for a in self.authors]} if include else {})
        }

    def _update(self):
        active = [item for item in self.values() if not item.trashed]
        self.count = len(active)
        self.author_ids = list(set(author.id for item in active for author in item.authors))
        self.app.r.oset(self.host[0].id, self.host[0])

class Trashable:
    def __init__(self, trashed, collection=None, activity=None):
        # ...
        self.__collection = collection

    def trash(self):
        # ...
        if self.__collection:
            self.__collection._update()

    def restore(self):
        # ...
        if self.__collection:
            self.__collection._update()

Event templates

Object.assign(this.eventTemplates, {
    "purr": ".foo-purr-event-template"
});
this.eventData = {makeURL: foo.makeURL};
let elem = document.importNode(ui.querySelector(ui.eventTemplates[event.type]).content, true);
micro.bind.bind(elem, Object.assign({event}, this.eventData));
li.appendChild(elem);

Roadmap outline

  1. MVP / Beta 1:
    • Target: App is usable in a minimal form. User mistakes are correctable.
    • Audience: Groups the developers are themselves part of
    • Feedback channel: Direct
  2. Beta 2:
    • Target: App is usable on a daily basis. Core functionality is implemented. Purpose is clearly apparent to first time users. Feedback from Beta 1 is incorporated.
    • Audience: Social networks of the developers
    • Feedback channel: Social networks / App
  3. Beta 3:
    • Target: App monitoring is available. Feedback from Beta 2 is incorporated.
    • Audience: Public, announced on various platforms, e.g.:
    • Feedback channel: App / Platforms
  4. Stable:
    • Target: Features expected from a self-contained app are implemented. Feedback from Beta 3 is incorporated.

Watchable.unwatch()

unwatch(prop, onUpdate) {
    let i = (watchers[prop] || []).findIndex(f => f === onUpdate);
    if (i === -1) {
        throw new Error();
    }
    watchers.splice(i, 1);
}

Data binding

Sophisticated user listing:

<p data-title="joinText list.authors ', ' map 'name'">
    <span class="fa fa-pencil"></span>
    <span data-content="join list.authors 'user' ', ' 3">
        <template><micro-user></micro-user></template>
        <template data-name="overflow">
            + <span data-content="sub list.authors.length 3"></span>
        </template>
    </span>
</p>

Format:

<span data-content="format 'Hello {user}!' 'user' user.name">
<span data-content="format 'Hello {user}!'">
    <template name="user"><micro-user data-user="user"></micro-user></template>
</span>

Translate (calls format):

<span data-content="translate 'hello' 'user' user.name">

Advanced translation example:

<p>
    <span data-content="translateEditedBy list.authors">
        <template data-name="authors">
            <span data-content="join list.authors 'user' ', ' 3">
                <template><micro-user></micro-user></template>
            </span>
        </template>
    </span>
</p>
<script>
    this.data.translateEditedBy = (ctx, authors) => {
        let n = authors.length;
        return micro.bind.translate(ctx, n > 3 ? "edited-by-overflow" : "edited-by", "n", n);
    }
    {
        "edited-by": "edited by {authors}",
        "edited-by-overflow": ["edited by {authors} and {n} other", "edited by {authors} and {n} others"]
    }
</script>

Update strategies

  • Python / JavaScript API:
    • Breaking changes okay
    • User decides update time
    • Sometimes non-breaking changes are not possible or only with disproportionate effort:
      • (JavaScript) Convert <custom-element> from/to <div is="custom-element"> (can only be registered once)
  • Web API:
    • Breaking changes only after deprecation period
    • Service provider decides update time, user needs time to make adjustments

Idea for non-breaking update path for function signatures:

def foo(a, b):
    # ...
# ->
def foo(a, x, b=None, __version__=1):
    if __version__ == 1:
        b, x = x, 'default'
    else:
        if b is None:
            raise Error()
    # ...
# ->
def foo(a, x, b, __version__=2):
    # ...

New features and API stability

Assume that micro introduces a new announcement feature with the following API:

  • type Announcement
  • Attribute Application.announcements
  • DB key announcements
  • endpoint /api/announcements
  • page /announcements
  • ...

If an existing micro application already makes use of any of those names, this could lead to breaking behavior (e.g. micro code modifies Application.announcements or posts to /api/announcements and they have different, unexpected semantics).

How can we solve this? New features that involve the extendable API could be major releases. New features could be introduced behind a feature toggle with a minor release and enabled by default with a major release. DB keys could be prefixed with micro.

Linting

Rules that may be worth considering:

Clone this wiki locally