Convert 3rd party REST API into html forms? - api

I want to convert an API service into a forms the user of my app can fill in. I think I need the right vocabulary to ask farther questions. Is there a term for this process?

Hydra is the only vocab I know about in the topic, though they don't support this kind of conversion. https://www.hydra-cg.com/spec/latest/core/ HAL forms is another solution: https://rwcbook.github.io/hal-forms/ but I guess based on the name that it is not abstract enough. You need to separate hyperlinks from forms. The former is for describing the interface of the webservice the later is for describing the GUI, which can use the webservice amongst many different types of clients. E.g. a select/choice parameter can be converted into radio button, checkbox, select input, range, etc. There needs to be some sort of mapping about this and this decision is made by the client developers, not by the service developers. Supporting multiple languages and labelling must be mapped too.
What you need is very detailed hyperlink descriptions and you can turn your hyperlinks into forms and hyperlink parameters into input fields. There are certain types of parameters e.g.
text (password?, multiline?, lengthRange, verified:regex|url|email|etc.)
repetition (source e.g. password double check)
select(alternatives[], selected[], selectionSize)
date(range, selectedRange, selectionSize)
time(range, selectedRange, selectionSize)
color(alternatives|range, selectedRange, selectionSize)
file(multi?, sizeRange, MIME-types[])
...
Better to support only what you need, because I think you need to write it yourself. Try to be very general, abstract instead of specific in this vocab. E.g. select can support select single and select multiple. The single can be supported with selectionSize=1. The alternatives can be added with a list or with another hyperlink which can be part of the response or lazy loaded, or it can be even an URI template which expects a keyword. The data of the fields can depend on each other, e.g. first you select city and the next field is street selection where you download the data by filling an URI template. So these fields can be interrelated and doing it properly requires a complex vocab.
From the REST API side of story binding these to the hyperlinks is relative easy:
{
id: "/api/users/123"
type: "/docs/User"
userid: 123,
addCar: {
type: "/docs/User/addCar",
method: "PUT",
uri: {
template: "/api/users/{id}/cars/{plateNumber}",
id: {
type: "docs/Text",
range: "docs/Car/id",
value: {
type: "/docs/Query",
context: "./resource",
select: "userid",
},
readOnly: true,
required: true
},
plateNumber: {
type: "/docs/Text",
range: {
id: "/docs/Car/plateNumber",
verified: {
type: "/docs/verification/regex",
pattern: "[A-Z]{4,4}\d{2,2}"
}
},
required: true
}
},
body: {
brand: {
type: "/docs/Selection",
range: "/docs/Car/Brand/id",
alternatives: {
type: "/docs/Car/Brand/listBrands",
method: "GET",
uri: "/api/car-brands"
},
value: null,
selectionSize: [1,1],
required: true
}
}
}
}
The generated HTML form can be something like the following with radio input, but you can use select input too:
<form onsubmit="magic(this); return false">
<input type="hidden" id="user_id" name="user_id" value="123">
<input type="text" id="plate_number" name="plate_number" pattern="[A-Z]{4,4}\d{2,2}" required="required"><br>
<label for="plate_number">Plate Number</label><br>
Select Brand:
<input type="radio" id="brand_vw" name="brand" value="VW" required="required">
<label for="brand_vw">Volkswagen</label><br>
<input type="radio" id="brand_ford" name="brand" value="Ford">
<label for="brand_ford">Ford</label><br>
<input type="submit" value="Add Car">
</form>
The request is something like:
PUT /api/users/123/cars/ABCD12 {brand: "Ford"}
When you do it with an automated client you do:
carService.getUser({id: 123}).addCar({plateNumber: "ABCD12", brand: "Ford"})
When you do it with GUI, then:
carService = new Service("/docs")
// GET /docs/* might be cached
// or you can download a single JSON-LD docs file and use # for terms
// you bookmarked the getUser link from a previous call
// or you get it with GET /api/
// you can fill it with the actual user
user = carService.getUser({id: jwt.userId})
// GET /api/users/123
// you find the addCar link and generate a form from it
// GET /api/car-brands
// the user fills the form and sends it with magic
user.addCar({plateNumber: "ABCD12", brand: "Ford"})
// PUT /api/users/123/cars/ABCD12 {brand: "Ford"}
As you can see this can be very complicated and the upper hyperlink description is ad-hoc. If you need a proper RDF vocab, then it takes serveral years to design it properly. Still this kind of technique could be used in theory.
A complete client cannot be generated, because that involves knowing what you are doing or what you possibly need and in which part of the client. E.g. in this case it is where to display the form, when do we need this form and why, where to get the actual user id and the hyperlink from, etc. If the user has to decide everything about this, then in theory it can be generated and would look like a simple webpage, which can be browsed.

