Start!
Learn ▼
Unit 0: Introduction
Unit 1: Structure
Unit 2: State
Unit 3: Style
Unit 4: Positioning
Unit 5: Custom Components
Unit 6: Routing
Unit 7: Animations
Unit 8: Storage
Docs
Sponsor
Playground
The learning content is split across several units. Each of them is focused on a key concept or functionality for the app. In this unit you will learn the fundamentals for oneJS programming. If you are an advanced JavaScript developer and you are familiar with Webpack and Expo, you can skip this unit.
A JavaScript function is a block of code designed to perform a particular task. Function parameters are listed inside the parentheses ()
in the function definition. In JavaScript there are two ways of defining functions:
function add(x, y) = {return x + y;}
const add = (x, y) => {return x + y;}
The arrow function is the preferred notation as it more compact in complex scenarios. When a JavaScript function reaches a return
statement, the function will stop executing.
How are functions related to functional programming? FP is a programming paradigm where programs are constructed by applying and composing functions. In functional programming, functions are treated as first-class citizens, meaning that they can be bound to names, passed as arguments, and returned from other functions, just as any other data type can. Functional programming is sometimes treated as synonymous with purely functional programming, a subset of functional programming which treats all functions as deterministic mathematical functions, or pure functions.
Pure functions are a subset of functions that possess a set of unique properties:
Pure functions always produce the same output for same arguments irrespective of anything else. This is true for oneJS. Components are defined as functions and if they are called with the same parameters (i.e. component properties), they will look and behave the same way.
Pure functions do not modify any arguments, local/global variables or input/output streams. This is only partially true for oneJS. An app composed uniquely of strictly pure functions would be completely static as no side effects means no changes in the app’s state. In oneJS, we follow what we call the No Implicit Side Effect philosophy. Functions will never modify external state implicitly in their definition. This would break function’s isolation, as the content of the function would need to be aware of its exterior. In oneJS, a function can only modify the state of the application if it is explicitly forced to do so when it is called. You will find how to do so in Unit 2: State.
Since every function only depends on its input arguments, they enable loosely coupled architectures like modular design. This increases increases overall productivity. Small modules can be coded quickly and have a greater chance of being re-usable. Moreover, modules can be tested separately which helps you reduce the time spent on unit testing and debugging.
Functional programming is easier to maintain as you don’t need to worry about accidentally changing anything outside the given function.
One of the advantages of oneJS is being able to use a single codebase for web and native apps. The first step is to set up your development environment using the app creator as indicated in the get started page. This creates a template project with the predefined structure described below:
assets
: All the non-source-code resources required by your app. These can include fonts, vector icons, logo images, texts, etc.public
: Contains the index.html
file that will render your web app.src
: The source code describing your app. The main file is App.js
..babelrc
: Enables importing .svg files directly into your source code.babel.config.js
: contains the Babel preset configuration for Expo.package.json
: Describes your npm package. Defines the dependencies you need to install and the scripts you can run to build and serve your app.webpack.config.js
: The configuration file for Webpack module bundler. Web only.app.json
: It is used for configuring how a project loads in Expo. You can think of this as an index.html
but for React Native apps. iOS/Android only.By default, oneJS uses bundling tools: A bundler is a tool that packages your code and all its dependencies together in a single JavaScript file. There are several bundlers you can choose from. Out of the box, oneJS uses Webpack for the web and Expo for iOS and Android. It is not the scope of this website to describe in detail the functionality of these products, but if you need more information about the tools that power oneJS, make sure to refer to the following links:
Now that you know what functions are, we are going to use them to create a simple app. There are three main pillars to any app: Structure, State and Style. In this unit you will learn all about structure, i.e., the fundamentals to render components in your app. Components in oneJS are functions that take properties and return the structure to be rendered. The structure represents the visible and interactable portion of your app. Given the same properties they will always return a component that looks and behaves in the same way. To distinguish component functions from other functions their name always starts with a capital letter. Let’s take a look at the two types of components:
They take properties and return a component. They do not require any additional structure. E.g.: inputs or icons.
<!–– HTML Definition ––>
<input type="text" id="myInput" style="color: red" />
/* oneJS Definition */
Input({type: 'text', id: 'myInput', style: {color: 'red'}});
They take properties as well as other structure as inputs to return a component. E.g views or texts. Wrapper components are curried functions. What this means in practice is that it takes two sets of parameters: first for the properties and second for the structure. This is just for convenience; this separation of concerns makes the code look cleaner and it’s easier to nest structures.
<!–– HTML Definition ––>
<div id="myView" style="background-color: blue" />
<p class="text">Hello World!</p>
</div>
/* oneJS Definition */
View({id: 'myView', style: {backgroundColor: 'blue'}})(
Text({class: 'text'})('Hello World!')
);
In some cases, you may need to provide more than one component. To do this, just provide an array with all the components to be rendered:
/* oneJS Definition */
View({id: 'myView', style: {backgroundColor: 'blue'}})([
Text({class: 'text'})('Hello World!'),
Input({type: 'text', id: 'myInput', style: {color: 'red'}})
]);
You may note that in most cases when we display code, we do not specify the target platform; web vs. iOS/Android. This is because the same code is valid for both. If we do not explicitly indicate that a difference exists, you can rightfully assume that the code presented is cross-platform.
In the previous examples, we have explored the nomenclature to create components, but these components will not be visible unless we render them on screen. The way to instruct oneJS to render our components is through the app
function. It takes the following properties and renders the output on screen:
state
: The state
variables governing your app’s behavior. Learn all about it in Unit 2./* Configure App State */
const state = {
stateVariable1: 'defaultValue1',
stateVariable2: 'defaultValue2'
}
app({component: App, state: state});
theme
: The theming options chosen to create a homogeneous user experience for your app. You can use oneJS’ preset theme
by providing the theme
property the value below: Learn all about it in Unit 3. /* Apply oneJS preset theme */
app({component: App, theme: {preset: 'oneJS'}});
style
: The JavaScript object describing your app’s styling. You can use it in your components by calling the readStyle
function with the corresponding style
id. Learn all about it in Unit 3./* Define App Style */
const style = {
mainTitle: {color: 'blue'},
mainIcon: {color: 'green'}
}
app({component: App, style: style});
font
: You can optionally provide custom font
s to be loaded and used in your app: /* Import Font */
const font = {
'Avenir Next': require('../assets/fonts/AvenirNext.otf'),
'Avenir Next Bold': require('../assets/fonts/AvenirNextBold.otf')
};
app({component: App, font: font});
firestore
: You can optionally provide a configured Firestore object to synchronize the app’s state with your database. Learn all about it in Unit 8./* Configure Firestore */
const firebaseConfig = {
apiKey: "API_KEY",
authDomain: "PROJECT_id.firebaseapp.com",
// The value of "databaseURL" depends on the location of the database
databaseURL: "https://DATABASE_NAME.firebaseio.com",
projectId: "PROJECT_id",
storageBucket: "PROJECT_id.appspot.com",
messagingSenderId: "SENDER_id",
appId: "APP_id",
// For Firebase JavaScript SDK v7.20.0 and later, "measurementId" is an optional field
measurementId: "G-MEASUREMENT_id",
};
const firebaseApp = initializeApp(firebaseConfig);
const firestoreDB = getFirestore();
app({component: App, firestore: firestoreDB});
component
: The component
function encapsulating your entire app. For clarity, we name this component
App in the examples but you can give it any name you want to. This component
function is a bit special because it takes no properties and just returns the desired structure:/* Imports */ import {app} from '@onejs-dev/core'; import {View, Text, Input} from '@onejs-dev/components'; /* App Component: Takes no properties and returns structure */ const App = () => { return View({id: 'myView'})([ Text({style: {fontSize: 20}})('Name: '), Input({type: 'text', style: {background: 'pink'}}) ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
oneJS puts the complete set of standard HTML and React Native components at your disposal. Just import them from the components module and use them as you would normally.
/* Imports */ import {app} from '@onejs-dev/core'; import {HtmlDiv, HtmlP, HtmlInput} from '@onejs-dev/components'; /* App Component: Takes no properties and returns structure */ const App = () => { return HtmlDiv({id: 'myView'})([ HtmlP({style: {fontSize: 20}})('Name: '), HtmlInput({type: 'text', style: {background: 'pink'}}) ]); }; /* App Function: Renders the App Component in the screen */ app({component: App});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
Behind the scenes, we wrap the standard components with a Higher Order Component. This allows to provide some extra functionality.
Lifecycle events properties allow you to provide functions that are called when these events are triggered. The function provided can contain a component property that you can use to attach listeners or perform other transformations to it.
onInit
: It is the first function being triggered. It is called before the component is attached to the DOM (Document Object Model). Note that this the only function that does not provide a component property.const initAction = () => {
alert('Initialized');
}
Input({type: 'text', onInit: initAction});
onCreate
: It is triggered when the component is attached to the DOM. This is where you should attach your listeners. It is equivalent to React’s useEffect(function, [])
hook.const handleClick = () => alert('Clicked');
const createAction = () => {
component.addEventListener('click', handleClick);
}
Input({type: 'text', onCreate: createAction});
onDestroy
: It is triggered when the component is removed from the DOM. This is where you should remove your listeners.const handleClick = () => alert('Clicked');
const destroyAction = () => {
component.removeEventListener('click', handleClick);
}
Input({type: 'text', onDestroy: destroyAction});
onPropertyChange
: It is triggered when a state property is modified. It takes an object whose keys are the property ids and values are the event handling functions. The handling function takes the new value and component as arguments./* Imports */ import {app, read, update} from '@onejs-dev/core'; import {View, Text, Input} from '@onejs-dev/components'; /* State Configuration */ const state = { inputValue: 'pink' }; /* Property Change Function */ const changeAction = (newValue, component) => { component.style.backgroundColor = newValue; } /* App Component: Takes no properties and returns structure */ const App = () => { return Input({ type: 'text', value: read('inputValue'), onChange: update('inputValue'), onPropertyChange: {value: changeAction} }); }; /* App Function: Renders the App Component in the screen */ app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
Style is one of the fundamentals for any app; the entire Unit 3 is dedicated to it.
On iOS and Android the style
property is directly mapped to React Native elements' style
property.
On the web, you can pass any JavaScript object supported by Emotion to your components' style
property. oneJS will compile it to generate a class name that is applied to your component. This is different from React’s style
property that applies the CSS inline, with oneJS you will be able to leverage the full power of CSS in JS.
const inputStyle = {
backgroundColor: 'pink',
'&:hover': { //Web only
backgroundColor: 'yellow'
}
};
Input({type: 'text', style: inputStyle});
This should be your last option, but if for any reason you are forced to use styles inline you can do so by feeding the inlineStyle
property (web only):
const inputStyle = {
backgroundColor: 'pink'
};
Input({type: 'text', inlineStyle: inputStyle});
This is a convenient property to make your components clickable redirecting the user to the url of your choice. This will be one of the key features for routing in Unit 6.
Input({type: 'button', title: 'Home Page', url: '/home'});
So far, we have only talked about standard components but oneJS provides its own set of additional components that aim to solve some of the grievances programmers face. We have also made these components cross platform so that if you are on the web they are transformed into HTML and if you are using iOS or Android, they are transformed into React Native components. This is a summary of the out of the box cross-platform components. You can compose almost any interface with a combination of these:
Learn the specifics for these components in the Docs section.
An app is a living entity, it is updated with every interaction from the user or the database. If we would take a snapshot of the app, the state describes the value for that moment in time of all the parameters that govern its behavior.
Thus, the state can be regarded as the collection of variables that govern the behavior and appearance of the app.
Let’s begin with a simple example; an app with a text input that we will use to update the name of the user. In this case, userName
is the only state variable required.
/* Imports */
import {app, read, update} from '@onejs-dev/core';
import {View, Text, Input} from '@onejs-dev/components';
/* App Component: Takes no properties and returns structure */
const App = () => {
return View({content: {direction: 'column'}})([
Text()('User Name: '),
Input({
type: 'text',
placeholder: 'Name'
})
]);
};
/* App Function: Renders the App Component in the screen */
app({component: App, theme: {preset: 'oneJS'}});
The first step is to declare all the state variables of the app as a configuration object. The object takes as keys the state variable ids. The value, is an object that can take the following properties:
The default value for the state variable. If no other value is retrieved or set, this will be its initial value. The default value can be provided as an object property or as a string:
/* State Configuration */
const state = {
userName: {default: 'my name'}
};
/* State Configuration */
const state = {
userName: 'my name'
};
source
, represent the origin for the value of the state variable. storage
is the location where the state variable’s value is saved. They can take the following values:
local:
For source
, the value is retrieved from the selected path in local storage
. For storage
, the value is saved in the selected path in the user’s local storage
./* State Configuration */
const state = {
userName: {
source: {local: 'userName'},
storage: {local: 'userName'}
}
};
firestore:
For source
, the value is retrieved from the selected path in the Cloud Firestore database. For storage
, the value is saved in the selected path in the Cloud Firestore database. You can use this configuration to sync your only database with the app. Learn all about source
and storage
in Unit 8./* State Configuration */
const state = {
userData: {
source: {firestore: 'users/id1234'},
storage: {firestore: 'users/id1234'}
}
};
url:
Retrieves the value from the selected path segment of the actual url if it matches the selected url. Otherwise, returns undefined
. The value to be fetched is represented by :
. You can use *
to match any value for a certain path segment. url can only be used as source
for the state variables./* State Configuration */
const state = {
page: {
source: {url: 'path/*/:'},
//Actual url = 'path/to/home' --> page = 'home';
//Actual url = 'other/path --> page = undefined;
}
};
Once the configuration object is created, feed this value to the app function’s state
property.
/* State Configuration */
const state = {
userName: {default: 'my name'}
};
app({component: App, state: state});
Once the state is defined, we need a means of accessing it. oneJS provides the read
function that returns the state variable’s current value for the selected state id; in this case userName
.
Now in the example we can check how the default value of the state variable is applied to the input, but we still need a way for the input to update its value. Learn about it in the following section.
/* Imports */
import {app, read} from '@onejs-dev/core';
import {View, Text, Input} from '@onejs-dev/components';
/* State Configuration */
const state = {
userName: ''
};
/* App Component: Takes no properties and returns structure */
const App = () => {
return View({content: {direction: 'column'}})([
Text()('User Name: ' + read('userName')),
Input({
type: 'text',
placeholder: 'Name',
value: read('userName'),
})
]);
};
/* App Function: Renders the App Component in the screen */
app({component: App, state: state, theme: {preset: 'oneJS'}});
In functional programming, functions should not directly update the state. This would break their isolation as they would need to “be aware” of the context where they are called in. On the other hand, an app unable to change its state would be unusable.
In oneJS, while functions will never directly access the state, we allow the user to bind to the component event functions such as the onChange
event. You can pass a function to the onChange
property that reads the event’s changed value and uses it to update the state. Exactly for this purpose, oneJS provides the update
function; it takes as first parameter the name of the state variable to be updated and automatically updates the state when the onChange
event is triggered.
/* Imports */ import {app, read, update} from '@onejs-dev/core'; import {View, Text, Input} from '@onejs-dev/components'; /* State Configuration */ const state = { userName: '' }; /* App Component: Takes no properties and returns structure */ const App = () => { return View({content: {direction: 'column'}})([ Text()('User Name: ' + read('userName')), Input({ type: 'text', placeholder: 'Name', value: read('userName'), onChange: update('userName'), }) ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
If the structure represents the skeleton, the style is the external appearance, the skin of the app. The same structure can look completely different in two apps that apply different styles to it.
In oneJS, you can style all your components using JavaScript. JavaScript style attributes match the CSS attributes on the web using camel case notation; e.g. border-radius
is written as borderRadius
. If you are already familiar with CSS, you can leverage all of its power to customize your app.
For the web, Emotion is used to read and compile style objects. For iOS and Android, oneJS uses the default React Native style property.
To apply your style, you can use the style
property in any component. The style
property can receive a JavaScript object or an array of object styles. The priority increases with the position in the array; the last style has precedence.
On the web, using Emotion, styles are internally compiled into a class and applied to your component. This enables to use nested structures, media queries, animations and much more!
In general, it should be avoided but if there is no other remedy, you can use inline styles by feeding the inlineStyle
property. This can only be done in web components as there is no such distinction between class and inline styles in React Native.
/* Imports */ import {app} from '@onejs-dev/core'; import {View, Input} from '@onejs-dev/components'; /* Style */ const button1Style = {color: 'white', backgroundColor: 'blue'}; const button2Style = {backgroundColor: 'red', borderRadius: 30}; /* App Component: Takes no properties and returns structure */ const App = () => { return View()([ Input({type: 'button', title: 'Button 1', style: button1Style}), Input({type: 'button', title: 'Button 2', style: [button1Style, button2Style]}), Input({type: 'button', title: 'Button 3', inlineStyle: {color: 'green'}}), ]); }; /* App Function: Renders the App Component in the screen */ app({component: App});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
As your app grows in complexity, it is often cleaner to wrap all your styles in single object. You can then feed this object to the app
function's style
property. This enables using the readStyle
function to apply styles to your components:
const style = {
button1Style: {color: 'white', backgroundColor: 'blue'},
button2Style: {backgroundColor: 'red', borderRadius: 30}
}
const App = () => {
return View()([
Input({type: 'button', title: 'Button 1', style: readStyle('button1Style')}),
Input({type: 'button', title: 'Button 2', style: readStyle('button1Style', 'button2Style')}),
]);
};
app({component: App, style: style});
For small apps it may be enough to apply styles directly to components. In larger apps, it is helpful to define a set of guidelines that govern the look and feel of components without having to specify their style every time they are used. oneJS provides scalable theming capabilities; we are confident that if you give them a try you are going to love them.
A theme
, is the collection of all the variables that you would use to customize the look and feel of your app. Out of the box, oneJS provides the following selection of theme
variables:
const themeVariables = {
/* The values below show the default values for oneJS preset theme */
//Color
backgroundColor: '#ffffff', //The color for the component's background.
primaryColor: '#094DFF', //The primary contrast color for the component.
neutralColor: '#b1b0c5', //Neutral color that can be used for backgrounds or disabled buttons.
acceptColor: '#60b33a', //Color for affirmative actions. Usually green.
rejectColor: '#ff5100', //Color for negative actions. Usually red.
warnColor: '#f7df00', //Color for warning. Usually yellow.
lightColor: '#e9e8ee', //Light color for icons or backgrounds.
darkColor: '#4c4b66', //Dark color for icons or backgrounds
//Text
textFont: 'Avenir Next', //Font family for texts.
textColor: '#666488', //Font color for texts.
textSize: 18, //Font size for texts.
textWeight: 'normal', //Font weight for texts.
//Border
radius: 10, //Border radius to create round corners.
borderWidth: 1, //Border width.
borderStyle: 'solid', //Border style.
borderColor: '#e9e8ee', //Border color.
//Shadow
shadow: {elevation: 0}, //The component's elevation creating a shadow. Value from 0 to 20.
};
These variables are then clustered into what we can flavor
s. A theme can have multiple flavor
s which in turn can have multiple variables. A flavor
is a cluster of theme variables that possess a value differing from the default one. Out of the box, oneJS provides the following selection of flavor
s:
const themeFlavors = {
//Shadow
shadow: {
shadow: {elevation: 10},
},
noShadow: {
shadow: {elevation: 0},
},
//Border
border: {
borderWidth: 1,
borderStyle: 'solid',
borderColor: themeVariables?.borderColor,
},
noBorder: {
borderWidth: 0,
borderStyle: 'none',
borderColor: 'transparent',
},
flat: {
shadow: {elevation: 0},
borderWidth: 0,
borderColor: 'transparent',
radius: 0
},
//Text
bold: {
textWeight: 'bold'
},
normal: {
textWeight: 'normal'
},
title: {
textWeight: 'bold',
textColor: themeVariables?.darkColor,
textSize: typeof themeVariables?.textSize === 'number' ?
themeVariables?.textSize * 3 : '300%'
},
header: {
textWeight: 'bold',
textColor: themeVariables?.darkColor,
textSize: typeof themeVariables?.textSize === 'number' ?
themeVariables?.textSize * 2 : '200%'
},
section: {
textWeight: 'bold',
textColor: themeVariables?.darkColor,
textSize: typeof themeVariables?.textSize === 'number' ?
Math.round(themeVariables?.textSize * 1.5) : '200%'
},
subsection: {
textWeight: 'bold',
textColor: themeVariables?.darkColor
},
summary: {
textSize: typeof themeVariables?.textSize === 'number' ?
Math.round(themeVariables?.textSize * 1.3) : '200%'
},
//Color
reverse: {
primaryColor: themeVariables?.backgroundColor,
textColor: themeVariables?.backgroundColor,
backgroundColor: themeVariables?.primaryColor,
},
selected: {
primaryColor: themeVariables?.primaryColor,
textColor: themeVariables?.primaryColor,
},
unselected: {
primaryColor: themeVariables?.neutralColor,
textColor: themeVariables?.neutralColor,
},
primary: {
primaryColor: themeVariables?.primaryColor,
textColor: themeVariables?.primaryColor
},
primaryBackground: {
backgroundColor: themeVariables?.primaryColor,
},
white: {
primaryColor: '#fff',
textColor: '#fff'
},
whiteBackground: {
backgroundColor: '#fff',
},
light: {
primaryColor: themeVariables?.lightColor,
textColor: themeVariables?.lightColor
},
lightBackground: {
backgroundColor: themeVariables?.lightColor
},
neutral: {
primaryColor: themeVariables?.neutralColor,
textColor: themeVariables?.neutralColor
},
neutralBackground: {
backgroundColor: themeVariables?.neutralColor,
},
dark: {
primaryColor: themeVariables?.darkColor,
textColor: themeVariables?.darkColor
},
darkBackground: {
backgroundColor: themeVariables?.darkBackground,
},
accept: {
primaryColor: themeVariables?.acceptColor,
textColor: themeVariables?.acceptBackground
},
acceptBackground: {
backgroundColor: themeVariables?.acceptBackground,
},
reject: {
primaryColor: themeVariables?.rejectColor,
textColor: themeVariables?.rejectColor
},
rejectBackground: {
backgroundColor: themeVariables?.rejectColor,
},
warn: {
primaryColor: themeVariables?.warnColor,
textColor: themeVariables?.warnColor
},
warnBackground: {
backgroundColor: themeVariables?.warnColor,
}
};
To use theme flavors in your app, you need to first provide a configuration object to the App component. This configuration object specifies the following parameters:
variables:
All the theme variables and corresponding values. You can override one, several or all the preset variables that were shown on the example above. You can also choose to create a totally different variable collection that suits your app better. flavors:
All of the flavors bundling the theme variables with their corresponding values. You can override one, several or all the preset flavors that were shown on the example above.const themeVariables = {
backgroundColor: 'white',
primaryColor: 'blue',
neutralColor: 'gray',
textSize: 18,
textWeight: 'normal',
radius: 10,
borderWidth: 1,
borderStyle: 'solid'
borderColor: 'black',
shadow: {elevation: 10},
};
const themeFlavors = {
flat: {
radius: 0,
borderWidth: 0,
borderColor: 'transparent',
shadow: {elevation: 0}
},
title: {
textSize: 32,
fontWeight: 'bold'
}
};
app({component: App, theme: {variables: themeVariables, flavors: themeFlavors}});
Now we need a way of applying these flavors to components. All oneJS components contain the flavor
property; use the readFlavor
function with the flavor id to retrieve the corresponding theme variables.
/* Imports */ import {app, readFlavor} from '@onejs-dev/core'; import {View, Input} from '@onejs-dev/components'; /* Theming */ const flavors = { button1Flavor: {textColor: 'white', radius: 30, backgroundColor: 'blue'} }; /* App Component: Takes no properties and returns structure */ const App = () => { return View()([ Input({type: 'button', title: 'Button 1', flavor: readFlavor('button1Flavor')}), ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, theme: {flavors: flavors}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
You may wonder, what can be accomplished with oneJS themes that you couldn’t with CSS classes. There is a critical difference, CSS classes talk the language of CSS properties while oneJS themes talk the language of designers and content creators and are coding language agnostic. Defining your own flavors is intuitive and you are not required to know anything about the underlying language. See the oneJS preset flavors in action. Spice up your creation with different flavors!
/* Imports */ import {app, readFlavor} from '@onejs-dev/core'; import {View, Input} from '@onejs-dev/components'; const presetFlavors = [ //Outline 'default', 'shadow', 'noShadow', 'border', 'noBorder', 'flat', //Text 'bold', 'normal', 'title', 'header', 'section', 'subsection', 'summary', //Selection 'reverse', 'selected', 'unselected', //Colors 'primary', 'primaryBackground', 'white', 'whiteBackground', 'light', 'lightBackground', 'neutral', 'neutralBackground', 'dark', 'darkBackground', 'accept', 'acceptBackground', 'reject', 'rejectBackground', 'warn', 'warnBackground' ]; /* App Component: Takes no properties and returns structure */ const App = () => { return View({content: {wrap: true, gap: 10}})([ presetFlavors.map(flavor => Input({ type: 'button', title: flavor, key: flavor, flavor: readFlavor(flavor) })) ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
One unique feature offered by oneJS is extensive support for linear gradients. You may have worked with gradients before, and there is a high change that you have found it quite cumbersome. We wanted to simplify this process by providing an intuitive and unified way of working with them. Gradients have many ways of being specified; CSS, SVG and React Native each follow their own convention. In oneJS we provide a wrapper for all of them following a concept you may already be familiar with, the unit circle.
To create a gradient you need to specify the following data; an angle in degrees and an array with two or more colors. The direction of the gradient is given by the angle θ
in the unit circle. Colors go from origin (center of the circumference) to terminal point (tip of the vector) and are evenly distributed along the length of the vector. For example, a 45° linear gradient from purple to blue and from blue to red would look like the following images:
In oneJS, gradients are created as part of flavors. You can apply gradients to texts, backgrounds and icons by specifying the textGradient
, backgroundGradient
and primaryGradient
theme variables respectively.
/* Imports */ import {app, readFlavor} from '@onejs-dev/core'; import {View, Text, Icon} from '@onejs-dev/components'; import {icons} from '@onejs-dev/icons'; /* Flavor Definition */ const themeFlavors = { myGradient: { textGradient: {angle: 0, colors: ['green', 'blue']}, primaryGradient: {angle: 45, colors: ['pink', 'red']}, backgroundGradient: {angle: 90, colors: ['silver', 'white']} } }; /* App Component: Takes no properties and returns structure */ const App = () => { return View({flavor: readFlavor('myGradient')})([ Text({flavor: readFlavor('myGradient')})('Create beautiful gradients'), Icon({icon: icons['heart'], flavor: readFlavor('myGradient')}) ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, theme: {flavors: themeFlavors}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
To add the icing on the cake, oneJS automatically creates the best looking gradients for the colors you choose for your theme. For example, if you have defined a theme variable named secondaryColor
oneJS automatically creates a flavor named secondaryColorGradient
that you can apply to your components. See below the gradients for the preset theme variables:
/* Imports */ import {app, readFlavor} from '@onejs-dev/core'; import {View, Input} from '@onejs-dev/components'; const presetGradients = [ 'primaryGradient', 'whiteGradient', 'lightGradient', 'neutralGradient', 'darkGradient', 'acceptGradient', 'rejectGradient', 'warnGradient', ]; /* App Component: Takes no properties and returns structure */ const App = () => { return View({content: {wrap: true, gap: 10}})([ presetGradients.map(flavor => Input({ type: 'button', title: flavor, key: flavor, flavor: readFlavor(flavor) })) ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
Congratulations! You have completed one of the most important units. Now you should be able to fully customize your app to your liking. Put all these new concepts to the test!
You may have already noticed the View
component in previous units’ examples. View
is much more than a simple wrapper, it provides intuitive positioning and animation capabilities. This positioning is based on the CSS flexbox specification with some adaptations. Why flexbox instead of CSS grid you may ask? The main reason is that React Native does not yet offer support for grid layouts.
In oneJS we divide positioning into two types: content
and self
-positioning. content
governs how children components are positioned, while self
controls how the component itself
it’s positioned with respect to its parent.
To understand content positioning, you need to become familiar with the following concepts:
direction:
The direction of the layout, i.e., how component’s children are organized. For row
: Content flows along the x-axis (horizontal). column
: Content flows along the y-axis (vertical). The default value is row
.wrap:
If true
, wraps the content adding new lines if it exceeds the space
in the content’s direction. If false
, it will not create more lines even if the space
is exhausted. The default value is false
.h:
Horizontal positioning of the component’s children. Possible values are: left
, center
, right
, space
, distribute
and stretch
. left
, center
and right
are self-explanatory. space
separates each of the children component as far as possible from each other. distribute
creates equal spacing between them and the edges. stretch
stretches children components across in the content’s transversal direction to fill the component; for the h
property, it will only take effect if the direction is column
.v:
Vertical positioning of the component’s children. Possible values are: top
, center
, bottom
, space
, distribute
and stretch
. top
, center
and bottom
are self-explanatory. space
separates each of the children component as far as possible from each other. distribute
creates equal spacing between them and the edges. stretch
stretches children components across in the content’s transversal direction to fill the component; for the v
property, it will only take effect if the direction is row
.gap:
Spacing between component’s children.Put these properties to the test:
/* Imports */ import {app, read, update, readFlavor, readStyle} from '@onejs-dev/core'; import {View, Input, Text, Icon} from '@onejs-dev/components'; import {icons} from '@onejs-dev/icons'; /* State configuration */ const state = { direction: 'row', wrap: true, h: 'left', v: 'top', gap: 0 } /* Input options */ const options = { direction: ['row', 'column', 'row-reverse', 'column-reverse'], wrap: [true, false], h: ['left', 'center', 'right', 'space', 'distribute', 'stretch'], v: ['top', 'center', 'bottom', 'space', 'distribute', 'stretch'] } /* Style object creation */ const style = { label: {width: 85, fontWeight: 'bold', textAlign: 'right', paddingRight: 10}, childrenSize: {width: 50, height: 50}, oneSize: {width: 35, height: 35}, containerSize: {width: 230, height: 230}, inputPanelSize: {width: 430}, inputSize: {minWidth: 50, width: 115} } /* App Component: Takes no properties and returns structure */ const App = () => { return [ /* Content option panel*/ Text({flavor: readFlavor('subsection')})('Content Options:'), View({flavor: readFlavor('outline'), content: {wrap: true}, style: readStyle('inputPanelSize')})([ Text({style: readStyle('label')})('direction'), Input({type: 'list', options: options.direction, value: read('direction'), onChange: update('direction'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('wrap'), Input({type: 'list', options: options.wrap, value: read('wrap'), onChange: update('wrap'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('h'), Input({type: 'list', options: options.h, value: read('h'), onChange: update('h'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('v'), Input({type: 'list', options: options.v, value: read('v'), onChange: update('v'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('gap: ' + read('gap')), Input({type: 'range', min: 0, max: 20, value: read('gap'), onChange: update('gap'), style: readStyle('inputSize')}) ]), /* View parent component for "content" property*/ View({ flavor: readFlavor('lightBackground'), style: readStyle('containerSize'), content: { direction: read('direction'), wrap: read('wrap') !== 'false' ? true : false, h: read('h'), v: read('v'), gap: Number(read('gap')) }, })([ View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('0')), View({flavor: readFlavor('primaryGradient'), style: readStyle('oneSize')})(Text({flavor: readFlavor('section', 'light')})('1')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('2')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('3')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('4')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('5')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('6')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('7')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('8')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('9')) ]) ]; }; /* App Function: Renders the App Component in the screen */ app({component: App, state: state, style: style, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
For self positioning, the following concepts will allow you to have full control of your component’s positioning with respect to its parent.
align:
Overrides the parent’s positioning in the content’s transversal direction. For example, if the parent instructs the content to flow in row
direction, align
will modify the vertical alignment. For row
direction the possible values are: top
, center
, bottom
, stretch
and auto
. For column
direction the possible values are: left
, center
, right
, stretch
and auto
.expand:
Capacity to grow if there is extra space. It is the equivalent to stretch
but in the content’s direction. The greater the number provide to expand
, the greater the expansion with respect to other sibling components. It is 0
by default, which means the component is unable to expand.shrink:
Capacity to shrink if there is not enough space. The greater the number, the greater the shrinkage with respect to other siblings. 0
means the component is unable to shrink. It is 1
by default./* Imports */ import {app, read, update, readFlavor, readStyle} from '@onejs-dev/core'; import {View, Input, Text, Icon} from '@onejs-dev/components'; import {icons} from '@onejs-dev/icons'; /* State configuration */ const state = { direction: 'row', wrap: true, h: 'left', v: 'top', gap: 0, align: 'auto', expand: 0, shrink: 1 } /* Input options */ const options = { direction: ['row', 'column', 'row-reverse', 'column-reverse'], wrap: [true, false], h: ['left', 'center', 'right', 'space', 'distribute', 'stretch'], v: ['top', 'center', 'bottom', 'space', 'distribute', 'stretch'], align: ['auto', 'left', 'right', 'top', 'bottom', 'center', 'stretch'] } /* Style object creation */ const style = { label: {width: 85, fontWeight: 'bold', textAlign: 'right', paddingRight: 10}, childrenSize: {width: 50, height: 50}, oneSize: {width: 35, height: 35}, containerSize: {width: 230, height: 230}, inputPanelSize: {width: 430}, inputSize: {minWidth: 50, width: 115} } /* App Component: Takes no properties and returns structure */ const App = () => { return [ /* Content option panel*/ Text({flavor: readFlavor('subsection')})('Content Options:'), View({flavor: readFlavor('outline'), content: {wrap: true}, style: readStyle('inputPanelSize')})([ Text({style: readStyle('label')})('direction'), Input({type: 'list', options: options.direction, value: read('direction'), onChange: update('direction'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('wrap'), Input({type: 'list', options: options.wrap, value: read('wrap'), onChange: update('wrap'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('h'), Input({type: 'list', options: options.h, value: read('h'), onChange: update('h'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('v'), Input({type: 'list', options: options.v, value: read('v'), onChange: update('v'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('gap: ' + read('gap')), Input({type: 'range', min: 0, max: 20, value: read('gap'), onChange: update('gap'), style: readStyle('inputSize')}) ]), /* Align option panel */ Text({flavor: readFlavor('subsection')})('Align Options:'), View({flavor: readFlavor('outline'), content: {wrap: true}, style: readStyle('inputPanelSize')})([ Text({style: readStyle('label')})('expand: '+ read('expand')), Input({type: 'range', min: 0, max: 1, value: read('expand'), onChange: update('expand'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('shrink: ' + read('shrink')), Input({type: 'range', min: 0, max: 1, value: read('shrink'), onChange: update('shrink'), style: readStyle('inputSize')}), Text({style: readStyle('label')})('align'), Input({type: 'list', options: options.align, value: read('align'), onChange: update('align'), style: readStyle('inputSize')}) ]), /* View parent component for "content" property*/ View({ flavor: readFlavor('lightBackground'), style: readStyle('containerSize'), content: { direction: read('direction'), wrap: read('wrap') !== 'false' ? true : false, h: read('h'), v: read('v'), gap: Number(read('gap')) }, })([ View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('0')), /* View child component for "self" property */ View({flavor: readFlavor('primaryGradient'), style: readStyle('oneSize'), self: { align: read('align'), expand: read('expand'), shrink: read('shrink') } })(Text({flavor: readFlavor('section', 'light')})('1')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('2')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('3')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('4')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('5')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('6')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('7')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('8')), View({flavor: readFlavor('darkGradient'), style: readStyle('childrenSize')})(Text({flavor: readFlavor('section', 'light')})('9')) ]) ]; }; /* App Function: Renders the App Component in the screen */ app({component: App, state: state, style: style, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
You can easily show and hide components by feeding the visible property. If true, the component will be visible on the app. If false, the visibility is set to none hiding it.
/* Imports */ import {app, read, update, readFlavor} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; /* State Configuration */ const state = { isVisible: true }; /* App Component: Takes no properties and returns structure */ const App = () => { return View({content: {gap: 10}})([ Input({type: 'switch', value: read('isVisible'), onChange: update('isVisible')}), View({ visible: read('isVisible'), flavor: readFlavor('primaryGradient'), style: {padding: 40} })(Text({flavor: readFlavor('light')})('Toggle visibility')) ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
This completes your knowledge on how to position components on screen. In the next unit you will learn how to create your own custom components.
This may be one of the most important units. After completion, you will be able to create your own reusable components and share them with the rest of the world.
Throughout this unit, we will create a Password Validator component to illustrate the different steps to component creation. As input, you can select the checks to be performed to the password, such as: have at least one number, capital letter, symbol, etc. The validations are performed with regular expressions (regex
):
const checksConfig = {
minCharacters: {regex: /.{8,}/, text: 'Be at least 8 characters'},
numbers: {regex: /\d+/, text: 'At least one number'},
letters: {regex: /[a-z]/, text: 'At least one letter'},
capitalLetters: {regex: /[A-Z]/, text: 'At least one capital letter'},
symbols: {regex: /[!-/:-@[-`{-~]/, text: 'At least one special character'},
...config
}
After the validations, the component displays the requirement or checks that have not yet been met. See the expected final result below:
So far, in previous units we have showed you predefined components, but it is time to learn how to create your own. We have already established that components are functions. These functions take properties as arguments and return a component structure. Depending on the type of component you want to build, besides properties it can also require structure as input. In the example below, the Greeting
custom component takes the name
property and displays a greeting message to the selected name
:
import {app} from '@onejs-dev/core'; import {View, Text} from '@onejs-dev/components'; /* Custom component WITHOUT structure input */ const Greeting = ({name}={}) => { return View()(Text()(`Hello ${name}!`)); } const App = () => Greeting({name: 'oneJS'}); app({component: App, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
Depending on the type of component you want to build, besides properties it can also require structure
as input. For convenience, instead of creating another property for the structure
, we place this property as the input for a nested function. This leads to cleaner and more legible code when the component is instantiated. Take the ColorCard
example below:
import {app} from '@onejs-dev/core'; import {View, Text} from '@onejs-dev/components'; /* Custom component WITH structure input */ const ColorCard = ({color}={}) => structure => { return View({style: {backgroundColor: color, padding: 10}})(structure); } const App = () => ColorCard({color: 'pink'})(Text()('Card Text')); app({component: App, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
The component functions above are valid, but we can still optimize them. Use the Component
function providing: 1.
a unique name, 2.
a Boolean
value indicating whether it requires structure
input or not, and 3.
your already designed component. This has several optimization advantages as it registers your component function as a React or React Native element, allowing you to use hooks inside of it.
/* Wrapped in 'Component' function
const Greeting = Component('Greeting', false, ({name}={}) => {
return View()(Text()(`Hello ${name}!`));
});
const ColorCard = Component('ColorCard', true, ({color}={}) => structure => {
return View({style: {backgroundColor: color, padding: 10}})(structure);
});
Let's see this concepts applied to our PasswordValidator
component:
const PasswordValidator = Component('Password', false, ({password, checks, config={}}={}) => {
const checksConfig = {
minCharacters: {regex: /.{8,}/, text: 'Be at least 8 characters'},
numbers: {regex: /\d+/, text: 'At least one number'},
letters: {regex: /[a-z]/, text: 'At least one letter'},
capitalLetters: {regex: /[A-Z]/, text: 'At least one capital letter'},
symbols: {regex: /[!-/:-@[-`{-~]/, text: 'At least one special character'},
...config
}
password = password ?? '';
const failedChecks = checks.filter(check => !password.match(checksConfig[check].regex));
const isValid = failedChecks.length === 0;
return View({content: {direction: 'column'}})([
Text()('Requirements:'),
failedChecks.map(check => Text({list: 'bullets'})(checksConfig[check].text))
]);
});
When designing a component, the best practice is to explicitly name the properties that impact the output of your component, but at the same time, it is useful to have the flexibility to pass any number of standard attributes
to your component. You can achieve this by provid
ing the attributes
as a rest parameter.
If you feed the attributes
to your component’s structure, you can automatically inherit any number of properties, such as class
, id
, style
, etc. You can see below how the PasswordValid
ator is updated to incorporate this:
const PasswordValidator = Component('Password', false, ({password, checks, config={}, ...attributes}={}) => {
const checksConfig = {
minCharacters: {regex: /.{8,}/, text: 'Be at least 8 characters'},
numbers: {regex: /\d+/, text: 'At least one number'},
letters: {regex: /[a-z]/, text: 'At least one letter'},
capitalLetters: {regex: /[A-Z]/, text: 'At least one capital letter'},
symbols: {regex: /[!-/:-@[-`{-~]/, text: 'At least one special character'},
...config
}
password = password ?? '';
const failedChecks = checks.filter(check => !password.match(checksConfig[check].regex));
const isValid = failedChecks.length === 0;
return View({content: {direction: 'column'}, ...attributes})([
Text()('Requirements:'),
failedChecks.map(check => Text({list: 'bullets'})(checksConfig[check].text))
]);
});
The best way to style a custom component is by leveraging the power of flavors
. These allow you to create components that will inherit the look and feel of your app or any app they are placed in. Just provide a flavor
property and read its variables to customize the style.
import {app, Component, readFlavor} from '@onejs-dev/core'; import {View, Text} from '@onejs-dev/components'; const Greeting = Component('Greeting', false, ({name, flavor, ...attributes}={}) => { const viewStyle = { padding: 10, borderRadius: flavor?.radius ?? 0, backgroundColor: flavor?.backgroundColor ?? 'transparent' }; const textStyle = { color: flavor?.textColor ?? 'black' }; return View({style: viewStyle, ...attributes})(Text({style: textStyle})(`Hello ${name}!`)); }); const App = () => View({content: {direction: 'column'}})([ Greeting({name: 'oneJS'}), Greeting({name: 'oneJS', flavor: readFlavor('light', 'primaryBackground')}), Greeting({name: 'oneJS', flavor: readFlavor('light', 'rejectBackground')}) ]); app({component: App, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
As you can see, now the component can be easily customized respecting the look and feel of the app. If you want, you can provide a default value to the flavor
property, so that you don’t need to explicitly provide a flavor every time the component function is called. You can see how this is implemented in the PasswordValidator
example:
const PasswordValidator = Component('Password', false, ({password, checks, config={},
flavor=readFlavor('default'), ...attributes}={}) => { //By default, use the 'default' flavor
const checksConfig = {
minCharacters: {regex: /.{8,}/, text: 'Be at least 8 characters'},
numbers: {regex: /\d+/, text: 'At least one number'},
letters: {regex: /[a-z]/, text: 'At least one letter'},
capitalLetters: {regex: /[A-Z]/, text: 'At least one capital letter'},
symbols: {regex: /[!-/:-@[-`{-~]/, text: 'At least one special character'},
...config
}
password = password ?? '';
const failedChecks = checks.filter(check => !password.match(checksConfig[check].regex));
const isValid = failedChecks.length === 0;
/* Create style using flavor */
const flavorStyle = {
backgroundColor: 'white',
borderRadius: flavor?.radius,
borderColor: flavor?.borderColor,
borderStyle: flavor?.borderStyle,
borderWidth: flavor?.borderWidth,
padding: 15
};
/* Apply the flavor style to the view */
return View({content: {direction: 'column'}, style: flavorStyle, ...attributes})([
Text({style: {marginBottom: '5px'}})('Requirements:'),
failedChecks.map(check => Text({list: 'bullets'})(checksConfig[check].text))
]);
});
Even if you expose the customization of your component through flavors, you can have even more control by leveraging the style
attribute. In the previous section, we have created styles based on the flavor properties. To enable further customization trough the style
attribute, we need to merge both the flavor’s and the attribute’s styles. oneJS provides the mergeStyles
function to this end; input the styles you want to merge in increasing priority and you will get the merged style as a result.
import {app, Component, readFlavor, mergeStyles} from '@onejs-dev/core'; import {View, Text} from '@onejs-dev/components'; const Greeting = Component('Greeting', false, ({name, flavor, ...attributes}={}) => { const viewStyle = { padding: 10, borderRadius: flavor?.radius ?? 0, backgroundColor: flavor?.backgroundColor ?? 'transparent' }; /* Styles are merged: attributes['style'] has priority over viewStyle */ attributes['style'] = mergeStyles(viewStyle, attributes['style']); /* You can also explicitly describe the style priority as displayed below */ const textStyle = { color: attributes?.style?.color ?? flavor?.textColor ?? 'black' }; return View(attributes)(Text({style: textStyle})(`Hello ${name}!`)); }); const App = () => View({content: {direction: 'column'}})([ Greeting({name: 'oneJS', style: {backgroundColor: 'pink'}}), Greeting({name: 'oneJS', style: {color: 'black'}, flavor: readFlavor('light', 'primaryBackground')}), Greeting({name: 'oneJS', flavor: readFlavor('light', 'rejectBackground')}) ]); app({component: App, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
As you probably already expect, let's apply these learnings to the PasswordValidator
component:
const PasswordValidator = Component('Password', false, ({password, checks, config={},
flavor=readFlavor('default'), ...attributes}={}) => {
const checksConfig = {
minCharacters: {regex: /.{8,}/, text: 'Be at least 8 characters'},
numbers: {regex: /\d+/, text: 'At least one number'},
letters: {regex: /[a-z]/, text: 'At least one letter'},
capitalLetters: {regex: /[A-Z]/, text: 'At least one capital letter'},
symbols: {regex: /[!-/:-@[-`{-~]/, text: 'At least one special character'},
...config
}
password = password ?? '';
const failedChecks = checks.filter(check => !password.match(checksConfig[check].regex));
const isValid = failedChecks.length === 0;
const flavorStyle = {
backgroundColor: 'white',
borderRadius: flavor?.radius,
borderColor: flavor?.borderColor,
borderStyle: flavor?.borderStyle,
borderWidth: flavor?.borderWidth,
padding: 15
};
/* Merge the external style with the internal flavor style */
attributes['style'] = mergeStyles(flavorStyle, attributes['style']);
/* 'attributes' already contains the style attribute */
return View({content: {direction: 'column'}, ...attributes})([
Text({style: {marginBottom: '5px'}})('Requirements:'),
failedChecks.map(check => Text({list: 'bullets'})(checksConfig[check].text))
]);
});
The final step to make a custom component interactive is giving it the ability to read
and update
the state of the app. As we have already established, a component should never directly update a state variable, instead, provide a property that takes a function. This function is then called by your component when a certain event occurs. For inputs, we name this function onChange
as it is called when the value of the input changes. The user of this custom component can then decide if they want to provide a function to update the state or not.
See the example below where the onChange
function in the custom Multiplier
component is used to update the number
state variable:
import {app, Component, read, update} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; const state = {number: 1}; /* Multiplies the input number times the pressed button */ const Multiplier = Component('Multiplier', false, ({value, onChange}={}) => { const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; return View()(numbers.map(number => Input({ type: 'button', title: number, onClick: () => {onChange(value * number)} }))); }); const App = () => View({content: {direction: 'column'}})([ Text()('Result: ' + read('number')), Multiplier({value: read('number'), onChange: update('number')}), ]); app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
With these notions we can finally complete the PasswordValidator
component we have been using as an example:
import {app, read, update, Component, readFlavor, mergeStyles} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; /* State Configuration */ const state = { password: '', isValid: false }; const PasswordValidator = Component('Password', false, ({password, checks, config={}, value, onChange=()=>{}, flavor=readFlavor('default'), ...attributes}={}) => { const checksConfig = { minCharacters: {regex: /.{8,}/, text: 'Be at least 8 characters'}, numbers: {regex: /\d+/, text: 'At least one number'}, letters: {regex: /[a-z]/, text: 'At least one letter'}, capitalLetters: {regex: /[A-Z]/, text: 'At least one capital letter'}, symbols: {regex: /[!-/:-@[-`{-~]/, text: 'At least one special character'}, ...config } password = password ?? ''; const failedChecks = checks.filter(check => !password.match(checksConfig[check].regex)); const isValid = failedChecks.length === 0; /* Trigger onChange function */ if(value !== isValid) onChange(isValid); const flavorStyle = { backgroundColor: 'white', borderRadius: flavor?.radius, borderColor: flavor?.borderColor, borderStyle: flavor?.borderStyle, borderWidth: flavor?.borderWidth, padding: 15 }; attributes['style'] = mergeStyles(flavorStyle, attributes['style']); return View({content: {direction: 'column'}, ...attributes})([ Text({style: {marginBottom: '5px'}})('Requirements:'), failedChecks.map(check => Text({list: 'bullets'})(checksConfig[check].text)) ]); }); const App = () => { return View({content: {direction: 'column', gap: 10}})([ Input({type: 'password', value: read('password'), onChange: update('password'), placeholder: 'Password'}), /* Bind to state variables */ PasswordValidator({ password: read('password'), checks: ['minCharacters', 'numbers', 'letters'], value: read('isValid'), onChange: update('isValid') }), View({visible: read('isValid')})(Input({type: 'button', title: 'Strong password', flavor: readFlavor('accept')})) ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
You are now prepared to create your own reusable components and start contributing to the oneJS community. We are excited to see what you are capable of creating. You have now surpassed the equator of our units. In the next unit you will learn how to make your app navigable.
Routing is the process of selecting a path for data in a network. It enables the user to navigate the app and move between pages. The url (uniform resource locator) is the property that indicates what is the data to be fetched and displayed to the user; the active page. Through interactions such as clicking on links the user can change the active url to navigate to a different page.
To enable routing, we need a way to change the current url. Well, in oneJS it is actually quite simple; just provide a value to the url
property to any component and see how the url of the app changes when clicked.
import {app} from '@onejs-dev/core'; import {View, Input} from '@onejs-dev/components'; const App = () => { return View({content: {direction: 'row', gap: 10}})([ Input({type: 'button', title: 'Home', url: '/'}), Input({type: 'button', title: 'Products', url: '/products'}) ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
Note: On the web, you can provide the url
property to any component and click on it to change the url. On ios and android not all the components are clickable. If you are developing cross-platform, we recommend placing the url
property exclusively on View
or Input({type: 'button'})
components.
How are url paths specified? In oneJS all urls are absolute
; the full path to the page needs to be specified. The root url is /
. All other urls also begin with /
and are followed by the path to the target page. The segments of the route are also separated by /
, e.g.: /path/to/page
.
You may have heard about relative
url trend, and it may sound attractive, but they need to know about the context they are defined in, breaking isolation. This is why we have not adopted them in oneJS, as it goes against the functional pattern.
View({url: '/products'})([
/* DO NOT do this: Relative url */
Input({type: 'button, url: './product1'}),
/* DO this: Absolute url */
Input({type: 'button, url: '/products/product1'})
]);
You now know how to change the current url but routing also involves displaying and hiding information on the screen based on the active url. You can modify the visibility of a View
, just by feeding the visible
property. Remember the Visibility section from Unit 4.
The only thing left to connect the pieces, is how to use the current url to change the visibility of View
components. Fortunately, oneJS provides a simple way of doing so. Since the url
is a property that changes and affects the behavior of your app, it needs to be part of the state
. Just specify the url to be matched as the source
for your state variable; when it matches it will automatically become true
and when it does not it will be false
. You can use asterisks (*
) in your path to indicate that any value is acceptable for a certain segment. It is way easier to see in action in the example below:
import {app, read} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; const state = { homePage: {source: {url: '/'}}, productsPage: {source: {url: '/products'}} } const App = () => { return View({content: {direction: 'column', gap: 10}})([ View()([ Input({type: 'button', title: 'Home', url: '/'}), Input({type: 'button', title: 'Products', url: '/products'}) ]), View({visible: read('homePage')})(Text()('Home Page')), View({visible: read('productsPage')})(Text()('Products Page')), ]); }; /* App Function: Renders the App Component in the screen */ app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
Behind the scenes oneJS is using the history API on the web, so you can seamlessly go back and forward in your url history.
There are complex scenarios where you want to render a list of items and dynamically generate the url on click. In these cases, you have no way of knowing in advance the full url, as it usually depends on the database id of the selected element. To address this, you can provide to the state variable source
, a url containing a :
value for the segment you want to retrieve data from. For instance, if the url source
is products/:
and the actual url is products/
, the state variable will be equal to product1
product1
. You can test this in the example below:
import {app, read, readFlavor} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; /* Variable in path to fetch the product id */ const state = {productId: {source: {url: '/:'}}}; /* Emulates a 'products' database response */ const products = { product1: {name: 'pizza', price: '$10'}, product2: {name: 'risotto', price: '$15'}, product3: {name: 'tiramisu', price: '$5'} }; const App = () => { return [ Object.keys(products).map(product => Input({type: 'button', title: product, url: '/' + product})), read('productId') && View({content: {direction: 'column', gap: 10}, flavor: readFlavor('shadow')})([ Text()('Id: ' + read('productId')), Text()('Name: ' + products[read('productId')].name), Text()('Price: ' + products[read('productId')].price), ]), ]; }; /* App Function: Renders the App Component in the screen */ app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
If you have followed this far, you are now able to create an interactive page enabling the user to serve different content based on their actions. Head to the following unit and learn how to add fantastic animations!
So far, your app is stylish, navigable, and well structured. You could leave it here, but animations often mark the difference between beginners and pros. In this unit you will learn how to use out of the box animations with View
components.
An animation is a change in visual properties that happens over a certain period of time. It must be triggered by a change in your components’ properties, and therefore, by a change in the state. These are the most common properties that are used to trigger animations:
visible:
Use this property to animate a component that becomes visible or invisible.intersect:
Use this property to animate an element as it enters (intersects) the screen. It is mainly used with scrolling views where an element becomes visible when the user reaches its position. It is a web-only property.The way to specify animations in your View
components is by providing a configuration object to the animation
property. This object specifies which property triggers the animation and which animation will be performed for each value. In the example below, the View
will be animated when the property visible
changes; if true
it will fade-in, and if false
it will fade-out:
View({animation: {visible: ['fade-in', 'fade-out']}})(
Text()('Animated View')
);
For Boolean
variables you can use an array
with two positions, as shown in the previous example. The first position, represents the animation to be displayed when true
, and the second, when false
. The example below builds upon the one from Unit 4, Visibility section, and adds an animation to the visibility
property:
import {app, read, update, readFlavor} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; const state = {isVisible: true}; const App = () => { return View({content: {gap: 10}})([ Input({type: 'switch', value: read('isVisible'), onChange: update('isVisible')}), View({ visible: read('isVisible'), flavor: readFlavor('primaryGradient'), style: {padding: 40}, /* Defining the animation */ animation: {visible: ['fade-in', 'fade-out']} })(Text({flavor: readFlavor('light')})('Toggle visibility')) ]); }; app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
If you want to animate a property that takes non-Boolean
values, create a JavaScript object
and provide an animation for each value:
animation: {customProperty: {
a: 'fade-in', //When 'customProperty' is equal to 'a' the animation 'fade-in' is triggered
b: 'fade-out', //When 'customProperty' is equal to 'b' the animation 'fade-out' is triggered
c: 'expand' //When 'customProperty' is equal to 'c' the animation 'expand' is triggered
}}
You can see this in action in the example below:
import {app, read, update, readFlavor} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; const state = {choice: ''}; const options = ['a', 'b', 'c']; const App = () => { return View({content: {gap: 10}})([ Input({type: 'list', options: options, value: read('choice'), onChange: update('choice')}), View({ /* Property triggering the animations */ customProperty: read('choice'), flavor: readFlavor('primaryGradient'), style: {padding: 40}, /* Defining the animation */ animation: {customProperty: {a: 'fade-in', b: 'fade-out', c: 'expand'}} })(Text({flavor: readFlavor('light')})('Animated Block')) ]); }; app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
To make your life a bit easier, oneJS provides several standard animations. In the previous examples, we have already displayed some of them, such as fade-in
, fade-out
and expand
. You can use the preset animations just by indicating their name as a string in the animation
property. The example below showcases the available presets at the moment. We are creating more for future releases. We would love to hear your opinion!
import {app, read, update, readFlavor} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; const state = { inAnimation: 'appear', outAnimation: 'disappear', isVisible: true }; /* Available preset animations */ const inAnimations = ['apprear', 'subtle-in', 'fade-in', 'fade-in-left', 'fade-in-right', 'slide-in-left', 'slide-in-right', 'slide-in-up', 'slide-in-down', 'expand', 'horizontal-expand', 'vertical-expand']; const outAnimations = ['disapprear', 'subtle-out', 'fade-out', 'fade-out-left', 'fade-out-right', 'slide-out-left', 'slide-out-right', 'slide-out-up', 'slide-out-down', 'shrink', 'horizontal-shrink', 'vertical-shrink']; const App = () => { return View({content: {direction: 'column', gap: 10}})([ Text()('In Animation: '), Input({type: 'list', options: inAnimations, value: read('inAnimation'), onChange: update('inAnimation')}), Text()('Out Animation: '), Input({type: 'list', options: outAnimations, value: read('outAnimation'), onChange: update('outAnimation')}), Input({type: 'switch', value: read('isVisible'), onChange: update('isVisible')}), View({ visible: read('isVisible'), flavor: readFlavor('primaryGradient'), style: {padding: 40}, /* Defining the animation */ animation: {visible: [read('inAnimation'), read('outAnimation')]} })(Text({flavor: readFlavor('light')})('Animated Block')) ]); }; app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
Preset animations are great, but you can also choose to create your own. Under the hood, oneJS is using the standard Web Animations API for the web and React Native’s Animated API for iOS and Android. This means that if you are an advanced user, you can create your own animations using the following parameters:
keyframes:
They each of the states on a timeline which mark the beginning or end of a transition. Keyframes define the properties to be interpolated in the animation and their values for each of the steps. For example, in a fade-in
animation, the opacity
property will be interpolated from 0
(transparent) to 1
(opaque).options:
They are the object describing how the animation will be executed. You can specify parameters like the duration
of the animation in milliseconds, or the easing
; the process of making an animation not so severe or pronounced by defining the curve to interpolate the animation. You can also provide a style
object indicating the style to be applied after the animation has ended. There are many more options besides these; make sure to refer to the Web Animations API and React Native’s Animated API for more information.The example below showcases how a fade-in-left
and shrink
animation definition would look like in the web. Please, note that the way transform keyframes and easing are specified in React Native differ from web.
import {app, read, update, readFlavor} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; const state = {isVisible: true}; /* Custom animation creation */ const inAnimation = { keyframes: {transform: ['translate3d(-100%, 0, 0)', 'none'], opacity: [0, 1]}, options: {duration: 1000, easing: 'ease'} }; const outAnimation = { keyframes: {scale: [1, 0], opacity: [1, 0]}, options: {duration: 1000, easing: 'ease-out'}, style: {display: 'none'} }; const App = () => { return View({content: {gap: 10}})([ Input({type: 'switch', value: read('isVisible'), onChange: update('isVisible')}), View({ visible: read('isVisible'), flavor: readFlavor('primaryGradient'), style: {padding: 40}, /* Defining the animation */ animation: {visible: [inAnimation, outAnimation]} })(Text({flavor: readFlavor('light')})('Animated Block')) ]); }; app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
That’s all there is to it. Use the animation
property in your View
components to breathe new life into your app.
In this unit you will complete your knowledge to begin your journey as a oneJS developer. Throughout this unit we will discuss the different types of storage. Storage is in fact what it seems, the process of storing and persisting data even if the app is closed. Depending on the topology (the location of the data) we can divide storage in local
, when data is stored in the user’s device, or online
, when data is stored in an external database.
Let’s begin by exploring local
storage
. The typical use-case for this type of storage
is possibly saving user specific configuration. Suppose our app lets the user choose the default language. You can leverage this type of storage
to retrieve this value every time the user launches the app.
To persist data, first choose the variable you would like to persist and make it part of the state, if it is not already. Then, specify the local
type of storage
and give it an id. This id will be used to store the variable. After this step, your variable will be saved every time its value is modified. Now, if you want to retrieve its value when the app is loaded, you need to define the source
. Just define local
as the source
providing the same id as the one used in the storage
.
const state = {
language: {
source: {local: 'myAppLanguage'},
storage: {local: 'myAppLanguage'}
}
};
Try to give a unique name to your ids, since local storage is shared by other apps running in your device. See the example below putting this in practice:
import {app, read, update, readFlavor} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; const state = { language: { default: 'English', source: {local: 'myAppLanguage'}, storage: {local: 'myAppLanguage'} } } const languages = ['Spanish', 'English', 'French', 'German']; const App = () => { return View({content: {direction: 'column', gap: 10}})([ Input({type: 'list', options: languages, value: read('language'), onChange: update('language')}), View()(Text()('Selected Language: ' + read('language'))) ]); }; app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
You can refresh the page and see how the selection in the example above persists. Internally, oneJS leverages the Storage API for the web and Async Storage package for iOS and Android. Using this type of storage you can save simple key-value pairs, but it is not adequate for complex data models. Keep reading to learn how to manage complex scenarios.
For some apps local storage may be enough, but as your app grows in complexity you may find it necessary to use an external database to store your data. oneJS support Firebase's Cloud Firestore database out of the box. It follows a document-oriented model as described on their website:
To use Cloud Firestore in your app, you first need to initialize your database. First, import
Cloud Firestore in your project and call the initializeApp
function with the configuration object corresponding to your project. Then, call the getFirestore
function using the output of the previous function. This function returns the configured Firestore database object. It should look something like the example below:
import {initializeApp} from 'firebase/app';
import {getFirestore} from 'firebase/firestore';
// TODO: Replace the following with your app's Firebase project configuration
// See: https://firebase.google.com/docs/web/learn-more#config-object
const firebaseConfig = {
// ...
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Cloud Firestore and get a reference to the service
const db = getFirestore(app);
Once you have the Cloud Firestore database object (db
in the example), feed it to the app
function’s firestore
property. This enables oneJS to perform read and write operations.
Finally, create a state variable that uses firestore
as its source
, storage
or both. Indicate the path to the document or collection that you want to retrieve, create or modify. For documents, you can include state variables in your path by wrapping their name in < >
. See the example below:
import {app} from '@onejs-dev/core';
import {initializeApp} from 'firebase/app';
import {getFirestore} from 'firebase/firestore';
/* Cloud Firestore Initialization */
const firebaseConfig = {
// ...
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const state = {
products: {source: {firestore: 'products'}, storage: {firestore: 'products'}},
selectedProductId: {default: 'pizza'},
product: {
source: {firestore: 'products/<selectedProductId>'},
storage: {firestore: 'products/<selectedProductId>'}
}
}
const App = () => {
// ...
};
app({component: App, firestore: db, state: state, theme: {preset: 'oneJS'}});
oneJS syncs the state with the database data automatically. This way, you can focus on modifying the state of your app, and oneJS will perform the dirty work for you. We are working to support more popular serverless databases. We would love to have feedback from you!
IndexedDB is an in-browser database that allows you to store significant amounts of structured data, including files/blobs. It is supported by most modern browsers and uses indexes to enable high-performance searches. While the Local Storage discussed in the previous section is useful for storing smaller amounts of data, it is less adequate for storing larger amounts of structured data.
IndexedDB uses a data model based on object stores and transactions. You can read all about it in the MDM Web Docs. For simplicity, you can think of it as Cloud Firestore's document-oriented model; the root level is a collection that can contain any number of documents. Documents are JavaScript objects that store key-value pairs. Unlike Cloud Firestore, IndexedDB does not allow collection nesting or subcollections.
Using IndexedDB in oneJS requires no set up, just create a state variable using IndexedDB as your source
and storage
indicating the path to the data. You can then modify the state variable and oneJS will sync updates to the IndexedDB database for you.
import {app} from '@onejs-dev/core';
const state = {
products: {source: {indexedDB: 'products'}, storage: {indexedDB: 'products'}},
selectedProductId: {default: 'pizza'},
product: {
source: {indexedDB: 'products/<selectedProductId>'},
storage: {indexedDB: 'products/<selectedProductId>'}
}
}
const App = () => {
// ...
};
app({component: App, state: state, theme: {preset: 'oneJS'}});
Working with more complex data also requires additional tools to update your data. In the previous units, we have displayed how to use the update
function; it takes the state variable's id and replaces its value when an event is triggered:
import {app, read, update} from '@onejs-dev/core';
import {Input} from '@onejs-dev/components';
const state = {
name: ''
}
const App = () => [
Input({value: read('name'), onChange: update('name'), placeholder: 'Name'}),
];
app({component: App, state: state, theme: {preset: 'oneJS'}});
When working with databases, you may just want to update a certain document within a collection and not the entire collection. You can achieve this with the update
function, just provide a second argument, in addition to the state id, which represents the document id to be updated:
import {app, read, update} from '@onejs-dev/core';
import {Input, Text} from '@onejs-dev/components';
const state = {
stateId: {default: [
{id: 'documentId', name: 'Default value'},
{id: 'otherDocumentId', name: 'Other default value'}
]},
name: ''
}
const App = () => [
Input({value: read('name'), onChange: update('name'), placeholder: 'Name'}),
Input({type: 'button', title: 'Save', onClick: () => {
update('stateId', 'documentId')({...read('stateId', 'documentId'), name: read('name')});
}})
];
app({component: App, state: state, theme: {preset: 'oneJS'}});
You may also want to add a new document into a collection without needing to overwrite the entire collection. In this case, you can use the add
function. Just provide the state id of the collection and a new document will be created when the add function is triggered:
import {app, read, update} from '@onejs-dev/core';
import {Input} from '@onejs-dev/components';
const state = {
stateId: {default: [
{id: 'documentId', name: 'Default value'},
{id: 'otherDocumentId', name: 'Other default value'}
]},
name: ''
}
const App = () => [
Input({value: read('name'), onChange: update('name'), placeholder: 'Name'}),
Input({type: 'button', title: 'Save', onClick: () => {
add('stateId')({name: read('name')});
}})
];
app({component: App, state: state, theme: {preset: 'oneJS'}});
Finally, to complete the CRUD operations, oneJS gives you the power to remove data from your database. Beware that with great power comes great responsibility 🕷. To delete an item from a collection, call the remove
function with the desired document id:
import {app, read, update} from '@onejs-dev/core';
import {Input} from '@onejs-dev/components';
const state = {
stateId: {default: [
{id: 'documentId', name: 'Default value'},
{id: 'otherDocumentId', name: 'Other default value'}
]},
name: ''
}
const App = () => [
Input({value: read('name'), onChange: update('name'), placeholder: 'Name'}),
Input({type: 'button', title: 'Remove', onClick: () => {
remove('stateId', 'documentId');
}})
];
app({component: App, state: state, theme: {preset: 'oneJS'}});
Let's see all this concepts in a real example; a TODO app. Do you know any other frameworks where you can write this in less than 30 lines of code?. Please let us know!
import {app, read, add, update, remove, readFlavor} from '@onejs-dev/core'; import {View, Input, Text} from '@onejs-dev/components'; const state = { todo: '', todos: {source: {indexedDB: 'todos'}, storage: {indexedDB: 'todos'}} }; const saveTodo = () => {add('todos')({name: read('todo'), done: false});update('todo')('');} const updateTodo = todo => () => {update('todos', todo?.id)({...todo, done: !todo.done});} const removeTodo = todo => () => {remove('todos', todo?.id);} const App = () => { return View({content: {direction: 'column', gap: 10}})([ Text({flavor: readFlavor('section')})('Write your TODOs:'), View()([ Input({value: read('todo'), onChange: update('todo')}), Input({type: 'button', title: 'Save', onClick: saveTodo, flavor: readFlavor('light','primaryBackground')}) ]), read('todos')?.length > 0 && read('todos').map(todo => View({content: {gap: 10}})([ Input({type: 'button', title: 'Delete', onClick: saveTodo, onPress: removeTodo(todo), flavor: readFlavor('light','rejectBackground')}), Input({type: 'checkbox', title: todo.name, value: todo.done, onChange: updateTodo(todo)}) ])) ]); }; app({component: App, state: state, theme: {preset: 'oneJS'}});
To enter the code editing mode, press Enter. To exit the edit mode, press Escape
You are editing the code. To exit the edit mode, press Escape
Well done! You have completed all the learning units, use this knowledge to build something amazing. You can head to the Playground section to test your ideas or check out the Docs for more in-depth content. To be continued… More units with more incredible features are coming.