Desmond  Gerber

Desmond Gerber

1685215140

Asm-dom: A minimal WebAssembly virtual DOM to build C++ SPA

:rage4: asm-dom :rage4:

A minimal WebAssembly virtual DOM to build C++ SPA (Single page applications)

Motivation

asm-dom is a minimal WebAssembly virtual DOM to build C++ SPA (Single page applications). You can write an entire SPA in C++ and compile it to WebAssembly (or asmjs as fallback) using Emscripten, asm-dom will call DOM APIs for you. This will produce an app that aims to execute at native speed by taking advantage of common hardware capabilities, also, you can use your C/C++ code without any change, you haven't to create a binding layer to use it (as we have to do if we want to use a C++ lib from JS). Basically we are creating an app in C++ that call javascript if needed instead of the opposite. You can write only once in C++ and share as much code as possible with desktop/mobile apps and web site. If you want to learn more about performance, please see this.

How can I structure my application with asm-dom?

asm-dom is a low-level virtual DOM library. It is unopinionated with regards to how you should structure your application.

How did you come up with the concept of asm-dom?

At the beginning asm-dom is born from the idea to test the powerful of WebAssembly in a common use case that is not gaming, VR, AR or Image / video editing. Unfortunately, at the moment, GC/DOM Integration is a future feature 🦄, so, asm-dom isn't totally developed in wasm. All interactions with the DOM are written in Javascript. This is a big disadvantage because of the overhead of the binding between JS and WASM, in the future asm-dom will be even more powerful, anyway results are satisfying.

Inline Example

#include "asm-dom.hpp"

using namespace asmdom;

int main() {
  Config config = Config();
  init(config);

  // asm-dom can be used with a JSX like syntax thanks to gccx
  VNode* vnode = (
    <div
      onclick={[](emscripten::val e) -> bool {
        emscripten::val::global("console").call<void>("log", emscripten::val("clicked"));
        return true;
      }}
    >
      <span style="font-weight: bold">This is bold</span>
      and this is just normal text
      <a href="/foo">I'll take you places!</a>
    </div>
  );

  // Patch into empty DOM element – this modifies the DOM as a side effect
  patch(
    emscripten::val::global("document").call<emscripten::val>(
      "getElementById",
      std::string("root")
    ),
    vnode
  );

  // without gccx
  VNode* newVnode = h("div",
    Data(
      Callbacks {
        {"onclick", [](emscripten::val e) -> bool {
          emscripten::val::global("console").call<void>("log", emscripten::val("another click"));
          return true;
        }}
      }
    ),
    Children {
      h("span",
        Data(
          Attrs {
            {"style", "font-weight: normal; font-style: italic"}
          }
        ),
        std::string("This is now italic type")
      ),
      h(" and this is just normal text", true),
      h("a",
        Data(
          Attrs {
            {"href", "/bar"}
          }
        ),
        std::string("I'll take you places!")
      )
    }
  );

  // Second `patch` invocation
  patch(vnode, newVnode); // asm-dom efficiently updates the old view to the new state

  return 0;
};

Getting started

asm-dom aims to be used from C++, however it can be used also from javascript, here you can find the doc of both:

Ecosystem

Here you can find a list of related projects:

  • gccx - CPX (JSX like syntax) support.
  • asm-dom-boilerplate - A simple boilerplate to start using asm-dom without configuration.

Examples

Examples are available in the examples folder.

Also, here is the list of third-party examples:

and online Demos:

Roadmap

  • [ ] Thunks support
  •  asm-dom aims to be even more powerful with GC/DOM Integration. Unfortunately this is a future feature 🦄, so, we have to be patient and wait a bit.

Change Log

This project adheres to Semantic Versioning.
Every release, along with the migration instructions, is documented on the Github Releases page.


Download Details:

Author: mbasso
Source Code: https://github.com/mbasso/asm-dom 
License: View license

#webassembly #dom #render #wasm 

Asm-dom: A minimal WebAssembly virtual DOM to build C++ SPA

A Flutter Package That Allows Customizable Link and URL Preview

Flutter Link Previewer #

Customizable link and URL preview extracted from the provided text with the ability to render from the cache. Ideal for chat applications.


 

🇺🇦🇺🇦 We are Ukrainians. If you enjoy our work, please consider donating to help save our country. 🇺🇦🇺🇦


Getting Started

import 'package:flutter_link_previewer/flutter_link_previewer.dart';

LinkPreview(
  enableAnimation: true,
  onPreviewDataFetched: (data) {
    setState(() {
      // Save preview data to the state              
    });
  },
  previewData: _previewData, // Pass the preview data from the state
  text: 'https://flyer.chat',
  width: MediaQuery.of(context).size.width,
)

Customization

final style = TextStyle(
  color: Colors.red,
  fontSize: 16,
  fontWeight: FontWeight.w500,
  height: 1.375,
);


LinkPreview(
  linkStyle: style,
  metadataTextStyle: style.copyWith(
    fontSize: 14,
    fontWeight: FontWeight.w400,
  ),
  metadataTitleStyle: style.copyWith(
    fontWeight: FontWeight.w800,
  ),
  padding: EdgeInsets.symmetric(
    horizontal: 24,
    vertical: 16,
  ),
  onPreviewDataFetched: _onPreviewDataFetched,
  previewData: _previewData,
  text: 'https://flyer.chat',
  textStyle: style,
  width: width,
);

License