Related

Can I pass metadata through Vue Formulate's schema API without affecting input attributes?

The goal:
generate form fields from JSON/CMS
have a param in the JSON that allows two fields to sit next to each other on a single line
The solution so far:
I’m using Vue Formulate's schema API to generate fields. In Vue Formulate's options, I can conditionally add a class to the outer container based on a parameter in the context.
classes: {
outer(context, classes) {
if (context.attrs.colspan === 1) {
return classes.concat('col-span-1')
}
return classes.concat('col-span-2')
},
I’m using Tailwind, which requires no classname concatenation and actually want the default to be col-span-2, so if you’re inclined to copy this, your logic may vary.
With a few classes applied to the FormulateForm, this works really well. No additional wrapper rows required thanks to CSS grid:
<FormulateForm
v-model="values"
class="sm:grid sm:grid-cols-2 sm:gap-2"
:schema="schema"
/>
The schema now looks something like this:
[
{
type: 'text',
name: 'first_name',
label: 'First name',
validation: 'required',
required: true,
colspan: 1,
},
The problem/question
Vue Formulate’s schema API passes all attributes defined (other than some reserved names) down to the input element. In my case, that results in:
<div
data-classification="text"
data-type="text"
class="formulate-input col-span-1"
data-has-errors="true"
>
<div class="formulate-input-wrapper">
<label
for="formulate-global-1"
class="formulate-input-label formulate-input-label--before"
>
First name
</label>
<div
data-type="text"
class="formulate-input-element formulate-input-element--text"
>
<input
type="text"
required="required"
colspan="1" <--------------- hmm…
id="formulate-global-1"
name="first_name"
>
</div>
</div>
</div>
I recognize that I can name my attribute data-colspan so that I’m not placing a td attribute on an input, but I think of colspan as metadata that I don’t want applied to the template. Is there a way to prevent this from being applied to the input—perhaps a reserved word in the schema API that allows an object of metadata to be accessed via context without getting applied to v-bind="$attrs"?
The vue-formulate team helped me out on this one. Very grateful. Much love.
There is a way to prevent it from landing on the input, and that's to use the reserved outer-class property in the schema:
[
{
type: 'text',
name: 'first_name',
label: 'First name',
validation: 'required',
required: true,
'outer-class': ['col-span-1'],
},
This means that I don't need to do this at all:
classes: {
outer(context, classes) {
if (context.attrs.colspan === 1) {
return classes.concat('col-span-1')
}
return classes.concat('col-span-2')
},
vue-formulate supports replacing or concatenating classes via props. I managed to overlook it because I didn't recognize that everything you pass into the schema API is ultimately the same as applying a prop of that name.
Classes can be applied to several other parts of the component as well—not just the outer/container. More information here:
https://vueformulate.com/guide/theming/customizing-classes/#changing-classes-with-props

Vue.js: binding select boxes, but don't want to ajax all the options

Good day. I'm using Vue.js to render an arbitrary number of select elements from the data in a component.
Here's sample JSON data that indicates there are two select elements, each with one or more options.
{
"dropdowns":[
{
"cd":"UG9ydGZvbGlv",
"formname":"sp_filter_UG9ydGZvbGlv",
"nm":"Portfolio",
"selected":"1a",
"options":[
{
"cd":"1a",
"val":"Option 1A"
}
]
},
{
"cd":"UHJvZHVjdCBOYW1l",
"formname":"sp_filter_UHJvZHVjdCBOYW1l",
"nm":"Product Name",
"selected":"2b",
"options":[
{
"cd":"2a",
"val":"Option 2A"
},
{
"cd":"2b",
"val":"Option 2B"
}
]
}
]
}
Here's the template HTML:
<form>
<div v-for="dropdown in dropdowns">
<div v-if="dropdown.availableToView">
<h4>{{dropdown.nm}}</h4>
<select v-model="dropdown.selected" v-on:change="triggerUpdate">
<option value="">(Make a selection)</option>
<option v-for="option in dropdown.options" :value="option.cd">{{option.val}}</option>
</select>
</div>
</div>
</form>
So far so good.
I've got the data loading and Vue is building the dropdowns.
When the user changes any select box (remember there can be an arbitrary number of them), the trigger action needs to submit ALL of the elements in the form via ajax. It sounds like the most correct option is to bind the form fields to the underlying component data, as I've done.
My triggerUpdate looks like this:
methods: {
triggerUpdate: function() {
axios({
method: "post",
url: actionURL,
data: this.dropdowns
})
.then(response => (this.data = response));
}
}
...but this submits the entire dropdowns data element, including all of the options in each select box. It's unnecessary to send all of the options in. I just want to send each field name along with its selected option (i.e. the "value").
I know i could serialize the whole form and make that my ajax payload. But that seems to be making an "end run" around Vue.js. Everyone talks about having your form fields bound to the Vue model...is it then correct to basically ignore the model when making an ajax request whose purpose is to then update the model?
I'm relatively new to Vue.js so I'd appreciate help with what I'm overlooking here. How should I go about sending in the data from the form (a) while using proper Vue.js binding and (b) without sending extraneous data?
Thanks for your time.
If you need to post only the selected values, and you store those in each dropdown's selected property, the sensible approach seems to be just mapping it to a simple array of name/value objects.
Try this (it assumes the name of each field is the formname property, if it isn't you can just replace it):
var submitData = this.dropdowns.map((dropdown) => {
return { name: dropdown.formname, value: dropdown.selected };
});
Then you send submitData in your ajax request.

Can I use vuelidate to validate "any kind of data", e.g. non-Vue, non-form data?

We're already using Vuelidate - a Vue.js model validation library to validate "configuration" for a "widget" in an "Edit Widget Configuration" form.
Now we also need to determine whether a "configuration" object (e.g. from JSON) it is valid, without the "configuration" object being inside a Vue object like a form, and would like to re-use the validation logic we've already written for the form.
Can I use vuelidate to validate such a non-vue "configuration" Javascript object? If so, how?
The question stems from a forum.vuejs.org post, where "wube" says:
I think you missed the purpose of Vuelidate. This is just a model-based validation library. Its goal is to give you an information whether the data is valid or not. I think that you probably got confused because all the examples in their docs are based on forms, but Vualidate can be used to validate any kind of data, not only forms data (in opposite to libraries like Parsley 43 that are designed for forms validation).
Exaclty. I'm trying to validate "any kind of data". How can I do that?
So how do I create the $v from:
let configuration = {
name: '',
age: 0
};
let validations = {
name: {
required,
minLength: minLength(4)
},
age: {
between: between(20, 30)
}
};
let $v = ???

OpenUI5 sap.m.Input Currency Formatting

This looks to be answered many different times but I can't seem to get it working with my implementation. I am trying to format and limit the data in a sap.m.Input element. I currently have the following:
var ef_Amount = new sap.m.Input({
label: 'Amount',
textAlign: sap.ui.core.TextAlign.Right,
value: {
path: '/amount',
type: 'sap.ui.model.type.Currency'
}
});
The first problem is that it kind of breaks the data binding. When I inspect the raw data (with Fiddler) submitted to the server it is an array like this:
"amount": [1234.25,null]
The server is expecting a single number and as such has issues with the array.
When I use the following, the binding works as desired but no formatting is performed.
var ef_Amount = new sap.m.Input({
label: 'Amount',
textAlign: sap.ui.core.TextAlign.Right,
value: '{/amount}'
});
The second problem is that the data entered is not limited to numbers.
I have tried using sap.m.MaskedInput instead but I don't like the usage of the placeholders because I never know the size of the number to be entered.
And lastly, it would be nice if when focus is placed on the input field, that all formatting is removed and re-formatted again when focus lost.
Should I be looking into doing this with jQuery or even raw Javascript instead?
Thank you for looking.
the array output is a normal one according to documentation. So you need to teach your server to acccept this format or preprocess data before submission;
this type is not intended to limit your data input;
good feature, but ui5 does not support this, because the Type object has no idea about control and it's events like "focus" it only deals with data input-output. So you have to implement this functionality on your own via extending the control or something else.
I would suggest using amount and currency separately. It's likely that user should be allowed to enter only valid currency, so you can use a combobox with the suggestions of the available currencies.
So, after much work and assistance from #Andrii, I managed to get it working. The primary issue was that onfocusout broke the updating of the model and the change event from firing. Simply replacing onfocusout with onsapfocusleave took care of the issues.
The final code in the init method of my custom control:
var me = this;
var numberFormat = sap.ui.core.NumberFormat.getCurrencyInstance({maxFractionDigits: 2});
me.addEventDelegate({
onAfterRendering: function() {
// for formatting the figures initially when loaded from the model
me.bindValue({
path: me.getBindingPath('value'),
formatter: function(value) {
return numberFormat.format(value);
}
});
},
onfocusin: function() {
// to remove formatting when the user sets focus to the input field
me.bindValue(me.getBindingPath('value'));
},
onsapfocusleave: function() {
me.bindValue({
path: me.getBindingPath('value'),
formatter: function(value) {
return numberFormat.format(value);
}
});
}
});

Handle different markets (language / locale) in Angular 2 application using an Web Api

I could use some advice how I should handle different markets in my angular 2 application. By that I mean a new market (like the German market) where the language is in German, as an example. Right now, I have hardcoded the text inside the html (in english ofc) to make it easy for myself.
An example you see here:
<div class="row">
<h2>Booking number: {{bookingNumber}}</h2>
Your changes has been confirmed.
</div>
I have read something about pipes in angular 2, and i guess I should be using something like that. My problem is, that I really don't know where to start.
Already have an Web Api application created in Visual Studio 2015 which I can use and call.
I'm thinking of making two lists in my Web Api project (one for english, one for german), but there should still be some sort of indicator. By that I mean something like:
BOOKING_NUMBER_TEXT, 'the text in english or german'
CONFIRMATION_TEXT, 'the text...'
That list should have two params like, string string or something like that.. any idea how I could make this?
From my angular 2 application, I'm thinking of calling the api and given it an id (number, lets say 1 and 2, where 1 = english, 2 = germany)
My Web Api finds the correct list and sends it back as JSON.
Then I'm guessing of building a pipe my own where I can filter the words I set in the html. I'm thinking of something like:
<div class="row">
<h2>{{BOOKING_NUMBER_TEXT | 'PIPE NAME' }}: {{bookingNumber}}</h2>
{{CONFIRMATION_TEXT | 'PIPE NAME' }}.
</div>
So when it has name BOOKING_NUMBER_TEXT, it should look into the pipe which has the list object, and take out the text from the right one and place it instead.
Is that a good plan or can you maybe give any advice? (I'm don't want to use any translate angular 2 frameworks, because I have to do different things on each market)
Cheers :)
UPDATE
Ok.. I have created some test data and allowed it to be send via my Web Api. Here is how it looks.
public Dictionary<string, string> createEnglishLocaleKeys()
{
Dictionary<string, string> Locale_EN = new Dictionary<string, string>();
// Account Component
Locale_EN.Add("ACCOUNT_LOGIN_TEXT", "Login");
Locale_EN.Add("ACCOUNT_LOGOUT_TEXT", "Logout");
// Booking Component
Locale_EN.Add("BOOKING_ACTIVE_HEADER_TEXT", "ACTIVE BOOKINGS");
Locale_EN.Add("BOOKING_LOADING_TEXT", "Loading bookings");
Locale_EN.Add("BOOKING_NONACTIVE_HEADER_TEXT", "NON ACTIVE BOOKINGS");
Locale_EN.Add("BOOKING_NOPREBOOKING_TEXT", "You currently do not have any previous bookings");
// Booking List Component
Locale_EN.Add("BOOKING_LIST_BOOKINGNUMBER_TEXT", "Booking number");
Locale_EN.Add("BOOKING_LIST_LEAVING_TEXT", "Leaving");
Locale_EN.Add("BOOKING_LIST_RETURNING_TEXT", "Returning");
Locale_EN.Add("BOOKING_LIST_ROUTE_TEXT", "Route");
Locale_EN.Add("BOOKING_LIST_PASSENGERS_TEXT", "Passengers");
Locale_EN.Add("BOOKING_LIST_VEHICLETYPE_TEXT", "Vehicle type");
// Menu Component
// Passenger Component
// DepartureDate Component
// Confirmation Component
Locale_EN.Add("KEY_NOT_FOUND", "KEY NOT FOUND");
return Locale_EN;
}
Have created an LocaleController which takes a string locale "EN" or "DE" as parameter. Then I'm injecting a service for the controller, which will, based on the locale string choose which method to run (For now I'm only sending back the LocaleEN dictionary).
How can I create an value in my Angular 2 application which should be EN as default and should be changeable?
By changeable, you should be able to set it in the URL or some sort of, like:
localhost:3000/amendment?locale=DE
There are several things here:
You could HTTP content negotiation Conneg - See this link: http://restlet.com/blog/2015/12/10/understanding-http-content-negotiation/) and the Accept-Language to tell the server which messages to return.
You need to wait for messages to be there before displaying the screen with for example: <div ngIf="messages">…</div>
I don't think you need to implement a pipe to display messages if they are defined in a map (key / value): {{messages['SOME_KEY']}}
If messages correspond to list a custom filtering pipe can be implemented and used like that: {{messages | key:'SOME_KEY'}}
The implementation of this pipe could be:
#Pipe({name: 'key'})
export class KeyPipe implements PipeTransform {
transform(value, args:string[]) : any {
// Assuming the message structure is:
// { key: 'SOME_KEY', value: 'some message' }
return value.find((message) => {
return (message.key === args[0]);
});
}
}