Basically I want to validate my observable before apply the bindings, so that I will never get that something something is not defined error.
Say I do have a javascript class that defines all that I need, and I want to validate an observable created from ajax against it.
Is there a generic way to do it?
Edit:
http://jsfiddle.net/rgneko/GuR8v/
Currently the demo will throw error because one of the items has no id property. I want to verify whether all items are valid.
$(document).ready(function () {
function ModelToTestAgainst(id, name, type, items) {
this.id = id;
this.name = name;
this.type = type;
this.items = items;
}
var data = {
items: [{
id: 1,
name: 'name1',
type: 'folder',
items: []
}, {
id: 2,
name: 'name2',
type: 'file',
items: []
}, {
name: 'name2',
type: 'file',
items: []
}]
};
var someRandomObservaleIGotFromWherever = ko.mapping.fromJS(data);
// I want to Validate(someRandomObservaleIGotFromWherever) before apply bindings
ko.applyBindings(someRandomObservaleIGotFromWherever);
});
There is a standard JSON Schema, defined in http://json-schema.org/.
An schema can be as simple as:
var schema = {"type" : "object"};
which requires the value to be an object. Or much more complex, like this example from json-schema.org:
{
"title": "Example Schema",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0
}
},
"required": ["firstName", "lastName"]
}
which requires several properties to exists, defines their types, and even a minimun value for one of them.
Then you need a library that allows to validate the JS objects with this kind of schema. In the same site, you can find a list of libraries for validating (and parsing, creating documentation...). Note that not all libraries are updated, and not all of them support the latest JSON schema version.
For example, using JSV, you can make a validation simply like this:
var report = env.validate(json, schema);
Why don't you map/wrap it into a view model that you know has all the properties that you need?
function ViewModel(model) {
this.x = ko.observable(model.x || 0);
this.y = ko.observable(model.y || 0);
}
var original = { x: 27 }; // Use your ajax object here.
var viewModel = new ViewModel(original);
What about using JSON Schema validation.
I have used library from Tiny Validator to enable validation. Validation is easy to add as an observable extension
$(document).ready(function () {
// Attach a validationSchema method to all observable types
ko.subscribable.fn["validateSchema"] = function(schema){
// Convert the observable back to an object literal to test.
var source = ko.mapping.toJS(this());
return tv4.validate(source, schema);
};
// Define schema for ModelToTestAgainst
var modelSchema = {
"$schema": "http://tempuri.org/ModelToTestAgainst",
"title": "ModelToTestAgainst Set",
"type": "array",
"items": {
"title": "ModelToTestAgainst",
"type": "object",
"properties" : {
"id" : {
"type": "number",
"minimum" : 1
},
"name" : { "type": "string" },
"type" : { "type": "string" },
"items" : { "type" : "array" }
},
"required": ["id", "name", "type", "items"]
}
};
function ModelToTestAgainst(id, name, type, items) {
this.id = id;
this.name = name;
this.type = type;
this.items = items;
}
var data = {
items: [{
id: 1,
name: 'name1',
type: 'folder',
items: []
}, {
id: 2,
name: 'name2',
type: 'file',
items: []
}, {
name: 'name2',
type: 'file',
items: []
}]
};
var obs = ko.mapping.fromJS(data));
var isValid = obs.items.validateSchema(modelSchema);
if( isValid ) {
ko.applyBindings(obs);
}
});
Related
My goal is to use create schema for json-schema-yup-transformer, therefore, I need to get this format :
const schema = {
type: "object",
properties: {
firstName: {
type: "string",
minLength: 2,
},
email: {
type: "string",
format: "email",
},
},
required: requiredFields,
};
I've got a schemaProperties :
const schemaProperties = formatData.map((item) => {
return {
[item.value]: { type: item.type, validation: item.validation },
};
});
That returns me :
[
{
"firstName": {
"type": "text",
"validation": {
"required": true,
"requiredMessage": "Prénom requis"
}
}
},
{
"lastName": {
"type": "text"
}
},
...
]
But, my problem is that schemaProperties is an array actually and not an object. I tried to destruct it and transform into an object, but its not working... Im sure its not something very complicated but I'm stuck...
I have the following Json
var myjson = [{
"files": [
{
"domain": "d",
"units": [
{
"key": "key1",
"type": "2"
},
{
"key": "key2",
"type": "2"
},
{
"key": "key3",
"type": "2"
}]
},
{
"domain": "d1",
"units": [
{
"key": "key11",
"type": "2"
},
{
"key": "key12",
"type": "2"
},
{
"key": "key13",
"type": "2"
}]
}]
},
{
"files": [
{
"domain": "d",
"units": [
{
......
I want to create an new array from this Json array. The length of array will be the number of "units" in this Json object.
So I need to extract "units" and add some data from parent objects.
units: [{
domain: "",
type: "",
key: ""
}, {
domain: "",
type: "",
key: ""
},
{
domain: "",
type: "",
key: ""
}
....
];
I guess i can probably do something like this:
var res = [];
myjson.forEach(function(row) {
row.files.forEach(function(tfile) {
tfile.units.forEach(function(unit) {
var testEntity = {
domain: tfile.domain,
type : unit.type,
key: unit.key
};
res.push(testEntity);
});
});
});
But it is difficult to read and looks not so good. I was thinking to do something like :
var RESULT = myjson.map(function(row) {
return row.files.map(function(tfile) {
return tfile.units.map(function(unit) {
return {
domain: tfile.domain,
type : unit.type,
key: unit.key
};
});
});
});
But This doesn't work and looks not better . Is there any way to do so it works, maybe in more declarative way. hoped Ramda.js could help.
It there any good approach in general to get data from any Nested json in readable way?
Implementing something like:
nestedjson.findAllOnLastlevel(function(item){
return {
key : item.key,
type: type.key,
domain : item.parent.domain}
});
Or somehow flatten this json so all properties from all parents object are moved to leafs children. myjson.flatten("files.units")
jsbin http://jsbin.com/hiqatutino/edit?css,js,console
Many thanks
The function you can use here is Ramda's R.chain function rather than R.map. You can think of R.chain as a way of mapping over a list with a function that returns another list and then flattens the resulting list of lists together.
// get a list of all files
const listOfFiles =
R.chain(R.prop('files'), myjson)
// a function that we can use to add the domain to each unit
const unitsWithDomain =
(domain, units) => R.map(R.assoc('domain', domain), units)
// take the list of files and add the domain to each of its units
const result =
R.chain(file => unitsWithDomain(file.domain, file.units), listOfFiles)
If you wanted to take it a step further then you could also use R.pipeK which helps with composing functions together which behave like R.chain between each of the given functions.
// this creates a function that accepts the `myjson` list
// then passes the list of files to the second function
// returning the list of units for each file with the domain attached
const process = pipeK(prop('files'),
f => map(assoc('domain', f.domain), f.units))
// giving the `myjson` object produces the same result as above
process(myjson)
Pure JS is very sufficient to produce the result in simple one liners. I wouldn't touch any library just for this job. I have two ways to do it here. First one is a chain of reduce.reduce.map and second one is a chain of reduce.map.map. Here is the code;
var myjson = [{"files":[{"domain":"d","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"d1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]},{"files":[{"domain":"e","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"e1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]}],
units = myjson.reduce((p,c) => c.files.reduce((f,s) => f.concat(s.units.map(e => (e.domain = s.domain,e))) ,p) ,[]);
units2 = myjson.reduce((p,c) => p.concat(...c.files.map(f => f.units.map(e => (e.domain = f.domain,e)))) ,[]);
console.log(units);
console.log(units2);
For ES5 compatibility i would suggest the reduce.reduce.map chain since there is no need for a spread operator. And replace the arrow functions with their conventional counterparts like the one below;
var myjson = [{"files":[{"domain":"d","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"d1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]},{"files":[{"domain":"e","units":[{"key":"key1","type":"2"},{"key":"key2","type":"2"},{"key":"key3","type":"2"}]},{"domain":"e1","units":[{"key":"key11","type":"2"},{"key":"key12","type":"2"},{"key":"key13","type":"2"}]}]}],
units = myjson.reduce(function(p,c) {
return c.files.reduce(function(f,s) {
return f.concat(s.units.map(function(e){
e.domain = s.domain;
return e;
}));
},p);
},[]);
console.log(units);
Something like this should work. .reduce is a good one for these kind of situations.
const allUnits = myjson.reduce((acc, anonObj) => {
const units = anonObj.files.map(fileObj => {
return fileObj.units.map(unit => {
return {...unit, domain: fileObj.domain})
})
return [...acc, ...units]
}, [])
Note that this relies on both array spreading and object spreading, which are ES6 features not supported by every platform.
If you can't use ES6, here is an ES5 implementation. Not as pretty, but does the same thing:
var allUnits = myjson.reduce(function (acc, anonObj) {
const units = anonObj.files.map(function(fileObj) {
// for each fileObject, return an array of processed unit objects
// with domain property added from fileObj
return fileObj.units.map(function(unit) {
return {
key: unit.key,
type: unit.type,
domain: fileObj.domain
}
})
})
// for each file array, add unit objects from that array to accumulator array
return acc.concat(units)
}, [])
Try this
var myjson = [{
"files": [{
"domain": "d",
"units": [{
"key": "key1",
"type": "2"
}, {
"key": "key2",
"type": "2"
}, {
"key": "key3",
"type": "2"
}]
},
{
"domain": "d1",
"units": [{
"key": "key11",
"type": "2"
}, {
"key": "key12",
"type": "2"
}, {
"key": "key13",
"type": "2"
}]
}
]
}];
//first filter out properties exluding units
var result = [];
myjson.forEach(function(obj){
obj.files.forEach(function(obj2){
result = result.concat(obj2.units.map(function(unit){
unit.domain = obj2.domain;
return unit;
}));
});
});
console.log(result);
I'm trying to dynamically generate a graphql scheme from a json config. But i'm unable to create a GraphQLList to itself.
json:
{
"label": "user",
"properties": [
{
"key": "name",
"type": "string"
},
{
"key": "id",
"type": "id"
},
{
"key": "birthday",
"type": "date"
},
{
"key": "gender",
"type": "string"
},
{
key: 'friends',
type: 'string'
}
]
}
The javascript code generating:
graphSchemes.forEach(function (graphScheme) {
graphQLObjects[graphScheme.label] = new graphql.GraphQLObjectType({
name: graphScheme.label,
fields: graphScheme.properties.reduce((fields, property) => {
if (property.key === 'friends') {
fields[property.key] = {
type: new graphql.GraphQLList(graphQLObjects[graphScheme.label])
};
return fields;
}
fields[property.key] = {
type: TYPES[property.type]
};
return fields;
}, {})
});
});
The issue here is:
type: new graphql.GraphQLList(graphQLObjects[graphScheme.label])
There is no "graphQLObjects[graphScheme.label]"
How can I go around this? Any suggestions?
It's possible a field to reference the type itself by putting the fields in a wrapper function.
an example:
var routeType = new GraphQLObjectType({
name: 'MessageRoute',
fields: function () {
return {
name: {
type: GraphQLString
},
routes: {
type: new GraphQLList(routeType),
resolve: (route) => {
return route.routes;
}
}
};
}
});
Does Dojo hat any utilities for sotring the data within MemoryStore, or optionally, within any data collection?
I'd need all data from the MemoryStore, but sorted by single evt. more columns. Something like Collections.sort in Java...
I'd expect Store to have sort function, but I couldn't find anything in the documentation.
The dojo/store API allows sorting data at query time only, as far as I know. For example:
var store = new Memory({
data: [{
"firstName": "Bird",
"name": "Schultz"
}, {
"firstName": "Brittany",
"name": "Berg"
}, {
"firstName": "Haley",
"name": "Good"
}, {
"firstName": "Randolph",
"name": "Phillips"
}, {
"firstName": "Bernard",
"name": "Small"
}, {
"firstName": "Leslie",
"name": "Wynn"
}, {
"firstName": "Mercado",
"name": "Singleton"
}, {
"firstName": "Esmeralda",
"name": "Huber"
}, {
"firstName": "Juanita",
"name": "Saunders"
}, {
"firstName": "Beverly",
"name": "Clemons"
}]
});
console.log("Alphabetically by first name:");
store.query({}, {
sort: [{
attribute: "firstName",
descending: false
}]
}).forEach(function(person) {
console.log(person.firstName + " " + person.name);
});
You can provide multiple sort attributes as well.
Full example can be found on JSFiddle: http://jsfiddle.net/9HtT3/
When we sort our Data, we do this actual before the Items are stored. We take the filtered Values that are saved in an array and use array.sort()and called inside a function SortByName or SortByNumbers
looks like this:
function streetsToCombobox(results){
var adress;
var values = [];
var testVals={};
var features = results.features;
require(["dojo/_base/array","dojo/store/Memory","dijit/registry","dojo/domReady!"], function(array,Memory,registry){
if (!features[0]) {
alert(noDataFound);
}
else {
array.forEach(features, function(feature, i){
adress = feature.attributes.STRASSE;
if (!testVals[adress]) {
testVals[adress] = true;
values.push({
name: adress
});
}
});
values.sort(SortByName);
var dataItems = {
identifier: 'name',
label: 'name',
items: values
};
storeStreet = new Memory({
data: dataItems
});
//fill existing Combobox ID,NAME,VALUE,SEARCHATTR,ONCHANGE,STORENAME,COMBOBOX
fillExistingCombobox(
"adrSearchSelectCB",
"adrSearchSelectCBName",
"",
"name",
getAdresses,
storeStreet,
registry.byId("adrSearchSelectCBId")
);
}
});
}
function SortByName(x,y) {
return ((x.name == y.name) ? 0 : ((x.name > y.name) ? 1 : -1 ));
}
Maybe this brings some Ideas to you how to solve your Question.
Regards, Miriam
Okay, so I have two observable datasources. One uses a model called TestRequest, of which I can have multiples. The other uses a model of TestResult of which multiples can exist for each TestRequest. I have a requestId data item that I am filtering on to populate my dependent grid. With declarative data and a declarative filter everything works fine. If I dynamically set the filter, I can no longer add new records to the filtered datasource. How can I get around this? I was going to set the "foreign" id in a handler on the data source's change event (this is how I maintain client side primary keys just fine). And I can I can tell from my handler that the "add" action is firing, but the editor popup (I am using popup editing on both grids) is not showing? Is there some other magic I need to enable?
Let me know if I need to post code and I will put my html and js up on a server for y'all to look at and criticize :)
Thanks in advance.
Relevant JavaScript:
var TestRequest = kendo.data.Model.define({
id: "Id",
fields: {
"accessionNumber": { type: "string" },
"specimenCollectionDate": { type: "date" },
"specimenReceivedDate": { type: "date" },
"resultReportDate": { type: "date" },
"testDescription": { type: "string" },
"relevantClincalInfo": { type: "string" },
"specimenSource": { type: "string" },
"resultStatus": { type: "string" },
"reasonForTest": { type: "string" },
"comments": { type: "string" }
}
});
var testRequestDataSource = new kendo.data.DataSource({
schema: {
model: TestRequest
}
});
testRequestDataSource.bind("change", function(e) {
if (e.action === "add") {
e.items[0].dirty = true;
kendo.data.ObservableObject.fn.set.call(e.items[0], "Id", viewModel.testsResults.nextRequestId);
viewModel.testsResults.nextRequestId++;
}
});
var TestResult = kendo.data.Model.define({
id: "Id",
fields: {
"requestId": {
type: "number",
editable: false
},
"foo": {
type: "string"
}
}
});
var testResultDataSource = new kendo.data.DataSource({
data: [
{
Id: 1,
requestId: 1,
foo: "bar"
},
{
Id: 2,
requestId: 1,
foo: "baz"
},
{
Id: 3,
requestId: 2,
foo: "beep"
}
],
schema: {
model: TestResult
},
filter: {
"field": "requestId",
"operator": "eq",
"value": 0
}
});
testResultDataSource.bind("change", function(e){
if (e.action === "add") {
e.items[0].dirty = true;
kendo.data.ObservableObject.fn.set.call(e.items[0], "Id", viewModel.testsResults.nextResulttId);
viewModel.testsResults.nextResultId++;
}
});
var viewModel = kendo.observable({
...
testsResults: {
nextRequestId: 1,
nextResultId: 4,
testRequests: testRequestDataSource,
testResults: testResultDataSource,
testRequestChange: function() {
var selectedItems =testRequestGrid.select();
var selectedRow = $(selectedItems[0]);
var selectedUid = selectedRow.data("uid");
var selectedData = testRequestGrid.dataSource.getByUid(selectedUid);
var requestId = selectedData.id;
this.testsResults.testResults.filter({
"field":"requestId",
"operator":"eq",
"value": requestId
});
},
testRequestEdited: function(e) {
var uid = e.model.uid;
// TODO: fix below code to use callback or better event
setTimeout(function() {
testRequestGrid.select(testRequestGrid.tbody.find(">tr[data-uid='"+uid+"']"));
}, 100);
},
....
....
The HTML:
<div id="testsResults">
<h2>Tests/Results</h2>
<fieldset>
<legend>Test Requests</legend>
<div id="testRequests">
<div data-role="grid"
data-bind="source: testsResults.testRequests, events: { change: testsResults.testRequestChange, save: testsResults.testRequestEdited }"
data-editable="popup"
data-selectable="true"
data-toolbar='["create"]'
data-columns='[{"field":"accessionNumber","title":"Accession #"},
{"field":"specimenCollectionDate", "title":"Specimen Collection Date", "hidden":"true"},
{"field":"specimenReceivedDate", "title":"Specimen Received Date", "hidden":"true"},
{"field":"resultReportDate", "title":"Result Report Date", "hidden":"true"},
{"field":"testDescription","title":"Test Description"},
{"field":"relevantClinicalInfo", "title":"Relevant Clinical Information", "hidden":"true"},
{"field":"specimenSource","title":"Specimen Source", "hidden":"true"},
{"field":"resultStatus","title":"Result Status","hidden":"true"},
{"field":"reasonForTest","title":"Reason For Test", "hidden":"true"},
{"field":"comments","title":"Comments","hidden":"true"},
{"command":["edit","destroy"], "title":" ", "width":"170px"}]'>
</div>
</div>
</fieldset>
<fieldset>
<legend>Test Results</legend>
<div id="testResults">
<div data-role="grid"
data-bind="source: testsResults.testResults"
data-editable="popup"
data-selectable="true"
data-toolbar='["create"]'
data-columns='[{"field":"requestId","title": "Test Request ID", "hidden":"true"},
{"field":"foo", "title":"Foo"},
{"command":["edit","destroy"], "title":" ", "width":"170px"}]'>
</div>
</div>
</fieldset>
</div>
Yay! I get to answer my own question. The problem is that the new record does NOT match the filter, so the edit popup cannot display. I added the missing piece in the change event handler of the data source and everything works nifty:
testResultDataSource.bind("change", function(e){
if (e.action === "add") {
e.items[0].dirty = true;
kendo.data.ObservableObject.fn.set.call(e.items[0], "Id", viewModel.testsResults.nextResulttId);
var filters = this.filter().filters;
var requestId = filters[0].value;
kendo.data.ObservableObject.fn.set.call(e.items[0], "requestId", requestId);
viewModel.testsResults.nextResultId++;
}
});