1685215140
A minimal WebAssembly virtual DOM to build C++ SPA (Single page applications)
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.
#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;
};
asm-dom aims to be used from C++, however it can be used also from javascript, here you can find the doc of both:
Here you can find a list of related projects:
Examples are available in the examples folder.
Also, here is the list of third-party examples:
and online Demos:
This project adheres to Semantic Versioning.
Every release, along with the migration instructions, is documented on the Github Releases page.
Author: mbasso
Source Code: https://github.com/mbasso/asm-dom
License: View license
1685034465
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. 🇺🇦🇺🇦
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,
)
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,
);
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.
Now in your Dart code, you can use:
import 'package:flutter_link_previewer/flutter_link_previewer.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
1684445460
React.js — популярная библиотека JavaScript для создания пользовательских интерфейсов. Он предлагает подход, основанный на компонентах, где каждый компонент представляет собой многократно используемый фрагмент кода со своим собственным состоянием и поведением. Одной из мощных функций React.js является концепция «Render Props», которая позволяет компонентам гибко и многократно использовать функциональность и данные совместно с другими компонентами.
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/
1684441740
React.js 是一个流行的用于构建用户界面的 JavaScript 库。它提供了一种基于组件的方法,其中每个组件代表一段可重用的代码,具有自己的状态和行为。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/
1684427166
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.
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.
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/
1678726080
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
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.
Author: Gazebosim
Source Code: https://github.com/gazebosim/gz-sim
License: Unknown, Apache-2.0 licenses found
1677151860
A Swift DSL for type-safe, extensible, and transformable HTML documents.
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.
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 😂.
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
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.
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:
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.
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.
You can add swift-html to an Xcode project by adding it as a package dependency.
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")
]
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:
Author: Pointfreeco
Source Code: https://github.com/pointfreeco/swift-html
License: MIT license
1669975357
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
1668618480
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 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.
Original article source at: https://sebhastian.com/
1668504660
This tutorial illustrates how to deploy a Flask application with a PostgreSQL database to production on Render.
Technologies used in this tutorial:
By the end of this tutorial, you'll be able to:
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.
There are some limitations when using the free tier services on Render:
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.
During the development phase of a project, the development server is typically used for running the app locally:
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.
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:
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.
There are several considerations when moving from a Flask app running on your local computer to deploying it to Render...
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.
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.
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.
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.
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:
Fill out the configuration for deploying the Web Service:
Fields:
More fields:
pip install -r requirements.txt
, poetry build
, etc.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".
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:
Once the build is successful and deployed, you'll see something similar to:
You can click on the "Logs" tab to see that Gunicorn is up and running:
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!
To configure PostgreSQL, on your dashboard, click on the "New +" button again and select "PostgreSQL".
Next, fill out the configuration for deploying the PostgreSQL database:
Fields:
<Name>_user
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":
At this point, you'll need to scroll down to the "Connections" section and copy the "Internal Database URL":
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".
Check the deployment status by clicking on the "Events" tab:
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.
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/
1665065426
Experimental Quake 2 map renderer written with Julia and modern OpenGL. Created at Hacker School. It is a toy and not actively developed.
Pkg.add()
):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.
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.
The bump and parallax effects breathe new life into the original, low resolution Quake 2 textures.
Author: jayschwa
Source Code: https://github.com/jayschwa/Quake2.jl
License: MIT license
1659881700
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...
#
for <div id="...">
and .
for <div class="...">
in the default configuration)html_safe?
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.
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.
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.
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 © #{@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.
|
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>
'
The single quote tells Slim to copy the line (similar to |
), but makes sure that a single trailing white space is appended.
<
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>
-
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
=
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.
=>
. Same as the single equals sign (=
), except that it adds a trailing white space.=<
. Same as the single equals sign (=
), except that it adds a leading white space.==
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.
==>
. Same as the double equals sign (==
), except that it adds a trailing white space.==<
. Same as the double equals sign (==
), except that it adds a leading white space./
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>
/!
Use the forward slash immediately followed by an exclamation mark for html comments (<!-- ... -->
).
/[...]
/[if IE]
p Get a better browser.
This renders as:
<!--[if IE]><p>Get a better browser.</p><![endif]-->
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">
/
)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.
<
, >
)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
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
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.
=
and ==
)Can make the call on the same line
body
h1 id="headline" = page_headline
Or nest it.
body
h1 id="headline"
= page_headline
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.
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
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=="&"
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...."
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.
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
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
*
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 '*'
*
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>
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>
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>
#
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
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
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.
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
.
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.
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:
Filter | Required gems | Type | Description |
---|---|---|---|
ruby: | none | Shortcut | Shortcut to embed ruby code |
javascript: | none | Shortcut | Shortcut to embed javascript code and wrap in script tag |
css: | none | Shortcut | Shortcut to embed css code and wrap in style tag |
sass: | sass | Compile time | Embed sass code and wrap in style tag |
scss: | sass | Compile time | Embed scss code and wrap in style tag |
less: | less | Compile time | Embed less css code and wrap in style tag |
coffee: | coffee-script | Compile time | Compile coffee script code and wrap in script tag |
markdown: | redcarpet/rdiscount/kramdown | Compile time + Interpolation | Compile markdown code and interpolate #{variables} in text |
textile: | redcloth | Compile time + Interpolation | Compile textile code and interpolate #{variables} in text |
rdoc: | rdoc | Compile time + Interpolation | Compile 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:
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>
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:
# 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
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
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.
Type | Name | Default | Purpose |
---|---|---|---|
String | :file | nil | Name of parsed file, set automatically by Slim::Template |
Integer | :tabsize | 4 | Number 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_engines | nil (All enabled) | List of enabled embedded engines (whitelist) |
Array<Symbol,String> | :disable_engines | nil (None disabled) | List of disabled embedded engines (blacklist) |
Boolean | :disable_capture | false (true in Rails) | Disable capturing in blocks (blocks write to the default buffer |
Boolean | :disable_escape | false | Disable automatic escaping of strings |
Boolean | :use_html_safe | false (true in Rails) | Use String#html_safe? from ActiveSupport (Works together with :disable_escape) |
Symbol | :format | :xhtml | HTML 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_attrs | false | Attributes 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_attrs | true | Sort attributes by name |
Symbol | :js_wrapper | nil | Wrap javascript by :comment, :cdata or :both. You can also :guess the wrapper based on :format. |
Boolean | :pretty | false | Pretty HTML indenting, only block level tags are indented (This is slower!) |
String | :indent | ' ' | Indentation string |
Boolean | :streaming | false (true in Rails, see below how to disable it!) | Enable output streaming, improves the perceived performance |
Class | :generator | Temple::Generators::StringBuffer/ RailsOutputBuffer | Temple 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.
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:
Slim::Template
options passed at engine instantiationSlim::Template.options
Slim::Engine.thread_options
, Slim::Engine.options
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
Slim currently provides plugins for logic less mode, includes and I18n. See the plugin documentation.
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.
require 'sinatra'
require 'slim'
get('/') { slim :index }
__END__
@@ index
doctype html
html
head
title Sinatra With Slim
body
h1 Slim Is Fun!
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.
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
Slim now supports Angular2 syntax. But you need to set some configuration options:
splat_prefix
optionThis 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:
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.Angular and slim both uses brackets in their syntax. So there are also two ways:
bind-...
and so on)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>
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>
There are plugins for various text editors (including the most important ones - Vim, Emacs and Textmate):
slimrb
or `Slim::ERBConverter' which are both included in the Slim gemYes, 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
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:
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.
Slim is released under the MIT license.
If you want to support this project please visit the Gittip and Flattr pages.
Currently the donations will be used to cover the hosting costs (domain name etc).
Template compilation framework:
Framework support:
Syntax highlighting:
Static code analysis:
Template Converters (HAML, ERB, ...):
Language ports/Similar languages:
Author: slim-template
Source code: https://github.com/slim-template/slim
License: MIT license
1659844320
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.
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>
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.
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.
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.
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.
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
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.
See viewcomponent.org for documentation.
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.
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
1659829500
View Components for Ruby and Rails.
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.
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.
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.
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.
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.
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.
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.
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)
.
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.
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
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 #=> <script>Dangerous</script>
Properties and escaping are documented here.
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 "haml", github: "haml/haml", ref: "7c7c169"
. Use cells-hamlit
instead.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
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.
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.
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
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.
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;"}
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.
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.
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.
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.
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.
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.
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.

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.
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
1659694200
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.
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
Here is a simple example showing what this gem is about:
Ryan Bates made a great screencast describing how to integrate Public Activity.
A great step-by-step guide on implementing activity feeds using public_activity by Ilya Bodrov.
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
You can install public_activity
as you would any other gem:
gem install public_activity
or in your Gemfile:
gem 'public_activity'
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
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
.
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.
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.
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.
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.
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) %>
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.
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
For more documentation go here
You can set up a default value for :owner
by doing this:
PublicActivity::StoreController
in your ApplicationController
like this:class ApplicationController < ActionController::Base
include PublicActivity::StoreController
end
: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.
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
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.
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.
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 :) ).
owner
and recipient
based on associationsclass Comment < ActiveRecord::Base
include PublicActivity::Model
tracked owner: :commenter, recipient: :commentee
belongs_to :commenter, :class_name => "User"
belongs_to :commentee, :class_name => "User"
end
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
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.
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 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
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