ember-tribe 2.4.8 → 2.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +670 -5
- package/README.md +777 -27
- package/blueprints/ember-tribe/files/app/services/types.js +60 -2
- package/blueprints/ember-tribe/index.js +19 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,55 +1,805 @@
|
|
|
1
|
-
ember-tribe
|
|
2
|
-
==============================================================================
|
|
1
|
+
# ember-tribe
|
|
3
2
|
|
|
4
3
|
An addon that connects EmberJS to Tribe API.
|
|
5
|
-
Tribe is a project management framework
|
|
4
|
+
Tribe is a collaborative project management framework - https://github.com/tribe-framework/tribe
|
|
6
5
|
|
|
6
|
+
https://junction.express provides an interface to build a Tribe compatibale no-code backend in minutes.
|
|
7
7
|
|
|
8
|
-
Compatibility
|
|
9
|
-
------------------------------------------------------------------------------
|
|
8
|
+
## Compatibility
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
- Ember.js v6.0 or above
|
|
11
|
+
- Ember CLI v6.0 or above
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
Installation
|
|
16
|
-
------------------------------------------------------------------------------
|
|
13
|
+
## Installation
|
|
17
14
|
|
|
18
15
|
1. Install
|
|
16
|
+
|
|
19
17
|
```
|
|
20
18
|
ember install ember-tribe
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
2. Configure
|
|
24
|
-
- Enter TRIBE_API_URL in .env file in Ember directory.
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
- Enter TRIBE_API_URL and TRIBE_API_KEY in .env file, copy of .env.sample
|
|
24
|
+
|
|
25
|
+
# Ember-Tribe Documentation
|
|
26
|
+
|
|
27
|
+
## Overview
|
|
28
|
+
|
|
29
|
+
ember-tribe is a powerful Ember.js addon that bridges the gap between backend CMS data structures and frontend application development. It helps you make an Ember app based on storylang.json and simplified-types.json files, if and when these files are availble.
|
|
30
|
+
|
|
31
|
+
## Purpose
|
|
32
|
+
|
|
33
|
+
Ember-tribe enables rapid application development by:
|
|
34
|
+
|
|
35
|
+
- Providing pre-configured addon ecosystem for common functionality
|
|
36
|
+
- Implementing standardized patterns for Tribe applications
|
|
37
|
+
- Creating responsive, Bootstrap-based interfaces with minimal configuration
|
|
38
|
+
- Supporting automatic model generation from backend track definitions in simplified-types.json
|
|
39
|
+
|
|
40
|
+
## Installation and Setup
|
|
41
|
+
|
|
42
|
+
### Prerequisites
|
|
43
|
+
|
|
44
|
+
- Ember.js 6.x
|
|
45
|
+
- Node.js (latest LTS)
|
|
46
|
+
- Junction backend
|
|
47
|
+
|
|
48
|
+
### Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
ember install ember-tribe
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The addon automatically configures following essential packages:
|
|
55
|
+
|
|
56
|
+
**Ember Addons:**
|
|
57
|
+
|
|
58
|
+
- `ember-cli-dotenv` - Environment configuration
|
|
59
|
+
- `ember-cli-sass` - SCSS support
|
|
60
|
+
- `ember-modifier` - DOM manipulation helpers
|
|
61
|
+
- `ember-composable-helpers` - Template utilities
|
|
62
|
+
- `ember-truth-helpers` - Boolean logic helpers
|
|
63
|
+
- `ember-file-upload` - File handling
|
|
64
|
+
- `ember-power-select` - Advanced select components
|
|
65
|
+
- `ember-table` - Data tables
|
|
66
|
+
- `ember-animated` - Smooth animations
|
|
67
|
+
|
|
68
|
+
**NPM Packages:**
|
|
69
|
+
|
|
70
|
+
- `bootstrap` - UI framework
|
|
71
|
+
- `@popperjs/core` - Bootstrap dependency
|
|
72
|
+
- `animate.css` - CSS animations
|
|
73
|
+
- `video.js` - Video player
|
|
74
|
+
- `swiper` - Touch sliders
|
|
75
|
+
- `howler` - Audio management
|
|
76
|
+
|
|
77
|
+
## Core Architecture
|
|
78
|
+
|
|
79
|
+
### Think in this order
|
|
80
|
+
|
|
81
|
+
ember-tribe follows a specific order that includes our learnings and Ember.js best practices, and must be followed exactly:
|
|
82
|
+
|
|
83
|
+
1. **router.js** - Application routing structure
|
|
84
|
+
2. **app/routes** - Route handlers and data loading
|
|
85
|
+
3. **app/templates & controllers** - UI templates and minimal controllers
|
|
86
|
+
4. **app/components** - Reusable UI components with component specific logic
|
|
87
|
+
5. **app/services** - Application-wide logic and state
|
|
88
|
+
|
|
89
|
+
This order ensures proper dependency resolution and optimal code organization.
|
|
90
|
+
|
|
91
|
+
### Package Philosophy
|
|
92
|
+
|
|
93
|
+
**Try using npm packages over ember addons because ember addons are sometimes outdated.** Ember-tribe prioritizes npm packages for better compatibility and maintenance.
|
|
94
|
+
|
|
95
|
+
### File Structure
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
app/
|
|
99
|
+
├── routes/
|
|
100
|
+
├── templates/
|
|
101
|
+
├── controllers/
|
|
102
|
+
├── components/
|
|
103
|
+
├── helpers/
|
|
104
|
+
├── modifiers/
|
|
105
|
+
├── services/
|
|
106
|
+
├── styles/app.scss
|
|
107
|
+
└── router.js
|
|
108
|
+
installer.sh
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Required File Outputs
|
|
112
|
+
|
|
113
|
+
Make separate, complete code files for each category:
|
|
114
|
+
|
|
115
|
+
### installer.sh
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
ember g route <name>;
|
|
119
|
+
ember g controller <name>;
|
|
120
|
+
ember g component <name> -gc;
|
|
121
|
+
ember g helper <name>;
|
|
122
|
+
ember g modifier <name>;
|
|
123
|
+
ember g service <name>;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### app/styles/app.scss
|
|
127
|
+
|
|
128
|
+
```scss
|
|
129
|
+
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
|
|
130
|
+
|
|
131
|
+
$font-family-sans-serif: 'IBM Plex Mono', serif !default;
|
|
132
|
+
$display-font-family: 'IBM Plex Mono', serif !default;
|
|
133
|
+
|
|
134
|
+
$primary: #000000 !default;
|
|
135
|
+
$secondary: #cccccc !default;
|
|
136
|
+
$success: #00ff00 !default;
|
|
137
|
+
$info: #0000ff !default;
|
|
138
|
+
$warning: #ffff00 !default;
|
|
139
|
+
$danger: #ff0000 !default;
|
|
140
|
+
$light: #eeeeee !default;
|
|
141
|
+
$dark: #333333 !default;
|
|
142
|
+
|
|
143
|
+
$enable-rounded: false !default;
|
|
144
|
+
$enable-negative-margins: true !default;
|
|
145
|
+
$enable-cssgrid: true !default;
|
|
146
|
+
|
|
147
|
+
$spacer: 1rem !default;
|
|
148
|
+
$spacers: (
|
|
149
|
+
0: 0,
|
|
150
|
+
1: $spacer * 0.25,
|
|
151
|
+
2: $spacer * 0.5,
|
|
152
|
+
3: $spacer,
|
|
153
|
+
4: $spacer * 1.5,
|
|
154
|
+
5: $spacer * 3,
|
|
155
|
+
6: $spacer * 4.5,
|
|
156
|
+
7: $spacer * 6,
|
|
157
|
+
8: $spacer * 7.5,
|
|
158
|
+
9: $spacer * 9,
|
|
159
|
+
10: $spacer * 12,
|
|
160
|
+
) !default;
|
|
161
|
+
|
|
162
|
+
@import 'node_modules/bootstrap/scss/bootstrap';
|
|
163
|
+
@import 'node_modules/animate.css/animate';
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### app/templates/application.hbs
|
|
167
|
+
|
|
168
|
+
```handlebars
|
|
169
|
+
{{page-title 'Your Application Name'}}
|
|
170
|
+
{{outlet}}
|
|
171
|
+
<BasicDropdownWormhole />
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Application.hbs Extension Guidelines:**
|
|
175
|
+
|
|
176
|
+
- Extend when adding global navigation components
|
|
177
|
+
- Include shared modals or dropdowns
|
|
178
|
+
- Add application-wide notification systems
|
|
179
|
+
- Insert global loading states or overlays
|
|
180
|
+
|
|
181
|
+
## EmberData Integration
|
|
182
|
+
|
|
183
|
+
### Automatic Model Generation
|
|
184
|
+
|
|
185
|
+
Ember-tribe automatically generates models from backend track definitions through the `types` service:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
// app/routes/application.js
|
|
189
|
+
export default class ApplicationRoute extends Route {
|
|
190
|
+
@service types;
|
|
191
|
+
|
|
192
|
+
async beforeModel() {
|
|
193
|
+
// Auto-generates models from backend
|
|
194
|
+
await this.types.fetchAgain();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Data Access Patterns
|
|
200
|
+
|
|
201
|
+
All backend data uses the `modules` key for field access:
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
// Accessing track data
|
|
205
|
+
let post = await this.store.findRecord('post', 1);
|
|
206
|
+
console.log(post.id); // Direct property
|
|
207
|
+
console.log(post.slug); // Direct property
|
|
208
|
+
console.log(post.modules.title); // Field access
|
|
209
|
+
console.log(post.modules.content_privacy); // Universal field
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Query Operations
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// Complex queries
|
|
216
|
+
this.store.query('post', {
|
|
217
|
+
modules: { status: 'published' }, // AND conditions
|
|
218
|
+
filter: { category: 'tech' }, // OR conditions
|
|
219
|
+
sort: 'title,-created_date', // Sort (- for desc)
|
|
220
|
+
page: { offset: 0, limit: 10 }, // Pagination
|
|
221
|
+
show_public_objects_only: false, // Include drafts
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Styling System
|
|
226
|
+
|
|
227
|
+
### Default Configuration (app.scss)
|
|
228
|
+
|
|
229
|
+
```scss
|
|
230
|
+
// Typography
|
|
231
|
+
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono...');
|
|
232
|
+
$font-family-sans-serif: 'IBM Plex Mono', serif !default;
|
|
233
|
+
|
|
234
|
+
// Color Palette
|
|
235
|
+
$primary: #000000 !default;
|
|
236
|
+
$secondary: #cccccc !default;
|
|
237
|
+
$success: #00ff00 !default;
|
|
238
|
+
// ... additional colors
|
|
239
|
+
|
|
240
|
+
// Bootstrap Configuration
|
|
241
|
+
$enable-rounded: false !default;
|
|
242
|
+
$enable-negative-margins: true !default;
|
|
243
|
+
$enable-cssgrid: true !default;
|
|
244
|
+
|
|
245
|
+
@import 'node_modules/bootstrap/scss/bootstrap';
|
|
246
|
+
@import 'node_modules/animate.css/animate';
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Design Principles
|
|
250
|
+
|
|
251
|
+
- **Minimal Controllers**: Logic should reside in components and services
|
|
252
|
+
- **Bootstrap 5.x Foundation**: Responsive, accessible design system
|
|
253
|
+
- **Subtle Animations**: animate.css for enhanced user experience
|
|
254
|
+
- **FontAwesome 6.x**: Comprehensive icon library
|
|
255
|
+
|
|
256
|
+
## Component Architecture
|
|
257
|
+
|
|
258
|
+
### Component Types
|
|
259
|
+
|
|
260
|
+
Based on storylang.json component definitions:
|
|
261
|
+
|
|
262
|
+
**Layout Components:**
|
|
263
|
+
|
|
264
|
+
- `table`, `figure`, `accordion`, `card`, `list-group`
|
|
265
|
+
- `navbar`, `nav`, `tab`, `breadcrumb`
|
|
266
|
+
|
|
267
|
+
**Interactive Components:**
|
|
268
|
+
|
|
269
|
+
- `button`, `button-group`, `dropdown`, `modal`
|
|
270
|
+
- `input-field`, `select`, `file-uploader`
|
|
271
|
+
- `pagination`, `popover`, `tooltip`
|
|
272
|
+
|
|
273
|
+
### Component Structure
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
// Component class
|
|
277
|
+
import Component from '@glimmer/component';
|
|
278
|
+
import { tracked } from '@glimmer/tracking';
|
|
279
|
+
import { action } from '@ember/object';
|
|
280
|
+
import { service } from '@ember/service';
|
|
281
|
+
|
|
282
|
+
export default class FileCardComponent extends Component {
|
|
283
|
+
@service store;
|
|
284
|
+
@tracked isSelected = false;
|
|
285
|
+
|
|
286
|
+
@action
|
|
287
|
+
toggleSelection() {
|
|
288
|
+
this.isSelected = !this.isSelected;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Template Patterns
|
|
294
|
+
|
|
295
|
+
```handlebars
|
|
296
|
+
<div
|
|
297
|
+
class='card {{if this.isSelected "border-primary"}}'
|
|
298
|
+
{{on 'click' this.toggleSelection}}
|
|
299
|
+
>
|
|
300
|
+
<div class='card-body'>
|
|
301
|
+
<h5 class='card-title'>{{@file.modules.title}}</h5>
|
|
302
|
+
<p class='card-text'>{{@file.modules.description}}</p>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Services Integration
|
|
308
|
+
|
|
309
|
+
### Built-in Services
|
|
310
|
+
|
|
311
|
+
- `store` - EmberData for CRUD operations
|
|
312
|
+
- `router` - Navigation and route management
|
|
313
|
+
- `types` - Automatic model generation
|
|
314
|
+
- `bootstrap` - Bootstrap component initialization
|
|
315
|
+
|
|
316
|
+
### Custom Services
|
|
317
|
+
|
|
318
|
+
Make services based on storylang.json service definitions:
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
// app/services/visualization-builder.js
|
|
322
|
+
import Service from '@ember/service';
|
|
323
|
+
import { tracked } from '@glimmer/tracking';
|
|
324
|
+
import { service } from '@ember/service';
|
|
325
|
+
|
|
326
|
+
export default class VisualizationBuilderService extends Service {
|
|
327
|
+
@service store;
|
|
328
|
+
@tracked supportedTypes = ['network', 'tree', 'sankey'];
|
|
329
|
+
|
|
330
|
+
buildVisualization(files, type, config) {
|
|
331
|
+
// Service logic implementation
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Route Generation
|
|
337
|
+
|
|
338
|
+
### Route Creation
|
|
339
|
+
|
|
340
|
+
Make routes based on storylang.json route definitions:
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
import Route from '@ember/routing/route';
|
|
344
|
+
import { service } from '@ember/service';
|
|
345
|
+
|
|
346
|
+
export default class FilesRoute extends Route {
|
|
347
|
+
@service store;
|
|
348
|
+
|
|
349
|
+
queryParams = {
|
|
350
|
+
page: { refreshModel: true },
|
|
351
|
+
search: { refreshModel: true },
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
model(params) {
|
|
355
|
+
return this.store.query('json_file', {
|
|
356
|
+
page: { offset: (params.page - 1) * 10, limit: 10 },
|
|
357
|
+
modules: params.search ? { title: params.search } : {},
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Helper System
|
|
364
|
+
|
|
365
|
+
### Global Helpers
|
|
366
|
+
|
|
367
|
+
Make helpers based on storylang.json helper requirements:
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
// app/helpers/format-date.js
|
|
371
|
+
import { helper } from '@ember/component/helper';
|
|
372
|
+
|
|
373
|
+
export default helper(function formatDate([date, format = 'short']) {
|
|
374
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
375
|
+
dateStyle: format,
|
|
376
|
+
}).format(new Date(date));
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Template Usage
|
|
381
|
+
|
|
382
|
+
```handlebars
|
|
383
|
+
<span class='text-muted'>
|
|
384
|
+
{{format-date @post.modules.created_date 'medium'}}
|
|
385
|
+
</span>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Modifier System
|
|
389
|
+
|
|
390
|
+
### DOM Interaction Modifiers
|
|
391
|
+
|
|
392
|
+
Make modifiers for specific DOM manipulation needs:
|
|
393
|
+
|
|
394
|
+
```javascript
|
|
395
|
+
// app/modifiers/tooltip.js
|
|
396
|
+
import { modifier } from 'ember-modifier';
|
|
397
|
+
import { Tooltip } from 'bootstrap';
|
|
398
|
+
|
|
399
|
+
export default modifier((element, [content]) => {
|
|
400
|
+
const tooltip = new bootstrap.Tooltip(element, {
|
|
401
|
+
title: content,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
return () => tooltip.dispose();
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Ember.js Reference Guide
|
|
409
|
+
|
|
410
|
+
### EmberData Patterns
|
|
411
|
+
|
|
412
|
+
Smart use of EmberData can significantly reduce size of the codebase. Make sure you take advantage of that.
|
|
413
|
+
|
|
414
|
+
EmberData operations always use a "modules" key for field access, except for `.id` and `.slug` properties. All field names from backend storage use underscore notation: `modules.any_field`.
|
|
415
|
+
|
|
416
|
+
**Universal Default Module:**
|
|
417
|
+
All objects include: `"content_privacy": "string | public, private, pending, draft"`
|
|
418
|
+
|
|
419
|
+
**Single Record Operations:**
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
// Find by ID or slug
|
|
423
|
+
this.store.findRecord('track', 30);
|
|
424
|
+
this.store.findRecord('track', 'some-slug-here');
|
|
425
|
+
|
|
426
|
+
// Access without network request (if already in store)
|
|
427
|
+
let post = this.store.peekRecord('post', 1);
|
|
428
|
+
|
|
429
|
+
// Usage pattern
|
|
430
|
+
this.store.findRecord('post', 1).then((post) => {
|
|
431
|
+
// Access: post.id, post.slug, post.modules.<field_name>
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Multiple Records:**
|
|
436
|
+
|
|
437
|
+
```javascript
|
|
438
|
+
this.store
|
|
439
|
+
.query('person', {
|
|
440
|
+
modules: { name: 'Peter', location: 'delhi' }, //results with AND
|
|
441
|
+
/*
|
|
442
|
+
filter: { name: 'Peter', location: 'delhi' } //results with OR
|
|
443
|
+
sort: "location,-age,name", //minus for descending order of that field, default is -id
|
|
444
|
+
page: { offset:0, limit:-1 }, //for pagination or smart uses, -1 means everything
|
|
445
|
+
ignore_ids: [10,14] //excludes these IDs from results
|
|
446
|
+
show_public_objects_only: false, //default = true, if set false results include content_privacy = drafts, private or pending
|
|
447
|
+
*/
|
|
448
|
+
})
|
|
449
|
+
.then((results) => {
|
|
450
|
+
// Process results
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Prefer using backend (this.store.query) for search, filter and sort, over front-end JS functions to achieve the same thing. Avoid using this.store.findAll altogether, use this.store.query with page: { offset:0, limit:-1 } instead.
|
|
455
|
+
|
|
456
|
+
**CRUD Operations:**
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
// Update
|
|
460
|
+
let post = await this.store.findRecord('post', 1);
|
|
461
|
+
post.modules.title = 'A new title';
|
|
462
|
+
await post.save(); // => PATCH request
|
|
463
|
+
|
|
464
|
+
// Delete
|
|
465
|
+
let post = this.store.peekRecord('post', 2);
|
|
466
|
+
post.destroyRecord(); // => DELETE request
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Helpers
|
|
470
|
+
|
|
471
|
+
Helper functions are JavaScript functions callable from Ember templates that perform computations or operations beyond basic template syntax, keeping templates clean while adding dynamic functionality.
|
|
472
|
+
|
|
473
|
+
**Local Helpers:**
|
|
474
|
+
|
|
475
|
+
- Defined as methods within component classes
|
|
476
|
+
- Scoped to specific component
|
|
477
|
+
- Called with `this.` prefix in templates
|
|
478
|
+
|
|
479
|
+
```javascript
|
|
480
|
+
// app/components/user-card.js
|
|
481
|
+
export default class UserCard extends Component {
|
|
482
|
+
formatName = (firstName, lastName) =>
|
|
483
|
+
`${firstName} ${lastName}`.toUpperCase();
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
```handlebars
|
|
488
|
+
<!-- app/components/user-card.hbs -->
|
|
489
|
+
<h2>{{this.formatName @user.modules.first_name @user.modules.last_name}}</h2>
|
|
27
490
|
```
|
|
28
|
-
|
|
491
|
+
|
|
492
|
+
**Global Helpers:**
|
|
493
|
+
|
|
494
|
+
- Defined in `app/helpers/` folder as separate files
|
|
495
|
+
- Available across all application templates
|
|
496
|
+
- Called directly by function name
|
|
497
|
+
|
|
498
|
+
```javascript
|
|
499
|
+
// app/helpers/format-currency.js
|
|
500
|
+
export default function formatCurrency(amount, currency = 'USD') {
|
|
501
|
+
return new Intl.NumberFormat('en-US', {
|
|
502
|
+
style: 'currency',
|
|
503
|
+
currency: currency,
|
|
504
|
+
}).format(amount);
|
|
505
|
+
}
|
|
29
506
|
```
|
|
30
|
-
|
|
31
|
-
|
|
507
|
+
|
|
508
|
+
```handlebars
|
|
509
|
+
<span>{{format-currency @item.modules.price 'EUR'}}</span>
|
|
32
510
|
```
|
|
33
|
-
|
|
511
|
+
|
|
512
|
+
**Helper Features:**
|
|
513
|
+
|
|
514
|
+
- Support positional arguments: `{{helper arg1 arg2}}`
|
|
515
|
+
- Support named arguments: `{{helper arg1 key=value}}`
|
|
516
|
+
- Can be nested: `{{outer-helper (inner-helper @value)}}`
|
|
517
|
+
- Built-in helpers available: `{{get}}`, `{{concat}}`, `{{let}}`, `{{array}}`, `{{hash}}`
|
|
518
|
+
|
|
519
|
+
**Usage Guidelines:**
|
|
520
|
+
|
|
521
|
+
- Local: Component-specific logic, simple transformations
|
|
522
|
+
- Global: Reusable functionality across multiple components (formatting, calculations)
|
|
523
|
+
|
|
524
|
+
### Component Architecture & Principle of Substitution
|
|
525
|
+
|
|
526
|
+
Ember components should be thought of as templates that re-execute from scratch whenever data changes. Write templates that produce correct output for any given input; Ember efficiently updates only what has changed.
|
|
527
|
+
|
|
528
|
+
**Template Patterns:**
|
|
529
|
+
|
|
530
|
+
```handlebars
|
|
531
|
+
<article title='{{@article.modules.title}}'>
|
|
532
|
+
<header><h1>{{@article.modules.title}}</h1></header>
|
|
533
|
+
<section>{{@article.modules.body}}</section>
|
|
534
|
+
</article>
|
|
34
535
|
```
|
|
35
|
-
|
|
536
|
+
|
|
537
|
+
**Dynamic Updates:**
|
|
538
|
+
|
|
539
|
+
- Text and Attributes: Use `{{}}` syntax for automatic DOM updates
|
|
540
|
+
- Conditional Logic: Use helpers for conditional attributes
|
|
541
|
+
|
|
542
|
+
```handlebars
|
|
543
|
+
<div class={{if @user.modules.is_admin 'superuser' 'standard'}}>
|
|
544
|
+
Welcome to my app.
|
|
545
|
+
</div>
|
|
36
546
|
```
|
|
37
|
-
|
|
547
|
+
|
|
548
|
+
**Event Handling:**
|
|
549
|
+
Use `{{on}}` element modifier for event handlers:
|
|
550
|
+
|
|
551
|
+
```handlebars
|
|
552
|
+
<button type='button' {{on 'click' this.increment}}>+</button>
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
```javascript
|
|
556
|
+
import Component from '@glimmer/component';
|
|
557
|
+
import { action } from '@ember/object';
|
|
558
|
+
import { tracked } from '@glimmer/tracking';
|
|
559
|
+
|
|
560
|
+
export default class CounterComponent extends Component {
|
|
561
|
+
@tracked count = 0;
|
|
562
|
+
|
|
563
|
+
@action
|
|
564
|
+
increment() {
|
|
565
|
+
this.count++;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Component Communication & Modifiers
|
|
571
|
+
|
|
572
|
+
**Design Pattern:**
|
|
573
|
+
|
|
574
|
+
1. Component manages state
|
|
575
|
+
2. Modifiers handle DOM interactions
|
|
576
|
+
3. Separation enables better reusability and testing
|
|
577
|
+
|
|
578
|
+
**Complex Interaction Example:**
|
|
579
|
+
|
|
580
|
+
```handlebars
|
|
581
|
+
<audio src={{@song.modules.src_url}} {{play-when this.isPlaying}} />
|
|
582
|
+
<button type='button' {{on 'click' this.play}}>Play</button>
|
|
583
|
+
<button type='button' {{on 'click' this.pause}}>Pause</button>
|
|
38
584
|
```
|
|
39
585
|
|
|
40
|
-
|
|
41
|
-
|
|
586
|
+
```javascript
|
|
587
|
+
// Component manages state
|
|
588
|
+
@tracked isPlaying = false;
|
|
42
589
|
|
|
43
|
-
|
|
590
|
+
@action
|
|
591
|
+
play() {
|
|
592
|
+
this.isPlaying = true;
|
|
593
|
+
}
|
|
594
|
+
```
|
|
44
595
|
|
|
596
|
+
```javascript
|
|
597
|
+
// Modifier handles DOM interaction
|
|
598
|
+
import { modifier } from 'ember-modifier';
|
|
45
599
|
|
|
46
|
-
|
|
47
|
-
|
|
600
|
+
export default modifier((element, [isPlaying]) => {
|
|
601
|
+
if (isPlaying) {
|
|
602
|
+
element.play();
|
|
603
|
+
} else {
|
|
604
|
+
element.pause();
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
```
|
|
48
608
|
|
|
49
|
-
|
|
609
|
+
**Modifier Forwarding:**
|
|
610
|
+
Modifiers applied to components pass through via `...attributes`:
|
|
50
611
|
|
|
612
|
+
```handlebars
|
|
613
|
+
<Tooltip {{custom-modifier}} />
|
|
614
|
+
<!-- Forwards to: -->
|
|
615
|
+
<div ...attributes>...</div>
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Services
|
|
619
|
+
|
|
620
|
+
Services are Ember objects that persist for the entire application duration, providing shared state or persistent connections across different parts of your app.
|
|
621
|
+
|
|
622
|
+
**Service Definition:**
|
|
623
|
+
|
|
624
|
+
```javascript
|
|
625
|
+
// app/services/shopping-cart.js
|
|
626
|
+
import { TrackedArray } from 'tracked-built-ins';
|
|
627
|
+
import Service from '@ember/service';
|
|
628
|
+
|
|
629
|
+
export default class ShoppingCartService extends Service {
|
|
630
|
+
items = new TrackedArray([]);
|
|
631
|
+
|
|
632
|
+
add(item) {
|
|
633
|
+
this.items.push(item);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
remove(item) {
|
|
637
|
+
this.items.splice(this.items.indexOf(item), 1);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
empty() {
|
|
641
|
+
this.items.splice(0, this.items.length);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
**Service Access:**
|
|
647
|
+
|
|
648
|
+
```javascript
|
|
649
|
+
import Component from '@glimmer/component';
|
|
650
|
+
import { service } from '@ember/service';
|
|
651
|
+
|
|
652
|
+
export default class CartContentsComponent extends Component {
|
|
653
|
+
// Loads service from: app/services/shopping-cart.js
|
|
654
|
+
@service shoppingCart;
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Usage Guidelines:**
|
|
659
|
+
|
|
660
|
+
- Use for application-wide state management
|
|
661
|
+
- Share functionality across multiple routes/components
|
|
662
|
+
- Maintain data that survives route transitions
|
|
663
|
+
|
|
664
|
+
## Code Generation Process
|
|
665
|
+
|
|
666
|
+
### File Upload Javascript Example
|
|
667
|
+
|
|
668
|
+
```javascript
|
|
669
|
+
import ENV from '<your-application-name>/config/environment';
|
|
670
|
+
|
|
671
|
+
@action
|
|
672
|
+
async uploadFile(file) {
|
|
673
|
+
try {
|
|
674
|
+
const response = await file.upload(ENV.TribeENV.API_URL + '/uploads.php');
|
|
675
|
+
response.json().then(async (data) => {
|
|
676
|
+
if (data.status == 'success') {
|
|
677
|
+
//data.file.name
|
|
678
|
+
//data.file.mime
|
|
679
|
+
//data.file.url
|
|
680
|
+
//if mime type is an image, following converted sizes are also available
|
|
681
|
+
//data.file.xl.url
|
|
682
|
+
//data.file.lg.url
|
|
683
|
+
//data.file.md.url
|
|
684
|
+
//data.file.sm.url
|
|
685
|
+
//data.file.xs.url
|
|
686
|
+
} else if (data.status == 'error') {
|
|
687
|
+
alert(data.error_message);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
} catch (error) {
|
|
691
|
+
file.state = 'aborted';
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### Installation Commands
|
|
697
|
+
|
|
698
|
+
```bash
|
|
699
|
+
# Write all installer.sh commands
|
|
700
|
+
ember g route files
|
|
701
|
+
ember g controller files
|
|
702
|
+
ember g component file-card -gc
|
|
703
|
+
ember g helper format-date
|
|
704
|
+
ember g modifier tooltip
|
|
705
|
+
ember g service visualization-builder
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Pre-approval Process
|
|
709
|
+
|
|
710
|
+
Before code generation, ember-tribe requests approval for additional packages:
|
|
711
|
+
|
|
712
|
+
```bash
|
|
713
|
+
# Example approval request
|
|
714
|
+
npm i chart.js
|
|
715
|
+
npm i lodash
|
|
716
|
+
ember install ember-table
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
## Application Structure
|
|
720
|
+
|
|
721
|
+
### Application Template
|
|
722
|
+
|
|
723
|
+
```handlebars
|
|
724
|
+
{{page-title 'Your Application Name'}}
|
|
725
|
+
{{outlet}}
|
|
726
|
+
<BasicDropdownWormhole />
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
## Best Practices
|
|
730
|
+
|
|
731
|
+
### Controller Minimization
|
|
732
|
+
|
|
733
|
+
- Keep controllers minimal - prefer component logic
|
|
734
|
+
- Use controllers only for query params and route-level actions
|
|
735
|
+
- Move business logic to services
|
|
736
|
+
|
|
737
|
+
### Animation Guidelines
|
|
738
|
+
|
|
739
|
+
- Use animate.css for enhanced UX
|
|
740
|
+
- Prefer subtle animations (fadeIn, slideIn)
|
|
741
|
+
- Avoid overwhelming users with excessive animation
|
|
742
|
+
|
|
743
|
+
### Data Flow
|
|
744
|
+
|
|
745
|
+
1. **Routes**: Fetch and prepare data
|
|
746
|
+
2. **Components**: Display and interact with data
|
|
747
|
+
3. **Services**: Handle business logic and state
|
|
748
|
+
4. **Helpers**: Transform data for display
|
|
749
|
+
|
|
750
|
+
### Performance Considerations
|
|
751
|
+
|
|
752
|
+
- Leverage EmberData caching with `peekRecord`
|
|
753
|
+
- Use backend filtering over frontend array manipulation
|
|
754
|
+
- Implement pagination for large datasets
|
|
755
|
+
- Minimize controller file count
|
|
756
|
+
|
|
757
|
+
## Integration Examples
|
|
758
|
+
|
|
759
|
+
### With Junction CMS
|
|
760
|
+
|
|
761
|
+
```javascript
|
|
762
|
+
// Automatic sync with Junction tracks
|
|
763
|
+
async beforeModel() {
|
|
764
|
+
await this.types.fetchAgain(); // Syncs with backend types
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### With External APIs
|
|
769
|
+
|
|
770
|
+
```javascript
|
|
771
|
+
// Service for external integrations
|
|
772
|
+
@service externalApi;
|
|
773
|
+
|
|
774
|
+
async model() {
|
|
775
|
+
const localData = await this.store.query('post', {});
|
|
776
|
+
const externalData = await this.externalApi.fetch('/posts');
|
|
777
|
+
return { local: localData, external: externalData };
|
|
778
|
+
}
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
## Troubleshooting
|
|
782
|
+
|
|
783
|
+
### Common Issues
|
|
784
|
+
|
|
785
|
+
1. **Model Not Found**: Ensure `types.fetchAgain()` completes in application route
|
|
786
|
+
2. **Module Access**: Remember to use `modules.field_name` for backend fields
|
|
787
|
+
3. **Bootstrap Components**: Initialize through modifier or service
|
|
788
|
+
4. **Animation Conflicts**: Check animate.css class conflicts
|
|
789
|
+
|
|
790
|
+
### Debug Commands
|
|
791
|
+
|
|
792
|
+
```javascript
|
|
793
|
+
// Check loaded models
|
|
794
|
+
this.store.peekAll('your-model-name');
|
|
795
|
+
|
|
796
|
+
// Verify service registration
|
|
797
|
+
this.owner.lookup('service:your-service');
|
|
798
|
+
|
|
799
|
+
// Route debugging
|
|
800
|
+
console.log(this.router.currentRouteName);
|
|
801
|
+
```
|
|
51
802
|
|
|
52
|
-
License
|
|
53
|
-
------------------------------------------------------------------------------
|
|
803
|
+
# License
|
|
54
804
|
|
|
55
|
-
This project is licensed under the [
|
|
805
|
+
This project is licensed under the [GNU GPL v3 License](LICENSE.md).
|