Optional Input Parameters in Procedures/Apps - openedge

I've being doing some research about this, but all the results are from a couple of years ago.
Is there a way to define an input parameter as optional when calling a Procedure or a .W file?
My program works in this way - I have an external procedure that sends email using SMTP and it needs a few input parameters (Like Subject, Send To, etc) and I need to add a new input field that will add an attachment for the email.
To not edit and compile old codes, is possible to create this new parameter as optional and if it doesn't receive any value the program consider as blank?
DEFINE INPUT PARAMETER p_tipo_alerta AS CHAR.
DEFINE INPUT PARAMETER p_grupo_email AS CHAR.
DEFINE INPUT PARAMETER p_obs AS CHAR.
DEFINE INPUT PARAMETER p_site AS CHAR.
DEFINE INPUT PARAMETER p_CodeRequest AS CHAR.
DEFINE INPUT PARAMETER p_DescRequest AS CHAR.
// New-Optional
DEFINE INPUT PARAMETER p_AttachPath AS CHAR.
Thanks!

The ABL language does not support this kind of programming. The closest you can come is using classes and have multiple overloaded methods, which then call the 'primary' method passing in whatever the default values are.
class Foo:
method public void m1(p1 as character):
define variable nullObj as Object.
define variable defaultDate as date initial today.
this-procedure:m1(p1, defaultDate, nullObj, nullObj).
end method.
method public void m1(p1 as character, p2 as date).
define variable nullObj as Object.
this-procedure:m1(p1, p2, nullObj, nullObj).
end method.
method public void m1(p1 as character, p2 as date, p3 as Object).
define variable nullObj as Object.
this-procedure:m1(p1, p2, p3, nullObj).
end method.
method public void m1(p1 as character, p2 as date, p3 as Object, p4 as Object):
// does what it does
end method.
end class.
Doing this with procedural code is more complex since (internal) procedure names cannot be overloaded, so the names needs need to be unique.
If you don't want to change the calling code, though, you may be out of luck.

Related

how can I override the toString method in openedge?

