Include SAP Gateway labels in Breeze custom metadata - angularjs

I am building an Angular 1.3.5 app using Breeze 1.5.1 to interface with a SAP gateway server. One requirement is to minimize duplicate UI translations by re-using the human-readable metadata labels provided by the server. From what I can gather, Breeze's support for custom metadata properties should allow me to load these labels alongside all the standard entity metadata, but I'm struggling to work out exactly how to approach this.
The metadata service provides data in the following format:
<EntityType Name="ContactPersonEmail" sap:content-version="1">
<Key>
<PropertyRef Name="email"/>
</Key>
<Property Name="cpGuid" Type="Edm.Guid" sap:label="Long Text String for XML and HTML Output"/>
<Property Name="primaryEmail" Type="Edm.Boolean" Nullable="false" sap:label="Standard No."/>
<Property Name="email" Type="Edm.String" Nullable="false" MaxLength="241" sap:label="E-Mail Address"/>
<Property Name="homeIndicator" Type="Edm.Boolean" Nullable="false" sap:label="Home address"/>
<Property Name="location" Type="Edm.String" Nullable="false" MaxLength="2" sap:label="Email Type"/>
<Property Name="emailType" Type="Edm.String" Nullable="false" MaxLength="2" sap:label="Email Type"/>
<Property Name="emailTypeText" Type="Edm.String" Nullable="false" MaxLength="60"/>
</EntityType>
where the sap:label attribute is the text I want to add to the Breeze entity type. Is this feasible? The Breeze documentation mostly seems to be focused on loading custom metadata from a local file or already-loaded data, rather than including an additional property on data load, so perhaps this approach is naive.
Once this is resolved I'll work out how to get the text onto the page, but that's for later.
Update: Solution
Per Jeremy's answer below, I was able to run the metadata response through an initialisation function and capture the label field via the metadataProperty's extension property. Example (pre-refactor) code as follows:
// ...
if (entityProperty) {
if (typeof metadataProperty.extensions !== "undefined" && metadataProperty.extensions.length) {
var extension = metadataProperty.extensions[0];
entityProperty[extension.name] = extension.value;
}
}
// ...
The extension.name in this instance is "label", and it's the only extension in the array where it appears. Now all that remains is to get the data onto the UI, but I'm sure that'll be trivial :)

I think this is possible although I'll admit I have only supplemented the Breeze Metadata JSON schema, never XML.
When fetching the metadata on the client you'll just need to do a bit of additional processing to supplement the breeze entity type with your custom metadata properties. In the example code below four custom metadata props are added: displayName, displayOrder, autoGenerateField and allowEmptyStrings.
function initializeMetadataStore(metadataStore, metadata) {
var metadataType, metadataProperty, entityProperty, i, j;
for (i = 0; i < metadata.schema.entityType.length; i++) {
metadataType = metadata.schema.entityType[i];
var entityType = metadataStore.getEntityType(metadataType.name);
for (j = 0; j < metadataType.property.length; j++) {
metadataProperty = metadataType.property[j];
entityProperty = entityType.getProperty(metadataProperty.name);
if (entityProperty) {
if (typeof metadataProperty.displayName !== 'undefined') {
entityProperty.displayName = metadataProperty.displayName;
}
if (typeof metadataProperty.displayOrder !== 'undefined') {
entityProperty.displayOrder = metadataProperty.displayOrder;
}
if (typeof metadataProperty.autoGenerateField !== 'undefined') {
entityProperty.autoGenerateField = metadataProperty.autoGenerateField;
}
if (typeof metadataProperty.allowEmptyStrings !== 'undefined') {
entityProperty.allowEmptyStrings = metadataProperty.allowEmptyStrings;
}
}
}
}
}
var entityManager = ....something...;
entityManager.fetchMetadata(function (metadata) {
return initializeMetadataStore(entityManager.metadataStore, metadata);
});
Here's my answer to a similar question however the OP's backend is .NET and they're using the Breeze Metadata JSON schema.

Related

Custom Salesforce Lightning App giving "You dont have access to this record"

