Skip to content

Instantly share code, notes, and snippets.

@huyphamily
Last active July 20, 2022 19:14
Show Gist options
  • Save huyphamily/5852d86e6b91f2764eda057dd05cd14f to your computer and use it in GitHub Desktop.
Save huyphamily/5852d86e6b91f2764eda057dd05cd14f to your computer and use it in GitHub Desktop.
XSTATE POC
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class extends Component {
@service('job-state') jobState;
}
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from "@ember/object";
export default class extends Component {
@service('job-state') jobState;
get model() {
return this.jobState.context.jobTitle;
}
get formError() {
return JSON.stringify(this.model.formError);
}
@action
onChange(event) {
this.jobState.send({
type: 'CHANGE',
key: event.target.name,
value: event.target.value,
});
}
}
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class extends Component {
@service('job-state') jobState;
}
import Controller from '@ember/controller';
export default class ApplicationController extends Controller {
appName = 'Ember Twiddle';
}
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from "@ember/object";
import { resolve, reject } from 'rsvp';
const { Machine, interpret, assign, send } = XState;
const jobTitleState = {
id: 'jobTitle',
context: {},
initial: 'editing',
states: {
editing: {
on: {
CHANGE: {
target: '',
actions: assign({
jobTitle: (context, event) => ({
...context.jobTitle,
[event.key]: event.value,
}),
}),
},
NEXT: 'validating',
},
},
validating: {
invoke: {
src: (context, event) => {
const error = {
isInvalid: false,
title: false,
location: false,
company: false,
};
if (!context.jobTitle.title) {
error.title = true;
error.isInvalid = true;
}
if (!context.jobTitle.location) {
error.location = true;
error.isInvalid = true;
}
if (!context.jobTitle.company) {
error.company = true;
error.isInvalid = true;
}
if (error.isInvalid) {
return reject(error);
}
return resolve(error);
},
onDone: {
target: 'submitting',
actions: [
assign({
jobTitle: (context, event, data) => ({
...context.jobTitle,
formError: event.data,
error: false,
}),
}),
],
},
onError: {
target: 'editing',
// you have context to set specific errors
actions: assign({
jobTitle: (context, event) => ({
...context.jobTitle,
formError: event.data,
error: true,
}),
}),
},
},
},
submitting: {
invoke: {
// post call here and pass context and it handles promises
src: (context, event) => resolve(),
onDone: {
actions: send('NEXT'),
},
onError: {
target: 'editing',
// you have context to set specific errors
actions: assign({
jobTitle: (context, event) => ({
...context.jobTitle,
error: true,
}),
}),
},
},
},
},
};
const jobStateMachine = Machine({
id: 'jobState',
context: {
jobTitle: {
error: false,
}
},
initial: 'jobTitle',
states: {
jobTitle: {
on: {
NEXT: {
target: 'jobMatch',
cond: (context, event) => !context.jobTitle.error,
},
},
...jobTitleState,
},
jobMatch: {
on: {
SELECT_CLAIMABLE: 'jobClaim',
SELECT_SHAREABLE: 'jobShare',
CREATE_JOB: 'jobDescription',
BACK: 'jobTitle',
}
},
jobClaim: {
on: {
BACK: 'jobMatch',
}
},
jobShare: {
on: {
BACK: 'jobMatch',
}
},
jobDescription: {
on: {
BACK: 'jobMatch',
}
},
hist: { type: 'history' },
}
});
const machine = interpret(jobStateMachine)
.start();
export default class JobState extends Service {
@tracked machine = machine;
constructor() {
super(...arguments);
machine.onTransition(() => {
this.machine = machine;
});
}
get currentState() {
return this.machine.state.toStrings();
}
get parentState() {
return this.machine.state.toStrings()[0];
}
get context() {
return this.machine.state.context;
}
get hasNext() {
return this.machine.state.nextEvents.includes('NEXT');
}
get hasBack() {
return this.machine.state.nextEvents.includes('BACK');
}
@action
send(event) {
this.machine.send(event);
}
@action
next() {
this.machine.send('NEXT');
}
@action
back() {
this.machine.send('BACK');
}
};
<JobsContainer/>
<JobsFooter/>
<button {{on "click" (fn this.jobState.send "CREATE_JOB")}}>create new job</button>
<ul>
<li><button {{on "click" (fn this.jobState.send "SELECT_SHAREABLE")}}>A shareable job</button></li>
<li><button {{on "click" (fn this.jobState.send "SELECT_CLAIMABLE")}}>A claimable job</button></li>
</ul>
<label>Job Title</label>
<input
name="title"
value="{{this.model.title}}"
{{on "input" this.onChange}}
style="display:block"
>
<label>Location</label>
<input
name="location"
value="{{this.model.location}}"
{{on "input" this.onChange}}
style="display:block"
>
<label>Company</label>
<input
name="company"
value="{{this.model.company}}"
{{on "input" this.onChange}}
style="display:block"
>
<div>
context:
title: {{this.model.title}}
location: {{this.model.location}}
company: {{this.model.company}}
error: {{this.model.error}}
formError: {{this.formError}}
</div>
<h4>Current State: {{this.jobState.currentState}}</h4>
<div>
{{#if (eq this.jobState.parentState "jobTitle")}}
<JobTitle/>
{{else if (eq this.jobState.parentState "jobMatch")}}
<JobMatch/>
{{/if}}
</div>
{
"version": "0.17.1",
"EmberENV": {
"FEATURES": {},
"_TEMPLATE_ONLY_GLIMMER_COMPONENTS": false,
"_APPLICATION_TEMPLATE_WRAPPER": true,
"_JQUERY_INTEGRATION": true
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.js",
"ember": "3.18.1",
"ember-template-compiler": "3.18.1",
"ember-testing": "3.18.1",
"xstate": "https://unpkg.com/xstate@4/dist/xstate.js"
},
"addons": {
"@glimmer/component": "1.0.0",
"ember-truth-helpers": "3.0.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment