Adding a button to Navigator to exercise a choice - daml

Navigator contains a feature where users can define their own table views, see DAML docs for Navigator.
Is it possible to create a view where one column renders a button that, when clicked, immediately exercises a choice?

Yes, this is possible. The customized views allow you to render arbitrary React components, so let's create one to exercise a choice.
First, start with a working frontend-config.js file. The DAML quickstart project contains one.
Then, make sure you import at least the following symbols at the top of the file:
import React from 'react';
import { Button, DamlLfValue, withExercise } from '#da/ui-core';
Then, define the following top level values (for example, just below export const version={...}):
// Create a React component to render a button that exercises a choice on click.
const ExerciseChoiceButtonBase = (props) => (
<Button
onClick={(e) => {
props.exercise(props.contractId, props.choiceName, props.choiceArgument);
e.stopPropagation();
}}
>
{props.title}
</Button>
)
ExerciseChoiceButtonBase.displayName = 'ExerciseChoiceButtonBase';
// Inject the `exercise` property to the props of the wrapped component.
// The value of that property is a convenience function to send a
// network request to exercise a choice.
const ExerciseChoiceButton = withExercise()(ExerciseChoiceButtonBase)
ExerciseChoiceButton.displayName = 'ExerciseChoiceButton';
Finally, use the following code in your table cell definition:
{
key: "id",
title: "Action",
createCell: ({rowData}) => {
// Render our new component.
// The contract ID and choice argument are computed from the current contract row.
return ({
type: "react",
value: <ExerciseChoiceButton
title='Transfer to issuer'
contractId={rowData.id}
choiceArgument={
DamlLfValue.record(undefined, [
{label: 'newOwner', value: DamlLfValue.party(DamlLfValue.toJSON(rowData.argument).issuer)}
])
}
choiceName='Iou_Transfer'
/>
});
},
sortable: true,
width: 80,
weight: 3,
alignment: "left"
}
Another option would be create a React component where the onClick handler sends a REST API request using fetch(). Inspect the network traffic when exercising a choice through the Navigator UI in order to find out the format of the request.

Related

Manage to access data and send from different components and handle them

I'm trying to create a layer where I can handle different actions from users.
From example, when you click a div, creates an layer of type 1 and then sends it to the layer component to start showing a list of dropdown (for example).
As I'm new in react, I've did something, but it thrown me tons of errors and it doesn't seems to work.
This is what I've build: https://codesandbox.io/s/laughing-agnesi-47suvp
What I want is this:
When you click a .group-item (from Header component) save as an object the ID of the player which .group-item you clicked correspond (also the clientX and clientY of the .group-item) and then pass a function to display the .dropdown inside layers component which will also have the data you stored from the Header component (ID, clientX, clientY)
There are indeed several mistakes:
In some places you access layer.type, but you create a layer with property layerType instead; make sure to be consistent
when you add a new layer, make sure to build an array:
const addNewLayer = (object) => {
listLayers((layerObject) => ([ // Note the square brackets, instead of curly braces
...layerObject, // Previous array of layers
object // New layer to be added
]));
};
In Layers.js, do not change a state directly at the root of your function component body: this creates an infinite rendering loop (the function re-runs whenever its state changes); in your case, you can wrap it in a useEffect that depends on layers prop:
useEffect(() => {
layers.forEach((l) => {
if (l.layerType == 1) {
console.log("layer type 1 found! .. set .dropdown to be seen");
setLayerActive(true);
}
});
}, [layers]); // Re-run only if the list of layers changes
Demo: https://codesandbox.io/s/quiet-browser-iiip34

Conditionally make a page read-only using react

I want to create a React webpage that has both editable and read-only versions, the whole page not just a few elements on the page. A version is displayed to the user based on user id and other conditions. How do I do it?
The only straight forward way I know is to create 2 pages one editable and one read-only and based on the condition show the appropriate version (html page) to the user.
Is there a better and smarter way to do this? Like can I create just one page for both versions and toggle the mode based on the condition to the users?
Your question should have provided an example of some code you had tried but based on the description, very rough example below of one of many possible solutions.
Suppose EditView component is your page and you are able to pass a value for permission based on whatever credential you need to apply.
Then you have a component, ExampleField that takes the permission and displays either an input or static text. A collection of multiple of these fields is mapped from a theoretical array of data that you'll have to fetch from somewhere and the fields are returned by the main component.
const EditView = ({permission}) => {
const [editable, setEditable] = useState();
const [values, setValues] = useState([]);
useEffect(() => {
setEditable(permission);
}, [permission]);
useEffect(() => {
//maybe fetch your data from a back end or whatever and assign it to `values`
//on page load
}, [])
const ExampleField = ({permission, val, index}) => {
const handleChange = (e) => {
let vals = [...values];
vals[index] = val;
setValues(vals);
}
return(
<>
{permission
? <input name="example" type="text" defaultValue={val}
onChange={handleChange} />
: <span>{val}</span>}
</>
)
}
const fields = values.map((value, i) => {
return <ExampleField permission={permission} val={value} index={i}/>
})
return(
<>
{fields}
</>
)
}
Most likely, you'll want to break out various field components into their own file and, instead of using useState, you would probably want to explore useContext or useStore type functionality to lift up your state and do all the react things.
*Haven't tested or even compiled this code - for illustration purposes only.

Is it possible to copy an object's properties from one component and display them in another component using refs?

Say I have 2 components. One is a table with a list of stores. Each store has properties like color, item, open, closed. The other component is one to create a store.
I want to be able to click on a little copy icon on one of the created stores already, and take that information to the create store component, and populate that component with the properties in order to make changes and create a completely new store.
Is this doable using refs? Or is there a better way of doing this?
Use ref to this task is a mistake. React works using a Virtual DOM that is a cleaner and faster Object Tree with information that will be through to DOM by React DOM the REF API is used to access direct the DOM information, and you don't need any information from DOM to do ur task.
https://reactjs.org/docs/refs-and-the-dom.html
A way yo do what you describe is create a state/setState on the parent component and pass a state for the store component and a setState to the table component for example:
import React, { useState } from 'react'
const StoreComponenet = ({ color, item, open})=>{
// logic of component
return (
<div>
// ...
</div>
)
}
const TableComponent = ({ setStore })=>{
// logic of component
return (
<table>
<tr onClick={() => setStore("blue", {id: 2, name: "BlueStore" }, false)}>
Build blue store
</tr>
...
</table>
)
}
const App = ()=>{
const [store, setStore] = useState(null)
return (
<TableComponent setStore={setStore} />
{
store &&
<StoreComponent
color={store?.color}
item={store?.item}
open={store?.open}
/>
}
)
}

adyen payment mount is not working inside reactjs

The following code is not rendering the html. I guess .mount is not re-render the html in the test-container class.
componentDidMount() {
const originKey = getOriginKey(this.props.subscriptionInfo);
const checkout = new window.AdyenCheckout({
locale: 'en-US',
originKey,
loadingContext: 'https://checkoutshopper
test.adyen.com/checkoutshopper/',
onChange: function() {},
onError: console.error
});
console.log(checkout);
window.securedFields = checkout
.create('securedfields', {
type: 'card',
groupTypes: ['mc', 'visa', 'amex', 'bcmc', 'maestro'],
allowedDOMAccess: false, // Whether encrypted blobs will be added to the DOM. OPTIONAL - defaults to false
autoFocus: true,
onFieldValid,
onConfigSuccess,
onAllValid,
onError
})
.mount('.test-container');
}
The .mount method appends iframes to existing form fields. In case of the secured field component, it mounts iframes to elements inside the Node that you pass to the .mount method (it uses document.querySelector internally).
This is more or less the structure from docs.
From my experience, it works on any element with correct data-cse attribute. Just put those elements in your render fn and mount Adyen checkout.

How to get client side data from Kendo Grid to controller

