Skip to content
This repository has been archived by the owner on Jun 27, 2018. It is now read-only.

[RFR] Clarify the map() logic #20

Merged
merged 6 commits into from
Jul 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions lib/DataStore/DataStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,6 @@ class DataStore {
});
}

createEntry(entityName, identifier, fields) {
let entry = new Entry.mapFromRest(entityName, identifier, fields, {});

fields.forEach(function (field) {
entry.values[field.name()] = field.defaultValue();
});

return entry;
}

mapEntry(entityName, identifier, fields, restEntry) {
let entry = new Entry.mapFromRest(entityName, identifier, fields, restEntry);

return entry;
}

mapEntries(entityName, identifier, fields, restEntries) {
return restEntries.map(e => this.mapEntry(entityName, identifier, fields, e));
}

fillReferencesValuesFromCollection(collection, referencedValues, fillSimpleReference) {
fillSimpleReference = typeof (fillSimpleReference) === 'undefined' ? false : fillSimpleReference;

Expand All @@ -71,6 +51,26 @@ class DataStore {
return collection;
}

/**
* Map a JS object from the REST API Response to an Entry
*
* @deprecated use Entry.createFromRest() instead
*/
mapEntry(entityName, identifier, fields, restEntry) {
console.log('DataStore.mapEntry() is deprecated, please use Entry.createFromRest() instead');
return new Entry.createFromRest(restEntry, fields, entityName, identifier.name());
}

/**
* Map an array of JS objects from the REST API Response to an array of Entries
*
* @deprecated use Entry.createArrayFromRest() instead
*/
mapEntries(entityName, identifier, fields, restEntries) {
console.log('DataStore.mapEntries() is deprecated, please use Entry.createArrayFromRest() instead');
return Entry.createArrayFromRest(restEntries, fields, entityName, identifier.name());
}

fillReferencesValuesFromEntry(entry, referencedValues, fillSimpleReference) {
for (let referenceField in referencedValues) {
let reference = referencedValues[referenceField],
Expand Down
63 changes: 51 additions & 12 deletions lib/Entry.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {clone, cloneAndFlatten, cloneAndNest} from './Utils/objectProperties';


class Entry {
constructor(entityName, values, identifierValue) {
this._entityName = entityName;
Expand All @@ -14,28 +17,64 @@ class Entry {
return this._identifierValue;
}

static mapFromRest(entityName, identifier, fields, restEntry) {
if (!restEntry) {
return new Entry(entityName);
static createForFields(fields, entityName) {
let entry = new Entry(entityName);
fields.forEach(field => {
entry.values[field.name()] = field.defaultValue();
});
return entry;

}

/**
* Map a JS object from the REST API Response to an Entry
*
* @return {Entry}
*/
static createFromRest(restEntry, fields, entityName, identifierName) {
if (!restEntry || Object.keys(restEntry).length == 0) {
return Entry.createForFields(fields, entityName);
}

let identifierValue = null;
let values = cloneAndFlatten(restEntry);

fields.forEach(function (field) {
fields.forEach(field => {
let fieldName = field.name();
if (fieldName in values) {
values[fieldName] = field.getMappedValue(values[fieldName], values);
}
});

return new Entry(entityName, values, values[identifierName]);
}

/**
* Map an array of JS objects from the REST API Response to an array of Entries
*
* @return {Array[Entry]}
*/
static createArrayFromRest(restEntries, fields, entityName, identifierName) {
return restEntries.map(e => Entry.createFromRest(e, fields, entityName, identifierName));
}

/**
* Transform an Entry to a JS object for the REST API Request
*
* @return {Object}
*/
transformToRest(fields) {

let restEntry = clone(this.values);
fields.forEach(field => {
let fieldName = field.name();
if (fieldName in restEntry) {
restEntry[fieldName] = field.getMappedValue(restEntry[fieldName], restEntry);
restEntry[fieldName] = field.getTransformedValue(restEntry[fieldName])
}
});

// Add identifier value
if (identifier) {
identifierValue = restEntry[identifier.name()];
}

return new Entry(entityName, restEntry, identifierValue);
return cloneAndNest(restEntry);
}

}

export default Entry;
5 changes: 5 additions & 0 deletions lib/Factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import WysiwygField from "./Field/WysiwygField";
import Menu from './Menu/Menu';
import Collection from './Collection';
import Dashboard from './Dashboard';
import Entry from './Entry';

class Factory {
constructor() {
Expand Down Expand Up @@ -81,6 +82,10 @@ class Factory {
return collection;
}

getEntryConstructor() {
return Entry;
}

getDataStore() {
return new DataStore();
}
Expand Down
47 changes: 39 additions & 8 deletions lib/Field/Field.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Field {
this._order = null;
this._label = null;
this._maps = [];
this._transforms = [];
this._attributes = {};
this._cssClasses = null;
this._validation = { required: false, minlength : 0, maxlength : 99999 };
Expand Down Expand Up @@ -74,6 +75,9 @@ class Field {
return this._detailLink = isDetailLink;
}

/**
* Add a function to be applied to the response object to turn it into an entry
*/
map(fn) {
if (!fn) return this._maps;
if (typeof(fn) !== "function") {
Expand All @@ -90,6 +94,41 @@ class Field {
return !!this._maps.length;
}

getMappedValue(value, entry) {
for (let i in this._maps) {
value = this._maps[i](value, entry);
}

return value;
}

/**
* Add a function to be applied to the entry to turn it into a response object
*/
transform(fn) {
if (!fn) return this._transforms;
if (typeof(fn) !== "function") {
let type = typeof(fn);
throw new Error(`transform argument should be a function, ${type} given.`);
}

this._transforms.push(fn);

return this;
}

hasTranforms() {
return !!this._transforms.length;
}

getTransformedValue(value, entry) {
for (let i in this._transforms) {
value = this._transforms[i](value, entry);
}

return value;
}

attributes(attributes) {
if (!arguments.length) {
return this._attributes;
Expand Down Expand Up @@ -122,14 +161,6 @@ class Field {
return this._cssClasses;
}

getMappedValue(value, entry) {
for (let i in this._maps) {
value = this._maps[i](value, entry);
}

return value;
}

validation(validation) {
if (!arguments.length) {
return this._validation;
Expand Down
74 changes: 74 additions & 0 deletions lib/Utils/objectProperties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
function isObject(value) {
if (value === null) return false;
if (typeof value !== 'object') return false;
if (Array.isArray(value)) return false;
if (Object.prototype.toString.call(value) === '[object Date]') return false;
return true;
}

export function clone(object) {
return Object.keys(object).reduce((values, name) => {
if (object.hasOwnProperty(name)) {
values[name] = object[name];
}
return values;
}, {});
}

/*
* Flatten nested object into a single level object with 'foo.bar' property names
*
* The parameter object is left unchanged. All values in the returned object are scalar.
*
* cloneAndFlatten({ a: 1, b: { c: 2 }, d: { e: 3, f: { g: 4, h: 5 } } })
* // { a: 1, 'b.c': 2, 'd.e': 3, 'd.f.g': 4, 'd.f.h': 5 }
*
* @param {Object} object
* @return {Object}
*/
export function cloneAndFlatten(object) {
if (typeof object !== 'object') {
throw new Error('Expecting an object parameter');
}
return Object.keys(object).reduce((values, name) => {
if (!object.hasOwnProperty(name)) return values;
if (isObject(object[name])) {
let flatObject = cloneAndFlatten(object[name]);
Object.keys(flatObject).forEach(flatObjectKey => {
if (!flatObject.hasOwnProperty(flatObjectKey)) return;
values[name + '.' + flatObjectKey] = flatObject[flatObjectKey];
})
} else {
values[name] = object[name];
}
return values;
}, {});
};

/*
* Clone flattened object into a nested object
*
* The parameter object is left unchanged.
*
* cloneAndNest({ a: 1, 'b.c': 2, 'd.e': 3, 'd.f.g': 4, 'd.f.h': 5 } )
* // { a: 1, b: { c: 2 }, d: { e: 3, f: { g: 4, h: 5 } } }
*
* @param {Object} object
* @return {Object}
*/
export function cloneAndNest(object) {
if (typeof object !== 'object') {
throw new Error('Expecting an object parameter');
}
return Object.keys(object).reduce((values, name) => {
if (!object.hasOwnProperty(name)) return values;
name.split('.').reduce((previous, current, index, list) => {
if (typeof previous[current] === 'undefined') previous[current] = {};
if (index < (list.length - 1)) {
return previous[current];
};
previous[current] = object[name];
}, values)
return values;
}, {})
}
2 changes: 1 addition & 1 deletion lib/Utils/stringUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default {
let f = text.charAt(0).toUpperCase();
text = f + text.substr(1);

return text.replace(/[-_](.)/g, function (match, group1) {
return text.replace(/[-_.\s](.)/g, function (match, group1) {
return ' ' + group1.toUpperCase();
});
}
Expand Down
19 changes: 19 additions & 0 deletions lib/View/View.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Entry from "../Entry";
import {clone, cloneAndFlatten, cloneAndNest} from '../Utils/objectProperties';

class View {
constructor(name) {
Expand Down Expand Up @@ -254,6 +255,24 @@ class View {
});
}

/**
* Map a JS object from the REST API Response to an Entry
*/
mapEntry(restEntry) {
return Entry.createFromRest(restEntry, this._fields, this.entity.name(), this.entity.identifier().name());
}

mapEntries(restEntries) {
return Entry.createArrayFromRest(restEntries, this._fields, this.entity.name(), this.entity.identifier().name());
}

/**
* Transform an Entry to a JS object for the REST API Request
*/
transformEntry(entry) {
return entry.transformToRest(this._fields);
}

/**
* @param {Boolean} optimized
* @param {Boolean} withRemoteComplete
Expand Down
39 changes: 0 additions & 39 deletions tests/lib/DataStore/DataStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,6 @@ describe('DataStore', function() {
dataStore = new DataStore();
});

it('should map some raw entities', function () {
var view = new View();
view
.addField(new Field('title'))
.setEntity(new Entity().identifier(new Field('post_id')));

var entries = dataStore.mapEntries(view.entity.name(), view.identifier(), view.getFields(), [
{ post_id: 1, title: 'Hello', published: true},
{ post_id: 2, title: 'World', published: false},
{ post_id: 3, title: 'How to use ng-admin', published: false}
]);

assert.equal(entries.length, 3);
assert.equal(entries[0].identifierValue, 1);
assert.equal(entries[1].values.title, 'World');
assert.equal(entries[1].values.published, false);
});

it('should map some one entity when the identifier is not in the view', function () {
var view = new View(),
field = new Field('title'),
entity = new Entity('posts');

view
.addField(field)
.setEntity(entity);

entity
.identifier(new Field('post_id'));

var entry = dataStore.mapEntry(entity.name(), view.identifier(), view.getFields(), {
post_id: 1,
title: 'Hello',
published: true
});
assert.equal(entry.identifierValue, 1);
assert.equal(entry.values.title, 'Hello');
});

describe('getReferenceChoicesById', function () {
it('should retrieve choices by id.', function () {
var ref = new ReferenceField('human_id'),
Expand Down
Loading