Custom Lightning App named "Stack" giving "You dont have access to this record Contact "
Trying to follow steps in How To Implement Full Search in Case Type using Salesforce?
Here is Org wide defaults of the custom object ERT Case Type data
Here is Apex code of stack.aspx
public class Stack {
#AuraEnabled(cacheable=true)
public static List<LookupSearchResult> search(String searchTerm, List<String> selectedIds){
if(String.isBlank(searchTerm) || searchTerm.length() < 2){
return null;
}
String t = '%' + searchTerm + '%'; // decide how you want to search, "starts with", "includes" or what
List<ERT_Case_Type_Data__c> records = [SELECT Id, Name, Level_1__c, Level_2__c, Level_3__c
FROM ERT_Case_Type_Data__c
WHERE Level_1__c LIKE :t OR Level_2__c LIKE :t OR Level_3__c LIKE :t
ORDER BY Level_1__c, Level_2__c, Level_3__c
LIMIT 20];
/* You could also experiment with SOSL?
records = [FIND :('*' + searchTerm + '*') IN ALL FIELDS
RETURNING Case_Type_Data__c(Id, Name, Level_1__c, Level_2__c, Level_3__c)][0];
*/
List<LookupSearchResult> results = new List<LookupSearchResult>();
for(ERT_Case_Type_Data__c ctd : records){
results.add(new LookupSearchResult(ctd.Id, 'ERT_Case_Type_Data__c', 'standard:case_wrap_up', ctd.Name,
String.join(new List<String>{ctd.Level_1__c , ctd.Level_2__c, ctd.Level_3__c}, '; ')
));
}
return results;
}
}
Here is Aura component(html part)
<aura:component implements="force:hasRecordId,force:appHostable,flexipage:availableForAllPageTypes,force:lightningQuickAction" access="global" controller="Stack">
<aura:attribute access="global" type="List" name="selection" default="[]"/>
<aura:attribute access="global" type="List" name="errors" default="[]"/>
<lightning:card title="New Case Type">
<lightning:recordEditForm aura:id="myForm" objectApiName="ERT_Case_Type__c" onsubmit="{!c.onSubmit}" onsuccess="{!c.onSuccess}">
<lightning:messages />
<c:Lookup selection="{!v.selection}" onSearch="{!c.lookupSearch}" onSelection="{!c.useSelected}" errors="{!v.errors}" label="Search" placeholder="Search Case Types Data"/>
<lightning:inputField aura:id="Level_1__c" fieldName="Level_1__c" />
<lightning:inputField aura:id="Level_2__c" fieldName="Level_2__c" />
<lightning:inputField aura:id="Level_3__c" fieldName="Level_3__c" />
<lightning:button class="slds-m-top_small" variant="brand" type="submit" name="save" label="Save" />
</lightning:recordEditForm>
</lightning:card>
</aura:component>
Here is Aura component - JS controller part
({
lookupSearch : function(component, event, helper) {
// Get the lookup component that fired the search event
const lookupComponent = event.getSource();
const serverSearchAction = component.get('c.search');
lookupComponent.search(serverSearchAction);
},
useSelected: function(component, event, helper) {
const selection = component.get('v.selection');
const errors = component.get('v.errors');
if (selection.length) {
if(errors.length){ // Clear errors, if any
component.set('v.errors', []);
}
let levels = selection[0].subtitle.split('; ');
component.find('Level_1__c').set('v.value', levels[0]);
component.find('Level_2__c').set('v.value', levels[1]);
component.find('Level_3__c').set('v.value', levels[2]);
}
},
onSubmit: function(component, event, helper) {
debugger;
event.preventDefault(); // stop the form from submitting
var fields = event.getParam('fields');
fields.Case__c = component.get('v.recordId'); // link to "this" Case
component.find('myForm').submit(fields);
},
onSuccess: function(component, event, helper){
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
"title": "Success!",
"message": "Case Type saved OK, refreshing",
"type": "success"
});
toastEvent.fire();
$A.get('e.force:refreshView').fire(); // reload page
}
})
Please help me in removing this access error
Regards,
Carolyn
I doubt it's sharing related (so not the org-wide settings). If it was sharing it'd simply always return 0 results but no big red errors.
If you remove the line with <c:Lookup selection="{!v.selection}" onSearch="{!c.lookupSearch}" onSelection="{!c.useSelected}" errors="{!v.errors}" label="Search" placeholder="Search Case Types Data"/> does the error go away? If it stays - it's problem with permissions around the custom code or Case_Type_Data__c). If it goes away - it's something with creating ERT_Case_Type__c)
Check the Profile (or Permission Sets if you use them) rights to:
Read the source object (Case_Type_Data__c) and all referenced fields (Level_1__c...)
Create the target object (ERT_Case_Type__c) and read/edit all referenced fields (Level1__c... but also Case__c)
Read on the Case object and Case.CaseNumber, Case.Subject fields
Permission to execute the Apex class (renamed to Stack, right?). And maybe add permission to run LookupSearchResult too.
I suspect you have enabled some critical updates (Setup -> Critical updates or Setup -> Release Updates) like "Restrict Access to #AuraEnabled Apex Methods for Authenticated Users Based on User Profile" or "Require Permission to View Record Names in Lookup Fields"

Display value as returned from controller method

Beginner in Salesforce so please bear with me.
I have created a lightning component and I would like to display on a page a value as returned by a component controller.
public class My_Controller {
#AuraEnabled
public static Decimal getRate(String currFrom, String currTo) {
Decimal value = 1.067773;
return value;
}
}
<aura:component controller="My_Controller">
<lightning:input type="string" name="res" aura:id="res" value= "
{!c.My_Controller.getRate('A', 'B')}" label="Result"/>
But it could not be so simple :) as I get: "Failed to save Rate.cmp: unexpected token: '(' at column 46 of expression: c.My_Controller.getRate('A', 'B'): Source"
What is the proper way to call the method?
You cannot call an Apex server controller method directly from Lightning clientside markup.
Instead, you'd need to declare an <aura:attribute> in your component markup and bind the value to that attribute.
<aura:attribute name="rate" type="String" />
<lightning:input type="string" name="res" aura:id="res" value="{! v.rate }" label="Result"/>
Then, your JavaScript client-side controller needs to make a server-side call, asynchronously, to get the value from Apex. Finally, the Lightning JavaScript callback from that async method would populate the return value into the <aura:attribute>, and the framework's data binding infrastructure will take care of updating the <lightning:input>.
It sounds complex, but it's mostly boilerplate code. The documentation linked above includes detailed examples.
public class My_Controller {
#AuraEnabled
public static Decimal getRate(String currFrom, String currTo) {
Decimal value = 1.067773;
return value;
}
}
<aura:component controller="My_Controller">
<aura:attribute name = "value" type= "Decimal"/>
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<lightning:input type="string" name="res" aura:id="res" value= "
{!v.value}" label="Result"/>
<aura:component>
add a new method in controller.js:-
({
doInit : function(component, event, helper) {
var action = component.get("c.getRate");
action.setParams({
"currFrom": 'Test',
"currTo" : 'Test'
});
action.setCallback( this, function(actionResult) {
var state = actionResult.getState();
component.set('v.spinner',false);
if (state === "SUCCESS"){
var result = actionResult.getReturnValue();
component.set("v.value",result);
}
});
$A.enqueueAction(action);
}
})

AvalonEdit - xshd for JSON highlighting

Is there an xshd ruleset for the AvalonEdit control to highlight the JSON syntax? I tried the definition for JavaScript, but it doesn't work well, i.e.:
{
"name" : "value"
}
both name and value have the same color using the JavaScript definition.
Is there a ruleset for JSON, and if not, how can I modify the xshd so that I get different colors for the name and value in JSON?
If somebody needs something like that, I worked it out in following way:
<?xml version="1.0" encoding="utf-8" ?>
<SyntaxDefinition name="Json" extensions=".js" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Digits" foreground="#8700FF" exampleText="3.14" />
<Color name="Value" foreground="#000CFF" exampleText="var text = "Hello, World!";" />
<Color name="ParamName" foreground="#057500" exampleText="var text = "Hello, World!";" />
<RuleSet ignoreCase="false">
<Keywords color="Digits" >
<Word>true</Word>
<Word>false</Word>
</Keywords>
<Span color="ParamName">
<Begin>"</Begin>
<End>(?=:)</End>
</Span>
<Span color="Value" multiline="true">
<Begin>
(?<=:)\040"[^"]*
</Begin>
<End>"</End>
</Span>
<Rule color="Digits">\b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?</Rule>
</RuleSet>
</SyntaxDefinition>
Not perfect, but for me enough.
Just use AvalonEdit's JavaScript highlighter.
Sample code:
using (var stream = Assembly.GetAssembly(typeof(ICSharpCode.AvalonEdit.TextEditor)).GetManifestResourceStream("ICSharpCode.AvalonEdit.Highlighting.Resources.JavaScript-Mode.xshd"))
{
using (var reader = new XmlTextReader(stream))
{
avalonEdit.SyntaxHighlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance);
SearchPanel.Install(avalonEdit);
}
}

wix bootstrapper application - install multiple packages on checkboxes

