Giter Site home page Giter Site logo

ratchet's Introduction

Ratchet

Build Status Hex Version

Ratchet is a friendly little transformer that's here to fix your views.

For use with Phoenix, check out PhoenixRatchet.

For more information, see the Documentation.

Given a plain HTML view template like this:

<section>
  <article data-prop="post">
    <h2 data-prop="title"></h2>
    <p data-prop="body"></p>
    <a data-prop="permalink"></a>
    <ul>
      <li data-prop="comments"></li>
    </ul>
  </article>
</section>

It can be transformed into your final view by applying data:

data = %{
  post: [
    %{title: "Ratchet is here!", body: "Hope you like it", permalink: {"Iamvery", href: "https://iamvery.com"}, comments: ["Not bad"]},
    %{title: "Robots", body: "What's the deal with them?", permalink: {"Google", href: "https://google.com"}, comments: ["Yea!", "Nah"]},
  ]
}
<section>
  <article data-prop="post">
    <h2 data-prop="title">Ratchet is here!</h2>
    <p data-prop="body">Hope you like it</p>
    <a href="https://iamvery.com" data-prop="permalink">Iamvery</a>
    <ul>
      <li data-prop="comments">Not bad</li>
    </ul>
  </article>
  <article data-prop="post">
    <h2 data-prop="title">Robots</h2>
    <p data-prop="body">What's the deal with them?</p>
    <a href="https://google.com" data-prop="permalink">Google</a>
    <ul>
      <li data-prop="comments">Yea!</li>
      <li data-prop="comments">Nah</li>
    </ul>
  </article>
</section>

Installation

  1. Install with Hex:

    def deps do
      [{:ratchet, "~> 0.4"}]
    end

Background

Ratchet is inspired by Pakyow's view transformation protocol. One of the benefits of this style of view templates is designers don't have to learn whatever the latest templating language. Instead views are plain HTML and CSS. Once you get an HTML prototype from design, you can sprinkle in the appropriate properties for data binding.

Release

  1. Bump the version in mix.exs.

  2. Add version to CHANGELOG.

  3. Commit version with Git tag vX.X.X.

  4. Draft GitHub release.

  5. Publish to Hex.

    $ mix do hex.publish, hex.publish docs
    

ratchet's People

Contributors

dgmcguire avatar iamvery 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

Watchers

 avatar  avatar

ratchet's Issues

Data coersion

All the examples given of ratchet show raw data as a static map. This is not realistic. We need to provide some functionality that helps map user's data into the form ratchet expects...

Loosen dependencies

Right now the lib is dependent on the most recent releases of various libs. In order for it to be generally useful to more folks, the dependencies should be lessened to the minimal working versions.

Visually impaired enhancements

Just opening an issue to start a discussion. I don't know what these improvements would look like I don't really know how visually impaired software tools do, but I get the feeling we could be doing good things in this space by default since ratchet already has pretty good high level semantics. #

Don't replace content on data-prop

There are certain cases where it is not desirable to replace the content of an element with data-prop.

For e.g., given a template such as:

<div data-scope="comment">
  <p data-prop="body"></p>
  <a data-prop="delete">Delete</a>
</div>

It is desirable to bind only attributes to the "delete" property, and leave the content unchanged, resulting in the following view:

<div data-scope="comment">
  <p data-prop="body">I have thoughts and feelings</p>
  <a href="/comments/1" method="delete" data-prop="delete">Delete</a>
</div>

Proposal

Just as attributes are bound when data property is of the form {content, attrs}, this case would also be dictated by the type of data that is given.

So with the above template and the following data:

data = %{
  comment: %{
    body: "I have thoughts and feelings",
    delete: [href: "/comments/1", method: "delete"],
  }
}

Only attributes would be written for the "delete" data property.

Does this work?

Internal variable naming ideas/conventions RFC

So whenever we talk about this problem to someone interested in this project, I find myself using this idea of expressing a domain when we're writing html. I'm wondering if this terminology maps out well enough that it would make our code more clear to someone reading our code (rather than our docs).

The idea is simply that we have data-scopes, data-attr and finally we have data.

In html we have tags, attributes, and finally content.

I think if we used the word domain in our code whenever we're capturing some variable that might represent data-* or data it might become more clear since our code is already naturally going to have scopes that aren't necessarily dealing with data-scope, for instance.

