appblocks
Version:
A lightweight javascript library for building micro apps for the front-end.
550 lines (445 loc) • 11.5 kB
Markdown
# Data Management
The `data` object is at the heart of every AppBlock. It stores all the information your app needs and automatically updates the UI when the data changes.
## The Basics
Data in AppBlocks is just a plain JavaScript object. You can structure it however you like, with the only rule being that `data` must be an object.
```js
var app = new AppBlock({
el: document.getElementById('app'),
template: document.getElementById('appTemplate'),
data: {
// Simple values
message: "Hello!",
count: 0,
isVisible: true,
// Arrays
items: ["Apple", "Banana", "Cherry"],
// Nested objects
user: {
name: "Alice",
email: "alice@example.com",
preferences: {
theme: "dark",
notifications: true
}
}
}
});
```
## Accessing Data in Templates
### Simple Properties
Use placeholders with dot notation to access data:
```html
<template id="appTemplate">
<h1>{data.message}</h1>
<p>Count: {data.count}</p>
<p c-if="data.isVisible">I am visible!</p>
</template>
```
### Nested Properties
Access nested data with multiple dots:
```html
<template id="appTemplate">
<div>
<h2>{data.user.name}</h2>
<p>Email: {data.user.email}</p>
<p>Theme: {data.user.preferences.theme}</p>
</div>
</template>
```
### Array Access
Use bracket notation or index to access array elements:
```html
<template id="appTemplate">
<!-- Access by index -->
<p>First item: {data.items[0]}</p>
<p>Second item: {data.items[1]}</p>
<!-- Loop through all items -->
<ul>
<li c-for="item in data.items">{item}</li>
</ul>
</template>
```
### Dynamic Property Access
Use bracket notation to access properties dynamically:
```js
var app = new AppBlock({
data: {
currentKey: 'name',
user: {
name: 'Bob',
age: 30,
city: 'NYC'
},
colors: ['red', 'green', 'blue']
}
});
```
```html
<template id="appTemplate">
<!-- Dynamic object property access -->
<p>{data.user[data.currentKey]}</p>
<!-- Can also use with arrays -->
<p>{data.colors[0]}</p>
</template>
```
## Updating Data
AppBlocks provides two ways to update data: using `setData()` for automatic re-rendering, or direct updates followed by manual `render()`.
### Method 1: setData() (Recommended)
`setData()` updates the data and automatically triggers a re-render:
```js
app.setData(newData, replaceData)
```
**Parameters:**
- `newData` (Object): The data to update
- `replaceData` (Boolean, optional): If `true`, replaces all data. Default: `false` (merges with existing data)
#### Partial Updates (Default)
By default, `setData()` merges new data with existing data:
```js
var app = new AppBlock({
data: {
name: "Alice",
age: 25,
city: "NYC"
}
});
// Update only age, keep name and city
app.setData({ age: 26 });
// Result: { name: "Alice", age: 26, city: "NYC" }
```
#### Complete Replacement
Pass `true` as the second parameter to replace all data:
```js
var app = new AppBlock({
data: {
name: "Alice",
age: 25,
city: "NYC"
}
});
// Replace entire data object
app.setData({ score: 100 }, true);
// Result: { score: 100 }
// name, age, and city are gone!
```
### Method 2: Direct Updates + render()
For performance optimization or batch updates, you can update data directly and call `render()` when ready:
```js
var app = new AppBlock({
data: {
items: [],
total: 0
},
methods: {
addMultipleItems(app, newItems) {
// Direct updates - no re-render yet
app.data.items = app.data.items.concat(newItems);
app.data.total = app.data.items.length;
// Manually trigger render once
app.render();
}
}
});
```
**When to use direct updates:**
- Making multiple related changes
- Performance-critical sections
- Updating data without immediate visual update
## Accessing Data in Methods
Methods receive the app instance as their first parameter, giving you access to data:
```js
var app = new AppBlock({
data: {
count: 0,
history: []
},
methods: {
// Access via app parameter (recommended)
increment(self) {
var newCount = self.data.count + 1;
self.setData({ count: newCount });
},
// Access via this.Parent
addToHistory() {
var entry = {
count: this.Parent.data.count,
timestamp: Date.now()
};
var newHistory = this.Parent.data.history.concat(entry);
this.Parent.setData({ history: newHistory });
}
}
});
```
## Working with Arrays
### Adding Items
```js
methods: {
addItem(self, newItem) {
var updated = self.data.items.concat(newItem);
self.setData({ items: updated });
},
// Or using spread operator
addItemSpread(self, newItem) {
self.setData({
items: [...self.data.items, newItem]
});
}
}
```
### Removing Items
```js
methods: {
removeItem(self, index) {
var updated = self.data.items.filter(function(item, i) {
return i !== index;
});
self.setData({ items: updated });
},
removeById(self, id) {
var updated = self.data.items.filter(function(item) {
return item.id !== id;
});
self.setData({ items: updated });
}
}
```
### Updating Items
```js
methods: {
updateItem(self, index, newValue) {
var updated = self.data.items.map(function(item, i) {
if (i === index) {
return newValue;
}
return item;
});
self.setData({ items: updated });
},
toggleComplete(self, id) {
var updated = self.data.items.map(function(item) {
if (item.id === id) {
return { ...item, completed: !item.completed };
}
return item;
});
self.setData({ items: updated });
}
}
```
## Working with Objects
### Updating Nested Properties
```js
var app = new AppBlock({
data: {
user: {
name: "Alice",
settings: {
theme: "light",
notifications: true
}
}
},
methods: {
updateTheme(self, newTheme) {
// Create new nested structure
var updatedSettings = {
...self.data.user.settings,
theme: newTheme
};
var updatedUser = {
...self.data.user,
settings: updatedSettings
};
self.setData({ user: updatedUser });
},
// Or update directly and render
toggleNotifications(self) {
self.data.user.settings.notifications = !self.data.user.settings.notifications;
self.render();
}
}
});
```
## Data Patterns and Best Practices
### 1. Initialize All Properties
Define all data properties upfront, even if empty:
```js
// Good
data: {
users: [],
selectedUser: null,
isLoading: false,
errorMessage: ''
}
// Avoid adding properties later
// app.data.newProperty = value; // Works but not ideal
```
### 2. Keep Data Flat When Possible
Flatter data structures are easier to work with:
```js
// Simpler
data: {
userName: 'Alice',
userAge: 25,
userCity: 'NYC'
}
// More complex (use when it makes sense)
data: {
user: {
name: 'Alice',
age: 25,
city: 'NYC'
}
}
```
### 3. Avoid Direct Mutations in Templates
Don't modify data in templates. Use methods instead:
```html
<!-- Bad: Don't do this -->
<button onclick="app.data.count++">Increment</button>
<!-- Good: Use events and methods -->
<button id="increment-btn">Increment</button>
```
```js
events: {
'click #increment-btn': function() {
this.Parent.setData({
count: this.Parent.data.count + 1
});
}
}
```
### 4. Batch Related Updates
When updating multiple properties, do it in one `setData()` call:
```js
// Good - single render
app.setData({
isLoading: false,
data: responseData,
error: null
});
// Less efficient - three renders
app.setData({ isLoading: false });
app.setData({ data: responseData });
app.setData({ error: null });
```
## Complete Example: Todo List
Here's a complete example demonstrating data management patterns:
```js
var app = new AppBlock({
el: document.getElementById('app'),
template: document.getElementById('appTemplate'),
data: {
todos: [
{ id: 1, text: 'Learn AppBlocks', done: false },
{ id: 2, text: 'Build an app', done: false }
],
newTodoText: '',
filter: 'all' // 'all', 'active', 'completed'
},
methods: {
addTodo(self) {
if (!self.data.newTodoText.trim()) return;
var newTodo = {
id: Date.now(),
text: self.data.newTodoText,
done: false
};
self.setData({
todos: self.data.todos.concat(newTodo),
newTodoText: ''
});
},
toggleTodo(self, id) {
var updated = self.data.todos.map(function(todo) {
if (todo.id === id) {
return { ...todo, done: !todo.done };
}
return todo;
});
self.setData({ todos: updated });
},
removeTodo(self, id) {
var updated = self.data.todos.filter(function(todo) {
return todo.id !== id;
});
self.setData({ todos: updated });
},
getFilteredTodos(self) {
if (self.data.filter === 'active') {
return self.data.todos.filter(function(todo) { return !todo.done; });
}
if (self.data.filter === 'completed') {
return self.data.todos.filter(function(todo) { return todo.done; });
}
return self.data.todos;
}
},
events: {
'click #add-btn': function() {
this.Parent.methods.addTodo(this.Parent);
},
'input #new-todo': function(e, el) {
this.Parent.setData({ newTodoText: el.value });
},
'click .toggle-btn': function(e, el) {
var id = parseInt(el.dataset.id);
this.Parent.methods.toggleTodo(this.Parent, id);
},
'click .remove-btn': function(e, el) {
var id = parseInt(el.dataset.id);
this.Parent.methods.removeTodo(this.Parent, id);
},
'click .filter-btn': function(e, el) {
this.Parent.setData({ filter: el.dataset.filter });
}
}
});
```
```html
<template id="appTemplate">
<div class="todo-app">
<h1>My Todos</h1>
<div class="add-todo">
<input
id="new-todo"
type="text"
placeholder="What needs to be done?"
value="{data.newTodoText}">
<button id="add-btn">Add</button>
</div>
<div class="filters">
<button class="filter-btn" data-filter="all">All</button>
<button class="filter-btn" data-filter="active">Active</button>
<button class="filter-btn" data-filter="completed">Completed</button>
</div>
<ul class="todo-list">
<li c-for="todo in getFilteredTodos()">
<input
type="checkbox"
class="toggle-btn"
data-id="{todo.id}"
c-if="todo.done"
checked>
<input
type="checkbox"
class="toggle-btn"
data-id="{todo.id}"
c-ifnot="todo.done">
<span class="todo-text">{todo.text}</span>
<button class="remove-btn" data-id="{todo.id}">×</button>
</li>
</ul>
<p c-if="getFilteredTodos().length == 0">No todos to display!</p>
</div>
</template>
```
## Summary
- Data is a plain JavaScript object
- Access data in templates with `{data.property}`
- Update data with `app.setData()` for automatic re-rendering
- Use direct updates + `app.render()` for batch operations
- Access data in methods via `app.data` or `this.Parent.data`
- Keep data structure simple and initialize all properties
- Batch related updates for better performance
**Next:** Learn about [Filters](filters.md) to transform your data before displaying it.