css Ember.js模型生成器 - 将JSON对象反向工程为Ember数据模型
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了css Ember.js模型生成器 - 将JSON对象反向工程为Ember数据模型相关的知识,希望对你有一定的参考价值。
(function (Ember, undefined) {
'use strict';
/* *** APPLICATION *** */
// initalize Ember application:
let App = Ember.Application.create();
/* *** ROUTER *** */
// initialize Ember router:
App.Router.map(function() {
this.route('models');
});
// set location mode to none:
App.Router.reopen({
location: 'none'
});
/* ***ROUTES *** */
App.IndexRoute = Ember.Route.extend({
/* SERVICES */
data: Ember.inject.service(),
/* HOOKS */
model() {
return {
links: this.get('data').getLinks(1),
specs: this.get('data').getSpecs()
}
},
/* ACTIONS */
actions: {
didTransition() {
// fix textarea height:
Ember.run.scheduleOnce('afterRender', this, function () {
$('#json-object').trigger('autoresize');
Ember.$('select').material_select();
})
}
}
});
App.ModelsRoute = Ember.Route.extend({
/* SERVICES */
data: Ember.inject.service(),
generator: Ember.inject.service(),
/* HOOKS */
model() {
return {
links: this.get('data').getLinks(2),
json: {
jsonObject: this.controllerFor('index').get('jsonObject'),
jsonSpecification: this.controllerFor('index').get('jsonSpecification'),
rootModelName: this.controllerFor('index').get('rootModelName')
},
models: this.get('generator').generateModels(
this.controllerFor('index').get('jsonObject'),
this.controllerFor('index').get('jsonSpecification'),
this.controllerFor('index').get('rootModelName').toLowerCase(),
this.controllerFor('index').get('useFragments')
)
}
},
/* ACTIONS */
actions: {
didTransition() {
// Prettify javascript:
Ember.run.scheduleOnce('afterRender', function () {
Ember.$('pre code').each(function(i, block) {
hljs.highlightBlock(block);
});
})
}
}
})
/* *** CONTROLLERS *** */
App.ApplicationController = Ember.Controller.extend({
actions: {
showCode() {
top.$('#preview .block-menu span:first').click().click();
event.target.hidden = true;
}
}
});
App.IndexController = Ember.Controller.extend({
/* PROPERTIES*/
jsonString: '',
jsonObject: null,
jsonValid: false,
jsonSpecification: '',
rootModelName: '',
useFragments: false,
jsonStringChanged: Ember.observer('jsonString', function () {
// parse JSON object:
this.set('jsonString', this.get('jsonString').replace('\n', '').replace(/\s{2,}/, ' '));
Ember.run.debounce(this, this.get('actions').validateJSON, 2000);
}),
jsonSpecificationChanged: Ember.observer('jsonSpecification', function() {
// guess JSON specification if its valid:
Ember.run.next(() => {
Ember.$('select').material_select();
});
}),
buttonEnabled: Ember.computed('jsonValid', 'jsonSpecification', 'rootModelName', function () {
return this.get('jsonValid') &&
this.get('jsonSpecification') !== '' &&
(this.get('jsonSpecification') !== 'freestyle' ||
(this.get('jsonSpecification') === 'freestyle' && this.get('rootModelName') !== ''));
}),
buttonNotEnabled: Ember.computed.not('buttonEnabled'),
/* ACTIONS */
actions: {
didTransition() {
// fix textarea height:
Ember.run.scheduleOnce('afterRender', this, function () {
$('#json-object').trigger('autoresize');
Ember.$('select').material_select();
})
},
/**
* Clears form and fixes textarea height
*/
resetForm() {
this.setProperties({
jsonString: '',
jsonObject: null,
jsonValid: false,
jsonSpecification: '',
rootModelName: '',
useFragments: false
});
Ember.run.next(function () {
$('#json-object').trigger('autoresize');
});
},
/**
* Toggels Ember Data Fragment flag
*/
toggleFragments() {
this.toggleProperty('useFragments');
},
/**
* Validates JSON structure and if is valid - tries to find JSON specification
* @param {string} strJSON JSON object content
*/
validateJSON() {
try {
this.set('jsonObject', JSON.parse(this.get('jsonString')));
this.set('jsonValid', true);
this.send('guessSpecification');
} catch (e) {
Materialize.toast('JSON object is not valid', 4000);
this.set('jsonValid', false);
}
},
/**
* Guesses given JSON object specification
*/
guessSpecification() {
// is it JSON API?
if (this.get('jsonObject').hasOwnProperty('data') &&
Array.isArray(this.get('jsonObject').data) &&
this.get('jsonObject').data[0].hasOwnProperty('type')) {
this.set('jsonSpecification', 'json');
Materialize.toast(`Looks like a JSON API spec, isn't it?`, 6000);
}
else if (Object.keys(this.get('jsonObject')).every(key => typeof this.get('jsonObject')[key] === 'object')) {
this.set('jsonSpecification', 'rest');
Materialize.toast(`Wait a minute... It's REST API spec, right???`, 6000);
}
}
}
});
App.ModelsController = Ember.Controller.extend({
/* SERVICES */
generator: Ember.inject.service()
});
/* *** COMPONENTS *** */
App.NavBarComponent = Ember.Component.extend({});
/* ***SERVICES *** */
App.DataService = Ember.Service.extend({
// list of links:
links: [
{
route: 'index',
text: 'CONFIGURATION'
},
{
route: 'models',
text: 'MODELS'
}
],
/**
* Gets links
* @param {number} intItems Number of links to return
* @returns {object[]}
*/
getLinks(intItems) {
return this.get('links').slice(0, intItems);
},
/**
* Gets JSON specifications list
* @returns {object[]}
*/
getSpecs() {
return [
{
key: 'json',
value: 'JSON API'
},
{
key: 'rest',
value: 'REST API'
},
{
key: 'freestyle',
value: 'Freestyle'
}
];
}
});
App.GeneratorService = Ember.Service.extend({
/* PROPERTIES */
models: [],
inflector: Ember.computed(function () {
return new Ember.Inflector(Ember.Inflector.defaultRules);
}),
/* ACTIONS */
/**
* Generates models out of JSON object
* @params {object} objJSON Input JSON object
* @params {string} strJSONSpecification Specification in use in JSON object
* @params {string} strRootModelName Root model name in case og freestyle JSON
* @params {boolean} blnUseFragments Indicates whether Ember-data-fragments are in use
* @returns {object[]}
*/
generateModels(objJSON, strJSONSpecification, strRootModelName, blnUseFragments) {
let objJSONInput;
// reset data:
this.set('models', []);
switch (strJSONSpecification) {
case 'json':
objJSON = this.get('convertJSONtoREST').call(this, objJSON);
// we continue to REST here:
case 'rest':
for (var strKey in objJSON) {
objJSONInput = Array.isArray(objJSON[strKey]) ? objJSON[strKey][0] : objJSON[strKey];
this.get('revealModel').call(this, this.get('formatModelName').call(this, strKey), objJSONInput, null, null, blnUseFragments);
}
break;
default: // freestyle
this.get('revealModel').call(this, strRootModelName, objJSON, null, null, blnUseFragments);
break;
}
// reverse models order:
return this.get('models').reverse();
},
/**
* Checks if there is already model which parsed with the same name
* @param {string} strModelName Model name
* @returns {boolean}
*/
hasModel(strModelName) {
return this.get('models').some(function (model) {
return model.name === strModelName;
});
},
/**
* Reveals model structure
* @param {string} strModelName Current model name
* @param {object} objJSON JSON object to parse
* @param {string} strParentModel Parent model name (used only on nested models)
* @param {string} strRelationType Relation type between model and parent (used only on nested models)
* @param {boolean} blnUseFragments Indicates whether Ember-data-fragments are in use
*/
revealModel(strModelName, objJSON, strParentModel, strRelationType, blnUseFragments) {
let modelDefinition = [];
if (this.hasModel(strModelName.dasherize()) === true) {
return;
}
modelDefinition.push(`/* app/models/${strModelName.dasherize()}.js */`);
modelDefinition.push(`import DS from 'ember-data';`);
if (blnUseFragments === true) {
modelDefinition.push(`import MF from 'model-fragments';`);
}
modelDefinition.push(``);
modelDefinition.push(`export default ${blnUseFragments && strParentModel ? 'MF.Fragment' : 'DS.Model'}.extend({`);
for (var strKey in objJSON) {
switch(typeof objJSON[strKey]) {
case 'boolean':
case 'date':
case 'number':
case 'string':
modelDefinition.push(` ${strKey}: DS.attr('${typeof objJSON[strKey]}'),`);
break;
default:
// sub-model - assuming one to many:
if (Array.isArray(objJSON[strKey])) {
if (objJSON[strKey].every(item => typeof item !== 'object')) {
if (blnUseFragments) {
modelDefinition.push(` ${strKey}: MF.array('${this.get('formatModelName').call(this, strKey)}'),`);
}
else {
modelDefinition.push(` ${strKey}: DS.hasMany('UNKNOWN'),`);
}
}
else {
this.revealModel(this.get('formatModelName').call(this, strKey), objJSON[strKey][0], strModelName, '1:*', blnUseFragments);
modelDefinition.push(` ${strKey}: ${blnUseFragments ? 'MF.fragmentArray' : 'DS.hasMany'}('${this.get('formatModelName').call(this, strKey)}'),`);
}
}
// sub-model - assuming one to one
else if (objJSON[strKey] && typeof objJSON[strKey] === 'object') {
this.revealModel(this.get('formatModelName').call(this, strKey), objJSON[strKey], strModelName, '1:1', blnUseFragments);
modelDefinition.push(` ${strKey}: ${blnUseFragments ? 'MF.fragment' : 'DS.belongsTo'}('${this.get('formatModelName').call(this, strKey)}'),`);
}
else {
modelDefinition.push(` ${strKey}: DS.attr('UNKNOWN'),`);
}
break;
}
}
// Add relation to parent, if any:
switch (strRelationType) {
case '1:1':
case '1:*':
modelDefinition.push(` ${strParentModel}: DS.belongsTo('${strParentModel}'),`);
break;
case '*:1':
case '*:*':
modelDefinition.push(` ${strParentModel}: DS.hasMany('${strParentModel}'),`);
break;
}
// remove last comma:
modelDefinition[modelDefinition.length - 1] = modelDefinition[modelDefinition.length - 1].substr(0, modelDefinition[modelDefinition.length - 1].length - 1);
// close model:
modelDefinition.push('});');
// add model to collection:
this.get('models').push({
name: strModelName,
definition: modelDefinition
});
},
/**
* Converts JSON API specification JSON object to REST
* @param {object} objJSON JSON object to convert
* @returns {object}
*/
convertJSONtoREST(objJSON) {
let objJSONOutput = {};
if (this.guessSpecification(objJSON) === 'json') {
// get main model:
objJSONOutput[objJSON.data[0].type] = objJSON.data[0].attributes;
// objJSONOutput[objJSON.data.type].id = objJSON.data.id;
// get included models:
if (objJSON.hasOwnProperty('included')) {
objJSON.included.forEach(function (objJSONIncluded) {
objJSONOutput[objJSONIncluded.type] = objJSONIncluded.attributes;
// objJSONOutput[objJSONIncluded.type].id = objJSON.id;
});
}
}
return objJSONOutput;
},
/**
* Singularizes & dasherizes model name
* @param {string} strModelName Model name
* @returns {string}
*/
formatModelName(strModelName) {
strModelName = this.get('inflector').singularize(strModelName);
return strModelName.dasherize();
},
/**
* Guesses given JSON object specification
*/
guessSpecification(objJSON) {
// is it JSON API?
if (objJSON.hasOwnProperty('data') &&
Array.isArray(objJSON.data) &&
objJSON.data[0].hasOwnProperty('type')) {
return 'json';
}
else if (Object.keys(objJSON).every(key => typeof objJSON[key] === 'object')) {
return 'rest';
}
}
}),
/* HELPERS */
App.EqHelper = Ember.Helper.extend({
compute(params/*, hash*/) {
return params[0] === params[1];
}
});
})(Ember);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="keywords" content="ember.js, ember-data, model, materializecss">
<meta name="description" content="Reverse engineer your JSON objects into Ember Data models">
<meta name="author" content="Nir Elbaz">
<title>Ember Models Generator</title>
<!--Import Google Icon Font-->
<link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
<!-- Code Highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/styles/github.min.css">
<!--Let browser know website is optimized for mobile-->
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="feditor:preset" content="preview"/>
</head>
<body>
<!-- Application template -->
<script type="text/x-handlebars">
<main>
{{outlet}}
</main>
<div class="show-code deep-orange darken-4 white-text" {{action "showCode"}}>Show Me The <Code>!</div>
</script>
<!-- Index (configuration) route template -->
<script type="text/x-handlebars" data-template-name="index">
{{nav-bar links=model.links}}
<div class="container">
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">Settings</span>
<p>
Generating and modifying Ember Data models can be a real bummer sometimes, especially when your database scheme is not fully baked yet.<br/>
<strong>EMBER MODEL GENERATOR</strong> can ease this task, by reverse engineering JSON objects produced by your APIs.<br><br>
<p>Fill in the form below and click on the orange button below to generate the models:</p>
</p>
<form>
<div class="row">
<div class="input-field col s12">
{{textarea id="json-object" value=jsonString class="materialize-textarea active" placeholder="Paste here the JSON object out of which you wish to extract model(s)" required=true}}
<label class="active" for="json-object">JSON Object</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 l6">
<select id="json-specification" name="json-specification" value=jsonSpecification onchange={{action (mut jsonSpecification) value="target.value"}}>
<option value="" selected={{eq "" jsonSpecification}}>Choose the format of your JSON object</option>
{{#each model.specs as |spec|}}
<option value={{spec.key}} selected={{eq spec.key jsonSpecification}}>{{spec.value}}</option>
{{/each}}
</select>
<label>JSON Specification</label>
</div>
<div class="input-field col s12 l6 {{unless (eq jsonSpecification "freestyle") "hide"}}">
{{input value=rootModelName placeholder="Type here the name of the root model in a singular form" id="root-model" type="text" class="validate"}}
<label class="active" for="root-model">Root Model Name</label>
</div>
</div>
<div class="row">
<div class="col s12">
<dl>
<dt>JSON Specification</dt>
<dd>Read more and see sample on the official <a href="http://jsonapi.org/">JSON API website</a></dd>
<dt>REST Specification</dt>
<dd>A hash which every key represents a different model in its singular form</dd>
<dt>Freestyle</dt>
<dd>JSON object itself is a model. Specifying the root model name is required.</dd>
</dl>
</div>
</div>
<div class="row">
<div class="col s12">
{{input type="checkbox" name="fragments" checked=useFragments}}
<label for="fragments" {{action "toggleFragments"}}>I use <a href="https://github.com/lytics/ember-data-model-fragments" target="_blank">Ember Data Model Fragments</a> in my project</label>
</div>
</div>
</form>
</div>
<div class="card-action">
<a class="waves-effect waves-teal btn-flat" {{action "resetForm"}}>Reset Form</a>
{{#link-to "models" class="waves-effect waves-light btn deep-orange lighten-1" classNameBindings="buttonEnabled::disabled" disabled=buttonNotEnabled}}
<i class="material-icons left">thumb_up</i>Do your magic
{{/link-to}}
</div>
</div>
</div>
</div>
</div>
</script>
<!-- Models (output) route template -->
<script type="text/x-handlebars" data-template-name="models">
{{nav-bar links=model.links}}
<div class="container">
<div class="row">
{{#each model.models as |m|}}
<div class="col s12 l6">
<div class="card model">
<div class="card-content">
<span class="card-title"><i class="material-icons">reorder</i> {{m.name}} Model</span>
<pre><code class="javascript">
{{#each m.definition as |line|}}
{{line}}
{{/each}}</code></pre>
</div>
</div>
</div>
{{/each}}
</div>
</div>
</script>
<!-- Navbar component template -->
<script type="text/x-handlebars" data-template-name="components/nav-bar">
<div class="navbar-fixed">
<nav>
<div class="nav-wrapper deep-orange darken-2">
<a class="brand-logo right">Ember Model Generator</a>
<div class="col s12">
{{#each links as |link|}}
{{#link-to link.route class="breadcrumb"}}{{link.text}}{{/link-to}}
{{/each}}
</div>
</div>
</nav>
</div>
</script>
<!-- Import jQuery before materialize.js -->
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<!-- Import my beloved, Ember.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.4.1/ember.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember-data.js/2.4.0/ember-data.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.4.1/ember-template-compiler.js"></script>
<!-- Compiled and minified materialize JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>
<!-- Syntax highlight -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/languages/javascript.min.js"></script>
</body>
</html>
body {
background-color: #faf4f1;
}
form {
margin-top: 1rem;
}
dt {
color: #999;
font-weight: bold;
}
.nav-wrapper {
padding-left: 1rem;
padding-right: 1rem;
}
.container {
margin-top: 2rem
}
.brand-logo {
font-weight: bold;
text-transform: uppercase;
}
.show-code {
border-top: 4px solid #ffab91;
border-bottom: 4px solid #ffab91;
cursor: pointer;
font-size: 1.5rem;
padding: 0.2rem;
text-align: center;
transform: rotateZ(45deg);
position: fixed;
top: 130px;
right: -60px;
width: 300px;
}
@media all and (max-width: 650px) {
.show-code {
top: 110px;
}
}
.card-title {
text-transform: capitalize;
font-weight: bold;
}
.card-title .material-icons {
vertical-align: sub;
}
.card-action {
text-align: right;
}
/* label color */
.input-field label {
/* color: #000; */
}
/* label focus color */
.input-field input[type=text]:focus + label,
textarea.materialize-textarea:focus:not([readonly])+label,
.dropdown-content li>span {
color: #ff8a65;
}
/* label underline focus color */
.input-field input[type=text]:focus {
border-bottom: 1px solid #ff8a65;
box-shadow: 0 1px 0 0 #ff8a65;
}
/* valid color */
.input-field input[type=text].valid {
border-bottom: 1px solid #ff8a65;
box-shadow: 0 1px 0 0 #ff8a65;
}
/* invalid color */
.input-field input[type=text].invalid,
textarea.materialize-textarea:focus:not([readonly]) {
border-bottom: 1px solid #f44336;
box-shadow: 0 1px 0 0 #f44336;
}
/* icon prefix focus color */
.input-field .prefix.active {
color: #000;
}
以上是关于css Ember.js模型生成器 - 将JSON对象反向工程为Ember数据模型的主要内容,如果未能解决你的问题,请参考以下文章
Ember.js - 将 ember-cli-mirage 用于假模型时未找到模型