I am trying to get Kendo Grid data which is hydrated from client side to a MVC controller method. My view contains several single fields like name, date of birth etc and tabular field which I hooked with a Kendo Grid. Since its a new operation I have no data in the grid ( and other fields) and user enters them from client side.
I have no idea how to proceed on this. Ideally I would like to get this data to a list in my viewmodal. So that when the user hits save, I have all other data and the grid data coming into a controller method.
I am able to successfully bind a list with kendo grid and display it. I have very little experience on JavaScript and Kendo and web programming.
If any of you can point me to the right direction, sample code would be greatly appreciated.
$("#departmet").kendoGrid({
dataSource: dataSource,
height: 250,
scrollable: true,
sortable: true,
filterable: true,
pageable: {
input: true,
numeric: false
},
columns: [
"DepartmentName",
"SubDivision"
]
});
From experience I know their documentation is not easy to navigate. It seems there is the documentation and then the API. The API is usually what you will always want to find. What you will need is the information from here https://docs.telerik.com/kendo-ui/api/javascript/ui/grid. If I understand the question correctly. There are several ways you can achieve posting. You could make use of editor templates. Click the Open in Dojo to get an idea how it looks.
https://docs.telerik.com/kendo-ui/api/javascript/ui/grid/configuration/editable.template
With this you do not have to worry about modifying the data via javascript. Assuming your grid is surrounded with a form element it will get posted when submitted. Note paging is not accounted for here. Also, this method by default can auto post after each edit. If you don't want this behavior then you will have to have advanced knowledge of the API.....Correction on that last statement. The API is different when dealing with the data all on the client side. Click the Open in Dojo to see it all on the client side. If you are not wanting to use editor templates and want to manage the data editing yourself then you need to use the grid methods provided.
Once you have your grid created. To access the data source of the grid you will need to get the dataSource.
$('#departmet').data('kendoGrid').dataSource;
https://docs.telerik.com/kendo-ui/api/javascript/data/datasource
If you need to use a different data source(or change it) you can use the setDataSource method below(grid function).
https://docs.telerik.com/kendo-ui/api/javascript/ui/grid/methods/setdatasource
To add to the data source use the add function to add a new object.
$('#departmet').data('kendoGrid').dataSource.add({ id: 2, name: 'name'});
https://docs.telerik.com/kendo-ui/api/javascript/data/datasource/methods/add
It is important with kendo to ALWAYS use the methods provided to change the data source so that the proper events can fire to update the UI accordingly. This includes if you need to set a property on a specific data item. In that case you need to use the set method on the item itself.
After you are done modifying your data. Within javascript get the data and either create DOM elements within a form
//JQuery sudo code example
var data = $("#departmet").data("kendoGrid").dataSource.data();
var dataLen = data.length;
var myForm = $('#my-form'); //Already within DOM
for (var i = 0; i < dataLen; i++) {
var item = data[i];
var idEl = $('<input type="hidden" name="userData[' + i + '].id" />');
idEl.val(item.id);
var nameEl = $('<input type="hidden" name="userData[' + i + '].name" />');
nameEl.val(item.name);
myForm.append(idEl);
myForm.append(nameEl);
}
myForm.submit();
This assumes your controller function(??) on the backend is expecting an array of objects with the property name of userData.
Alternatively, you can post it via ajax. For example, the ajax jquery function. Passing your data as the data of the ajax call.
http://api.jquery.com/jquery.ajax/
Don't want to ramble. Let me know if you need more help.
SO won't let me comment yet so have to add another answer. You will not need to define the data source within the .NET code when dealing with client only data. Just use this.
.DataSource(dataSource => dataSource
.Ajax()
.ServerOperation(false)
)
If you will have data coming from the backend then you need to use the generic-less constructor and pass in the object else keep what you have.
Html.Kendo().Grid(Model.MyList)
However, if you are preprocessing some client data on the screen that you want to initialize then you will need to do this on ready. Don't worry about the schema part of the data source. It already knows this when you used the .NET MVC wrapper because you gave it the schema(type) via the generic or the parameter provided.
var initialDS= new kendo.data.DataSource({
data: [
{ ActionName: "Some Name", ActionType: "Some Type" }
]
});
$(document).ready(function () {
$('#docworkflow').data('kendoGrid').setDataSource(initialDS);
});
As I mentioned in the other answer. Use the data source functions for adding additional data to the data source. No need to setDataSource each time you want to add. Just
//Assuming you have 2 inputs on the screen the user is entering info into
var nameEntry = $('#action-name').val();
var typeEntry = $('#action-type').val();
$('#docworkflow').data('kendoGrid').dataSource.add({ ActionName: nameEntry , ActionType: typeEntry });
So after some efforts I come up with. But I don't know where to specify the
data in the html code. Is it possible this way?
#(Html.Kendo().Grid <DockData.Action> ()
.Name("docworkflow")
.Columns(columns =>
{
columns.Bound(e => e.ActionName);
columns.Bound(e => e.ActionType);
}).DataSource( **How do I load a script variable here***)
//This script variable should be fed to the above code.
This variable is populatedwhen the user adds data from the UI which works fine.
var dataSource = new kendo.data.DataSource({
data: result,
schema: {
model: {
fields: {
ActionName: { type: "string" },
ActionType: { type: "string" }
}
}
},
pageSize: 20
});