Last active
September 28, 2021 01:15
-
-
Save zaru/0b294cea3a55687c3c8e0b5df070d488 to your computer and use it in GitHub Desktop.
Stimulus example ( switch tabs / custom input / form sync value / editable label / form validator )
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
sleep(1); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
sleep(1); | |
http_response_code(500); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://unpkg.com/stimulus/dist/stimulus.umd.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script> | |
<link href="style.css" media="all" rel="stylesheet"> | |
<script> | |
(() => { | |
_.mixin({ | |
memoizeDebounce: function(func, wait=0) { | |
var mem = _.memoize(function() { | |
return _.debounce(func, wait) | |
}); | |
return function(){mem.apply(this, arguments).apply(this, arguments)} | |
} | |
}); | |
const application = Stimulus.Application.start(); | |
application.register('validator', class extends Stimulus.Controller { | |
static targets = ['fields', 'errors', 'submit', 'dependentFields']; | |
static classes = ['error']; | |
errors = {}; | |
initialize() { | |
this.validate = _.memoizeDebounce(this.validate, 250); | |
} | |
connect() { | |
this.fieldsTargets.forEach((field) => { | |
const error = this.checkRule(field); | |
if (error) { | |
this.disableSubmit(); | |
} | |
}); | |
this.checkDependsOn(); | |
this.errorsTargets.forEach((error) => { | |
error.style.display = 'none'; | |
}); | |
} | |
async validate(event) { | |
const field = event.target; | |
const useServerValidation = !!field.dataset.validateEndpoint; | |
let error = useServerValidation ? await this.checkRuleByServer(event.target, true) : ''; | |
if (!error) { | |
error = this.checkRule(event.target, true); | |
} | |
if (error) { | |
this.errors[field.name] = error; | |
} else { | |
delete this.errors[field.name]; | |
} | |
const errorElement = this.errorsTargets.find((error) => error.dataset.fieldName === event.target.name); | |
if (error) { | |
errorElement.textContent = error; | |
errorElement.style.display = 'inline-block'; | |
this.disableSubmit(); | |
} else { | |
errorElement.textContent = ''; | |
errorElement.style.display = 'none'; | |
if (Object.keys(this.errors).length === 0) { | |
this.enableSubmit(); | |
} | |
} | |
this.checkDependsOn(); | |
} | |
// ๅๅใฎ checkbox ๅฟ ้ ใใงใใฏใใใใใใซใๆไฝ1ใคใงใใใงใใฏใใคใใฐไปใฎ checkbox ใฎๅฟ ้ ็ขบ่ชใใใชใใใใซใใ | |
syncCheckbox(event) { | |
const name = event.currentTarget.name; | |
const checkboxGroups = this.fieldsTargets.filter((field) => field.name === name); | |
const checked = checkboxGroups.find((checkbox) => checkbox.checked); | |
if (checked) { | |
checkboxGroups.forEach((checkbox) => { | |
checkbox.required = false; | |
}); | |
} else { | |
checkboxGroups.forEach((checkbox) => { | |
checkbox.required = true; | |
}); | |
} | |
} | |
// ใxxx ใใใงใใฏใใใฆใใใๅฟ ้ ้ ็ฎใซใชใใใจใใฃใไพๅญๆง้ ใฎๅถๅพกใ่กใ | |
// ็พๆ็นใงใฏไพๅญๅ ใ type="radio" ใฎใฟๅฏพๅฟ | |
checkDependsOn() { | |
this.dependentFieldsTargets.forEach((field) => { | |
const [dependsOnFieldName, value] = field.dataset.dependsOn.split('#'); | |
const dependsOnField = this.fieldsTargets.find((field) => field.name === dependsOnFieldName && field.checked); | |
if (dependsOnField.value === value) { | |
field.required = true; | |
const error = this.checkRule(field); | |
if (error) { | |
this.disableSubmit(); | |
} | |
} else { | |
field.required = false; | |
delete this.errors[field.name]; | |
if (Object.keys(this.errors).length === 0) { | |
this.enableSubmit(); | |
} | |
} | |
}); | |
} | |
checkRule(field, showError = false) { | |
const valid = field.checkValidity(); | |
if (showError && !valid) { | |
field.classList.add(this.errorClass); | |
} else { | |
field.classList.remove(this.errorClass); | |
} | |
return field.validationMessage; | |
} | |
async checkRuleByServer(field, showError = false) { | |
const endpoint = field.dataset.validateEndpoint; | |
const data = { | |
[field.name]: field.value | |
}; | |
const result = await fetch(endpoint,{ | |
method: 'POST', | |
mode: 'cors', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(data) | |
}); | |
const json = await result.json(); | |
if (showError && json.result !== 'ok') { | |
field.classList.add(this.errorClass); | |
} else { | |
field.classList.remove(this.errorClass); | |
} | |
return json.error_message; | |
} | |
disableSubmit() { | |
this.submitTarget.disabled = true; | |
} | |
enableSubmit() { | |
this.submitTarget.disabled = false; | |
} | |
}); | |
})(); | |
</script> | |
</head> | |
<body> | |
<h1>Form validation</h1> | |
<form data-controller="validator" | |
data-validator-error-class="error"> | |
<dl> | |
<dt>name (required)</dt> | |
<dd> | |
<input type="text" | |
name="name" | |
data-validator-target="fields" | |
data-action="keyup->validator#validate" | |
required> | |
<span data-validator-target="errors" data-field-name="name"></span> | |
</dd> | |
<dt>number (required / between 1 and 10)</dt> | |
<dd> | |
<input type="number" | |
name="number" | |
data-validator-target="fields" | |
data-action="keyup->validator#validate" | |
min="1" | |
max="10" | |
required> | |
<span data-validator-target="errors" data-field-name="number"></span> | |
</dd> | |
<dt>email (required / email format)</dt> | |
<dd> | |
<input type="email" | |
name="email" | |
data-validator-target="fields" | |
data-action="keyup->validator#validate" | |
required> | |
<span data-validator-target="errors" data-field-name="email"></span> | |
</dd> | |
<dt>foo,bar,baz (required / pattern /^(foo|bar|baz)$/)</dt> | |
<dd> | |
<input type="text" | |
name="foobarbaz" | |
data-validator-target="fields" | |
data-action="keyup->validator#validate" | |
pattern="^(foo|bar|baz)$" | |
required> | |
<span data-validator-target="errors" data-field-name="foobarbaz"></span> | |
</dd> | |
<dt>selector</dt> | |
<dd> | |
<select name="select" | |
data-validator-target="fields" | |
data-action="change->validator#validate" | |
required> | |
<option value=""></option> | |
<option value="foo">foo</option> | |
<option value="bar">bar</option> | |
<option value="baz">baz</option> | |
</select> | |
<span data-validator-target="errors" data-field-name="select"></span> | |
</dd> | |
<dt>category (required)</dt> | |
<dd> | |
<label><input type="checkbox" name="category[]" value="foo" | |
data-validator-target="fields" | |
data-action="change->validator#syncCheckbox change->validator#validate" | |
required> foo</label> | |
<label><input type="checkbox" name="category[]" value="bar" | |
data-validator-target="fields" | |
data-action="change->validator#syncCheckbox change->validator#validate" | |
required> bar</label> | |
<label><input type="checkbox" name="category[]" value="baz" | |
data-validator-target="fields" | |
data-action="change->validator#syncCheckbox change->validator#validate" | |
required> baz</label> | |
<span data-validator-target="errors" data-field-name="category[]"></span> | |
</dd> | |
<dt>radio (required)</dt> | |
<dd> | |
<label><input type="radio" name="radio" value="foo" | |
data-validator-target="fields" | |
data-action="change->validator#validate" | |
required>foo</label> | |
<label><input type="radio" name="radio" value="bar" | |
data-validator-target="fields" | |
data-action="change->validator#validate" | |
required>bar</label> | |
<label><input type="radio" name="radio" value="baz" | |
data-validator-target="fields" | |
data-action="change->validator#validate" | |
required>baz</label> | |
<span data-validator-target="errors" data-field-name="radio"></span> | |
</dd> | |
<dt>input (server side validation / valid value -> zaru)</dt> | |
<dd> | |
<input type="text" name="serverside" | |
data-validator-target="fields" | |
data-action="keyup->validator#validate" | |
data-validate-endpoint="validation.php" | |
required> | |
<span data-validator-target="errors" data-field-name="serverside"></span> | |
</dd> | |
</dl> | |
<input type="submit" value="post" data-validator-target="submit"> | |
</form> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://unpkg.com/stimulus/dist/stimulus.umd.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script> | |
<link href="style.css" media="all" rel="stylesheet"> | |
<script> | |
(() => { | |
_.mixin({ | |
memoizeDebounce: function(func, wait=0, options={}) { | |
var mem = _.memoize(function() { | |
return _.debounce(func, wait, options) | |
}, options.resolver); | |
return function(){mem.apply(this, arguments).apply(this, arguments)} | |
} | |
}); | |
const application = Stimulus.Application.start(); | |
application.register('switch-tab', class extends Stimulus.Controller { | |
static targets = ['tabContents', 'tabs']; | |
static classes = ['active']; | |
connect() { | |
this.tabContentsTargets.forEach((content) => { | |
if (content.dataset.tabDefault === 'true') { | |
const tabName = content.dataset.tab; | |
const tab = this.tabsTargets.find((tab) => tab.dataset.tab === tabName); | |
tab.classList.add(this.activeClass); | |
content.style.display = 'block'; | |
} else { | |
content.style.display = 'none'; | |
} | |
}); | |
} | |
switch(event) { | |
const button = event.currentTarget; | |
const targetTab = button.dataset.tab; | |
this.tabsTargets.forEach((tab) => { | |
tab.classList.remove(this.activeClass); | |
}); | |
button.classList.add(this.activeClass); | |
this.tabContentsTargets.forEach((content) => { | |
if (content.dataset.tab === targetTab) { | |
content.style.display = 'block'; | |
} else { | |
content.style.display = 'none'; | |
} | |
}); | |
} | |
}); | |
application.register('custom-input', class extends Stimulus.Controller { | |
static targets = ['select', 'input']; | |
connect() { | |
this.toggleInputParts(); | |
this.setValue(); | |
} | |
selectValue(event) { | |
this.toggleInputParts(); | |
this.setValue(); | |
} | |
toggleInputParts() { | |
if (this.selectTarget.value) { | |
this.inputTarget.style.display = 'none'; | |
} else { | |
this.inputTarget.style.display = 'inline-block'; | |
} | |
} | |
setValue() { | |
this.inputTarget.value = this.selectTarget.value; | |
this.inputTarget.dispatchEvent(new CustomEvent('change', { bubbles: true })); | |
} | |
}); | |
application.register('sync-data', class extends Stimulus.Controller { | |
static values = { | |
endpoint: String | |
}; | |
static classes = ['loading', 'success', 'fail']; | |
async sync(event) { | |
this.resetClass(); | |
const input = event.currentTarget; | |
const data = { | |
[input.name]: input.value | |
} | |
const result = await fetch(this.endpointValue, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(data) | |
}); | |
if (result.status === 200) { | |
this.setSuccessClass(); | |
} else { | |
this.setFailClass(); | |
} | |
} | |
resetClass() { | |
this.element.classList.remove(this.successClass); | |
this.element.classList.remove(this.failClass); | |
this.element.classList.add(this.loadingClass); | |
} | |
setSuccessClass() { | |
this.element.classList.remove(this.loadingClass); | |
this.element.classList.add(this.successClass); | |
} | |
setFailClass() { | |
this.element.classList.remove(this.loadingClass); | |
this.element.classList.add(this.failClass); | |
} | |
}); | |
application.register('editable-label', class extends Stimulus.Controller { | |
static targets = ['label', 'field', 'actions']; | |
connect() { | |
this.fieldTarget.style.display = 'none'; | |
this.labelTarget.innerHTML = this.fieldTarget.value; | |
this.showLabel(); | |
} | |
edit() { | |
this.showField(); | |
} | |
apply() { | |
this.labelTarget.innerHTML = this.fieldTarget.value; | |
this.showLabel(); | |
} | |
cancel() { | |
this.fieldTarget.innerHTML = this.labelTarget.value; | |
this.showLabel(); | |
} | |
showLabel() { | |
this.hideActions(); | |
this.fieldTarget.style.display = 'none'; | |
this.labelTarget.style.display = 'inline-block'; | |
} | |
showField() { | |
this.showActions(); | |
this.fieldTarget.style.display = 'inline-block'; | |
this.labelTarget.style.display = 'none'; | |
} | |
showActions() { | |
this.actionsTarget.style.display = 'block'; | |
} | |
hideActions() { | |
this.actionsTarget.style.display = 'none'; | |
} | |
}); | |
application.register('validator', class extends Stimulus.Controller { | |
static targets = ['fields', 'errors', 'submit']; | |
static classes = ['error']; | |
initialize() { | |
this.validate = _.memoizeDebounce(this.validate, 250) | |
} | |
connect() { | |
this.fieldsTargets.forEach((field) => { | |
const error = this.checkRule(field) | |
if (error) { | |
this.disableSubmit(); | |
} | |
}); | |
this.errorsTargets.forEach((error) => { | |
error.style.display = 'none'; | |
}); | |
} | |
validate(event) { | |
const error = this.checkRule(event.target, true); | |
const errorElement = this.errorsTargets.find((error) => error.dataset.fieldName === event.target.name); | |
if (error) { | |
errorElement.textContent = error; | |
errorElement.style.display = 'inline-block'; | |
this.disableSubmit(); | |
} else { | |
errorElement.textContent = ''; | |
errorElement.style.display = 'none'; | |
const otherError = this.fieldsTargets.find((field) => this.checkRule(field)); | |
if (!otherError) { | |
this.enableSubmit(); | |
} | |
} | |
} | |
syncCheckbox(event) { | |
const name = event.currentTarget.name; | |
const checkboxGroups = this.fieldsTargets.filter((field) => field.name === name); | |
const checked = checkboxGroups.find((checkbox) => checkbox.checked); | |
if (checked) { | |
checkboxGroups.forEach((checkbox) => { | |
checkbox.required = false; | |
}) | |
} else { | |
checkboxGroups.forEach((checkbox) => { | |
checkbox.required = true; | |
}) | |
} | |
} | |
checkRule(field, showError = false) { | |
const valid = field.checkValidity(); | |
if (showError && !valid) { | |
field.classList.add(this.errorClass); | |
} else { | |
field.classList.remove(this.errorClass); | |
} | |
return field.validationMessage; | |
} | |
disableSubmit() { | |
this.submitTarget.disabled = true; | |
} | |
enableSubmit() { | |
this.submitTarget.disabled = false; | |
} | |
showError() { | |
console.log('showError'); | |
} | |
}); | |
})(); | |
</script> | |
</head> | |
<body> | |
<h1>Stimulus example</h1> | |
<h2>Switch tabs</h2> | |
<div data-controller="switch-tab" data-switch-tab-active-class="active"> | |
<div class="tabs"> | |
<button type="button" data-switch-tab-target="tabs" data-tab="tab1" data-action="click->switch-tab#switch">tab1</button> | |
<button type="button" data-switch-tab-target="tabs" data-tab="tab2" data-action="click->switch-tab#switch">tab2</button> | |
<button type="button" data-switch-tab-target="tabs" data-tab="tab3" data-action="click->switch-tab#switch">tab3</button> | |
</div> | |
<div class="tab-contents" data-switch-tab-target="tabContents" data-tab="tab1" data-tab-default="true"> | |
tab1 content | |
<div data-controller="switch-tab" data-switch-tab-active-class="active"> | |
<h3>Nested tabs</h3> | |
<div class="tabs"> | |
<button type="button" data-switch-tab-target="tabs" data-tab="nested-tab1" data-action="click->switch-tab#switch">nested tab1</button> | |
<button type="button" data-switch-tab-target="tabs" data-tab="nested-tab2" data-action="click->switch-tab#switch">nested tab2</button> | |
</div> | |
<div class="tab-contents" data-switch-tab-target="tabContents" data-tab="nested-tab1" data-tab-default="true"> | |
nested tab1 content | |
</div> | |
<div class="tab-contents" data-switch-tab-target="tabContents" data-tab="nested-tab2"> | |
nested tab2 content | |
</div> | |
</div> | |
</div> | |
<div class="tab-contents" data-switch-tab-target="tabContents" data-tab="tab2"> | |
tab2 content | |
</div> | |
<div class="tab-contents" data-switch-tab-target="tabContents" data-tab="tab3"> | |
tab3 content | |
</div> | |
</div> | |
<h2>Custom input</h2> | |
<div data-controller="custom-input"> | |
<select data-custom-input-target="select" data-action="change->custom-input#selectValue"> | |
<option value="30">30</option> | |
<option value="60">60</option> | |
<option value="90">90</option> | |
<option value="">custom</option> | |
</select> | |
<input type="text" data-custom-input-target="input"> | |
</div> | |
<h2>Data sync</h2> | |
<h3>Success</h3> | |
<div data-controller="sync-data" | |
data-sync-data-endpoint-value="dummy_endpoint_200.php" | |
data-sync-data-loading-class="sync--busy" | |
data-sync-data-fail-class="sync--fail" | |
data-sync-data-success-class="sync--success" | |
> | |
<input type="text" name="name" data-action="blur->sync-data#sync"> | |
</div> | |
<h3>Fail</h3> | |
<div data-controller="sync-data" | |
data-sync-data-endpoint-value="dummy_endpoint_500.php" | |
data-sync-data-loading-class="sync--busy" | |
data-sync-data-fail-class="sync--fail" | |
data-sync-data-success-class="sync--success" | |
> | |
<input type="text" name="name" data-action="blur->sync-data#sync"> | |
</div> | |
<h3>Multiple fields</h3> | |
<div data-controller="sync-data" | |
data-sync-data-endpoint-value="dummy_endpoint_200.php" | |
data-sync-data-loading-class="sync--busy" | |
data-sync-data-fail-class="sync--fail" | |
data-sync-data-success-class="sync--success" | |
> | |
<input type="text" name="name1" data-action="blur->sync-data#sync"> | |
<input type="text" name="name2" data-action="blur->sync-data#sync"> | |
</div> | |
<h2>Editable label</h2> | |
<div class="editable__label__form" data-controller="editable-label"> | |
<span class="editable__label" data-editable-label-target="label" data-action="click->editable-label#edit"></span> | |
<input class="editable__field" data-editable-label-target="field" type="text" value="init value"> | |
<div class="editable__actions" data-editable-label-target="actions"> | |
<button type="button" data-action="click->editable-label#apply">โ </button> | |
<button type="button" data-action="click->editable-label#cancel">โ</button> | |
</div> | |
</div> | |
<h2>Form validation</h2> | |
<form data-controller="validator" | |
data-validator-error-class="error"> | |
<dl> | |
<dt>name (required)</dt> | |
<dd> | |
<input type="text" | |
name="name" | |
data-validator-target="fields" | |
data-action="keyup->validator#validate invalid->validator#showError" | |
required> | |
<span data-validator-target="errors" data-field-name="name"></span> | |
</dd> | |
<dt>number (required / between 1 and 10)</dt> | |
<dd> | |
<input type="number" | |
name="number" | |
data-validator-target="fields" | |
data-action="keyup->validator#validate" | |
min="1" | |
max="10" | |
required> | |
<span data-validator-target="errors" data-field-name="number"></span> | |
</dd> | |
<dt>email (required / email format)</dt> | |
<dd> | |
<input type="email" | |
name="email" | |
data-validator-target="fields" | |
data-action="keyup->validator#validate" | |
required> | |
<span data-validator-target="errors" data-field-name="email"></span> | |
</dd> | |
<dt>foo,bar,baz (required / pattern /^(foo|bar|baz)$/)</dt> | |
<dd> | |
<input type="text" | |
name="foobarbaz" | |
data-validator-target="fields" | |
data-action="keyup->validator#validate" | |
pattern="^(foo|bar|baz)$" | |
required> | |
<span data-validator-target="errors" data-field-name="foobarbaz"></span> | |
</dd> | |
<dt>selector</dt> | |
<dd> | |
<select name="select" | |
data-validator-target="fields" | |
data-action="change->validator#validate" | |
required> | |
<option value=""></option> | |
<option value="foo">foo</option> | |
<option value="bar">bar</option> | |
<option value="baz">baz</option> | |
</select> | |
<span data-validator-target="errors" data-field-name="select"></span> | |
</dd> | |
<dt>category (required)</dt> | |
<dd> | |
<label><input type="checkbox" name="category[]" value="foo" | |
data-validator-target="fields" | |
data-action="change->validator#syncCheckbox change->validator#validate" | |
required> foo</label> | |
<label><input type="checkbox" name="category[]" value="bar" | |
data-validator-target="fields" | |
data-action="change->validator#syncCheckbox change->validator#validate" | |
required> bar</label> | |
<label><input type="checkbox" name="category[]" value="baz" | |
data-validator-target="fields" | |
data-action="change->validator#syncCheckbox change->validator#validate" | |
required> baz</label> | |
<span data-validator-target="errors" data-field-name="category[]"></span> | |
</dd> | |
<dt>radio (required)</dt> | |
<dd> | |
<label><input type="radio" name="radio" value="foo" | |
data-validator-target="fields" | |
data-action="change->validator#validate" | |
required>foo</label> | |
<label><input type="radio" name="radio" value="bar" | |
data-validator-target="fields" | |
data-action="change->validator#validate" | |
required>bar</label> | |
<label><input type="radio" name="radio" value="baz" | |
data-validator-target="fields" | |
data-action="change->validator#validate" | |
required>baz</label> | |
<span data-validator-target="errors" data-field-name="radio"></span> | |
</dd> | |
</dl> | |
<input type="submit" value="post" data-validator-target="submit"> | |
</form> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html, body { | |
color: #333; | |
line-height: 1.6; | |
font-size: 16px; | |
} | |
select, input[type=text], input[type=email], input[type=number] { | |
font-size: 16px; | |
padding: 5px 15px; | |
} | |
select.error, input[type=text].error, input[type=email].error, input[type=number].error { | |
border-color: red; | |
} | |
button { | |
padding: 5px 15px; | |
font-size: 18px; | |
} | |
button.active { | |
border-color: aqua; | |
} | |
.tab-contents { | |
margin: 20px auto; | |
padding: 20px; | |
border: 1px solid #999; | |
} | |
.sync--busy:after { | |
content: '๐'; | |
display: inline-block; | |
line-height: 1; | |
width: 16px; | |
height: 16px; | |
animation: rotate 1s linear infinite; | |
} | |
.sync--success:after { | |
content: 'โ '; | |
} | |
.sync--fail:after { | |
content: 'โ'; | |
} | |
@keyframes rotate { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.editable__label__form { | |
display: flex; | |
} | |
.editable__label, .editable__field { | |
border-width: 1px; | |
display: inline-block; | |
box-sizing: border-box; | |
margin: 0; | |
padding: 5px 10px; | |
font-size: 16px; | |
font-weight: normal; | |
} | |
.editable__label { | |
cursor: pointer; | |
} | |
.editable__label:after { | |
content: '๐'; | |
margin-left: 5px; | |
cursor: pointer; | |
} | |
.editable__actions button { | |
background: none; | |
border: none; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
header("Content-Type: application/json; charset=utf-8"); | |
$json = json_decode(file_get_contents("php://input"), true); | |
if ($json['serverside'] == 'zaru') { | |
$output = [ | |
'result' => 'ok', | |
'error_message' => '' | |
]; | |
} else { | |
$output = [ | |
'result' => 'ng', | |
'error_message' => 'ใตใผใใตใคใใฎใใงใใฏใๅคฑๆใใพใใ' | |
]; | |
} | |
echo json_encode($output); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment