SAP UI5 : binding JSON model data to detail view - data-binding

I'm trying to display the data of a JSON file into the detail view file. As I'm new to SAP UI 5, I don't understand why I get the error : "oModel.createKey is not a function" in the console.
Can anyone explain me the cause of this error please ?
I searched for some solutions and this is what I did for the moment.
Here is the main.view.xml file :
<Table id="tab1" items="{path: 'articles>/Articles'}">
<columns>
<Column width="11rem">
<Label text="ID" />
</Column>
<Column width="11rem">
<Label text="Description" />
</Column>
<Column width="11rem">
<Label text="Date début de validité" />
</Column>
<Column width="11rem">
<Label text="Date fin de validité" />
</Column>
<Column width="11rem">
<Label text="Montant" />
</Column>
</columns>
<ColumnListItem press=".onPress" type="Navigation">
<Text text="{articles>id}" />
<Text text="{articles>description}" />
<Text text="{articles>debutValidite}" />
<Text text="{articles>finValidite}" />
<Text text="{articles>prix}" />
</ColumnListItem>
</Table>
The main.controller.js file :
onInit: function () {
var oModel = new sap.ui.model.json.JSONModel();
oModel.loadData("./model/Articles.json");
this.getView().setModel(oModel,"articles");
},
onPress: function(oEvent){
var oItem = oEvent.getSource();
var oContext = oItem.getBindingContext("articles");
var sKeyId = oContext.getObject("id");
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("detail", {articleId: sKeyId});
The detail.view.xml file :
<f:SimpleForm id="form1">
<!--<f:content>-->
<Label text="ID"/>
<Text text="{articles>id'}"/>
<Label text="Description"/>
<Text text="{articles>description}"/>
<Label text="Date début de validité"/>
<Text text="{articles>debutValidite}"/>
<Label text="Date fin de validite"/>
<Text text="{articles>finValidite}"/>
<Label text="Montant"/>
<Text text="{articles>prix}"/>
<!--</f:content>-->
</f:SimpleForm>
The detail.controller.js file :
onInit: function () {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.getRoute("detail").attachPatternMatched(this._onObjectMatched, this);
},
_onObjectMatched: function (oEvent){
var oModel = this.getView("detail").getModel("articles");
console.log(oModel);
var that= this;
var sKeyId = oEvent.getParameter("arguments").articleId;
console.log(sKeyId);
oModel.dataLoaded().then(function(){
var sPath = oModel.createKey("/Articles", {
id: sKeyId
});
that.getView().bindElement({ path: sPath});
});
},
onBack: function () {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("main");
}
The Articles.json file :
{
"Articles": [{
"id": "AR00000111",
"description": "Pièce de rechange",
"debutValidite": "01.02.2020",
"finValidite": "01.05.2021",
"prix": "150"
}, {
"id": "AR00000112",
"description": "Chaise",
"debutValidite": "01.03.2020",
"finValidite": "01.05.2021",
"prix": "200"
}, {
"id": "AR00000113",
"description": "Pièce de rechange",
"debutValidite": "01.02.2020",
"finValidite": "01.09.2021",
"prix": "250"
}
]
}
And the manifest file :
"sap.ui5": {
...
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "demo.testdemo.i18n.i18n"
}
},
"articles": {
"preload": true,
"type": "sap.ui.model.json.JSONModel",
"uri": "model/Articles.json"
}
...
"routes": [{
"name": "main",
"pattern": "",
"target": ["main"]
}, {
"name": "detail",
"pattern": "detail/{articleId}",
"target": ["detail"]
}],
"targets": {
"main": {
"viewName": "main"
},
"detail": {
"viewName": "detail"
}
}
Here is the detail.view.xml :
https://i.stack.imgur.com/dvPbL.png
And the main.view.xml :
https://i.stack.imgur.com/0A5tf.png

As I understand it your model is a JSONModel. JSONModels do not have the method createKey. This method only exists in the class ODataModel.
Instead you have to work with the JSON structure. So if you have an array you have to use the indices.
const iIndex = oModel.getProperty("/Articles").findIndex(article => article.id === sKeyId);
const sPath = `/Articles/${iIndex}`;

Related

Can't display retrieved data from Strapi in the UI, but can see them in the console Using Next.Js

I am trying to retrieve data from strapi using the NextJs as the front-end. The problem is that I can't display them on the UI, but I can see them in the console.
I used Graphql for querying the data using this commend:
query {
menus(filters: {language: {code: {eq: "en"}}}) {
data {
attributes {
Entry {
title
link
}
}
}
}
}
, I got this:
{
"data": {
"menus": {
"data": [
{
"attributes": {
"Entry": [
{
"title": "home",
"link": "/home.html"
},
{
"title": "classes",
"link": "./calss.html"
},
{
"title": "events",
"link": "/events.html"
},
{
"title": "news",
"link": "/news.html"
},
{
"title": "reosurces",
"link": "./resources.html"
},
{
"title": "links",
"link": "./links.html"
}
]
}
}
]
}
}
}
and this is how the UI handled:
const mainMenuAdapter = (menuData) => {
return menuData.map(md => {
const entry = md['attributes']['Entry']
console.log('title', entry)
return {
link: entry.link,
title: entry.title,
}
})
}
I use useContext;
const { menuData } = useContext(GlobalContext)
const menus = mainMenuAdapter(menuData)
here is how the mapping is handled.
<div className="bg-green-400 relative filosofia_regular bg-grayDark flex-wrap md:flex flex-row items-end md:justify-around p-2 lg:justify-evenly text-center mx-auto w-full">
{!!menus && menus.map((menu, i) => {
return (
<div key={i}>
<Link href="/">
<a className="text-gray-100 px-2 text-sm lg:text-2xl xl:text-4xl whitespace-nowrap">
{t(menu.title)}
</a>
</Link>
{i < menus.length - 1 && (
<a className="text-white">|</a>
)}
</div>
);
})}
</div>
and here is the console.
(6) [{…}, {…}, {…}, {…}, {…}, {…}]
0:
link: "/home.html"
title: "home"
__typename: "ComponentEntryComEntry"
[[Prototype]]: Object
1: {title: 'classes', link: './calss.html', __typename: 'ComponentEntryComEntry'}
2: {title: 'events', link: '/events.html', __typename: 'ComponentEntryComEntry'}
3: {title: 'news', link: '/news.html', __typename: 'ComponentEntryComEntry'}
4: {title: 'reosurces', link: './resources.html', __typename: 'ComponentEntryComEntry'}
5: {title: 'links', link: './links.html', __typename: 'ComponentEntryComEntry'}
length: 6
[[Prototype]]: Array(0)
Can anyone please tell me what I am missing?
Thanks

react-table TypeError: Cannot read properties of undefined (reading 'forEach')

TypeError: Cannot read properties of undefined (reading 'forEach')
This is an error message I get when I am trying to render OrderTable component. I struggled fixing it for a good few hours and I don't have idea what is wrong.
Other almost similar tables works fine.
const orderProducts = useSelector((state) => state.orders.order.products)
I use this structure instead of state.orders.order because from serializer I got nested data that does not works. Tried to take products from order after selector and it does not also work. Data seems to be fine, but I attach JSON anyway.
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getOrder } from "../../features/orders/orderSlice";
import {
useTable,
useRowSelect,
useSortBy,
useFilters,
useGlobalFilter,
} from "react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from "#mui/material";
import Paper from "#mui/material/Paper";
import KeyboardArrowUpIcon from "#mui/icons-material/KeyboardArrowUp";
import KeyboardArrowDownIcon from "#mui/icons-material/KeyboardArrowDown";
import TableContainer from "#mui/material/TableContainer";
import IndeterminateCheckbox from "../Checkbox";
import ColumnFilter from "../ColumnFilter";
// import GlobalFilter from "../GlobalFilter";
const OrderTable = () => {
const dispatch = useDispatch();
useEffect(() => {
const id = window.location.pathname.split("/orders/order/").pop();
dispatch(getOrder(id));
}, []);
const loading = useSelector((state) => state.orders.loading);
const orderProducts = useSelector((state) => state.orders.order.products);
const data = React.useMemo(() => orderProducts, [orderProducts]);
const columns = React.useMemo(
() => [
{ Header: "ID", accessor: "id", Filter: ColumnFilter },
{ Header: "Nazwa", accessor: "product.title", Filter: ColumnFilter },
{ Header: "Ilość", accessor: "quantity", Filter: ColumnFilter },
{ Header: "Cena", accessor: "product.price", Filter: ColumnFilter },
{ Header: "Wartość", accessor: "total_price", Filter: ColumnFilter },
{
Header: "Zamówiony",
accessor: (d) => (d.ordered ? "Tak" : "Nie"),
Filter: ColumnFilter,
},
],
[]
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
state,
setGlobalFilter,
prepareRow,
selectedFlatRows,
} = useTable(
{ columns, data },
useGlobalFilter,
useFilters,
useSortBy,
useRowSelect,
(hooks) => {
hooks.visibleColumns.push((columns) => [
// Let's make a column for selection
{
id: "selection",
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox
Header: ({ getToggleAllRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
},
...columns,
]);
}
);
const { globalFilter } = state;
return (
<>
{loading ? (
"loading"
) : (
<TableContainer component={Paper} sx={{ ml: 3, mt: 2 }}>
<Table {...getTableProps()} size="small">
<TableHead>
{headerGroups.map((headerGroup) => (
<TableRow {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<TableCell {...column.getHeaderProps()}>
<div {...column.getSortByToggleProps()}>
{column.render("Header")}
{column.isSorted ? (
column.isSortedDesc ? (
<KeyboardArrowUpIcon fontSize="small" />
) : (
<KeyboardArrowDownIcon fontSize="small" />
)
) : (
""
)}
</div>
<div>
{column.canFilter ? column.render("Filter") : null}
</div>
</TableCell>
))}
</TableRow>
))}
</TableHead>
<TableBody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
return (
<TableRow {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<TableCell {...cell.getCellProps()}>
{cell.render("Cell")}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
)}
</>
);
};
export default OrderTable;
JSON
{
"id": 37,
"products": [
{
"id": 68,
"product": {
"id": 1,
"created": "2021-11-11T02:01:15.897446Z",
"active": true,
"title": "Kapustka",
"description": "biała",
"quantity": -143,
"price": "3.24",
"category": 1
},
"quantity": 8,
"total_price": "25.92",
"ordered": true,
"user": 1
},
{
"id": 69,
"product": {
"id": 2,
"created": "2021-11-12T17:16:43.422467Z",
"active": true,
"title": "Marchew",
"description": null,
"quantity": 10,
"price": "2.00",
"category": null
},
"quantity": 11,
"total_price": "22.00",
"ordered": true,
"user": 1
},
{
"id": 70,
"product": {
"id": 5,
"created": "2021-11-13T20:03:01.336712Z",
"active": true,
"title": "Makaron",
"description": null,
"quantity": 0,
"price": "0.00",
"category": null
},
"quantity": 1,
"total_price": "0.00",
"ordered": true,
"user": 1
},
{
"id": 71,
"product": {
"id": 6,
"created": "2021-11-13T20:03:12.384605Z",
"active": true,
"title": "Pomidor",
"description": null,
"quantity": 0,
"price": "0.00",
"category": null
},
"quantity": 1,
"total_price": "0.00",
"ordered": true,
"user": 1
}
],
"title": "Zamowienie",
"created": "2021-11-13T19:26:59.545602Z",
"value": "49.92",
"is_paid": false,
"ordered": true,
"user": 1
}

Redux action (fetch from db) not firing when useEffect() is called

I am trying to build an eCommerce website to learn Redux.
At the moment I am trying to fetch the categories when the component mounts. Since I am using functional components, I understood that this is achieved by calling the useEffect() method.
Also, I am using json-server as a REST Api.
I am quite sure I have managed to compose my enhancers to pass to the store (dev tools and thunk), created actions, reducers and all.
My problem is that the action doesn't fire when the component mounts.
N.B. before introducing the Middleware and therefore the fetch request, everything worked just fine. Also consider that the fetch request is successful.
Hereafter is the code involved.
'src/index.js'
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore, applyMiddleware, compose } from 'redux'
import { Provider } from 'react-redux'
import rootReducer from './reducers'
import thunk from 'redux-thunk'
const composedEnhancers = compose(applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
const store = createStore(
rootReducer, /* preloadedState, */
composedEnhancers
);
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();
the action type in 'actions/index.js':
export const FETCH_CATEGORIES = 'FETCH_CATEGORIES'
the action creator in 'actions/index.js':
export const fetchCategories = () => (dispatch) => {
fetch("http://localhost:7000/categories")
.then(response => response.json())
.then(categories => {
return {
type: FETCH_CATEGORIES,
payload: categories
}
})
}
'reducers/index.js'
import * as actions from './../actions'
const initState = {
categories: [],
currentCategory: 'any',
toggler: 'hidden'
}
const rootReducer = (state = initState, action) => {
switch (action.type) {
case actions.SELECT_CATEGORY:
return { ...state, currentCategory: action.payload.value }
case actions.FETCH_CATEGORIES:
return { ...state, categories: action.payload }
case actions.TOGGLE:
return { ...state, toggler: action.payload.toggler }
default:
return state
}
}
export default rootReducer
the 'Filter.js' component
import React, { useEffect } from 'react';
import { connect } from 'react-redux'
import { selectCategory, fetchCategories } from '../../../../actions'
const Filter = (props) => {
// const [minPrice, setMinPrice] = useState(0)
// const handleMinPrice = event => {
// setMinPrice(event.target.value)
// }
// const [maxPrice, setMaxPrice] = useState(0)
// const handleMaxPrice = event => {
// setMaxPrice(event.target.value)
// }
// const [department, setDepartment] = useState("select")
// const handleDepartment = event => {
// console.log(event.target.value)
// setDepartment(event.target.value)
// }
// console.log(props);
const handleChange = event => {
event.preventDefault()
props.selectCategory(event.target.value)
}
useEffect(() => {
props.fetchCategories()
})
return (
<div className="filter-form col-12">
<form id="filter-category">
<label htmlFor="category">Category</label>
<select className="col-12" id="category" name="category" size="5" value={props.currentCategory} onChange={(event) => handleChange(event)}>
{props.categories.map(category => <option key={category.value} value={category.value}>{category.name}</option>)}
</select>
</form>
{props.currentCategory !== 'any' && <form id="filter-department">
<label htmlFor="department">Department</label>
<select className="col-12" id="department" name="department" size="5" value='{department}' onChange='{handleDepartment}'>
<option value="select">--- Select ---</option>
<option value="desktop PCs">Desktop PCs</option>
<option value="laptops">Laptops</option>
<option value="gamepads">Gamepads</option>
<option value="headphones">Headphones</option>
<option value="microphones">Microphones</option>
<option value="keyboards">Keyboards</option>
</select>
</form>}
{/* <form id="filter-price">
<label htmlFor="minimum-price">Min. Price: {minPrice}£</label>
<input type="range" min="1" max="100" value={minPrice} className="slider col-xs-12" id="minimum-price" onChange={handleMinPrice} />
<label htmlFor="maximum-price">Max. Price: {maxPrice}£</label>
<input type="range" min="100" max="1000" value={maxPrice} className="slider col-xs-12" id="maximum-price" onChange={handleMaxPrice} />
</form> */}
</div>
);
}
const mapStateToProps = (state) => {
return {
categories: state.categories,
currentCategory: state.currentCategory
}
}
const mapDispatchToProps = (dispatch) => {
return {
selectCategory: (value) => {
dispatch(selectCategory(value))
},
fetchCategories: () => {
dispatch(fetchCategories())
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Filter);
Also, here is 'db.json'
{
"categories": [
{
"id": "1",
"value": "any",
"name": "--- Any ---",
"departments": []
},
{
"id": "2",
"value": "computers-and-accessories",
"name": "Computers and Accessories",
"departments": [
{
"id": "1",
"value": "desktop-pc",
"name": "Desktop PCs"
},
{
"id": "2",
"value": "laptops",
"name": "Laptops"
},
{
"id": "3",
"value": "keyboards",
"name": "Keyboards"
},
{
"id": "4",
"value": "headphones",
"name": "Headphones"
},
{
"id": "5",
"value": "mouses",
"name": "Mouses"
},
{
"id": "6",
"value": "gamepads",
"name": "Gamepads"
}
]
},
{
"id": "3",
"value": "fashion",
"name": "Fashion",
"departments": [
{
"id": "1",
"value": "dresses",
"name": "dresses"
},
{
"id": "2",
"value": "shoes",
"name": "Shoes"
},
{
"id": "3",
"value": "pants",
"name": "Pants"
},
{
"id": "4",
"value": "sunglasses",
"name": "Sunglasses"
},
{
"id": "5",
"value": "handbags",
"name": "Handbags"
},
{
"id": "6",
"value": "hats",
"name": "Hats"
}
]
},
{
"id": "4",
"value": "digital-music",
"name": "Digital Music",
"departments": [
{
"id": "1",
"value": "rock",
"name": "Rock"
},
{
"id": "2",
"value": "pop",
"name": "Pop"
},
{
"id": "3",
"value": "house-and-techno",
"name": "House and Techno"
},
{
"id": "4",
"value": "trap",
"name": "Trap"
},
{
"id": "5",
"value": "indie",
"name": "Indie"
},
{
"id": "6",
"value": "hip-hop",
"name": "Hip-Hop"
}
]
},
{
"id": "5",
"value": "house",
"name": "House",
"departments": [
{
"id": "1",
"value": "kitchen",
"name": "kitchen"
},
{
"id": "2",
"value": "garden",
"name": "Garden"
},
{
"id": "3",
"value": "bedroom",
"name": "Bedroom"
},
{
"id": "4",
"value": "bathroom",
"name": "Bathroom"
},
{
"id": "5",
"value": "livingroom",
"name": "Livingroom"
},
{
"id": "6",
"value": "cleaning",
"name": "Cleaning"
}
]
},
{
"id": "6",
"value": "grocery",
"name": "Grocery",
"departments": [
{
"id": "1",
"value": "vegetables",
"name": "Vegetables"
},
{
"id": "2",
"value": "pasta and rice",
"name": "Pasta and Rice"
},
{
"id": "3",
"value": "snacks",
"name": "Snacks"
},
{
"id": "4",
"value": "canned-food",
"name": "Canned Food"
},
{
"id": "5",
"value": "frozen",
"name": "Frozen"
},
{
"id": "6",
"value": "dairy",
"name": "Dairy"
}
]
}
]
}
What am I missing here?
Try dispatching like this in the action which will fire the reducer FETCH_CATEGORIES:
export const fetchCategories = () => (dispatch) => {
fetch("http://localhost:7000/categories")
.then(response => response.json())
.then(categories => {
// **Changes start here
dispatch ({
type: FETCH_CATEGORIES,
payload: categories
})
// **Changes end here
})
}

push notification in ionic3 using phonegap push plugin

I am new to ionic 3 and I have implemented phonegap push notification on an app I am building and I am not getting any kind of notification, not even alert message which should show when the app is registered, so can someone provide me with some help for this issue.
Thanks beforehand.
app.component.ts
import { Component, ViewChild } from '#angular/core';
import { Platform, Nav, Icon,AlertController} from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import firebase from 'firebase';
import { EventlistPage } from '../pages/eventlist/eventlist';
import { SigninPage } from '../pages/signin/signin';
import { SignupPage } from '../pages/signup/signup';
import { settingsPage } from '../pages/settings/settings';
import { NewsPage } from '../pages/news/news';
// import { AddeventPage } from '../pages/addevent/addevent';
// import {EventdetailsPage} from '../pages/eventdetails/eventdetails';
import { ProfilePage } from '../pages/profile/profile';
import {AuthService} from '../services/auth'
import { ENV } from '../environments/environment.dev';
import { Push, PushObject, PushOptions } from '#ionic-native/push';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
#ViewChild (Nav) nav: Nav; //can't inject nav controller in the constructor in the root component
rootPage:any = EventlistPage;
eventsListPage=EventlistPage;
signinPage = SigninPage;
signupPage = SignupPage;
isAuthenticated = false;
show: boolean = false;
pages: Array<{title: string, component: any,icon:string}>;
constructor(
private authService:AuthService,
public platform: Platform,
statusBar: StatusBar,
splashScreen: SplashScreen,
private push: Push,
public alertCtrl: AlertController) {
firebase.initializeApp(ENV);
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.isAuthenticated = true;
this.rootPage=EventlistPage;//as firebase checks the user state asynchronously before the nav gets initialized
this.show = true;
} else {
this.isAuthenticated = false;
this.rootPage=SigninPage;
}
if(!this.isAuthenticated){
//this.rootPage=SigninPage;
this.pages = [
{ title: 'SignIn', component: SigninPage , icon: "log-in"},
{ title: 'Register', component: SignupPage, icon: "book"}
];
}else{
this.pages = [
{ title: 'news', component: NewsPage, icon: "paper"},
{ title: 'events', component: EventlistPage, icon: "albums" },
{ title: 'profile', component: ProfilePage , icon: "person"},
{ title: 'settings', component: settingsPage, icon: "settings"}
];
}
});
// platform.ready().then(() => {
// // Okay, so the platform is ready and our plugins are available.
// // Here you can do any higher level native things you might need.
// statusBar.styleDefault();
// splashScreen.hide();
// });
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
this.pushsetup();
statusBar.styleDefault();
if (platform.is('android')) {
statusBar.overlaysWebView(false);
statusBar.backgroundColorByHexString('#000000');
}
splashScreen.hide();
});
}
onLogOut(){
this.authService.logOut();
this.show = false;
}
openPage(page) {
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.rootPage=page.component
//this.nav.setRoot(page.Component);
}
pushsetup() {
if (!this.platform.is('cordova')) {
console.warn('Push notifications not initialized. Cordova is not available - Run in physical device');
return;
}
const options: PushOptions = {
android: {
senderID: '333057397510'
},
ios: {
alert: 'true',
badge: false,
sound: 'true'
},
windows: {}
};
const pushObject: PushObject = this.push.init(options);
pushObject.on('registration').subscribe((data: any) => {
alert('device token -> ' + data.registrationId);
//TODO - send device token to server
});
pushObject.on('notification').subscribe((data: any) => {
console.log('message -> ' + data.message);
//if user using app and push notification comes
if (data.additionalData.foreground) {
// if application open, show popup
let confirmAlert = this.alertCtrl.create({
title: 'New Notification',
message: data.message,
buttons: [{
text: 'Ignore',
role: 'cancel'
}, {
text: 'View',
handler: () => {
//TODO: Your logic here
this.nav.push(EventlistPage, { message: data.message });
}
}]
});
confirmAlert.present();
} else {
//if user NOT using app and push notification comes
//TODO: Your logic on click of push notification directly
this.nav.push(EventlistPage, { message: data.message });
console.log('Push notification clicked');
}
});
pushObject.on('error').subscribe(error => console.error('Error with Push plugin' + error));
}
}
config.xml
<?xml version='1.0' encoding='utf-8'?>
<widget id="dfd.dfd.dfd" version="0.0.3" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>hghg</name>
<description>An awesome Ionic/Cordova app.</description>
<author email="hi#ionicframework" href="http://ionicframework.com/">Ionic Framework Team</author>
<content src="index.html" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<preference name="ScrollEnabled" value="false" />
<preference name="android-minSdkVersion" value="19" />
<preference name="BackupWebStorage" value="none" />
<preference name="SplashMaintainAspectRatio" value="true" />
<preference name="FadeSplashScreenDuration" value="300" />
<preference name="SplashShowOnlyFirstTime" value="false" />
<preference name="SplashScreen" value="screen" />
<preference name="SplashScreenDelay" value="3000" />
<platform name="android">
<allow-intent href="market:*" />
<icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png" />
<icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png" />
<icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png" />
<icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png" />
<icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png" />
<icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png" />
<splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png" />
<splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png" />
<splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png" />
<splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png" />
<splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png" />
<splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png" />
<splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png" />
<splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png" />
<splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png" />
<splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png" />
<splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png" />
<splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png" />
<resource-file src="google-services.json" target="app/google-services.json" />
<resource-file src="google-services.json" target="google-services.json" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
<icon height="57" src="resources/ios/icon/icon.png" width="57" />
<icon height="114" src="resources/ios/icon/icon#2x.png" width="114" />
<icon height="40" src="resources/ios/icon/icon-40.png" width="40" />
<icon height="80" src="resources/ios/icon/icon-40#2x.png" width="80" />
<icon height="120" src="resources/ios/icon/icon-40#3x.png" width="120" />
<icon height="50" src="resources/ios/icon/icon-50.png" width="50" />
<icon height="100" src="resources/ios/icon/icon-50#2x.png" width="100" />
<icon height="60" src="resources/ios/icon/icon-60.png" width="60" />
<icon height="120" src="resources/ios/icon/icon-60#2x.png" width="120" />
<icon height="180" src="resources/ios/icon/icon-60#3x.png" width="180" />
<icon height="72" src="resources/ios/icon/icon-72.png" width="72" />
<icon height="144" src="resources/ios/icon/icon-72#2x.png" width="144" />
<icon height="76" src="resources/ios/icon/icon-76.png" width="76" />
<icon height="152" src="resources/ios/icon/icon-76#2x.png" width="152" />
<icon height="167" src="resources/ios/icon/icon-83.5#2x.png" width="167" />
<icon height="29" src="resources/ios/icon/icon-small.png" width="29" />
<icon height="58" src="resources/ios/icon/icon-small#2x.png" width="58" />
<icon height="87" src="resources/ios/icon/icon-small#3x.png" width="87" />
<icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
<splash height="1136" src="resources/ios/splash/Default-568h#2x~iphone.png" width="640" />
<splash height="1334" src="resources/ios/splash/Default-667h.png" width="750" />
<splash height="2208" src="resources/ios/splash/Default-736h.png" width="1242" />
<splash height="1242" src="resources/ios/splash/Default-Landscape-736h.png" width="2208" />
<splash height="1536" src="resources/ios/splash/Default-Landscape#2x~ipad.png" width="2048" />
<splash height="2048" src="resources/ios/splash/Default-Landscape#~ipadpro.png" width="2732" />
<splash height="768" src="resources/ios/splash/Default-Landscape~ipad.png" width="1024" />
<splash height="2048" src="resources/ios/splash/Default-Portrait#2x~ipad.png" width="1536" />
<splash height="2732" src="resources/ios/splash/Default-Portrait#~ipadpro.png" width="2048" />
<splash height="1024" src="resources/ios/splash/Default-Portrait~ipad.png" width="768" />
<splash height="960" src="resources/ios/splash/Default#2x~iphone.png" width="640" />
<splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
<splash height="2732" src="resources/ios/splash/Default#2x~universal~anyany.png" width="2732" />
</platform>
<plugin name="cordova-plugin-whitelist" spec="1.3.3" />
<plugin name="cordova-plugin-statusbar" spec="2.4.2" />
<plugin name="cordova-plugin-device" spec="2.0.2" />
<plugin name="cordova-plugin-splashscreen" spec="5.0.2" />
<plugin name="cordova-plugin-ionic-webview" spec="^2.0.0" />
<plugin name="cordova-plugin-ionic-keyboard" spec="^2.0.5" />
<plugin name="cordova-plugin-file" spec="^6.0.1" />
<plugin name="cordova-plugin-filechooser" spec="^1.0.1" />
<plugin name="cordova-plugin-filepath" spec="^1.4.2" />
<plugin name="phonegap-plugin-push" spec="^2.2.3">
<variable name="SENDER_ID" value="jjhgjgjhgjhgj" />
</plugin>
<engine name="android" spec="7.0.0" />
</widget>
package.json
{
"name": "events",
"version": "0.0.1",
"author": "Ionic Framework",
"homepage": "http://ionicframework.com/",
"private": true,
"config": {
"ionic_webpack": "./config/webpack.config.js"
},
"scripts": {
"start": "ionic-app-scripts serve",
"clean": "ionic-app-scripts clean",
"build": "ionic-app-scripts build",
"lint": "ionic-app-scripts lint"
},
"dependencies": {
"#angular/animations": "5.2.11",
"#angular/common": "5.2.11",
"#angular/compiler": "5.2.11",
"#angular/compiler-cli": "5.2.11",
"#angular/core": "5.2.11",
"#angular/forms": "5.2.11",
"#angular/http": "5.2.11",
"#angular/platform-browser": "5.2.11",
"#angular/platform-browser-dynamic": "5.2.11",
"#ionic-native/core": "^4.12.2",
"#ionic-native/file": "^4.15.0",
"#ionic-native/file-chooser": "^4.15.0",
"#ionic-native/file-path": "^4.15.0",
"#ionic-native/push": "^4.16.0",
"#ionic-native/splash-screen": "~4.12.0",
"#ionic-native/status-bar": "~4.12.0",
"#ionic/storage": "2.1.3",
"angularfire2": "^5.0.2",
"cordova-android": "7.0.0",
"cordova-plugin-device": "^2.0.2",
"cordova-plugin-file": "^6.0.1",
"cordova-plugin-filechooser": "^1.0.1",
"cordova-plugin-filepath": "^1.4.2",
"cordova-plugin-ionic-keyboard": "^2.1.3",
"cordova-plugin-ionic-webview": "^2.2.0",
"cordova-plugin-splashscreen": "^5.0.2",
"cordova-plugin-statusbar": "^2.4.2",
"cordova-plugin-whitelist": "^1.3.3",
"firebase": "^5.5.3",
"ionic-angular": "3.9.2",
"ionicons": "3.0.0",
"ngx-show-hide-password": "^1.2.6",
"phonegap-plugin-push": "^2.2.3",
"rxjs": "^6.0.0",
"rxjs-compat": "^6.3.2",
"sw-toolbox": "3.6.0",
"zone.js": "0.8.26"
},
"devDependencies": {
"#ionic/app-scripts": "3.2.0",
"#ionic/lab": "1.0.11",
"typescript": "~2.6.2"
},
"description": "An Ionic project",
"cordova": {
"plugins": {
"cordova-plugin-whitelist": {},
"cordova-plugin-statusbar": {},
"cordova-plugin-device": {},
"cordova-plugin-splashscreen": {},
"cordova-plugin-ionic-webview": {
"ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+"
},
"cordova-plugin-ionic-keyboard": {},
"cordova-plugin-filechooser": {},
"cordova-plugin-file": {},
"cordova-plugin-filepath": {},
"phonegap-plugin-push": {
"SENDER_ID": "ghghfhgfh"
}
},
"platforms": [
"android"
]
}
}
have you configured your file google-services.json in the root of the project? In case you are building for IOS you have to configure the GoogleService-info.plist and the proper certificates on your account
Well, I got the answer from phonegap plugin's issue page in github.
I removed android 7.0.0 and added 7.1.0
Then edited gradle files by rearranging maven and jcenter in the project.

How can I get the id of the workflow instance in workflow form?

I have the FreeMarker template, which is the custom field for display some data about the business process.
Let's say, userdetails.ftl:
<#link href="${url.context}/res/components/workflow/workflow-form.css" group="workflow"/>
<#include "/org/alfresco/components/form/controls/common/utils.inc.ftl" />
<div class="form-field">
<div id="...">
<table id="..." class="yui-dt">
<tr>
<th class="yui-dt-liner">Field_1</th>
<th class="yui-dt-liner">Field_2</th>
<th class="yui-dt-liner">Field_3</th>
<th class="yui-dt-liner">Field_4</th>
...
<th class="yui-dt-liner">Field_N</th>
</tr>
...
</table>
</div>
</div>
<script>
// Here I want to call REST back-end and parse the JSON response
</script>
Relevant part of the share-config-custom.xml:
<config evaluator="task-type" condition="mswf:reviewTask">
<forms>
<form>
<field-visibility>
<show id="mswf:userDetails" />
</field-visibility>
<appearance>
<set id="userDetails" appearance="title" label="User Details" />
<field id="mswf:userDetails" set="userDetails" label="User Details">
<control template="/org/alfresco/components/form/controls/workflow/userdetails.ftl" />
</field>
<field id="transitions" set="response" />
</appearance>
</form>
</forms>
</config>
This template contains a table in which I would like to display the list of users who will participate in the business process (who was assigned).
Inside tags <script></script> I want to invoke an embedded Alfresco WebScript (or my own - it's not important in this case) which returns me all the necessary information about the business process:
http://localhost:8080/alfresco/api/-default-/public/workflow/versions/1/processes/26205/variables
,where 26205 == id of the process instance.
I get the following:
{
"list":{
"pagination":{
"count":36,
"hasMoreItems":false,
"totalItems":36,
"skipCount":0,
"maxItems":100
},
"entries":[
{
"entry":{
"name":"bpm_packageActionGroup",
"type":"d:text"
}
},
{
"entry":{
"name":"mswf_approveCount",
"type":"d:double",
"value":0.0
}
},
{
"entry":{
"name":"bpm_reassignable",
"type":"d:boolean",
"value":true
}
},
{
"entry":{
"name":"bpm_priority",
"type":"d:int",
"value":2
}
},
{
"entry":{
"name":"initiator",
"type":"d:noderef",
"value":"admin"
}
},
{
"entry":{
"name":"mswf_actualRejectPercent",
"type":"d:double",
"value":0.0
}
},
{
"entry":{
"name":"bpm_percentComplete",
"type":"d:int",
"value":0
}
},
{
"entry":{
"name":"bpm_sendEMailNotifications",
"type":"d:boolean",
"value":false
}
},
{
"entry":{
"name":"mswf_reviewOutcome",
"type":"d:text",
"value":"Reject"
}
},
{
"entry":{
"name":"bpm_workflowPriority",
"type":"d:int",
"value":1
}
},
{
"entry":{
"name":"bpm_hiddenTransitions",
"type":"d:text"
}
},
{
"entry":{
"name":"workflowinstanceid",
"type":"d:text",
"value":"activiti$26205"
}
},
{
"entry":{
"name":"taskFormKey",
"type":"d:text",
"value":"mswf:activitiReviewTask"
}
},
{
"entry":{
"name":"bpm_workflowDueDate",
"type":"d:date",
"value":"2017-03-12T20:00:00.000+0000"
}
},
{
"entry":{
"name":"mswf_requiredPercent",
"type":"d:double",
"value":100.0
}
},
{
"entry":{
"name":"mswf_reviewerCount",
"type":"d:double",
"value":2.0
}
},
{
"entry":{
"name":"bpm_package",
"type":"bpm:workflowPackage",
"value":"workspace://SpacesStore/72086323-6c65-46f9-a1ad-8877630a6b40"
}
},
{
"entry":{
"name":"mswf_actualPercent",
"type":"d:double",
"value":0.0
}
},
{
"entry":{
"name":"nrOfActiveInstances",
"type":"d:int",
"value":2
}
},
{
"entry":{
"name":"bpm_workflowDescription",
"type":"d:text",
"value":"message..."
}
},
{
"entry":{
"name":"mswf_rejectCount",
"type":"d:double",
"value":0.0
}
},
{
"entry":{
"name":"bpm_packageItemActionGroup",
"type":"d:text",
"value":"edit_package_item_actions"
}
},
{
"entry":{
"name":"companyhome",
"type":"d:noderef",
"value":"workspace://SpacesStore/64f9f3c3-9227-425d-b336-bd255ab94c83"
}
},
{
"entry":{
"name":"_startTaskCompleted",
"type":"d:datetime",
"value":"2017-03-13T07:03:12.612+0000"
}
},
{
"entry":{
"name":"bpm_assignees",
"type":"cm:person",
"value":[
"first",
"second"
]
}
},
{
"entry":{
"name":"loopCounter",
"type":"d:int",
"value":1
}
},
{
"entry":{
"name":"bpm_description",
"type":"d:text",
"value":"message... "
}
},
{
"entry":{
"name":"nrOfInstances",
"type":"d:int",
"value":2
}
},
{
"entry":{
"name":"initiatorhome",
"type":"d:noderef",
"value":"workspace://SpacesStore/64f9f3c3-9227-425d-b336-bd255ab94c83"
}
},
{
"entry":{
"name":"bpm_outcomePropertyName",
"type":"d:qname",
"value":"mswf:reviewOutcome"
}
},
{
"entry":{
"name":"cm_name",
"type":"d:text",
"value":"Task"
}
},
{
"entry":{
"name":"bpm_status",
"type":"d:text",
"value":"Not Yet Started"
}
},
{
"entry":{
"name":"cancelled",
"type":"d:boolean",
"value":false
}
},
{
"entry":{
"name":"reviewAssignee",
"type":"d:noderef",
"value":"second"
}
},
{
"entry":{
"name":"mswf_requiredApprovePercent",
"type":"d:int",
"value":100
}
},
{
"entry":{
"name":"nrOfCompletedInstances",
"type":"d:int",
"value":0
}
}
]
}
}
In addition to everything else I'm interested in this part:
"entry":{
"name":"bpm_assignees",
"type":"cm:person",
"value":[
"first",
"second"
]
}
Here is the list of the users who will participate in the business process (in this specific instance).
To get all this information, I need to know the ID of the process instance (26205 in this case).
Can I determine this identifier in my userdetails.ftl template?
The workflow ID is indeed one of the variables available in your workflow, I suggest you add it to your form using the hidden form-control hidden.ftl and then, from you JS snippet, use dom to access that hidden input by ID/name and use it in your logic as you see fit !

Resources