I have a serializable class that I would like to provide my own toString when being serialized to JSON.
DEFINE PUBLIC PROPERTY address1 AS CHARACTER NO-UNDO
GET.
SET.
METHOD PUBLIC OVERRIDE CHARACTER toString( ):
DEFINE VARIABLE result AS CHARACTER NO-UNDO.
RETURN address1 + address2 + city + country.
END METHOD.
END CLASS. ```
I am also assigning the class to a temptable and using the write-json method of a dataset to output but I get the standard toString representation .."myClass": {
"prods:objId": 1,
"myClass": {
"address1": "xxxxx"
}
}
can I somehow override the toString being used ?
The JsonSerializer does not use ToString() ,nor does it give you any control over the format that's produced. The Serialize method describes what data is written. If you want this ability added into the ABL, you can add an "Idea" at https://openedge.ideas.aha.io/ideas ; OE product management review these ideas periodically.
If you want control today over what is written, you will need to roll your own. By way of example, OE has the IJsonSerializer interface, which allows types to declare that they can be serialised using the JsonSerializer class.

Provide a Converter for data-binding by defining a pair of SerializableFunction objects

In Vaadin 8 Framework, and Vaadin 10 Flow, the data-binding capability lets us provide a Converter to mediate between the widget’s expected data type (such as String for a TextField) and the data type of the backing bean property (such as Integer number).
In this example, the built-in Converter implementation StringToIntegerConverter is used.
binder
.forField( this.phaseField )
.withConverter(
new StringToIntegerConverter( "Must enter an integer number" )
)
.bind( Panel::getPhase , Panel::setPhase ) ;
But what about defining a Converter for other types? How can I easily define a short-and-sweet Converter? For example, a String-to-UUID converter. I want to show the canonical 36-character hex string in a TextField, and going the other direction, parse that string back into a UUID.
// String to UUID
UUID uuid = UUID.fromString( myString ) ;
// UUID to String
String myString = uuid.toString() ;
I see that Binder.BindingBuilder offers the pair of methods withConverter that both take a pair of SerializableFunction objects.
Binder.BindingBuilder::withConverter(SerializableFunction<TARGET,NEWTARGET> toModel, SerializableFunction<NEWTARGET,TARGET> toPresentation)
Binder.BindingBuilder::withConverter(SerializableFunction<TARGET,NEWTARGET> toModel, SerializableFunction<NEWTARGET,TARGET> toPresentation, String errorMessage)
➥ So how do I define the pair of SerializableFunction objects/classes?
I noticed that this interface lists a known subinterface ValueProvider<SOURCE,TARGET>. That looks familiar, and I have a hunch it is the key to easily defining a short simple converter. But I do not quite comprehend the syntax with lambdas and all that is going on here.
I am not asking how to write a class implementing Converter. I am asking how to write the pair of SerializableFunction arguments to pass to the Binder.BindingBuilder::withConverter methods listed above as bullet items.
Quoting that JavaDoc:
Interface Binder.BindingBuilder<BEAN,TARGET>
…
withConverter
default <NEWTARGET> Binder.BindingBuilder<BEAN,NEWTARGET> withConverter(SerializableFunction<TARGET,NEWTARGET> toModel, SerializableFunction<NEWTARGET,TARGET> toPresentation)
Maps the binding to another data type using the mapping functions and a possible exception as the error message.
The mapping functions are used to convert between a presentation type, which must match the current target data type of the binding, and a model type, which can be any data type and becomes the new target type of the binding. When invoking bind(ValueProvider, Setter), the target type of the binding must match the getter/setter types.
For instance, a TextField can be bound to an integer-typed property using appropriate functions such as: withConverter(Integer::valueOf, String::valueOf);
Type Parameters:
NEWTARGET - the type to convert to
Parameters:
toModel - the function which can convert from the old target type to the new target type
toPresentation - the function which can convert from the new target type to the old target type
Returns:
a new binding with the appropriate type
Throws:
IllegalStateException - if bind has already been called
You can do it by passing two lambda expressions to withConverter, so something like this:
binder.forField(textField)
.withConverter(text -> UUID.fromString(text), uuid -> uuid.toString())
.bind(/* ... */);
If you need a more complicated conversion, then the right-hand side of the lambda can be surrounded with brackets, e.g.
binder.forField(textField).withConverter( text -> {
if ( text == null ) {
return something;
} else {
return somethingElse;
}
}, uuid -> { return uuid.toString(); } )
.bind(/* ... */);
If you need your converter multiple times, I recommend creating a separate class implementing interface com.vaadin.data.Converter. However, using lambdas is possible, too, as you already know (see answer of #ollitietavainen). But this is not Vaadin specific, it's a Java 8+ feature you can read about e.g. here. Basically, you can use lambdas whereever an object implementing an interface with only one method is required.

Progress OpenEdge passing a dataset as parameter looses values

I am trying to pass a dataset as a parameter to a class, but the dataset keeps loosing it's values.
The idea is to put a customer number in the dataset, pass the dataset to the server and let the server fill the dataset with all the customer information and then pass it back to the client.
First calling procedure
This is a unittest procedure calling the ServiceInterface in the server.
USING OpenEdge.Core.Assert.
BLOCK-LEVEL ON ERROR UNDO, THROW.
{USS/Common/Invoice/Include/dsInvoice.i}
DEFINE VARIABLE hProc AS HANDLE NO-UNDO.
RUN USS/Server/Invoice/ServiceInterfaces.p PERSISTENT SET hProc.
TEMP-TABLE ttInvoice:TRACKING-CHANGES = TRUE.
ttInvoice.CustomerNr = CustomerNr.
TEMP-TABLE ttInvoice:TRACKING-CHANGES = FALSE.
RUN UpdateCustomer IN hProc(INPUT CustomerNr, INPUT-OUTPUT DATASET dsInvoice BY-VALUE).
Assert:Equals("MIDDELLANDBAAN 1 B", ttInvoice.DeliveryStreet).
DELETE PROCEDURE hProc.
Service interface on the server
At this moment the dataset still contains all the values. These values are passed to a Business Entity where other values should be added.
PROCEDURE UpdateCustomer:
DEFINE INPUT PARAMETER CustomerNr AS INT.
DEFINE INPUT-OUTPUT PARAMETER DATASET-HANDLE phdsInvoice.
USS.Server.Invoice.BusinessEntity.InvoiceEntity:Instance:UpdateCustomer(INPUT CustomerNr, INPUT-OUTPUT DATASET dsInvoice BY-REFERENCE).
RETURN.
END PROCEDURE.
Business entity
The Business Entity is a singleton, containing an UpdateCustomer method.
When the dataset is passed to this method, it is completely empty.
USING Progress.Lang.*.
USING USS.Common.Interfaces.IBusinessEntity.
USING USS.Server.Invoice.DataAccess.InvoiceBE-DA.
BLOCK-LEVEL ON ERROR UNDO, THROW.
CLASS USS.Server.Invoice.BusinessEntity.InvoiceEntity IMPLEMENTS IBusinessEntity:
{ USS\Common\Invoice\Include\dsInvoice.i &CLassAccess = "private" }
DEFINE PRIVATE VARIABLE InvoiceDA AS InvoiceBE-DA NO-UNDO.
DEFINE PRIVATE VARIABLE hDSEventHandlers AS HANDLE NO-UNDO.
DEFINE PUBLIC STATIC PROPERTY Instance AS USS.Server.Invoice.BusinessEntity.InvoiceEntity
GET.
PRIVATE SET.
CONSTRUCTOR STATIC InvoiceEntity ():
USS.Server.Invoice.BusinessEntity.InvoiceEntity:Instance = NEW USS.Server.Invoice.BusinessEntity.InvoiceEntity().
END CONSTRUCTOR.
CONSTRUCTOR PUBLIC InvoiceEntity ():
SUPER().
InvoiceDA = NEW InvoiceBE-DA().
END CONSTRUCTOR.
METHOD PUBLIC VOID UpdateCustomer(INPUT pCustomerNr AS INT, INPUT-OUTPUT DATASET dsInvoice ):
DEF VAR hUpdateCustomerService AS HANDLE NO-UNDO.
RUN USS/Server/Invoice/Services/UpdateCustomer.p PERSISTENT SET hUpdateCustomerService.
RUN UpdateCustomer IN hUpdateCustomerService (INPUT pCustomerNr, INPUT-OUTPUT DATASET dsInvoice BY-REFERENCE).
RETURN.
END METHOD.
END CLASS.
I have been working on this for a while now and I hope someone can help me figure this one out.
Yes, as Tim mentioned, you use 2 different datasets in internal procedure UpdateCustomer in ServiceInterfaces.p.
You can change the PARAMETER DATASET-HANDLE to:
DEFINE INPUT-OUTPUT PARAMETER DATASET FOR dsSelectionList.

Can I cast a string object passed on command line argument to the actual object?

Is it possible to cast a command-line passed string object back to actual object?
I want to do the following, but throwing error can't cast.
Button objPro = (Button) sender;
cProduct cp = (cProduct) objPro.CommandArgument;
If no, then why?
This is what the string holds.
cProduct cpObj = (cProduct)e.Row.DataItem;
Button btnAddProduct = (Button)e.Row.FindControl("btnAddProduct");
if (btnAddProduct != null)
{
btnAddProduct.CommandArgument = cpObj.ToString();
}
You probably can't, because it's a string. It's not a cProduct (whatever that is - consider following .NET naming conventions and naming it Product instead).
Now you could do this if you had a explicit conversion operator in cProduct to create an instance from a string.
You haven't really explained what's in the string, or what's in the type - but if your cProduct type provides a ToString method which contains all the data in a reversible form, then you could easily write a method or a constructor to create the product again:
Product product = new Product(objPro.CommandArgument);
or maybe:
Product product = Product.Parse(objPro.CommandArgument);
You'll have to write that constructor/method, of course.
I would strongly recommend using a constructor or method instead of an operator, just to keep your code clearer - it's very rarely a good idea to write your own conversion operators.
Take a look at CommandArgument on MSDN. The property is a string, when you assign the a value to the property, you aren't casting some complex type to string, you are setting a string value on the property. Can you cast a string back to your object type anyway, regardless of it being a CommandArgument. I doubt it. If the argument is an int you could try int.Parse or similar for other types which have a parse method.

How to set an empty Object's properties programatically?

I'm doing some Actionscript work right now and I'd like to know whether there's a way to initiate an empty object's value programatically like this:
var myObj:Object = new Object;
myObj.add("aKey","aValue");
To add a property called aKey whose value is aValue
I need to create a "Dumb" (data-only) object to use as a parameter to send via POST. So I don't know offhand how long and/or how many attributes it's gonna have.
Or something like that.
Thanks
ActionScript 3 allows you to create new Objects using an expressive Object Literal syntax similar to the one found in JavaScript:
const myObj : Object = {
aKey: "aValue",
};
trace(myObj.aKey); // "aValue"
If you want to assign properties after the object has been constructed then you can use either dot notation or square bracket notation, eg:
const myObj : Object = {}; // create an empty object.
myObj.aKey = "aValue";
myObj["anotherKey"] = "anotherValue";
If you plan on sending the data over HTTP, you may wish to consider looking at the URLVariables class which will take care of URL encoding the data for you.

Resources