MIT

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_link_previewer

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_link_previewer: ^3.2.1

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_link_previewer/flutter_link_previewer.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' show PreviewData;
import 'package:flutter_link_previewer/flutter_link_previewer.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) => const MaterialApp(
        home: MyHomePage(),
      );
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Map<String, PreviewData> datas = {};

  List<String> get urls => const [
        'github.com/flyerhq',
        'https://u24.gov.ua',
        'https://twitter.com/SpaceX/status/1564975288655630338',
      ];

  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);

    return Scaffold(
      backgroundColor: Colors.white,
      body: ListView.builder(
        itemCount: urls.length,
        itemBuilder: (context, index) => Align(
          alignment: Alignment.centerLeft,
          child: Container(
            key: ValueKey(urls[index]),
            margin: const EdgeInsets.all(16),
            decoration: const BoxDecoration(
              borderRadius: BorderRadius.all(
                Radius.circular(20),
              ),
              color: Color(0xfff7f7f8),
            ),
            child: ClipRRect(
              borderRadius: const BorderRadius.all(
                Radius.circular(20),
              ),
              child: LinkPreview(
                enableAnimation: true,
                onPreviewDataFetched: (data) {
                  setState(() {
                    datas = {
                      ...datas,
                      urls[index]: data,
                    };
                  });
                },
                previewData: datas[urls[index]],
                text: urls[index],
                width: MediaQuery.of(context).size.width,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Download Details:

Author: flyer.chat

Source Code: https://github.com/flyerhq/flutter_link_previewer

#flutter #android #ios #link #url #preview #cache #render 

A Flutter Package That Allows Customizable Link and URL Preview

Рендеринг реквизита в React.js

Введение

React.js — популярная библиотека JavaScript для создания пользовательских интерфейсов. Он предлагает подход, основанный на компонентах, где каждый компонент представляет собой многократно используемый фрагмент кода со своим собственным состоянием и поведением. Одной из мощных функций React.js является концепция «Render Props», которая позволяет компонентам гибко и многократно использовать функциональность и данные совместно с другими компонентами.

Что такое Render Props в React.js?

Render Props — это шаблон проектирования, который позволяет совместно использовать код между компонентами через свойство, значением которого является функция. Эта функция, часто называемая рендерингом или дочерними элементами, передается в качестве реквизита от родительского компонента к дочернему компоненту. Затем дочерний компонент может вызвать эту функцию и передать любые необходимые данные для отображения содержимого или выполнения определенных действий.

Чтобы лучше понять, как работают Render Props, давайте рассмотрим пример. Предположим, у нас есть компонент <Mouse>, который отслеживает положение мыши на экране. Мы хотим визуализировать компонент <Cat>, который следует за мышью, куда бы она ни двигалась.

class Mouse extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (event) => {
    this.setState({ x: event.clientX, y: event.clientY });
  };

  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

class Cat extends React.Component {
  render() {
    const { x, y } = this.props.mouse;
    return (
      <div style={{ position: 'absolute', left: x, top: y }}>
        ??
      </div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse!</h1>
        <Mouse render={mouse => <Cat mouse={mouse} />} />
      </div>
    );
  }
}

В этом примере компонент <Mouse> прослушивает событие onMouseMove и обновляет свое состояние текущими координатами мыши. Затем он вызывает функцию рендеринга, переданную в качестве реквизита родительским компонентом, и передает текущее состояние в качестве аргумента.

Компонент <App> является оболочкой для компонента <Mouse> и предоставляет функцию для визуализации компонента <Cat>. Эта функция принимает состояние мыши в качестве аргумента и возвращает компонент <Cat> со свойством мыши, установленным в текущее состояние.

Используя шаблон Render Props, мы можем инкапсулировать логику отслеживания мыши внутри компонента <Mouse> и повторно использовать ее с другими компонентами рендеринга. Мы достигаем разделения задач, когда компонент <Mouse> фокусируется на отслеживании мыши, а компонент <Cat> фокусируется на рендеринге кошки в позиции мыши.

Реквизиты рендеринга предоставляют гибкий способ совместного использования кода в React.js и способствуют повторному использованию. Это позволяет компонентам определять свою логику рендеринга, в то же время используя общие функции других компонентов. С помощью Render Props вы можете создавать более компонуемые и расширяемые компоненты, делая кодовую базу более удобной в сопровождении и масштабируемой.

Хотя реквизиты рендеринга предоставляют мощный механизм повторного использования кода, важно использовать их разумно. Чрезмерное использование Render Props может привести к увеличению количества компонентов и вложенных функций, что может затруднить чтение и понимание кода. Важно найти баланс и оценить, могут ли другие шаблоны, такие как композиция компонентов или компоненты более высокого порядка, быть более подходящими для вашего конкретного случая использования.

Заключение

Render Props — это ценный шаблон проектирования в React.js, который позволяет компоновать компоненты и совместно использовать код. Это дает разработчикам возможность создавать повторно используемые компоненты с определенными обязанностями. Понимая и эффективно используя Render Props, вы можете создавать надежные и гибкие приложения React.js.

Оригинальный источник статьи: https://www.c-sharpcorner.com/

#react #render #prop 

Рендеринг реквизита в React.js
田辺  桃子

田辺 桃子

1684441740

在 React.js 中渲染道具

介绍

React.js 是一个流行的用于构建用户界面的 JavaScript 库。它提供了一种基于组件的方法,其中每个组件代表一段可重用的代码,具有自己的状态和行为。React.js 中的一个强大功能是“Render Props”的概念,它允许组件灵活地与其他组件共享功能和数据并可重用。

React.js 中的 Render Props 是什么?

Render Props 是一种设计模式,它通过值为函数的 prop 实现组件之间的代码共享。这个函数通常被命名为 render 或 children,作为 prop 从父组件传递到子组件。然后子组件可以调用此函数并传递任何必要的数据以呈现内容或执行特定操作。

为了更好地理解 Render Props 是如何工作的,让我们看一个例子。假设我们有一个跟踪鼠标在屏幕上位置的 <Mouse> 组件。我们想要渲染一个 <Cat> 组件,它跟随鼠标移动到任何地方。

class Mouse extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (event) => {
    this.setState({ x: event.clientX, y: event.clientY });
  };

  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

class Cat extends React.Component {
  render() {
    const { x, y } = this.props.mouse;
    return (
      <div style={{ position: 'absolute', left: x, top: y }}>
        ??
      </div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse!</h1>
        <Mouse render={mouse => <Cat mouse={mouse} />} />
      </div>
    );
  }
}

在此示例中,<Mouse> 组件侦听 onMouseMove 事件并使用当前鼠标坐标更新其状态。然后它调用由父组件作为 prop 传递的渲染函数,并将当前状态作为参数传递。

<App> 组件包装了 <Mouse> 组件并提供了一个函数来渲染 <Cat> 组件。此函数将鼠标状态作为参数,并返回鼠标属性设置为当前状态的 <Cat> 组件。

使用 Render Props 模式,我们可以将鼠标跟踪逻辑封装在 <Mouse> 组件中,并在不同的渲染组件中重用它。我们实现了关注点分离,其中 <Mouse> 组件专注于跟踪鼠标,而 <Cat> 组件专注于在鼠标位置渲染猫。

Render Props 提供了一种灵活的方式来共享 React.js 中的代码并提高可重用性。它允许组件定义它们的渲染逻辑,同时仍然利用其他组件的共享功能。使用 Render Props,您可以构建更多可组合和可扩展的组件,使您的代码库更易于维护和扩展。

尽管 Render Props 为代码重用提供了强大的机制,但明智地使用它们很重要。过度使用 Render Props 会导致组件和嵌套函数的激增,这会使代码更难阅读和理解。必须取得平衡并评估其他模式(例如组件组合或高阶组件)是否更适合您的特定用例。

结论

Render Props 是 React.js 中一个有价值的设计模式,它支持组件组合和代码共享。它使开发人员能够创建具有集中职责的可重用组件。通过有效地理解和利用 Render Props,您可以构建健壮且灵活的 React.js 应用程序。

文章原文出处:https: //www.c-sharpcorner.com/

#react #render #prop 

在 React.js 中渲染道具
Oral  Brekke

Oral Brekke

1684427166

Render Props in React.js

Introduction

React.js is a popular JavaScript library for building user interfaces. It offers a component-based approach, where each component represents a reusable piece of code with its own state and behavior. One powerful feature in React.js is the concept of "Render Props," which allows components to share functionality and data with other components flexibly and reusable.

What is Render Props in React.js?

Render Props is a design pattern that enables the sharing of code between components through a prop whose value is a function. This function, often named render or children, is passed as a prop from a parent component to a child component. The child component can then invoke this function and pass any necessary data to render content or perform specific actions.

To better understand how Render Props work, let's take a look at an example. Suppose we have a <Mouse> component that tracks the mouse position on the screen. We want to render a <Cat> component that follows the mouse wherever it moves.

class Mouse extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (event) => {
    this.setState({ x: event.clientX, y: event.clientY });
  };

  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

class Cat extends React.Component {
  render() {
    const { x, y } = this.props.mouse;
    return (
      <div style={{ position: 'absolute', left: x, top: y }}>
        ??
      </div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse!</h1>
        <Mouse render={mouse => <Cat mouse={mouse} />} />
      </div>
    );
  }
}

In this example, the <Mouse> component listens to the onMouseMove event and updates its state with the current mouse coordinates. It then invokes the render function passed as a prop by the parent component and passes the current state as an argument.

The <App> component wraps the <Mouse> component and provides a function to render the <Cat> component. This function takes the mouse state as an argument and returns the <Cat> component with the mouse prop set to the current state.

Using the Render Props pattern, we can encapsulate the mouse-tracking logic inside the <Mouse> component and reuse it with different rendering components. We achieve separation of concerns, where the <Mouse> component focuses on tracking the mouse, and the <Cat> component focuses on rendering the cat at the mouse position.

Render Props provide a flexible way to share code in React.js and promote reusability. It allows components to define their rendering logic while still leveraging shared functionality from other components. With Render Props, you can build more composable and extensible components, making your codebase more maintainable and scalable.

Although Render Props provide a powerful mechanism for code reuse, it's important to use them judiciously. Overusing Render Props can lead to a proliferation of components and nested functions, which can make the code harder to read and understand. It's essential to strike a balance and evaluate if other patterns, such as component composition or higher-order components, might be more suitable for your specific use case.

Conclusion

Render Props is a valuable design pattern in React.js that enables component composition and code sharing. It empowers developers to create reusable components with focused responsibilities. By understanding and utilizing Render Props effectively, you can build robust and flexible React.js applications.

Original article source at: https://www.c-sharpcorner.com/

#react #render #prop 

Render Props in React.js
Royce  Reinger

Royce Reinger

1678726080

GZ-sim: Open Source Robotics Simulator

Gazebo Sim : A Robotic Simulator

Maintainer: michael AT openrobotics DOT org

Gazebo Sim is an open source robotics simulator. Through Gazebo Sim, users have access to high fidelity physics, rendering, and sensor models. Additionally, users and developers have multiple points of entry to simulation including a graphical user interface, plugins, and asynchronous message passing and services.

Gazebo Sim is derived from Gazebo Classic and represents over 16 years of development and experience in robotics and simulation. This library is part of the Gazebo project.

Features

Dynamics simulation: Access multiple high-performance physics engines through Gazebo Physics.

Advanced 3D graphics: Through Gazebo Rendering, it's possible to use rendering engines such as OGRE v2 for realistic rendering of environments with high-quality lighting, shadows, and textures.

Sensors and noise models: Generate sensor data, optionally with noise, from laser range finders, 2D/3D cameras, Kinect style sensors, contact sensors, force-torque, IMU, GPS, and more, all powered by Gazebo Sensors

Plugins: Develop custom plugins for robot, sensor, and environment control.

Graphical interface: Create, introspect and interact with your simulations through plugin-based graphical interfaces powered by Gazebo GUI.

Simulation models: Access numerous robots including PR2, Pioneer2 DX, iRobot Create, and TurtleBot, and construct environments using other physically accurate models available through Gazebo Fuel. You can also build a new model using SDF.

TCP/IP Transport: Run simulation on remote servers and interface to Gazebo Sim through socket-based message passing using Gazebo Transport.

Command line tools: Extensive command line tools for increased simulation introspection and control.

Install

See the installation tutorial.

Usage

Gazebo Sim can be run from the command line, once installed, using:

gz sim

For help, and command line options use:

gz sim -h

Known issue of command line tools

In the event that the installation is a mix of Debian and from source, command line tools from gz-tools may not work correctly.

A workaround for a single package is to define the environment variable GZ_CONFIG_PATH to point to the location of the Gazebo library installation, where the YAML file for the package is found, such as

export GZ_CONFIG_PATH=/usr/local/share/gz

However, that environment variable only takes a single path, which means if the installations from source are in different locations, only one can be specified.

Another workaround for working with multiple Gazebo libraries on the command line is using symbolic links to each library's YAML file.

mkdir ~/.gz/tools/configs -p
cd ~/.gz/tools/configs/
ln -s /usr/local/share/gz/fuel8.yaml .
ln -s /usr/local/share/gz/transport12.yaml .
ln -s /usr/local/share/gz/transportlog12.yaml .
...
export GZ_CONFIG_PATH=$HOME/.gz/tools/configs

This issue is tracked here.

Documentation

See the installation tutorial.

Testing

See the installation tutorial.

See the Writing Tests section of the contributor guide for help creating or modifying tests.

Folder Structure

Refer to the following table for information about important directories and files in this repository.

gz-sim
├── examples                     Various examples that can be run against binary or source installs of gz-sim.
│   ├── plugin                   Example plugins.
│   ├── standalone               Example standalone programs that use gz-sim as a library.
│   └── worlds                   Example SDF world files.
├── include/gz/sim               Header files that downstream users are expected to use.
│   └── detail                   Header files that are not intended for downstream use, mainly template implementations.
├── src                          Source files and unit tests.
│   ├── gui                      Graphical interface source code.
│   └── systems                  System source code.
├── test
│   ├── integration              Integration tests.
│   ├── performance              Performance tests.
│   ├── plugins                  Plugins used in tests.
│   ├── regression               Regression tests.
│   └── tutorials                Tutorials, written in markdown.
├── Changelog.md                 Changelog.
├── CMakeLists.txt               CMake build script.
├── Migration.md                 Migration guide.
└── README.md                    This readme.

Contributing

Please see CONTRIBUTING.md.

Code of Conduct

Please see CODE_OF_CONDUCT.md.

Versioning

This library uses Semantic Versioning. Additionally, this library is part of the Gazebo project which periodically releases a versioned set of compatible and complimentary libraries. See the Gazebo website for version and release information.


BuildStatus
Test coveragecodecov
Ubuntu FocalBuild Status
HomebrewBuild Status
WindowsBuild Status

Download Details:

Author: Gazebosim
Source Code: https://github.com/gazebosim/gz-sim 
License: Unknown, Apache-2.0 licenses found

#machinelearning #cpluplus #robotics #render #physics 

GZ-sim: Open Source Robotics Simulator
Rupert  Beatty

Rupert Beatty

1677151860

A Swift DSL for Type-safe, Extensible, & Transformable HTML Documents

🗺 swift-html  

A Swift DSL for type-safe, extensible, and transformable HTML documents.

Motivation

The popular choice for rendering HTML in Swift these days is to use templating languages, but they expose your application to runtime errors and invalid HTML. Our library prevents these runtime issues at compile-time by embedding HTML directly into Swift’s powerful type system.

Examples

HTML documents can be created in a tree-like fashion, much like you might create a nested JSON document:

import Html

let document: Node = .document(
  .html(
    .body(
      .h1("Welcome!"),
      .p("You’ve found our site!")
    )
  )
)

Underneath the hood these tag functions html, body, h1, etc., are just creating and nesting instances of a Node type, which is a simple Swift enum. Because Node is just a simple Swift type, we can transform it in all kinds of interesting ways. For a silly example, what if we wanted to remove all instances of exclamation marks from our document?

func unexclaim(_ node: Node) -> Node {
  switch node {
  case .comment:
    // Don't need to transform HTML comments
    return node

  case .doctype:
    // Don't need to transform doctypes
    return node

  case let .element(tag, attrs, children):
    // Recursively transform all of the children of an element
    return .element(tag, attrs, unexclaim(children))

  case let .fragment(children):
    // Recursively transform all of the children of a fragment
    return .fragment(children.map(unexclaim))

  case let .raw(string):
    // Transform text nodes by replacing exclamation marks with periods.
    return .raw(string.replacingOccurrences(of: "!", with: "."))
  case let .text(string):
    // Transform text nodes by replacing exclamation marks with periods.
    return .text(string.replacingOccurrences(of: "!", with: "."))
  }
}

unexclaim(document)

Once your document is created you can render it using the render function:

render(document)
// <!doctype html><html><body><h1>Welcome!</h1><p>You’ve found our site!</p></body></html>

And of course you can first run the document through the unexlaim transformation, and then render it:

render(unexclaim(document))
// <!doctype html><html><body><h1>Welcome.</h1><p>You’ve found our site.</p></body></html>

Now the document is very stern and serious 😂.

Safety

Because we are embedding our DSL in Swift we can take advantage of some advanced Swift features to add an extra layer of safety when constructing HTML documents. For a simple example, we can strengthen many HTML APIs to force their true types rather than just relying on strings.

let imgTag = Node.img(attributes: [.src("cat.jpg"), .width(400), .height(300)])

render(imgTag)
// <img src="cat.jpg" width="400" height="300">

Here the src attribute takes a string, but width and height take integers, as it’s invalid to put anything else in those attributes.

For a more advanced example, <li> tags can only be placed inside <ol> and <ul> tags, and we can represent this fact so that it’s impossible to construct an invalid document:

let listTag = Node.ul(
  .li("Cat"),
  .li("Dog"),
  .li("Rabbit")
) // ✅ Compiles!

render(listTag)
// <ul><li>Cat</li><li>Dog</li><li>Rabbit</li></ul>

Node.div(
  .li("Cat"),
  .li("Dog"),
  .li("Rabbit")
) // 🛑 Compile error

Design

The core of the library is a single enum with 6 cases:

public enum Node {
  case comment(String)
  case doctype(String)
  indirect case element(String, [(key: String, value: String?)], Node)
  indirect case fragment([Node])
  case raw(String)
  case text(String)
}

This type allows you to express every HTML document that can ever exist. However, using this type directly can be a little unwieldy, so we provide a bunch of helper functions for constructing every element and attribute from the entire HTML spec in a type-safe manner:

// Not using helper functions
Node.element("html", [], [
  .element("body", [], [
    .element("p", [], [.text("You’ve found our site!")])
    ])
  ])

// versus

// Using helper functions
Node.html(
  .body(
    .h1("Welcome!"),
    .p("You’ve found our site!")
  )
)

This makes the “Swiftification” of an HTML document looks very similar to the original document.

FAQ

Can I use this with existing Swift web frameworks like Kitura and Vapor?

Yes! We even provide plug-in libraries that reduce the friction of using this library with Kitura and Vapor. Find out more information at the following repos:

Why would I use this over a templating language?

Templating languages are popular and easy to get started with, but they have many drawbacks:

Stringy APIs: Templating languages are always stringly typed because you provide your template as a big ole string, and then at runtime the values are interpolated and logic is executed. This means things we take for granted in Swift, like the compiler catching typos and type mismatches, will go unnoticed until you run the code.

Incomplete language: Templating languages are just that: programming languages. That means you should expect from these languages all of the niceties you get from other fully-fledged languages like Swift. That includes syntax highlighting, IDE autocompletion, static analysis, refactoring tools, breakpoints, debugger, and a whole slew of features that make Swift powerful like let-bindings, conditionals, loops and more. However, the reality is that no templating language supports all of these features.

Rigid: Templating languages are rigid in that they do not allow the types of compositions and transformations we are used to performing on data structures in Swift. It is not possible to succinctly traverse over the documents you build, and inspect or transform the nodes you visit. This capability has many applications, such as being able to pretty print or minify your HTML output, or writing a transformation that allows you to inline a CSS stylesheet into an HTML node. There are entire worlds closed off to you due to how templating languages work.

The DSL in this library fixes all of these problems, and opens up doors that are completely closed to templating languages.

When is it more appropriate to use a templating language over swift-html?

There are a few reasons you might want to still use a templating language:

A designer delivers a large HTML document to you and all you want to do is hook in a little bit of value interpolation or logic. In this case you can simply copy and paste that HTML into your template, add a few interpolation tokens, and you're well on your way to having a full page served from your web application.

You need to render non-HTML documents. The beauty of templating languages is that it outputs straight to plain text, and so it can model any type of document, whether it be HTML, markdown, XML, RSS, ATOM, LaTeX, and more.

Creating very large documents in a single expression can cause compile times to go up, whereas templates are not compiled by Swift and so do not influence compile times. Luckily this isn't a problem too often because it is very easy to break up a document into as many small pieces as you want, which will probably lead to more reusable code in the long run.

If you do decide that a templating language better suites your needs, then you should consider HypertextLiteral, which gives you template-like capabilities but in a safer manner.

Installation

You can add swift-html to an Xcode project by adding it as a package dependency.

https://github.com/pointfreeco/swift-html

If you want to use swift-html in a SwiftPM project, it's as simple as adding it to a dependencies clause in your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-html", from: "0.4.0")
]

Interested in learning more?

These concepts (and more) are explored thoroughly in a series of episodes on Point-Free, a video series exploring functional programming and Swift hosted by Brandon Williams and Stephen Celis.

The ideas for this library were explored in the following episodes:

Download Details:

Author: Pointfreeco
Source Code: https://github.com/pointfreeco/swift-html 
License: MIT license

#swift #html #render #server #side

A Swift DSL for Type-safe, Extensible, & Transformable HTML Documents
Taki Rahal

Taki Rahal

1669975357

Deploying Spring Boot Applications on Render.com for FREE

In this article, you’ll learn how to host a Spring boot application on Render platform

Prerequisites
Java Development Kit (JDK) version 11.
Maven 3.8.2 or newer.
PostgreSQL
Docker

Full code source
 https://github.com/TakiRahal/spring-boot-render 

#springboot  #java  #postgres , #docker , #render, #programming  #developer  

Deploying Spring Boot Applications on Render.com for FREE
Mike  Kozey

Mike Kozey

1668618480

React Render Props Pattern

React render props pattern

Let's learn about React render props pattern

React render props pattern is another advanced pattern in React code beside higher-order component.

Put simply, A render props pattern emerges when you have a component that dynamically renders element to the screen from its prop value.

Yes, it’s a bit confusing, so let me show you a simple example. A render prop needs at least two components:

  • the wrapper component who passes the render prop
  • the base component who has calls the render prop

The wrapper component could look like this:

class WrapperComponent extends Component {
  render(){
    return <BaseComponent render={ name => (
      <h1>Hello, {name} </h1>
    )}/>
  }
}

As you can see, the WrapperComponent will call on BaseComponent and passed a render prop. Then the BaseComponent would call the render function like this:

class BaseComponent extends Component {
  state = {
    name: "Danny"
  }

  render(){
    this.props.render(this.state.name)
  }
}

Here’s the Code Sandbox example

This way, the BaseComponent can share its logic across many components, and in the case of the example above, it can share its state value to be rendered in any way the WrapperComponent wants to:

class WrapperComponent extends Component {
  render(){
    return <BaseComponent render={ name => (
      <h1>Good to see you today, {name}!</h1>
    )}/>
  }
}

class AnotherWrapper extends Component {
  render(){
    return <BaseComponent render={ name => (
      <p>Howdy Mister {name}!</p>
    )}/>
  }
}

Just like higher-order component, the render props pattern was very popular to use before the release of React hooks.

For example, to share state like the example above, you can create a new custom hook like this:

export function useName(initialValue = "Danny") {
  const [name, setName] = useState(initialValue);
  return [name, setName];
}

Then the wrapper components can simply import the function:

function WrapperComponent() {
  const [name, setName] = useName();

  return <h1>Good to see you today, {name}!</h1>
}

function AnotherWrapper extends Component {
  const [name, setName] = useName();

  return <p>Howdy Mister {name}!</p>
}

As you can see, using React hooks are more easy to your eyes and far simpler than render props.

If you’re working on new projects, consider using custom hooks instead of render props.

More about React hooks

Original article source at: https://sebhastian.com/

#react #render #pattern 

React Render Props Pattern
Gordon  Matlala

Gordon Matlala

1668504660

How to Deploying A Flask App To Render

This tutorial illustrates how to deploy a Flask application with a PostgreSQL database to production on Render.

Technologies used in this tutorial:

  1. Flask - Python web framework
  2. Flask-SQLAlchemy - Python package for interfacing with relational databases
  3. PostgreSQL - relational database
  4. Gunicorn - Python WSGI HTTP Server
  5. Render - web hosting service

Objectives

By the end of this tutorial, you'll be able to:

  1. Explain how to transition a Flask app from deployment to production
  2. Describe how a Flask app runs on Render
  3. Deploy a Flask app to Render
  4. Configure a Flask app to communicate with a PostgreSQL database on Render

Why Render?

Render is an easy-to-use Platform as a Service (PaaS) solution that's great for hosting Flask apps.

Plus, they have a free tier that allows you to easily test out their platform. Additionally, they have reasonably priced options for hosting apps and databases.

Since Heroku is discontinuing it's free tier on November 28th, 2022, I've toyed around with a number of Heroku alternatives and found that Render is the best. Render's developer experience is nice and the configuration steps are very intuitive for setting up web services and databases.

Free-Tier Limitations

There are some limitations when using the free tier services on Render:

  1. 90-day limit for PostgreSQL databases
  2. Slower build and deployment times
  3. No shell access for "Web Services"

The slower build and deploy times are to be expected, as you're sharing resources with other users.

It's worth noting that build and deploy times are fast for web services on the paid plan.

Flask Production Setup

Flask in Development

During the development phase of a project, the development server is typically used for running the app locally:

Flask Development Server

The Flask development server is run using:

$ flask --app app --debug run

Where --app specifies the file for the Flask app (app.py) and --debug enables the debug mode (interactive debugger and automatic reload when code is changed).

You can view the app by navigating to http://127.0.0.1:5000/ in your browser of choice.

Flask in Production

At a certain point when developing a Flask app, you'll want to deploy it app to production so that others can access it.

The Flask development server to great for serving up a Flask app locally. As the name suggests, the "development" server is not meant for production, though. Instead, you should use Gunicorn, a production-grade WSGI web app server.

WSGI, which stands for Web Server Gateway Interface, is an interface between a web server and a web app since a web server cannot talk directly to a Python app. For more, review WSGI.

The following diagram illustrates how the Flask app will be deployed to production using Render:

Flask in Production on Render

When deploying to Render, a "Web Service" will run the WSGI server (Gunicorn) and the Flask app. Render provides the web server, which will route HTTP traffic to Gunicorn. Additionally, the "PostgreSQL Service" will run the PostgreSQL database that the Flask app will interact with.

Prerequisites

There are several considerations when moving from a Flask app running on your local computer to deploying it to Render...

Gunicorn

In your virtual environment, install Gunicorn:

# pip
(venv)$ pip install gunicorn
(venv)$ pip freeze > requirements.txt

Feel free to swap out virtualenv and pip for Poetry or Pipenv. For more, review Modern Python Environments.

If you're using pip, make sure to save the Python package dependencies in a requirements.txt file, as this file should be used in the "Build" step on Render.

PostgreSQL

SQLite is a great database for small projects and development efforts. However, once you transition to production, you'll want to use a production-level relational database, such as PostgreSQL.

Luckily, Flask-SQLAlchemy makes it easy to swap out SQLite for PostgreSQL.

To get started, there are two Python packages needed for interacting with a PostgreSQL database:

(venv)$ pip install psycopg2-binary
(venv)$ pip freeze > requirements.txt

psycopg2 is a PostgreSQL database adapter for Python.

Additionally, you need to make sure your Flask app utilizes an environment variable (such as DATABASE_URL) for determining the URI to the database:

class Config(object):
    ...
    # Since SQLAlchemy 1.4.x has removed support for the 'postgres://' URI scheme,
    # update the URI to the postgres database to use the supported 'postgresql://' scheme
    if os.getenv('DATABASE_URL'):
        SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL').replace("postgres://", "postgresql://", 1)
    else:
        SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(BASEDIR, 'instance', 'app.db')}"
    ...

A key conversion to make is to update the URI to the PostgreSQL database to use the supported postgresql:// scheme instead of the postgres:// URI.

You can see a full example here.

Logging

When running a Flask app on Render, the console log will show all the log messages from the Gunicorn logger, but not from the Flask app.

However, the Flask app can be configured to utilize the Gunicorn logger:

if app.config['LOG_WITH_GUNICORN']:
    gunicorn_error_logger = logging.getLogger('gunicorn.error')
    app.logger.handlers.extend(gunicorn_error_logger.handlers)
    app.logger.setLevel(logging.DEBUG)
else:
    ... standard logging configuration ...

You can see a full example here.

Database Initialization

This section only applies when using the free tier "Web Service" on Render.

Typically, a CLI command should be created for initializing the database when the Flask app is first initialized:

@app.cli.command('init_db')
def initialize_database():
    """Initialize the database."""
    db.drop_all()
    db.create_all()
    echo('Initialized the database!')

However, the free tier "Web Service" on Render does not support access to a console to be able to run this CLI command.

Therefore, a solution to this problem is to check if the database needs to be initialized when the Flask app is created:

# Check if the database needs to be initialized
engine = sa.create_engine(app.config['SQLALCHEMY_DATABASE_URI'])
inspector = sa.inspect(engine)
if not inspector.has_table("users"):
    with app.app_context():
        db.drop_all()
        db.create_all()
        app.logger.info('Initialized the database!')
else:
    app.logger.info('Database already contains the users table.')

Assuming that an Application Factory Function is being used to create the Flask app, this block of code can be placed in the Application Factory Function after the Flask app has been instantiated and initialized.

You can see a full example here.

Render Deployment Steps

Web Service

Start by creating a new account with Render (if you don't have one). Then, navigate to your dashboard, click on the "New +" button, and select "Web Service".

Connect your Render account to either your GitLab or GitHub account. Once connected, select the repository to deploy:

Deploy to Render - Step 1

Fill out the configuration for deploying the Web Service:

Deploy to Render - Step 2

Fields:

  1. Name - select a unique name for the app, as this will be used for the URL
  2. Root Directory - the service's root directory (the default value is the top-level directory); all build commands will be run based on this root directory
  3. Environment - select "Python 3"
  4. Region - select the region closest to you

Deploy to Render - Step 3

More fields:

  1. Branch - select the branch from your git repository to deploy from, typically "main" or "master"
  2. Build Command - command to get your application ready for production, such as installing the necessary Python packages -- e.g., pip install -r requirements.txt, poetry build, etc.
  3. Start Command - use the default value of gunicorn app:app or specify the number of workers and the log level using gunicorn --workers=2 --log-level=info app:app

Select the Plan to use.

Next, you can set the specific version of Python to use via environment variables. To set environment variables, click on the "Advanced" button. Then, add an environment variable called "PYTHON_VERSION", specifying the Python version for your application -- e.g., "3.10.7".

Deploy to Render - Step 4

The "PYTHON_VERSION" environment variable must include the major, minor, and patch versions, so "3.10.7" is valid but "3.10" is not.

Finally, click on "Create Web Service" at the bottom of the page.

You'll then see all the Python packages from requirements.txt being installed:

Deploy to Render - Step 5

Once the build is successful and deployed, you'll see something similar to:

Deploy to Render - Step 6

You can click on the "Logs" tab to see that Gunicorn is up and running:

Deploy to Render - Step 7

At this point, you can navigate to see the home page of the app. Just keep in mind that we still need to set up PostgreSQL!

PostgreSQL Service

To configure PostgreSQL, on your dashboard, click on the "New +" button again and select "PostgreSQL".

Configuration

Next, fill out the configuration for deploying the PostgreSQL database:

Deploy PostgreSQL to Render - Step 1

Fields:

  1. Name - enter a friendly (and unique) name for the database, which will be used to identify this database in the Render dashboard
  2. Database - enter a 'dbname' or leave blank to have it randomly generated
  3. User - enter a user name or leave blank to have automatically generated as <Name>_user
  4. Region - select the region closest to you
  5. PostgreSQL Version - select the required PostgreSQL version for your app ("14" is the default)

Select the Plan to use.

The free tier database will be destroyed after 90 days. Just keep that in mind. This plan is still a great option for experimenting with.

Click on "Create Database" at the bottom of the page.

When the database is done being created, you'll see the "Status" update to "Available":

Deploy PostgreSQL to Render - Step 2

At this point, you'll need to scroll down to the "Connections" section and copy the "Internal Database URL":

Deploy PostgreSQL to Render - Step 3

Update Environment Variables

Now, you'll want to set the database URL as an environment variable so you're application can use it.

Within the dashboard, select the "Web Service" you just created and then click the "Environment" tab.

You should see the "PYTHON_VERSION" environment variable, which we set earlier. Add the "DATABASE_URL" environment variable using the "Internal Database URL". Depending on how you configure your Flask app, you may need to add additional environment variables, such as "SECRET_KEY".

Configuration Updates on Render - Step 1

Check the deployment status by clicking on the "Events" tab:

Configuration Updates on Render - Step 2

You'll see that the "Deploy" is live once all the configuration changes are applied and the service is updated.

You can find the Flask app that I used for this tutorial at https://flask-user-management-app.onrender.com.

Conclusion

This tutorial provided a walk-through of deploying a Flask app with a PostgreSQL database to production using Render.

Render provides an excellent hosting solution for Flask apps. It’s a great experience to deploy an app and the free tier is great for experimenting with deploying.

If you're interested in learning more about Flask, check out my course on how to build, test, and deploy a Flask app:

Original article source at: https://testdriven.io/

#flask #deploying #render 

How to Deploying A Flask App To Render

Experimental Quake 2 Map Renderer Written with Julia and Modern OpenGL

Quake2.jl

Experimental Quake 2 map renderer written with Julia and modern OpenGL. Created at Hacker School. It is a toy and not actively developed.

Features

  • Map (.bsp) rendering
  • Per-pixel lighting
  • Support for bump and parallax effects from heightmaps

Requirements

  • Julia v0.2 (still in development - must be built from source)
  • Official Julia packages (can be added with Pkg.add()):
  • Unofficial Julia packages (must be added manually):
    • GL (requires at least OpenGL 3.x)
  • Quake 2 game data (i.e. pak0.pak)

Due to the fast-moving nature of Julia development and its packages, setting up an environment is not easy. In its current state, the code will likely not run outside the author's environment.

Lighting

Traditional Phong shading is applied per-pixel and used in conjunction with point lights parsed from the BSP's entity list. Lightmaps baked into the BSP are not currently being used due to their low resolution and lack of direction information.

Bump and parallax effects can be created by providing an optional height map. Normal maps are calculated automatically from the height map at initialization.

Height map

Normal map

Bump and parallax

The bump and parallax effects breathe new life into the original, low resolution Quake 2 textures.

Diffuse

Combined effect

Download Details:

Author: jayschwa
Source Code: https://github.com/jayschwa/Quake2.jl 
License: MIT license

#julia #opengl #map #render 

Experimental Quake 2 Map Renderer Written with Julia and Modern OpenGL
Josefa  Corwin

Josefa Corwin

1659881700

A Pattern Language Whose Goal Is to Reduce Syntax To The Essentials

Slim

Slim is a template language whose goal is to reduce the view syntax to the essential parts without becoming cryptic. It started as an exercise to see how much could be removed from a standard html template (<, >, closing tags, etc...). As more people took an interest in Slim, the functionality grew and so did the flexibility of the syntax.

A short list of the features...

  • Elegant syntax
    • Short syntax without closing tags (Using indentation instead)
    • HTML style mode with closing tags
    • Configurable shortcut tags (# for <div id="..."> and . for <div class="..."> in the default configuration)
  • Safety
    • Automatic HTML escaping by default
    • Support for Rails' html_safe?
  • Highly configurable
  • Extensible via the following plugins:
    • Logic less mode similar to Mustache
    • Includes
    • Translator/I18n
  • High performance
    • Comparable speed to ERB/Erubis
    • Streaming support in Rails
  • Supported by all major frameworks (Rails, Sinatra, ...)
  • Full Unicode support for tags and attributes
  • Embedded engines like Markdown and Textile

Introduction

What is Slim?

Slim is a fast, lightweight templating engine with support for Rails 3 and later. It has been heavily tested on all major ruby implementations. We use continuous integration (travis-ci).

Slim's core syntax is guided by one thought: "What's the minimum required to make this work".

As more people have contributed to Slim, there have been syntax additions influenced from their use of Haml and Jade. The Slim team is open to these additions because we know beauty is in the eye of the beholder.

Slim uses Temple for parsing/compilation and is also integrated into Tilt, so it can be used together with Sinatra or plain Rack.

The architecture of Temple is very flexible and allows the extension of the parsing and compilation process without monkey-patching. This is used by the logic less plugin and the translator plugin which provides I18n. In logic-less mode you can use Slim if you like the Slim syntax to build your HTML but don't want to write Ruby in your templates.

Why use Slim?

  • Slim allows you to write very minimal templates which are easy to maintain and pretty much guarantees that you write well-formed HTML and XML
  • The Slim syntax is aesthetic and makes it more fun to write templates. Since you can use Slim as a drop-in replacement in all the major frameworks it is easy to adopt.
  • The Slim architecture is very flexible and allows you to write syntax extensions and plugins.

Yes, Slim is speedy! Slim was developed right from the start with performance in mind. Benchmarks are done for every commit at http://travis-ci.org/slim-template/slim. Don't trust the numbers? That's as it should be. Please try the benchmark rake task yourself!

However in our opinion you should use Slim because of its features and syntax. We just ensure that Slim doesn't have a negative impact on the performance of your application.

How to start?

Install Slim as a gem:

gem install slim

Include Slim in your Gemfile with gem 'slim' or require it with require 'slim'. That's it! Now, just use the .slim extension and you're good to go.

Syntax example

Here's a quick example to demonstrate what a Slim template looks like:

doctype html
html
  head
    title Slim Examples
    meta name="keywords" content="template language"
    meta name="author" content=author
    link rel="icon" type="image/png" href=file_path("favicon.png")
    javascript:
      alert('Slim supports embedded javascript!')

  body
    h1 Markup examples

    #content
      p This example shows you how a basic Slim file looks.

    == yield

    - if items.any?
      table#items
        - for item in items
          tr
            td.name = item.name
            td.price = item.price
    - else
      p No items found. Please add some inventory.
        Thank you!

    div id="footer"
      == render 'footer'
      | Copyright &copy; #{@year} #{@author}

Indentation matters, but the indentation depth can be chosen as you like. If you want to first indent 2 spaces, then 5 spaces, it's your choice. To nest markup you only need to indent by one space, the rest is gravy.

Line indicators

Verbatim text |

The pipe tells Slim to just copy the line. It essentially escapes any processing. Each following line that is indented greater than the pipe is copied over.

body
  p
    |
      This is a test of the text block.

The parsed result of the above:

<body><p>This is a test of the text block.</p></body>

If the text starts on the same line, the left margin is set at the indent of the pipe + one space. Any additional spaces will be copied over.

body
  p
    | This line is on the left margin.
       This line will have one space in front of it.
         This line will have two spaces in front of it.
           And so on...

You can also embed html in the text line

- articles.each do |a|
  | <tr><td>#{a.name}</td><td>#{a.description}</td></tr>

Verbatim text with trailing white space '

The single quote tells Slim to copy the line (similar to |), but makes sure that a single trailing white space is appended.

Inline html <

You can write html tags directly in Slim which allows you to write your templates in a more html like style with closing tags or mix html and Slim style. The leading < works like an implicit |:

<html>
  head
    title Example
  <body>
    - if articles.empty?
    - else
      table
        - articles.each do |a|
          <tr><td>#{a.name}</td><td>#{a.description}</td></tr>
  </body>
</html>

Control code -

The dash denotes control code. Examples of control code are loops and conditionals. end is forbidden behind -. Blocks are defined only by indentation. If your ruby code needs to use multiple lines, append a backslash \ at the end of the lines. If your line ends with comma , (e.g because of a method call) you don't need the additional backslash before the linebreak.

body
  - if articles.empty?
    | No inventory

Output =

The equals sign tells Slim it's a Ruby call that produces output to add to the buffer. If your ruby code needs to use multiple lines, append a backslash \ at the end of the lines. For example:

= javascript_include_tag \
   "jquery",
   "application"

If your line ends with comma , (e.g because of a method call) you don't need the additional backslash before the linebreak. For trailing or leading whitespace the modifiers > and < are supported.

  • Output with trailing white space =>. Same as the single equals sign (=), except that it adds a trailing white space.
  • Output with leading white space =<. Same as the single equals sign (=), except that it adds a leading white space.

Output without HTML escaping ==

Same as the single equals sign (=), but does not go through the escape_html method. For trailing or leading whitespace the modifiers > and < are supported.

  • Output without HTML escaping and trailing white space ==>. Same as the double equals sign (==), except that it adds a trailing white space.
  • Output without HTML escaping and leading white space ==<. Same as the double equals sign (==), except that it adds a leading white space.

Code comment /

Use the forward slash for code comments - anything after it won't get displayed in the final render. Use / for code comments and /! for html comments

body
  p
    / This line won't get displayed.
      Neither does this line.
    /! This will get displayed as html comments.

The parsed result of the above:

<body><p><!--This will get displayed as html comments.--></p></body>

HTML comment /!

Use the forward slash immediately followed by an exclamation mark for html comments (<!-- ... -->).

IE conditional comment /[...]

/[if IE]
    p Get a better browser.

This renders as:

<!--[if IE]><p>Get a better browser.</p><![endif]-->

HTML tags

<!DOCTYPE> declaration

The doctype keyword can be used to generate the complex doctypes in a very simple manner.

XML VERSION

doctype xml
  <?xml version="1.0" encoding="utf-8" ?>

doctype xml ISO-8859-1
  <?xml version="1.0" encoding="iso-8859-1" ?>

XHTML DOCTYPES

doctype html
  <!DOCTYPE html>

doctype 5
  <!DOCTYPE html>

doctype 1.1
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

doctype strict
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

doctype frameset
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

doctype mobile
  <!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN"
    "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">

doctype basic
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
    "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">

doctype transitional
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

HTML 4 DOCTYPES

doctype strict
  <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
    "http://www.w3.org/TR/html4/strict.dtd">

doctype frameset
  <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
    "http://www.w3.org/TR/html4/frameset.dtd">

doctype transitional
  <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">

Closed tags (trailing /)

You can close tags explicitly by appending a trailing /.

img src="image.png"/

Note, that this is usually not necessary since the standard html tags (img, br, ...) are closed automatically.

Trailing and leading whitespace (<, >)

You can force Slim to add a trailing whitespace after a tag by adding a >.

a> href='url1' Link1
a> href='url2' Link2

You can add a leading whitespace by adding <.

a< href='url1' Link1
a< href='url2' Link2

You can also combine both.

a<> href='url1' Link1

Inline tags

Sometimes you may want to be a little more compact and inline the tags.

ul
  li.first: a href="/a" A link
  li: a href="/b" B link

For readability, don't forget you can wrap the attributes.

ul
  li.first: a[href="/a"] A link
  li: a[href="/b"] B link

Text content

Either start on the same line as the tag

body
  h1 id="headline" Welcome to my site.

Or nest it. You must use a pipe or an apostrophe to escape processing

body
  h1 id="headline"
    | Welcome to my site.

Or enable and rely on smart text instead

body
  h1 id="headline"
    Welcome to my site.

Dynamic content (= and ==)

Can make the call on the same line

body
  h1 id="headline" = page_headline

Or nest it.

body
  h1 id="headline"
    = page_headline

Attributes

You write attributes directly after the tag. For normal text attributes you must use double " or single quotes ' (Quoted attributes).

a href="http://slim-lang.com" title='Slim Homepage' Goto the Slim homepage

You can use text interpolation in the quoted attributes.

Attributes wrapper

If a delimiter makes the syntax more readable for you, you can use the characters {...}, (...), [...] to wrap the attributes. You can configure these symbols (See option :attr_list_delims).

body
  h1(id="logo") = page_logo
  h2[id="tagline" class="small tagline"] = page_tagline

If you wrap the attributes, you can spread them across multiple lines:

h2[id="tagline"
   class="small tagline"] = page_tagline

You may use spaces around the wrappers and assignments:

h1 id = "logo" = page_logo
h2 [ id = "tagline" ] = page_tagline

Quoted attributes

Example:

a href="http://slim-lang.com" title='Slim Homepage' Goto the Slim homepage

You can use text interpolation in the quoted attributes:

a href="http://#{url}" Goto the #{url}

The attribute value will be escaped by default. Use == if you want to disable escaping in the attribute.

a href=="&amp;"

You can break quoted attributes with backslash \

a data-title="help" data-content="extremely long help text that goes on\
  and on and on and then starts over...."

Ruby attributes

Write the ruby code directly after the =. If the code contains spaces you have to wrap the code into parentheses (...). You can also directly write hashes {...} and arrays [...].

body
  table
    - for user in users
      td id="user_#{user.id}" class=user.role
        a href=user_action(user, :edit) Edit #{user.name}
        a href=(path_to_user user) = user.name

The attribute value will be escaped by default. Use == if you want to disable escaping in the attribute.

a href==action_path(:start)

You can also break ruby attributes with backslash \ or trailing , as described for control sections.

Boolean attributes

The attribute values true, false and nil are interpreted as booleans. If you use the attribute wrapper you can omit the attribute assigment.

input type="text" disabled="disabled"
input type="text" disabled=true
input(type="text" disabled)

input type="text"
input type="text" disabled=false
input type="text" disabled=nil

Attribute merging

You can configure attributes to be merged if multiple are given (See option :merge_attrs). In the default configuration this is done for class attributes with the white space as delimiter.

a.menu class="highlight" href="http://slim-lang.com/" Slim-lang.com

This renders as:

<a class="menu highlight" href="http://slim-lang.com/">Slim-lang.com</a>

You can also use an Array as attribute value and the array elements will be merged using the delimiter.

a class=["menu","highlight"]
a class=:menu,:highlight

Splat attributes *

The splat shortcut allows you to turn a hash into attribute/value pairs.

.card*{'data-url'=>place_path(place), 'data-id'=>place.id} = place.name

This renders as:

<div class="card" data-id="1234" data-url="/place/1234">Slim's house</div>

You can also use methods or instance variables which return a hash as shown here:

.card *method_which_returns_hash = place.name
.card *@hash_instance_variable = place.name

The hash attributes which support attribute merging (see Slim option :merge_attrs) can be given as an Array

.first *{class: [:second, :third]} Text

This renders as:

div class="first second third"

Splat attributes prefix may be configured via splat_prefix option. Default value is '*'

Dynamic tags *

You can create completely dynamic tags using the splat attributes. Just create a method which returns a hash with the :tag key.

ruby:
  def a_unless_current
    @page_current ? {tag: 'span'} : {tag: 'a', href: 'http://slim-lang.com/'}
  end
- @page_current = true
*a_unless_current Link
- @page_current = false
*a_unless_current Link

This renders as:

<span>Link</span><a href="http://slim-lang.com/">Link</a>

Shortcuts

Tag shortcuts

You can define custom tag shortcuts by setting the option :shortcut. In Rails apps, you need to put this code for your shortcuts into an initializer like config/initializers/slim.rb. In Sinatra, you simply add the same configuration anywhere below the line where you require 'slim'.

Slim::Engine.set_options shortcut: {'c' => {tag: 'container'}, '#' => {attr: 'id'}, '.' => {attr: 'class'} }

We can use it in Slim code like this

c.content Text

which renders to

<container class="content">Text</container>

Attribute shortcuts

You can define custom shortcuts (Similar to # for id and . for class).

In this example we add & to create a shortcut for the input elements with type attribute.

Slim::Engine.set_options shortcut: {'&' => {tag: 'input', attr: 'type'}, '#' => {attr: 'id'}, '.' => {attr: 'class'}}

We can use it in Slim code like this

&text name="user"
&password name="pw"
&submit

which renders to

<input type="text" name="user" />
<input type="password" name="pw" />
<input type="submit" />

In another example we add @ to create a shortcut for the role attribute.

Slim::Engine.set_options shortcut: {'@' => {attr: 'role'}, '#' => {attr: 'id'}, '.' => {attr: 'class'}}

We can use it in Slim code like this

.person@admin = person.name

which renders to

<div class="person" role="admin">Daniel</div>

You can also set multiple attributes with same value at once using one shortcut.

Slim::Engine.set_options shortcut: {'@' => {attr: %w(data-role role)}}

We can use it in Slim code like this

.person@admin = person.name

which renders to

<div class="person" role="admin" data-role="admin">Daniel</div>

You can also set additional fixed value attributes to a shortcut.

Slim::Engine.set_options shortcut: {'^' => {tag: 'script', attr: 'data-binding',
  additional_attrs: { type: "text/javascript" }}}

Then

^products
  == @products.to_json

which renders to

<script data-binding="products" type="text/javascript">
[{"name": "product1", "price": "$100"},
 {"name": "prodcut2", "price": "$200"}]
</script>

ID shortcut # and class shortcut .

You can specify the id and class attributes in the following shortcut form

body
  h1#headline
    = page_headline
  h2#tagline.small.tagline
    = page_tagline
  .content
    = show_content

This is the same as

body
  h1 id="headline"
    = page_headline
  h2 id="tagline" class="small tagline"
    = page_tagline
  div class="content"
    = show_content

Helpers, capturing and includes

If you use Slim you might want to extend your template with some helpers. Assume that you have the following helper

module Helpers
  def headline(&block)
    if defined?(::Rails)
      # In Rails we have to use capture!
      "<h1>#{capture(&block)}</h1>"
    else
      # If we are using Slim without a framework (Plain Tilt),
      # this works directly.
      "<h1>#{yield}</h1>"
    end
  end
end

which is included in the scope that executes the Slim template code. The helper can then be used in the Slim template as follows

p
  = headline do
    ' Hello
    = user.name

The content in the do block is then captured automatically and passed to the helper via yield. As a syntactic sugar you can omit the do keyword and write only

p
  = headline
    ' Hello
    = user.name

Capturing to local variables

Using the Binding you can capture to local variables as follows:

module Helpers
  def capture_to_local(var, &block)
    set_var = block.binding.eval("lambda {|x| #{var} = x }")
    # In Rails we have to use capture!
    # If we are using Slim without a framework (Plain Tilt),
    # you can just yield to get the captured block.
    set_var.call(defined?(::Rails) ? capture(&block) : yield)
  end
end

The helper can then be used in the Slim template as follows

/ The captured_content variable must be known by the Binding beforehand.
= capture_to_local captured_content=:captured_content
  p This will be captured in the variable captured_content
= captured_content

Another interesting use case is to use an enumerable and capture for each element. The helper could look like this

module Capture
  def capture(var, enumerable = nil, &block)
    value = enumerable ? enumerable.map(&block) : yield
    block.binding.eval("lambda {|x| #{var} = x }").call(value)
    nil
  end
end

and it would be used as follows

- links = { 'http://slim-lang.com' => 'The Slim Template Language' }
= capture link_list=:link_list, links do |url, text|
  a href=url = text

Afterwards, link_list contains the captured content.

Include helper

If you want includes which are processed at compile time, you can take a look at Include partials. However you can also execute subtemplates at runtime (similar to Rails' #render). You have to write your own include helper:

module Helpers
  def include_slim(name, options = {}, &block)
    Slim::Template.new("#{name}.slim", options).render(self, &block)
  end
end

This helper can then be used as follows

nav = include_slim 'menu'
section = include_slim 'content'

However this helper doesn't do any caching. You should therefore implement a more intelligent version of the helper which fits your purposes. You should also be aware that most frameworks already bring their own include helper, e.g. Rails has render.

Text interpolation

Use standard Ruby interpolation. The text will be html escaped by default, but you can avoid escaping by using double braces.

body
  h1 Welcome #{current_user.name} to the show.
  | Unescaped #{{content}} is also possible.

To escape the interpolation (i.e. render as is)

body
  h1 Welcome \#{current_user.name} to the show.

Embedded engines (Markdown, ...)

Thanks to Tilt, Slim has extensive support for embedding other template engines.

Examples:

coffee:
  square = (x) -> x * x

markdown:
  #Header
    Hello from #{"Markdown!"}
    Second Line!

p: markdown: Tag with **inline** markdown!

Supported engines:

FilterRequired gemsTypeDescription
ruby:noneShortcutShortcut to embed ruby code
javascript:noneShortcutShortcut to embed javascript code and wrap in script tag
css:noneShortcutShortcut to embed css code and wrap in style tag
sass:sassCompile timeEmbed sass code and wrap in style tag
scss:sassCompile timeEmbed scss code and wrap in style tag
less:lessCompile timeEmbed less css code and wrap in style tag
coffee:coffee-scriptCompile timeCompile coffee script code and wrap in script tag
markdown:redcarpet/rdiscount/kramdownCompile time + InterpolationCompile markdown code and interpolate #{variables} in text
textile:redclothCompile time + InterpolationCompile textile code and interpolate #{variables} in text
rdoc:rdocCompile time + InterpolationCompile rdoc code and interpolate #{variables} in text

The embedded engines can be configured in Slim by setting the options directly on the Slim::Embedded filter. Example:

Slim::Embedded.options[:markdown] = {auto_ids: false}

You can also specify HTML attributes for the following embedded engines:

  • Javascript
  • CSS
  • CoffeeScript
  • LESS
  • SASS
  • SCSS

Example:

scss class="myClass":
  $color: #f00;
  body { color: $color; }

This will generate the following HTML:

<style class="myClass" type="text/css">body{color:red}</style>

Configuring Slim

Slim and the underlying Temple framework are highly configurable. The way how you configure Slim depends a bit on the compilation mechanism (Rails or Tilt). It is always possible to set default options per Slim::Engine class. This can be done in Rails' environment files. For instance, in config/environments/development.rb you probably want:

Default options

# Indent html for pretty debugging and do not sort attributes
Slim::Engine.set_options pretty: true, sort_attrs: false

You can also access the option hash directly:

Slim::Engine.options[:pretty] = true

Setting options at runtime

There are two ways to set options at runtime. For Tilt templates (Slim::Template) you can set the options when you instantiate the template:

Slim::Template.new('template.slim', optional_option_hash).render(scope)

The other possibility is to set the options per thread which is interesting mostly for Rails:

Slim::Engine.with_options(option_hash) do
  # Any Slim engines which are created here use the option_hash
  # For example in Rails:
  render :page, layout: true
end

You have to be aware that the compiled engine code and the options are cached per template in Rails and you cannot change the option afterwards.

# First render call
Slim::Engine.with_options(pretty: true) do
   render :page, layout: true
end

# Second render call
Slim::Engine.with_options(pretty: false) do
   render :page, layout: true # :pretty is still true because it is cached
end

Available options

The following options are exposed by the Slim::Engine and can be set with Slim::Engine.set_options. There are a lot of them but the good thing is, that Slim checks the configuration keys and reports an error if you try to use an invalid configuration key.

TypeNameDefaultPurpose
String:filenilName of parsed file, set automatically by Slim::Template
Integer:tabsize4Number of white spaces per tab (used by the parser)
String:encoding"utf-8"Set encoding of template
String:default_tag"div"Default tag to be used if tag name is omitted
Hash:shortcut{'.' => {attr: 'class'}, '#' => {attr: 'id'}}Attribute shortcuts
Hash:code_attr_delims{'(' => ')', '[' => ']', '{' => '}'}Attribute delimiters for Ruby code attributes
Hash:attr_list_delims{'(' => ')', '[' => ']', '{' => '}'}Attribute list delimiter
Array<Symbol,String>:enable_enginesnil (All enabled)List of enabled embedded engines (whitelist)
Array<Symbol,String>:disable_enginesnil (None disabled)List of disabled embedded engines (blacklist)
Boolean:disable_capturefalse (true in Rails)Disable capturing in blocks (blocks write to the default buffer
Boolean:disable_escapefalseDisable automatic escaping of strings
Boolean:use_html_safefalse (true in Rails)Use String#html_safe? from ActiveSupport (Works together with :disable_escape)
Symbol:format:xhtmlHTML output format (Possible formats :html, :xhtml, :xml)
String:attr_quote'"'Character to wrap attributes in html (can be ' or ")
Hash:merge_attrs{'class' => ' '}Joining character used if multiple html attributes are supplied (e.g. class="class1 class2")
Array<String>:hyphen_attrs%w(data)Attributes which will be hyphenated if a Hash is given (e.g. data={a_foo:1,b:2} will render as data-a_foo="1" data-b="2")
Boolean:hyphen_underscore_attrsfalseAttributes that have underscores in their names will be hyphenated (e.g. data={a_foo:1,b_bar:2} will render as data-a-foo="1" data-b-bar="2")
Boolean:sort_attrstrueSort attributes by name
Symbol:js_wrappernilWrap javascript by :comment, :cdata or :both. You can also :guess the wrapper based on :format.
Boolean:prettyfalsePretty HTML indenting, only block level tags are indented (This is slower!)
String:indent' 'Indentation string
Boolean:streamingfalse (true in Rails, see below how to disable it!)Enable output streaming, improves the perceived performance
Class:generatorTemple::Generators::StringBuffer/ RailsOutputBufferTemple code generator (default generator generates string buffer)
String:buffer'_buf' ('@output_buffer' in Rails)Variable used for buffer
String:splat_prefix'*'Prefix used for splat attributes

There are more options which are supported by the Temple filters but which are not exposed and are not officially supported. You have to take a look at the Slim and Temple code for that.

Option priority and inheritance

For developers who know more about Slim and Temple architecture it is possible to override default options at different positions. Temple uses an inheritance mechanism to allow subclasses to override options of the superclass. The option priorities are as follows:

  1. Slim::Template options passed at engine instantiation
  2. Slim::Template.options
  3. Slim::Engine.thread_options, Slim::Engine.options
  4. Parser/Filter/Generator thread_options, options (e.g Slim::Parser, Slim::Compiler)

It is also possible to set options for superclasses like Temple::Engine. But this will affect all temple template engines then.

Slim::Engine < Temple::Engine
Slim::Compiler < Temple::Filter

Plugins

Slim currently provides plugins for logic less mode, includes and I18n. See the plugin documentation.

Framework support

Tilt

Slim uses Tilt to compile the generated code. If you want to use the Slim template directly, you can use the Tilt interface.

Tilt.new['template.slim'].render(scope)
Slim::Template.new('template.slim', optional_option_hash).render(scope)
Slim::Template.new(optional_option_hash) { source }.render(scope)

The optional option hash can have to options which were documented in the section above. The scope is the object in which the template code is executed.

Sinatra

require 'sinatra'
require 'slim'

get('/') { slim :index }

 __END__
@@ index
doctype html
html
  head
    title Sinatra With Slim
  body
    h1 Slim Is Fun!

Rails

Rails generators are provided by slim-rails. slim-rails is not necessary to use Slim in Rails though. Just install Slim and add it to your Gemfile with gem 'slim'. Then just use the .slim extension and you're good to go.

Streaming

HTTP streaming is enabled by default if you use a Rails version which supports it. However you have to be aware that streaming only improves the perceived performance. The rendering time in total will increase. If you want to disable it use:

Slim::RailsTemplate.set_options streaming: false

Angular2

Slim now supports Angular2 syntax. But you need to set some configuration options:

splat_prefix option

This option tells parser what syntax to use for splat attributes. Default value is asterisk: splat_prefix: '*' Asterisk is also used in Angular2 for structural directives such as *ngIf and others, so default configuration causes a conflict between slim and angular2 syntax.

There are two ways to resolve it:

  • Set splat_prefix to any custom value, double asterisk, for example: splat_prefix: '**'. Now structural directives should work as expected. Remember that now splat attributes should be written with new custom prefix before them.
  • Use alternative directive syntax without asterisk.

Attribute delimeters

Angular and slim both uses brackets in their syntax. So there are also two ways:

  • Use alternative syntax for binding (bind-... and so on)
  • Limit attribute delimeters to curly braces only:
code_attr_delims: {
 '{' => '}',
},
attr_list_delims: {
 '{' => '}',
},

Now you can use something like this:

h1{ #var (bind1)="test" [bind2]="ok" [(bind3)]="works?" *ngIf="expr" *ngFor="expression" } {{it works}}

Will be compiled to:

<h1 #var="" (bind1)="test" [bind2]="ok" [(bind3)]="works?" *ngIf="expr" *ngFor="expression">
  {{it works}}
</h1>

Tools

Slim Command 'slimrb'

The gem 'slim' comes with the small tool 'slimrb' to test Slim from the command line.

$ slimrb --help Usage: slimrb [options]    -s, --stdin                      Read input from standard input instead of an input file        --trace                      Show a full traceback on error    -c, --compile                    Compile only but do not run    -e, --erb                        Convert to ERB        --rails                      Generate rails compatible code (Implies --compile)    -r, --require library            Load library or plugin with -r slim/plugin    -p, --pretty                     Produce pretty html for debugging purposes    -o, --option name=code           Set slim option    -l, --locals Hash|YAML|JSON      Set local variables    -h, --help                       Show this message    -v, --version                    Print version

Start 'slimrb', type your code and press Ctrl-d to send EOF. In Windows Command Prompt press Ctrl-z, Enter to send EOF. Example usage:

$ slimrb markdown:  First paragraph.  Second paragraph.  * one  * two  * three //Enter Ctrl-d <p>First paragraph </p> <p>Second paragraph </p> <ul> <li>one</li> <li>two</li> <li>three</li> </ul>

Syntax Highlighters

There are plugins for various text editors (including the most important ones - Vim, Emacs and Textmate):

Template Converters (HAML, ERB, ...)

Testing

Benchmarks

Yes, Slim is one of the fastest Ruby template engines out there! In production mode Slim is nearly as fast as Erubis (which is the fastest template engine). But we would be happy if you chose Slim also for any other reason, we assure you performance will not be an obstacle.

Run the benchmarks with rake bench. You can add the option slow to run the slow parsing benchmark which needs more time. You can also increase the number of iterations.

$ rake bench slow=1 iterations=1000

We run the benchmarks for every commit on Travis-CI. Take a look at the newest benchmarking results: http://travis-ci.org/slim-template/slim

Test suite and continuous integration

Slim provides an extensive test-suite based on minitest. You can run the tests with 'rake test' and the rails integration tests with 'rake test:rails'.

We are currently experimenting with human-readable literate tests which are written as markdown files: TESTS.md

Travis-CI is used for continuous integration testing: http://travis-ci.org/slim-template/slim

Slim is working well on all major Ruby implementations:

  • Ruby 2.0, 2.1, 2.2 and 2.3
  • JRuby 1.9 mode
  • Rubinius 2.0

Contributing

If you'd like to help improve Slim, clone the project with Git by running:

$ git clone git://github.com/slim-template/slim

Work your magic and then submit a pull request. We love pull requests!

Please remember to keep the compatibility with Ruby versions 2.0.0, 2.1.0, 2.2.0 and 2.3.0.

If you find the documentation lacking, help us out and update this README.md. If you don't have the time to work on Slim, but found something we should know about, please submit an issue.

License

Slim is released under the MIT license.

Authors

Donations and sponsoring

If you want to support this project please visit the Gittip and Flattr pages.

Flattr donate button

Currently the donations will be used to cover the hosting costs (domain name etc).

Discuss

Related projects

Template compilation framework:

Framework support:

Syntax highlighting:

Static code analysis:

Template Converters (HAML, ERB, ...):

Language ports/Similar languages:

Links


Author: slim-template
Source code: https://github.com/slim-template/slim
License: MIT license

#ruby   #ruby-on-rails 

A Pattern Language Whose Goal Is to Reduce Syntax To The Essentials

ViewComponent: View Components for Rails

A framework for building reusable, testable & encapsulated view components in Ruby on Rails.

ViewComponent (v2.64.0)

A framework for creating reusable, testable & encapsulated view components, built to integrate seamlessly with Ruby on Rails.

What’s a ViewComponent?

Think of ViewComponents as an evolution of the presenter pattern, inspired by React. A ViewComponent is a Ruby object and template:

# app/components/message_component.rb
class MessageComponent < ViewComponent::Base
  def initialize(name:)
    @name = name
  end
end
<%# app/components/message_component.html.erb %>
<h1>Hello, <%= @name %>!</h1>

Which is instantiated and passed to Rails’ #render:

<%# app/views/demo/index.html.erb %>
<%= render(MessageComponent.new(name: "World")) %>

Returning markup:

<h1>Hello, World!</h1>

Why use ViewComponents?

TL;DR

ViewComponents work best for templates that are reused or benefit from being tested directly. Partials and templates with significant amounts of embedded Ruby often make good ViewComponents.

Single responsibility

Rails applications often scatter view-related logic across models, controllers, and helpers, diluting their intended responsibilities. ViewComponents consolidate the logic needed for a template into a single class, resulting in a cohesive object that is easy to understand.

ViewComponent methods are implemented within the scope of the template, encapsulating them in proper object-oriented fashion. This cohesion is especially evident when multiple methods are needed for a single view.

Testing

Unlike traditional Rails templates, ViewComponents can be unit tested. In the GitHub codebase, ViewComponent unit tests are over 100x faster than similar controller tests.

With ViewComponent, integration tests can be reserved for end-to-end assertions, with permutations covered at the unit level.

For example, to test the MessageComponent above:

class MessageComponentTest < GitHub::TestCase
  include ViewComponent::TestHelpers

  test "renders message" do
    render_inline(MessageComponent.new(name: "World"))

    assert_selector "h1", text: "Hello, World!"
  end
end

ViewComponent unit tests leverage the Capybara matchers library, allowing for complex assertions traditionally reserved for controller and browser tests.

Data Flow

Traditional Rails templates have an implicit interface, making it hard to reason about their dependencies. This can lead to subtle bugs when rendering the same template in different contexts.

ViewComponents use a standard Ruby initializer that clearly defines what’s needed to render, making reuse easier and safer than partials.

Performance

Based on several benchmarks, ViewComponents are ~10x faster than partials in real-world use-cases.

The primary optimization is pre-compiling all ViewComponent templates at application boot, instead of at runtime like traditional Rails views.

For example, the MessageComponent template is compiled onto the Ruby object like so:

# app/components/message_component.rb
class MessageComponent < ViewComponent::Base
  def initialize(name:)
    @name = name
  end

  def call
    @output_buffer.safe_append='<h1>Hello, '.freeze
    @output_buffer.append=( @name )
    @output_buffer.safe_append='!</h1>'.freeze
    @output_buffer.to_s
  end
end

Code quality

Template code often fails basic Ruby standards: long methods, deep conditional nesting, and mystery guests abound.

ViewComponents are Ruby objects, making it easy to follow (and enforce) code quality standards.

Documentation

See viewcomponent.org for documentation.

Contributing

This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the Contributor Covenant code of conduct. We recommend reading the contributing guide as well.

License

ViewComponent is available as open source under the terms of the MIT License.


Author: github
Source code: https://github.com/github/view_component
License: MIT license

#ruby #ruby-on-rails 

ViewComponent: View Components for Rails

Cells: View Components for Ruby and Rails

Cells

View Components for Ruby and Rails.   

Overview

Cells allow you to encapsulate parts of your UI into components into view models. View models, or cells, are simple ruby classes that can render templates.

Nevertheless, a cell gives you more than just a template renderer. They allow proper OOP, polymorphic builders, nesting, view inheritance, using Rails helpers, asset packaging to bundle JS, CSS or images, simple distribution via gems or Rails engines, encapsulated testing, caching, and integrate with Trailblazer.

Full Documentation

Cells is part of the Trailblazer framework. Full documentation is available on the project site.

Cells is completely decoupled from Rails. However, Rails-specific functionality is to be found here.

Rendering Cells

You can render cells anywhere and as many as you want, in views, controllers, composites, mailers, etc.

Rendering a cell in Rails ironically happens via a helper.

<%= cell(:comment, @comment) %>

This boils down to the following invocation, that can be used to render cells in any other Ruby environment.

CommentCell.(@comment).()

You can also pass the cell class in explicitly:

<%= cell(CommentCell, @comment) %>

In Rails you have the same helper API for views and controllers.

class DashboardController < ApplicationController
  def dashboard
    @comments = cell(:comment, collection: Comment.recent)
    @traffic  = cell(:report, TrafficReport.find(1)).()
  end

Usually, you'd pass in one or more objects you want the cell to present. That can be an ActiveRecord model, a ROM instance or any kind of PORO you fancy.

Cell Class

A cell is a light-weight class with one or multiple methods that render views.

class CommentCell < Cell::ViewModel
  property :body
  property :author

  def show
    render
  end

private
  def author_link
    link_to "#{author.email}", author
  end
end

Here, show is the only public method. By calling render it will invoke rendering for the show view.

Logicless Views

Views come packaged with the cell and can be ERB, Haml, or Slim.

<h3>New Comment</h3>
  <%= body %>

By <%= author_link %>

The concept of "helpers" that get strangely copied from modules to the view does not exist in Cells anymore.

Methods called in the view are directly called on the cell instance. You're free to use loops and deciders in views, even instance variables are allowed, but Cells tries to push you gently towards method invocations to access data in the view.

File Structure

In Rails, cells are placed in app/cells or app/concepts/. Every cell has their own directory where it keeps views, assets and code.

app
├── cells
│   ├── comment_cell.rb
│   ├── comment
│   │   ├── show.haml
│   │   ├── list.haml

The discussed show view would reside in app/cells/comment/show.haml. However, you can set any set of view paths you want.

Invocation Styles

In order to make a cell render, you have to call the rendering methods. While you could call the method directly, the preferred way is the call style.

cell(:comment, @song).()       # calls CommentCell#show.
cell(:comment, @song).(:index) # calls CommentCell#index.

The call style respects caching.

Keep in mind that cell(..) really gives you the cell object. In case you want to reuse the cell, need setup logic, etc. that's completely up to you.

Parameters

You can pass in as many parameters as you need. Per convention, this is a hash.

cell(:comment, @song, volume: 99, genre: "Jazz Fusion")

Options can be accessed via the @options instance variable.

Naturally, you may also pass arbitrary options into the call itself. Those will be simple method arguments.

cell(:comment, @song).(:show, volume: 99)

Then, the show method signature changes to def show(options).

Testing

A huge benefit from "all this encapsulation" is that you can easily write tests for your components. The API does not change and everything is exactly as it would be in production.

html = CommentCell.(@comment).()
Capybara.string(html).must_have_css "h3"

It is completely up to you how you test, whether it's RSpec, MiniTest or whatever. All the cell does is return HTML.

In Rails, there's support for TestUnit, MiniTest and RSpec available, along with Capybara integration.

Properties

The cell's model is available via the model reader. You can have automatic readers to the model's fields by using ::property.

class CommentCell < Cell::ViewModel
  property :author # delegates to model.author

  def author_link
    link_to author.name, author
  end
end

HTML Escaping

Cells per default does no HTML escaping, anywhere. Include Escaped to make property readers return escaped strings.

class CommentCell < Cell::ViewModel
  include Escaped

  property :title
end

song.title                 #=> "<script>Dangerous</script>"
Comment::Cell.(song).title #=> &lt;script&gt;Dangerous&lt;/script&gt;

Properties and escaping are documented here.

Installation

Cells runs with any framework.

gem "cells"

For Rails, please use the cells-rails gem. It supports Rails >= 4.0.

gem "cells-rails"

Lower versions of Rails will still run with Cells, but you will get in trouble with the helpers. (Note: we use Cells in production with Rails 3.2 and Haml and it works great.)

Various template engines are supported but need to be added to your Gemfile.

gem "cells-erb"

In Rails, this is all you need to do. In other environments, you need to include the respective module into your cells.

class CommentCell < Cell::ViewModel
  include ::Cell::Erb # or Cell::Hamlit, or Cell::Haml, or Cell::Slim
end

Namespaces

Cells can be namespaced as well.

module Admin
  class CommentCell < Cell::ViewModel

Invocation in Rails would happen as follows.

cell("admin/comment", @comment).()

Views will be searched in app/cells/admin/comment per default.

Rails Helper API

Even in a non-Rails environment, Cells provides the Rails view API and allows using all Rails helpers.

You have to include all helper modules into your cell class. You can then use link_to, simple_form_for or whatever you feel like.

class CommentCell < Cell::ViewModel
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::CaptureHelper

  def author_link
    content_tag :div, link_to(author.name, author)
  end

As always, you can use helpers in cells and in views.

You might run into problems with wrong escaping or missing URL helpers. This is not Cells' fault but Rails suboptimal way of implementing and interfacing their helpers. Please open the actionview gem helper code and try figuring out the problem yourself before bombarding us with issues because helper xyz doesn't work.

View Paths

In Rails, the view path is automatically set to app/cells/ or app/concepts/. You can append or set view paths by using ::view_paths. Of course, this works in any Ruby environment.

class CommentCell < Cell::ViewModel
  self.view_paths = "lib/views"
end

Asset Packaging

Cells can easily ship with their own JavaScript, CSS and more and be part of Rails' asset pipeline. Bundling assets into a cell allows you to implement super encapsulated widgets that are stand-alone. Asset pipeline is documented here.

Render API

Unlike Rails, the #render method only provides a handful of options you gotta learn.

def show
  render
end

Without options, this will render the state name, e.g. show.erb.

You can provide a view name manually. The following calls are identical.

render :index
render view: :index

If you need locals, pass them to #render.

render locals: {style: "border: solid;"}

Layouts

Every view can be wrapped by a layout. Either pass it when rendering.

render layout: :default

Or configure it on the class-level.

class CommentCell < Cell::ViewModel
  layout :default

The layout is treated as a view and will be searched in the same directories.

Nested Cells

Cells love to render. You can render as many views as you need in a cell state or view.

<%= render :index %>

The #render method really just returns the rendered template string, allowing you all kind of modification.

def show
  render + render(:additional)
end

You can even render other cells within a cell using the exact same API.

def about
  cell(:profile, model.author).()
end

This works both in cell views and on the instance, in states.

View Inheritance

You can not only inherit code across cell classes, but also views. This is extremely helpful if you want to override parts of your UI, only. It's documented here.

Collections

In order to render collections, Cells comes with a shortcut.

comments = Comment.all #=> three comments.
cell(:comment, collection: comments).()

This will invoke cell(:comment, comment).() three times and concatenate the rendered output automatically.

Learn more about collections here.

Builder

Builders allow instantiating different cell classes for different models and options. They introduce polymorphism into cells.

class CommentCell < Cell::ViewModel
  include ::Cell::Builder

  builds do |model, options|
    case model
    when Post; PostCell
    when Comment; CommentCell
    end
  end

The #cell helper takes care of instantiating the right cell class for you.

cell(:comment, Post.find(1)) #=> creates a PostCell.

Learn more about builders here.

Caching

For every cell class you can define caching per state. Without any configuration the cell will run and render the state once. In following invocations, the cached fragment is returned.

class CommentCell < Cell::ViewModel
  cache :show
  # ..
end

The ::cache method will forward options to the caching engine.

cache :show, expires_in: 10.minutes

You can also compute your own cache key, use dynamic keys, cache tags, and conditionals using :if. Caching is documented here and in chapter 8 of the Trailblazer book.

The Book

Cells is part of the Trailblazer project. Please buy my book to support the development and to learn all the cool stuff about Cells. The book discusses many use cases of Cells.

![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg) 

  • Basic view models, replacing helpers, and how to structure your view into cell components (chapter 2 and 4).
  • Advanced Cells API (chapter 4 and 6).
  • Testing Cells (chapter 4 and 6).
  • Cells Pagination with AJAX (chapter 6).
  • View Caching and Expiring (chapter 8).

The book picks up where the README leaves off. Go grab a copy and support us - it talks about object- and view design and covers all aspects of the API.

This is not Cells 3.x!

Temporary note: This is the README and API for Cells 4. Many things have improved. If you want to upgrade, follow this guide. When in trouble, join the Zulip channel.


Author:  trailblazer
Source code: https://github.com/trailblazer/cells

#ruby  #ruby-on-rails 

Cells: View Components for Ruby and Rails
Beth  Cooper

Beth Cooper

1659694200

Easy Activity Tracking for Models, Similar to Github's Public Activity

PublicActivity

public_activity provides easy activity tracking for your ActiveRecord, Mongoid 3 and MongoMapper models in Rails 3 and 4.

Simply put: it can record what happens in your application and gives you the ability to present those recorded activities to users - in a similar way to how GitHub does it.

!! WARNING: README for unreleased version below. !!

You probably don't want to read the docs for this unreleased version 2.0.

For the stable 1.5.X readme see: https://github.com/chaps-io/public_activity/blob/1-5-stable/README.md

About

Here is a simple example showing what this gem is about:

Example usage

Tutorials

Screencast

Ryan Bates made a great screencast describing how to integrate Public Activity.

Tutorial

A great step-by-step guide on implementing activity feeds using public_activity by Ilya Bodrov.

Online demo

You can see an actual application using this gem here: http://public-activity-example.herokuapp.com/feed

The source code of the demo is hosted here: https://github.com/pokonski/activity_blog

Setup

Gem installation

You can install public_activity as you would any other gem:

gem install public_activity

or in your Gemfile:

gem 'public_activity'

Database setup

By default public_activity uses Active Record. If you want to use Mongoid or MongoMapper as your backend, create an initializer file in your Rails application with the corresponding code inside:

For Mongoid:

# config/initializers/public_activity.rb
PublicActivity.configure do |config|
  config.orm = :mongoid
end

For MongoMapper:

# config/initializers/public_activity.rb
PublicActivity.configure do |config|
  config.orm = :mongo_mapper
end

(ActiveRecord only) Create migration for activities and migrate the database (in your Rails project):

rails g public_activity:migration
rake db:migrate

Model configuration

Include PublicActivity::Model and add tracked to the model you want to keep track of:

For ActiveRecord:

class Article < ActiveRecord::Base
  include PublicActivity::Model
  tracked
end

For Mongoid:

class Article
  include Mongoid::Document
  include PublicActivity::Model
  tracked
end

For MongoMapper:

class Article
  include MongoMapper::Document
  include PublicActivity::Model
  tracked
end

And now, by default create/update/destroy activities are recorded in activities table. This is all you need to start recording activities for basic CRUD actions.

Optional: If you don't need #tracked but still want the comfort of #create_activity, you can include only the lightweight Common module instead of Model.

Custom activities

You can trigger custom activities by setting all your required parameters and triggering create_activity on the tracked model, like this:

@article.create_activity key: 'article.commented_on', owner: current_user

See this entry http://rubydoc.info/gems/public_activity/PublicActivity/Common:create_activity for more details.

Displaying activities

To display them you simply query the PublicActivity::Activity model:

# notifications_controller.rb
def index
  @activities = PublicActivity::Activity.all
end

And in your views:

<%= render_activities(@activities) %>

Note: render_activities is an alias for render_activity and does the same.

Layouts

You can also pass options to both activity#render and #render_activity methods, which are passed deeper to the internally used render_partial method. A useful example would be to render activities wrapped in layout, which shares common elements of an activity, like a timestamp, owner's avatar etc:

<%= render_activities(@activities, layout: :activity) %>

The activity will be wrapped with the app/views/layouts/_activity.html.erb layout, in the above example.

Important: please note that layouts for activities are also partials. Hence the _ prefix.

Locals

Sometimes, it's desirable to pass additional local variables to partials. It can be done this way:

<%= render_activity(@activity, locals: {friends: current_user.friends}) %>

Note: Before 1.4.0, one could pass variables directly to the options hash for #render_activity and access it from activity parameters. This functionality is retained in 1.4.0 and later, but the :locals method is preferred, since it prevents bugs from shadowing variables from activity parameters in the database.

Activity views

public_activity looks for views in app/views/public_activity.

For example, if you have an activity with :key set to "activity.user.changed_avatar", the gem will look for a partial in app/views/public_activity/user/_changed_avatar.html.(|erb|haml|slim|something_else).

Hint: the "activity." prefix in :key is completely optional and kept for backwards compatibility, you can skip it in new projects.

If you would like to fallback to a partial, you can utilize the fallback parameter to specify the path of a partial to use when one is missing:

<%= render_activity(@activity, fallback: 'default') %>

When used in this manner, if a partial with the specified :key cannot be located it will use the partial defined in the fallback instead. In the example above this would resolve to public_activity/_default.html.(|erb|haml|slim|something_else).

If a view file does not exist then ActionView::MisingTemplate will be raised. If you wish to fallback to the old behaviour and use an i18n based translation in this situation you can specify a :fallback parameter of text to fallback to this mechanism like such:

<%= render_activity(@activity, fallback: :text) %>

i18n

Translations are used by the #text method, to which you can pass additional options in form of a hash. #render method uses translations when view templates have not been provided. You can render pure i18n strings by passing {display: :i18n} to #render_activity or #render.

Translations should be put in your locale .yml files. To render pure strings from I18n Example structure:

activity:
  article:
    create: 'Article has been created'
    update: 'Someone has edited the article'
    destroy: 'Some user removed an article!'

This structure is valid for activities with keys "activity.article.create" or "article.create". As mentioned before, "activity." part of the key is optional.

Testing

For RSpec you can first disable public_activity and add require helper methods in the rails_helper.rb with:

#rails_helper.rb
require 'public_activity/testing'

PublicActivity.enabled = false

In your specs you can then blockwise decide whether to turn public_activity on or off.

# file_spec.rb
PublicActivity.with_tracking do
  # your test code goes here
end

PublicActivity.without_tracking do
  # your test code goes here
end

Documentation

For more documentation go here

Common examples

Set the Activity's owner to current_user by default

You can set up a default value for :owner by doing this:

  1. Include PublicActivity::StoreController in your ApplicationController like this:
class ApplicationController < ActionController::Base
  include PublicActivity::StoreController
end
  1. Use Proc in :owner attribute for tracked class method in your desired model. For example:
class Article < ActiveRecord::Base
  tracked owner: Proc.new{ |controller, model| controller.current_user }
end

Note: current_user applies to Devise, if you are using a different authentication gem or your own code, change the current_user to a method you use.

Disable tracking for a class or globally

If you need to disable tracking temporarily, for example in tests or db/seeds.rb then you can use PublicActivity.enabled= attribute like below:

# Disable p_a globally
PublicActivity.enabled = false

# Perform some operations that would normally be tracked by p_a:
Article.create(title: 'New article')

# Switch it back on
PublicActivity.enabled = true

You can also disable public_activity for a specific class:

# Disable p_a for Article class
Article.public_activity_off

# p_a will not do anything here:
@article = Article.create(title: 'New article')

# But will be enabled for other classes:
# (creation of the comment will be recorded if you are tracking the Comment class)
@article.comments.create(body: 'some comment!')

# Enable it again for Article:
Article.public_activity_on

Create custom activities

Besides standard, automatic activities created on CRUD actions on your model (deactivatable), you can post your own activities that can be triggered without modifying the tracked model. There are a few ways to do this, as PublicActivity gives three tiers of options to be set.

Instant options

Because every activity needs a key (otherwise: NoKeyProvided is raised), the shortest and minimal way to post an activity is:

@user.create_activity :mood_changed
# the key of the action will be user.mood_changed
@user.create_activity action: :mood_changed # this is exactly the same as above

Besides assigning your key (which is obvious from the code), it will take global options from User class (given in #tracked method during class definition) and overwrite them with instance options (set on @user by #activity method). You can read more about options and how PublicActivity inherits them for you here.

Note the action parameter builds the key like this: "#{model_name}.#{action}". You can read further on options for #create_activity here.

To provide more options, you can do:

@user.create_activity action: 'poke', parameters: {reason: 'bored'}, recipient: @friend, owner: current_user

In this example, we have provided all the things we could for a standard Activity.

Use custom fields on Activity

Besides the few fields that every Activity has (key, owner, recipient, trackable, parameters), you can also set custom fields. This could be very beneficial, as parameters are a serialized hash, which cannot be queried easily from the database. That being said, use custom fields when you know that you will set them very often and search by them (don't forget database indexes :) ).

Set owner and recipient based on associations

class Comment < ActiveRecord::Base
  include PublicActivity::Model
  tracked owner: :commenter, recipient: :commentee

  belongs_to :commenter, :class_name => "User"
  belongs_to :commentee, :class_name => "User"
end

Resolve parameters from a Symbol or Proc

class Post < ActiveRecord::Base
  include PublicActivity::Model
  tracked only: [:update], parameters: :tracked_values
  
  def tracked_values
   {}.tap do |hash|
     hash[:tags] = tags if tags_changed?
   end
  end
end

Setup

Skip this step if you are using ActiveRecord in Rails 4 or Mongoid

The first step is similar in every ORM available (except mongoid):

PublicActivity::Activity.class_eval do
  attr_accessible :custom_field
end

place this code under config/initializers/public_activity.rb, you have to create it first.

To be able to assign to that field, we need to move it to the mass assignment sanitizer's whitelist.

Migration

If you're using ActiveRecord, you will also need to provide a migration to add the actual field to the Activity. Taken from our tests:

class AddCustomFieldToActivities < ActiveRecord::Migration
  def change
    change_table :activities do |t|
      t.string :custom_field
    end
  end
end

Assigning custom fields

Assigning is done by the same methods that you use for normal parameters: #tracked, #create_activity. You can just pass the name of your custom variable and assign its value. Even better, you can pass it to #tracked to tell us how to harvest your data for custom fields so we can do that for you.

class Article < ActiveRecord::Base
  include PublicActivity::Model
  tracked custom_field: proc {|controller, model| controller.some_helper }
end

Help

If you need help with using public_activity please visit our discussion group and ask a question there:

https://groups.google.com/forum/?fromgroups#!forum/public-activity

Please do not ask general questions in the Github Issues.


Author: public-activity
Source code: https://github.com/public-activity/public_activity
License: MIT license

#ruby  #ruby-on-rails 

Easy Activity Tracking for Models, Similar to Github's Public Activity