Something as simple as matching domain-scope = "data-scope" might do just enough to make the code reading more clear to someone really trying to grok what we're doing here.

Fast data with macros?

Currently the data functions (Ratchet.Data) are implemented with Map.put, etc. Perhaps there is a performance concern with this? Would it be possible to use macros to perform these manipulations at compilation time?

defmacro scope({:%{}, meta, pairs}, property, property_data) do
  {:%{}, meta, [{property, property_data}|pairs]}
end

This implementation would only work when called with literal Maps. For other values, including using scope/3 in a pipeline, the first argument would have to be evaluated before it could be manipulated ๐Ÿค”

Vulnerability?

Is #21 a concern? It seems to still be problematic...

attributes:

screen shot 2016-09-30 at 8 06 00 am

### rendered markup:

screen shot 2016-09-30 at 8 06 17 am

### click:

screen shot 2016-09-30 at 8 06 40 am

So the question is, does this matter? What if any are the attack vectors? Seems like if there is a concern, it would come inserting user content into these attributes ๐Ÿค”

Remove prototype elements when data is present

Would like to be able to designate certain elements as prototype. This would allow multiple versions of a thing to be shown in prototype, but exclude it in the final render.

For example,

<div data-prop="foo">First</div>
<div data-proto="foo">Second</div>

When rendered with no data, the prototype would be included. However when rendered with data, it would look like:

data = %{foo: "lolwat"}
<div data-prop="foo">lolwat</div>
<!-- note the prototype element is excluded -->

Fix coupling to Phoenix

#22 couples the lib to Phoenix in that the EEx engine rendering the view must be able to handle "Phoenix safe" strings, e.g. {:safe, "..."}. Would be great to not have this requirement of the compiled EEx so that the lib is generally useful outside of the context of Phoenix.

Warning when property has no data

When a property is encountered without data, the prototype is displayed. However, that may have happen by mistake, so we should warn the user to pay attention in case this was by mistake (e.g. typo)

Proposal: new data format

In attempt to build a functioning app with ratchet, a number of edge cases have been encountered. This leads me to believe that we may need to rethink it's design.

Overview

Fundamentally ratchet implements a protocol for binding data to HTML elements. There are only two places where data may be stored in an HTML document:

  1. element attributes
  2. element content

Protocol

Previously, we had the concept of scopes and properties. A scope represented an area of concern, or domain, in the view. A scope might have a number of properties which data would be bound to. I propose we simplify this to just properties. Scopes are more or less a semantic which could be introduced later as a synonym for properties.

<!-- Previously -->
<article data-scope="post">
  <p data-prop="body"></p>
</article>

<!-- Now -->
<article data-prop="post">
  <p data-prop="body"></p>
</article>

Edges

The following edge cases were encoutered while trying to translate existing apps with ratchet. These scenarios cannot be solved with the current version of ratchet (0.2.0).

Case # 1

Updating only the attributes of an element. For e.g., you have an anchor tag with static text content. Only the href needs be bound when rendered.

<!-- Template -->
<a>Click here!</a>

<!-- Rendered -->
<a href="https://iamvery.com" rel="nofollow">Click here!</div>

Case # 2

Updating attributes of an element as well as properties within its content. For e.g., you have a form tag that must have data bound to properties in its content as well as action and method attributes when rendered.

<!-- Template -->
<form>
  <input type="text">
  <button type="submit">Submit</button>
</form>

<!-- Rendered -->
<form action="..." method="post">
  <input type="text" name="full_name">
  <button type="submit">Submit</button>
</form>

Case # 3

Currently there is no way to compose views from other files. For e.g., the application layout is in one file and the view specific to the particular page is in another file to be included. I'm on the fence whether this is really a responsibility of ratchet. On one hand, a claim of the lib is writing plain HTML views so that designers can easy prototype things. On the other hand, any including of external HTML breaks that whole idea anyway.

At this time, I'm thinking we just shell out to EEx and call render.

Case # 4

There may be a case where it is desirable to prepend/append content on an element rather than overwriting it. For e.g. a designer would have no reason to implement hidden form elements such as CSRF token as they do not affect the view.

<!-- Template -->
<form>
  <input type="text">
  <button type="submit">Submit</button>