I'm a newbie to Wix burn. I'm making a wix Bootstrapper Application (BA) with multiple msi to install and an UI consisting of one panel with checkboxes and a button install. Each checkbox invites the user to select/unselect a msi, then the user presses "Install" and my BA should install the checked msi.
In my Chain element in the main .wxs file, I plan to use MsiPackage elements with a condition attribute to determine whether yes or no the user has selected it.
Now my question is : how to interface these condition attributes with the checkboxes ? Or in other words: how to get the checkbox information into the Wix .wxs file ?
I can explain how I do it. May there's a better way.
My checkBoxes are bound to properties in a ViewModel class. When a checkBox value is changed in the setter of the property I set the value of a variable
defined in the Bundle.wxs file.
private bool _installApp1Checked;
public bool InstallApp1Checked
{
get { return _installApp1Checked; }
set
{
_installApp1Checked = value;
if (value == true)
{
Bootstrapper.Engine.StringVariables["InstallApp1"] = "1";
}
else
{
Bootstrapper.Engine.StringVariables["InstallApp1"] = string.Empty;
}
RaisePropertyChanged("InstallApp1Checked");
}
}
private bool _installApp2Checked;
public bool InstallApp2Checked
{
get { return InstallApp2Checked; }
set
{
_installApp2Checked = value;
if (value == true)
{
Bootstrapper.Engine.StringVariables["InstallApp2"] = "1";
}
else
{
Bootstrapper.Engine.StringVariables["InstallApp2"] = string.Empty;
}
RaisePropertyChanged("InstallApp2Checked");
}
}
private bool _installApp3Checked;
public bool InstallApp3Checked
{
get { return _installApp3Checked; }
set
{
_installApp3Checked = value;
if (value == true)
{
Bootstrapper.Engine.StringVariables["InstallApp3"] = "1";
}
else
{
Bootstrapper.Engine.StringVariables["InstallApp3"] = string.Empty;
}
RaisePropertyChanged("InstallApp3Checked");
}
}
And in the Bundle.wxs I have:
<Wix ...>
<Bundle ...>
...
<Chain>
...
<MsiPackage>
...
<MsiProperty Name="InstallApp1" Value="[InstallApp1]"/>
<MsiProperty Name="InstallApp2" Value="[InstallApp2]"/>
<MsiProperty Name="InstallApp3" Value="[InstallApp3]"/>
...
</MsiPackage>
</Chain>
</Bundle>
</Wix>
By using the tag the properties of the ViewModel class are available in the wsx file.
Then these values are available at the moment of the installation in my product.wxs:
<Product >
...
<Property Id="InstallApp1">
</Property>
<Property Id="InstallApp2">
</Property>
<Property Id="InstallApp3">
</Property>
<Feature Id="ProductFeature" Title="Alvenos" Level="0">
<ComponentRef Id="ProductComponents" />
<Condition Level="1">InstallApp1</Condition>
</Feature>
<Feature Id="AlvenosVSIXFeature" Title="Alvenos" Level="0">
<ComponentRef Id="AlvenosVsix" />
<Condition Level="1">InstallApp2</Condition>
</Feature>
<Feature Id="AlvenosServerVSIXFeature" Title="Alvenos" Level="0">
<ComponentRef Id="AlvenosServerVsix" />
<Condition Level="1">InstallApp3</Condition>
</Feature>
...
</Product>
You can see the the default value of the Level attribute of the Feature tag is set to 0. That means that the app will not be istalled.
But if in the Condition tag InstallApp[1],[2] or [3] is set 1 the Level is set to 1 and the app is installed.
Use ComponentRef to refernce a Component tag that will contain information about the destination folder of the app that you will install.
<Fragment>
<ComponentGroup Id="InstallApp1" Directory="[target directory id]>
<Component Id="ProductComponent">
<File Source="[your app part of the installer]" />
</Component>
...
</Fragment>
I hope you get the idea.

How to bind form to model in NancyFX

I'm new to NancyFX and trying to simply bind a posted form to my model.
In the module when trying to access the posted values I run following statement:
string email = this.Context.Request.Form["Email"];
Debug.WriteLine(email);
Result is:
"Nancy.DynamicDictionaryValue" instead of posted value
Can anybody tell me what newbie mistake I'm doing:
The form looks like:
<form method="post" action="account">
<input type="text" id="Email" />
<input type="password" id="Password" />
<input type="submit" value="Create" />
</form>
the routing in Module contructor:
Post["/"] = parameters => CreateAccount(parameters);
The dynamic dictionary returns a dynamic value, if you cast it to a string (implicitly or explicitly) you'll get what you want, or just use the build in model binder https://github.com/NancyFx/Nancy/wiki/Model-binding
Just adding to the correct answer above in the hope it is useful to nancy-newbies like me.
Because the Nancy Form and Query are of type dynamic you can access the values using the name of the form or query-string param (see terms and max in the example code). I use a simple base class just to make the syntax terser throughout the rest of my modules.
Note: The ExpandoObject Model in the base class is there so I can just throw values at my view-model and not have to worry about cluttering things up with strongly typed data-transfer classes (this also helps prevent exposing any secret domain instance data).
public class SearchModule : _BaseModule
{
public SearchModule(ISearchService searchService)
{
Get["/search"] = _ =>
{
if (!Query.terms.HasValue) return HttpStatusCode.BadRequest;
var terms = (string) Query.terms;
var max = (Query.max.HasValue) ? (int) Query.max : 3;
Model.SearchResults = searchService.GetResults(max, terms);
...
};
}
}
public class _BaseModule : NancyModule
{
protected dynamic Model = new ExpandoObject();
public dynamic Query { get { return Request.Query; } }
public dynamic Form { get { return Request.Form; } }
}

Resources