Fiori Elements - Customize $batch query - odata

I have Fiori applications using fiori element and I want to tweak the odata queries that UI5 generated for OData in $batch calls.
I have livemode turned for the list report along with smartfilter for selection/filter and list of values using ValueList annotations. But the problem is when I type in the filter value in selection fields (lets say for sold to) the $batch call fires the below query for OData.
../invoice_list.xsodata/vlsoldto?sap-client=100&$skip=0&$top=10&$filter=startswith(SOLDTO___T,%27TEST%27)
I want to tweak the the odata call to use 'substringof' instead of 'startswith'.. so something like below.
../invoice_list.xsodata/vlsoldto?sap-client=100&$skip=0&$top=10&$filter=substringof(%27TEST%27,CRM_SOLDTO___T)
I don't know the spot where I can do this customization. I know how to do Fiori elements extensions, but looking for some info if its an extension then which type of extension, which event, or any other approach if not extension. I have very little idea on where to start.
Any help is appreciated.

You can add your own fields in the SmartFilterBar and then create your own custom filter:
https://sapui5.hana.ondemand.com/#/topic/3a515829ffd74239878ebc0d453d001d
Edit: if you want to use the existing fields you can just push a new filter with sap.ui.model.FilterOperator.Contains on the beforeRebindTable event.
Step 1: Register your extension in your manifest.json file
"extends": {
"extensions": {
...
"sap.ui.controllerExtensions": {
...
"sap.suite.ui.generic.template.ListReport.view.Details": {
...
"controllerName": "com.acme.app.controller.ListReportExtension",
...
}
}
...
Step 2: implement the controller method:
sap.ui.controller("com.acme.app.controller.ListReportExtension", {
onBeforeRebindTableExtension: function(oEvent) {
var oBindingParams = oEvent.getParameter("bindingParams");
oBindingParams.parameters = oBindingParams.parameters || {};
var oSmartTable = oEvent.getSource();
var oSmartFilterBar = this.byId(oSmartTable.getSmartFilterId());
var vCategory;
if (oSmartFilterBar instanceof sap.ui.comp.smartfilterbar.SmartFilterBar) {
//Custom price filter
var oCustomControl = oSmartFilterBar.getControlByKey("CustomPriceFilter");
if (oCustomControl instanceof sap.m.ComboBox) {
vCategory = oCustomControl.getSelectedKey();
switch (vCategory) {
case "0":
oBindingParams.filters.push(new sap.ui.model.Filter("Price", "LE", "100"));
break;
case "1":
oBindingParams.filters.push(new sap.ui.model.Filter("Price", "GT", "100"));
break;
default:
break;
}
}
}
}
});

Related

SuiteScript SO from RMA

Looking for a place to start with this. NetSuite allows you to create a return authorization from a sales order but I'm looking to go the other way. Sales Order from a Return Authorization. Not a script writer, looking to understand how difficult this would be, and a place to start.
Per Suite Answer 45156 transforming a record via SuiteScript from Return Authorization to Sales Order is not supported. A potential workaround is to create a Scheduled Script or Map/Reduce Script (both execute according to schedule) or Client Script (execute on press of button). To create a button use the Client Script type and set the button from the UI on the script record page, or add the script to a specific form and create a custom action/button.
use N/search module to search for RMAs
use N/record module to create SO, set values, and save SO
outline:
require(['N/search'], function(search) {
function loadAndRunSearch() {
//create search in UI that identifies all of the RMAs or use search.create()
var mySearch = search.load({
id: 'customsearch_my_so_search'
});
//for each search result
mySearch.run().each(function(result) {
//get values
var entity = result.getValue({name: 'entity'});
var subsidiary = result.getValue({name: 'subsidiary'});
//create SO
var newSORec = record.create({
type: record.Type.SALES_ORDER
});
//set values
newSORec.setValue({
fieldId: 'entity',
value: entity
});
newSORec.setValue({
fieldId: 'subsidiary',
value: subsidiary
});
//save SO
var newSORecId = newSORec.save();
});
}
return {
loadAndRunSearch: loadAndRunSearch
}
});

How to best access data from QueryRenderer in a parent component in Relay Modern?

As you can see from the picture below, I'm rendering the popover using a react-relay QueryRenderer since the request is slow, and I do not want the rest of the page to wait for events to be fetched.
My problem is that in the navigation I have a button to show/hide the popover. That button should only be rendered when events has loaded, and the button also needs to show a count of how many events there is.
So my question is how to pass events data up from QueryRenderer (popover) to a parent component (toggle button)?
My first idea was to reuse my QueryRenderer for events and pass in dataFrom={'STORE_ONLY'}, to avoid a new HTTP request and use the cache instead, but unfortunately 'STORE_ONLY' is not an option... YET...
From looking at https://github.com/relay-tools/relay-hooks/issues/5 it seems like store-only will be supported by useQuery in the future, so is that the recommended solution to go about it, or how is the recommended way? Surely facebook, and many other applications, must have had this need frequently?
You can achieve redux-like relay store with custom handlers and local schema.
I'll be guessing what your queries, components and fields might be named like so don't forget to change it to correct values
Somewhere in project's src folder create a file ClientState.client.graphql to extend your root query type with new field for client state:
// ClientState.client.graphql
type ClientState {
showToggleButton: Boolean!
eventsCount: Int
}
extend type Query {
clientState: ClientState!
}
this will allow you to wrap Toggle button with fragment like this:
fragment ToggleButton_query on Query {
clientState {
showToggleButton
eventsCount
}
}
and spread this fragment in parent query (probably AppQuery)
Then in your second query, where you'll be fetching events, add #__clientField directive, to define custom handle for that field:
query EventModal {
events #__clientField(handle: "eventsData") {
totalCount
}
}
Create EventsDataHandler for handle eventsData:
// EventsDataHandler.js
// update method will be called every time when field with `#__clientField(handle: "eventsData")` is fetched
const EventsDataHandler = {
update (store, payload) {
const record = store.get(payload.dataID)
if (!record) {
return
}
// get "events" from record
const events = record.getLinkedRecord(payload.fieldKey)
// get events count and set client state values
const eventsCount = events.getValue('totalCount')
const clientState = store.getRoot().getLinkedRecord('clientState')
clientState.setValue(eventsCount, 'eventsCount')
clientState.setValue(true, 'showToggleButton')
// link "events" to record, so the "events" field in EventModal is not undefined
record.setLinkedRecord(events, payload.handleKey)
}
}
export default EventsDataHandler
Last thing to do is to assign custom (and default) handlers to environment and create init store values:
// environment.js
import { commitLocalUpdate, ConnectionHandler, Environment, RecordSource, Store, ViewerHandler } from 'relay-runtime'
import EventsDataHandler from './EventsDataHandler'
// ...
const handlerProvider = handle => {
switch (handle) {
case 'connection':
return ConnectionHandler
case 'viewer':
return ViewerHandler
case 'eventsData':
return EventsDataHandler
default:
throw new Error(`Handler for ${handle} not found.`)
}
}
const environment = new Environment({
network,
store,
handlerProvider
})
// set init client state values
commitLocalUpdate(environment, store => {
const FIELD_KEY = 'clientState'
const TYPENAME = 'ClientState'
const dataID = `client:${FIELD_KEY}`
const record = store.create(dataID, TYPENAME)
record.setValue(false, 'showToggleButton')
// prevent relay from removing client state
environment.retain({
dataID,
variables: {},
node: { selections: [] }
})
store.getRoot().setLinkedRecord(record, FIELD_KEY)
})

BreezeJS - Using expand

I am querying the server to get an entity with expand
function _loadIncidents() {
var deffered = Q.defer(),
queryObj = new breeze.EntityQuery().from('Incidents').expand(['Deployments', 'IncidentComments', 'DTasks', 'ExtendedProperties', 'IncidentEvents']);
dataRepository.fetchEntitiesByQuery(queryObj, true).then(function (incidents) {
var query = breeze.EntityQuery.from("DTasks"),
incidentIds = dataRepository.getEntitiesByQuerySync(query);
deffered.resolve();
}, function(err) {
deffered.reject(err);
});
return deffered.promise;
};
I am getting the results and all is fine, how ever when I query breeze cache to get the entities - I am getting empty collection. So when using expand does the expanded entities are added to the cache?
Yes the related entities identified in the expand should be in cache ... if the query is "correct" and the server interpreted your request as you intended.
Look at the payload of the response from the first request. Are the related entities present? If not, perhaps the query was not well received on the server. As a general rule, you want to make sure the data are coming over the wire before wondering whether Breeze is doing the right thing with those data.
I do find myself wondering about the spelling of the items in your expand list. They are all in PascalCase. Are they these the names of navigation properties of the Incident type? Or are they the names of the related EntityTypes? They need to be former (nav property names), not the latter.
I Had problem with the navigation property - as I am not using OData webapi not using EF , there is problem with the navigation properties so for the current time i just wrote
Object.defineProperty(this, 'Deployments', {
get: function () {
return (this.entityAspect && this.entityAspect.entityManager) ?
this.entityAspect.entityManager.executeQueryLocally(new breeze.EntityQuery("Deployments").
where('IncidentID', 'eq', this.IncidentID)) :
[];
},
set: function (value) { //used only when loading incidents from the server
if (!value.results) {
return;
}
var i = 0,
dataRepository = require('sharedServices/dataRepository');
for (i; i < value.results.length; i++) {
dataRepository.addUnchangedEntity('Deployment', value.results[i]);
}
},
enumerable: true
});

Refreshing lookups in SPA with Breeze.js when value has changed

I have an application that records a transaction and the user can pick the category from a drop down.
Categories are loaded up at application startup as they are "mostly" static / rarely going to change.
So, in my datacontext.js I do the usual and prime my data;
var primeData = function () {
var promise = Q.all([
getLookups(),
getBankAccountPartials(null, true)])
.then(applyValidators);
return promise.then(success);
function success() {
datacontext.lookups = {
categories: getLocal('Categories', 'name', true),
transactiontypes: getLocal('TransactionTypes', 'name', true),
payees: getLocal('Payees', 'name', true)
};
log('Primed data', datacontext.lookups);
}
function applyValidators() {
model.applyBankAccountValidators(manager.metadataStore);
}
};
function getLookups() {
return EntityQuery.from('Lookups')
.using(manager).execute()
.then(processLookups)
.fail(queryFailed);
}
Now, occasionally in an Admin screen the user can edit and add a category.
In the categoryadd.js viewmodel my save code looks something like this (extract shown);
save = function () {
isSaving(true);
datacontext.saveChanges()
.then(goToEditView).fin(complete);
function goToEditView(result) {
router.replaceLocation('#/categorydetail/' + category().id());
}
function complete() {
isSaving(false);
}
},
How do I refresh just the Categories lookup data? Or, am I just doing this wrong and should perhaps NOT have categories as a lookup?
Thanks.
Breeze.js synchronises automatically and knows to search out the Category and update it in its lookup list.
I checked this by calling datacontext.lookups from the browser console after the save had been performed and inspecting the objects it showed me the category name had been refreshed.

what is the right emberjs way to switch between various filtering options?

I've an individualStore (extends from Em.ArrayController), whose task is to keep an array of individual objects. There are several APIs that my application calls, and they return individual objects which are sent to the store. Think about it as the database of cached individual records in my application.
App.individualStore = App.ArrayController.create({
allIndividuals: function () {
return this.get('content').sort(function (a, b) {
return (b.votes_count - a.votes_count);
});
}.property('#each.votes_count').cacheable(),
aliveIndividuals: function () {
return this.get('content').filter(function (individual) {
return (!!individual.living);
}).sort(function (a, b) {
return (b.votes_count - a.votes_count);
});
}.property('#each.living', '#each.votes_count').cacheable(),
deceasedIndividuals: function () {
return this.get('content').filter(function (individual) {
return (!individual.living);
}).sort(function (a, b) {
return (b.votes_count - a.votes_count);
});
}.property('#each.living', '#each.votes_count').cacheable()
});
My view has a `individualsBinding: 'App.individualStore.allIndividuals', which renders up as intended perfectly.
I want to add filtering buttons, e.g. Show: All | Alive | Deceased. What would be the right way to change the filtering here? Keep in mind that whatever the criteria is, I'd like it to keep in sync with individualStore always.
Someone suggested to change bindings on runtime,
this.bind('users', Ember.Binding.from('App.individualStore.aliveIndividuals'));
This works in my first two-three clicks on these buttons, but then it freezes the browser (sort of infinite loop?).
This also doesn't feel like the best option to me. I'm new to ember, so anything you say would be helpful. Thanks in advance.
I would make the filter function itself a property and by changing a filterName on the controller, you are notified and accordingly update the filtered content, see http://jsfiddle.net/pangratz666/ypcLq/
App.controller = Ember.ArrayProxy.create({
content: [],
filterName: 'all',
allFilter: function() {
return true;
},
aliveFilter: function(individual) {
return ( !! individual.living);
},
deceasedFilter: function(individual) {
return (!individual.living);
},
filtered: function() {
var filterName = this.get('filterName');
var filterFunc = this.get(filterName + 'Filter');
return this.filter(filterFunc).sort(function(a, b) {
return (b.votes_count - a.votes_count);
});
}.property('content.#each', 'filterName').cacheable()
});
So you can later in your view set the filter which shall be used via App.controller.set('filterName', 'alive').
Just as a note: you can chain filters via this.filter(filterFunc1).filter(filterFunc2) so you could for example filter all alive individuals of a specific age, ...

Resources