</form>

<!-- Rendered -->
<form>
  <input type="hidden" name="csrf" value="...">
  <input type="text">
  <button type="submit">Submit</button>
</form>

However, I don't think it's the responsibility of ratchet to generate to HTML content. IMO the developer should be able to retroactively add elements to the view and bind data to them as expected.

Transformations

There are 5 transformations which may take place in the view.

  1. Element content
  2. Element attributes
  3. Element content and attributes
  4. Multiple elements (recursively applying 1-3 above)
  5. Attributes and nested elements

Element content

<!-- Template -->
<p data-prop="body"></p>

<!-- Rendered -->
<p data-prop="body">Content has been inserted</p>

Element attributes

<!-- Template -->
<a data-prop="link">Click here!</a>

<!-- Rendered -->
<a href="https://iamvery.com" rel="nofollow" data-prop="link">Click here!</div>

Element content and attributes

<!-- Template -->
<a data-prop="link"></div>

<!-- Rendered -->
<a href="https://iamvery.com" rel="nofollow" data-prop="link">iamvery</div>

Multiple elements

<!-- Template -->
<article data-prop="post"></article>

<!-- Rendered -->
<article data-prop="post">...</article>
<article data-prop="post">...</article>
<article data-prop="post">...</article>

Attributes and nested elements

<!-- Template -->
<form data-prop="new_comment">
  <input data-prop="body">
</form>

<!-- Rendered -->
<form action="/comments" method="post" data-prop="new_comment">
  <input name="comment[body]" data-prop="body">
</form>

Data

As previously stated, there are two places for data in the view: content and attributes. Therefore there are two basic types of data: strings and keyword lists. These basic types may be combined into complex data structures to support complex views.

Consider the following prototype template for some posts with comments:

<article data-prop="posts">
  <h2>
    <a href="#" data-prop="title">Post Title</a>
  </h2>
  <p data-prop="body">Thoughts and opinions.</p>
  <a href="#" data-prop="permalink">Permalink</a>
  <ul>
    <li data-prop="comments">
      <span data-prop="user">Jay</span>
      <span data-prop="body">Yes and no</span>
    </li>
  </ul>
  <form data-prop="new_comment">
    <input data-prop="body">
    <button type="submit">New comment</button>
  </form>
</article>

When rendered with data, the final view would look like the following:

<article data-prop="posts">
  <h2><a href="/posts/1" data-prop="title">A good post</a></h2>
  <p data-prop="body">This post is about things.</p>
  <a href="/posts/1" data-prop="permalink">Permalink</a>
  <ul>
    <li data-prop="comments">
      <span data-prop="user">Jon</span>
      <span data-prop="body">Good read.</span>
    </li>
    <li data-prop="comments">
      <span data-prop="user">Les</span>
      <span data-prop="body">Can you even?</span>
    </li>
  </ul>
  <form action="/comments" method="post" data-prop="new_comment">
    <input name="comment[body]" data-prop="body">
    <button type="submit">New comment</button>
  </form>
</article>

The data:

data = [
  posts: [
    title: {"A good post", href: "/posts/1"},
    body: "This post is about things.",
    permalink: [href: "/posts/1"],
    comments: [
      [user: "Jon", body: "Good read."],
      [user: "Les", body: "Can you even?"],
    ],
    new_comment: {
      [body: [name: "comment[body]"]],
      action: "/comments", method: "post",
    }
  ]
]

Let's talk about the data...

String, e.g. post body

The body of the post is plain text content.

Data:

"This is the post body"

Applied:

<p data-prop="body">This is the post body</p>

Keyword List, e.g. post permalink

Only attributes need be bound on the permalink element. The keyword list has the general form [attribute: value].

Data:

[href: "/posts/1"]

Applied:

<a href="/posts/1" data-prop="permalink">Permalink</a>

Tuple, e.g. post title

Both the content and attributes of the title are updated. The tuple has the general form {content, attributes}.

Data:

{"A good post", href: "/posts/1"}

Applied:

<a href="/posts/1" data-prop="title">A good post</a>

List, e.g. comments

A regular list is used as a collection of property data. The general form is [data, ...]

Data:

comments: [[...], [...]]

Applied:

<li data-prop="comments">...</li>
<li data-prop="comments">...</li>

Map, e.g. a post

Maps are used as a collection of properties to their data. They have the general form %{property: data, ...}

Data:

%{
  body: "...",
  permalink: [...],
  comments: [...],
  new_comment: {...},
}

Applied:

<article data-prop="post">...</article>

Combined concepts, e.g. new comment form

Both the content and attributes of the form must be updated, so a tuple is used. In this particular case, the content is recursively updated with additional properties.

Data:

{%{body: [name: "comment[body]"]}, action: "/comments", method: "post"}

Applied:

<form action="/comments" method="post" data-prop="new_comment">
  <input name="comment[body]" data-prop="body">
</form>

EEx

An implementation detail of Ratchet is that it uses EEx as an intermediary format. Provided for example is the above template compiled to EEx:

<%= for post <- List.wrap(data.posts) do %>
  <article <%= Ratchet.Data.attributes(post, [{"data-prop", "posts"}]) %>>
    <%= if Ratchet.Data.content?(post) do %>
      <%= Ratchet.Data.content(post) %>
    <% else %>
      <h2>
        <%= for title <- List.wrap(post.title) do %>
          <a <%= Ratchet.Data.attributes(title, [{"href", "#"}, {"data-prop", "title"}]) %>>
            <%= if Ratchet.Data.content?(title) do %>
              <%= Ratchet.Data.content(title) %>
            <% else %>
              Post Title
            <% end %>
          </a>
        <% end %>
      </h2>
      <%= for body <- List.wrap(post.body) do %>
        <p <%= Ratchet.Data.attributes(body, [{"data-prop", "body"}]) %>>
          <%= if Ratchet.Data.content?(body) do %>
            <%= Ratchet.Data.content(body) %>
          <% else %>
            Thoughts and opinions.
          <% end %>
        </p>
      <% end %>
      <%= for permalink <- List.wrap(post.permalink) do %>
        <a <%= Ratchet.Data.attributes(permalink, [{"data-prop", "permalink"}]) %>>
          <%= if Ratchet.Data.content?(permalink) do %>
            <%= Ratchet.Data.content(permalink) %>
          <% else %>
            Permalink
          <% end %>
        </a>
      <% end %>
      <ul>
        <%= for comments <- List.wrap(post.comments) do %>
          <li <%= Ratchet.Data.attributes(comments, [{"data-prop", "comments"}]) %>>
            <%= if Ratchet.Data.content?(comments) do %>
              <%= Ratchet.Data.content(comments) %>
            <% else %>
              <%= for user <- List.wrap(comments.user) do %>
                <span <%= Ratchet.Data.attributes(user, [{"data-prop", "user"}]) %>>
                  <%= if Ratchet.Data.content?(user) do %>
                    <%= Ratchet.Data.content(user) %>
                  <% else %>
                    Jay
                  <% end %>
                </span>
              <% end %>
              <%= for body <- List.wrap(comments.body) do %>
                <span <%= Ratchet.Data.attributes(body, [{"data-prop", "body"}]) %>>
                  <%= if Ratchet.Data.content?(body) do %>
                    <%= Ratchet.Data.content(body) %>
                  <% else %>
                    Yes and no
                  <% end %>
                </span>
              <% end %>
            <% end %>
          </li>
        <% end %>
      </ul>
      <%= for new_comment <- List.wrap(post.new_comment) do %>
        <form <%= Ratchet.Data.attributes(new_comment, [{"data-prop", "new_comment"}]) %>>
          <%= if Ratchet.Data.content?(new_comment) do %>
            <%= Ratchet.Data.content(new_comment) %>
          <% else %>
            <%= for body <- List.wrap(new_comment.body) do %>
              <input <%= Ratchet.Data.attributes(body, [{"data-prop", "body"}]) %>>
            <% end %>
            <button type="submit">New comment</button>
          <% end %>
        </form>
      <% end %>
    <% end %>
  </article>
<%= end %>

(๐Ÿ˜ฑ WHAT HAVE I DONE)

Support content "safety"

Need to be able to mark content as "safe" for Phoenix. Feels like this might need to go in the phoenix_ratchet lib ๐Ÿค”

Handle data props with hyphens

I have to fight the urge to handle data-props defined with a hyphen, e.g. data-prop="new-post". Right now it's apparently a compiler error in the template...

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.