SAP UI5 reuse component - javascript

I am writing a UI5 faceless component which should be used in multiple other applications. The target applications are located in a package of an SAPUI5 ABAP respository. The component that should be included in the targets is in a different package in the same SAPUI5 ABAP repository.
How do I include this component? From documentation I added changes to the target manifest.json in the following way:
"dependencies": {
"minUI5Version": "1.30.0",
"libs": { "sap.m": {}, "sap.ui.core": {}, "sap.ui.layout": {} },
"components": { "cls.dva": {} }
},
But how is the component actually found ? Here I used the SICF node path. Do I have to add ".Component" to the path? Is there another place where I have to reference the dependent component in the target app?
Another questions is how and where do I instantiate the component? Is the init() function of the target Component.js the right place? There are also some parameters I would have to pass to properly inialize it. In the end the component and its data should be available app wide.
Thank you,
Christian

another Christian here... :-)
regarding to your question you need to keep two things in mind:
The id of your faceless component
The name of your faceless component in the SAP ABAP repository
The dependencies section of your app is the right place to put the component you want to load into.
Thereby it is not important in which package of the SAP ABAP repository your faceless component is laying. Important is the id.
Let's take a look into the manifest of your faceless component:
{
"_version": "1.4.0",
"sap.app": {
"id": "cls.dva",
"type": "component",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"resources": "resources.json",
...
},
"sap.ui": {
...
}
...
}
As you can see the Id of this component is equal to the Id you type into the dependencies section of the app that shall use your component. That's all you need to take care of.
Now, if you want to load and access your dependent component in the init function of your Component.js (which is a good place if it should be available in the whole app), you can do the following (here you need the name of the reuse component that you gave it during the first deployment, e.g. z_cls_dva):
init: function() {
sap.ui.component({
manifestUrl: "/sap/bc/ui5_ui5/sap/z_cls_dva/manifest.json",
async: true,
manifestFirst: true,
componentData: { componentSetting: true }
}).then(function(oComponent) {
this._clsDvaReuse = oComponent
}.bind(this)).catch(function(sError) {
jQuery.sap.log.error(sError, "-", this.getManifestEntry("sap.app").id)
}.bind(this))
}
Via the componentData you are able to provide parameters to the reuse component. There you can call this.getComponentData() to access them. You could also use the settings property to provide your configuration to the component. Read the sap.ui.component documentation for more details.
Notice: As of SAPUI5 1.48 the recommendation for loading reuse components changed. See the corresponding article in the sdk documentation.

Related

AG Grid: Add license before export

We use AG Grid. I want to add our license as a side effect before exporting the React component for use, rather than having to add the license every time we use the grid component.
What I tried below doesn't work. I thought side effects would run before import/export if declared like this, but clearly my mental model was wrong. I assume the build tool may affect what happens too, we use Gulp in this particular case.
GridSupport.js (in a design package/repo)
/**
* AG Grid License
*/
import { LicenseManager } from "#ag-grid-enterprise/core";
LicenseManager.setLicenseKey('…some license key…');
// Export below happens, but no license set above :(
export { AgGridReact as default } from "#ag-grid-community/react";
Grid.js (in another package/repo)
import { AgGridReact } from 'GridSupport';
const Grid = (props) => {
// AgGridReact should be usable without printing license warnings to the console
return <AgGridReact {...props} />
}
What should I do instead?
Why not Initialize the license manager at the top level (App.js, or index.js)? it would cover all the encompassing grids.
As a side note: Just make sure to check that you are not mixing packages and modules for the grid when using enterprise package.
The question was edited to clarify that the two files were in two different packages.
We landed on adding the GridSupport.js to sideEffects in our design package/repo. This ensured that the license was set correctly as a side effect before the export happened.
package.json
{
"name": "#ourcompany/design",
"sideEffects": [
"some/path/GridSupport.js"
],
"dependencies": [
"ag-grid-community": "...",
"ag-grid-enterprise": "...",
"ag-grid-react": "..."
]
}

Generate appropriate Angular Element dynamically without bloating the build size?

Summary:
When createCustomElement() is called multiple times inside a switch case, all of the components will be included in the build, instead of just the one that will actually be used. This increases the build size with duplicate code.
Details
I have an Angular 11 app with a multi-site architecture. There is a project in the angular.json for each site, so they can be built independently and generate their own dist bundles based on the appropriate environment.ts file that contains a "siteCode".
For one of the big components in my site -- let's call it myWidget -- I also export it as a generic web component (a.k.a "Angular Element", a.k.a. "custom element") for other sites to consume. So I have myWidget in a sub-project of the main app, and it also has its own projects listed in angular.json. Meaning I can run a build that should contain just myWidget for a given site (along with the core Angular framework, obviously).
app.module.ts of myWidget sub-project (simplified):
import { MyWidgetSite1Component } from './my-widget/site-widgets/my-widget-site1.component';
import { MyWidgetSite2Component } from './my-widget/site-widgets/my-widget-site2.component';
import { MyWidgetSite3Component } from './my-widget/site-widgets/my-widget-site3.component';
#NgModule({
declarations: [AppComponent],
imports: [MyWidgetModule]
})
export class AppModule {
constructor(private injector: Injector) {
//Create generic web component version of myWidget. Use correct child version per site.
switch (environment.siteCode) {
case "site1": {
const myWidgetCustomElement = createCustomElement(MyWidgetSite1Component , { injector: this.injector });
customElements.define('site1-myWidget', myWidgetCustomElement);
break;
}
case "site2": {
const myWidgetCustomElement = createCustomElement(MyWidgetSite2Component , { injector: this.injector });
customElements.define('site2-myWidget', myWidgetCustomElement);
break;
}
case "site3": {
const myWidgetCustomElement = createCustomElement(MyWidgetSite3Component , { injector: this.injector });
customElements.define('site3-myWidget', myWidgetCustomElement);
break;
}
}
}
}
Problem: it includes all three of those components in the build, instead of just the one that will be used for that site (verified with webpack bundle analyzer).
Futher background
The three site-specific myWidget components here all inherit from a common base component where all the real logic is, so they are nearly-identical. I'm doing this so I can load the appropriate CSS files for that site and bundle them inside the exported MyWidget web component as component-specific CSS. It uses shadowDom encapsulation and this way the web component is completely sealed off from the parent page that it's inserted into. So the components in question look like this:
my-widget-site1.component.ts
#Component({
selector: 'my-widget-site1',
templateUrl: '../my-widget/my-widget.component.html', //Shares base myWidget template
//First file does nothing but import appropriate site's stylesheets from main project. It would
//have been better to directly include them here, but that resulted in odd build errors.
styleUrls: [
'./my-widget-site1.component.scss',
'../my-widget/my-widget.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom
})
export class MyWidgetSite1Component extends MyWidgetComponent implements OnInit {
//Do not add any code here
}
my-widget-site1.component.scss
#import '../../../../../../src/app/sites/site1/theme.scss';
#import '../../../../../../src/styles.scss';
#import '../../../../../../src/app/sites/site1/styles.scss';
Conclusion
I can think of a few general ideas to solve this:
1) Some trick to load the desired component dynamically instead of in a switch case?
I haven't been able to find anything. It seems as long as I have to import the component, it will be included in the build.
2) Avoid having multiple versions of the component per site entirely?
I would love to do this, but I don't know how to get around the CSS problem. The appropriate CSS files for a given site need to be bundled into this component at build time so they are encapsulated in the shadow-root of the web component and not built as a separate CSS file that gets imported into the global scope of the consuming page. That means I can't just list them in the "styles" section of the project build in angular.json
I tried to do a dynamic #import statement on the scss but that doesn't seem possible.
Can you script something into the build process somehow to choose the right scss files at build time? I would have no idea where to start with something like that.
I figured out an interesting solution to this.
I can get rid of the need for multiple component files and effectively pull off a dynamic #import by using a 'shortcut' to point at the necessary scss files instead of the full path:
#import "theme";
#import "styles";
#import "site-styles";
You can configure the folders it will find the specified files in via angular.json:
"stylePreprocessorOptions": {
"includePaths": [
"src/app/sites/site1/", //theme.scss and site-styles.scss are here
"src/" //styles.scss is here
]
}
So now I can use one component that always has the same imports, but at build time it will actually use the right file for the site being built.
Info about the shortcut syntax: https://www.digitalocean.com/community/tutorials/angular-shortcut-to-importing-styles-files-in-components

Avoid relative path import hell in react-native?

I'm new to react-native coming from vue background, one thing I hate in react is having to use relative path imports in my components, I find myself doing something like:
import HomeScreen from '../../components/screens/HomeScreen'
If my folder structure changes it means I will have to do a search and replace in all of m components using that component, not cool and prone to be a dev hell.
Is it possible to use absolute paths like with node modules, is there a library where I can use aliases or similar, with vue I could import my components in the app.js like Vue.component('home-screen ...) and I could use them without having to import.
you can add a package.json file with a name key
{
"name": "#components"
}
in any folder and metro will recognize it.
You can then import as #components/screens/HomeScreen
If you are using vscode, you can add a jsconfig.json file to your project root to enable import autocomplete.
Here is mine:
{
"compilerOptions": {
"baseUrl": "",
"paths": {
"#components/*": ["src/components/*"],
"#helper/*": ["src/helper/*"],
"#actions/*": ["src/actions/*"],
"#constants/*": ["src/constants/*"],
"#primitives": ["src/primitives/index.js"],
"#graphql": ["src/graphql/index.js"],
"#services/*": ["src/services/*"],
"#assets/*": ["assets/*"]
}
}
}
The easy method
Add a package.json to important directories with a {"name":"<prefix>"}, similar to a monorepo.
If you are doing this, you can probably also think about making it a full monorepo since there is very little extra work
The incredibly complex method
This method is easier for using things like webpack resolver, etc.
Create a metro.config.js at the root with the following content
module.export = {
resolver: {
resolveRequest: (
context: ResolutionContext,
moduleName: string,
platform: string | null
) => {
//...
return Resolution;
}
}
}
Resolution Context
The resolution context is an object that holds attributes of the running resolution, most importantly originModulePath which is the path of the requiring module.
Resolution
The resolution is an object of either:
{ type: "empty" } for empty sources
{ type: "sourceFile", filePath: string } for JS, JSON, etc. files
{ type: "assetFiles", filePaths: string[] } for any other type (e.g. not metro compilable)
For this method, I would recommend looking at metro-resolver's types since this method is abysmally documented

Include JSON files into React build

I know this question maybe exist in stack overflow but I didn't get any good answers, and I hope in 2020 there is better solution.
In my react app I have a config JSON file, it contains information like the title, languages to the website etc..
and this file is located in 'src' directory
{
"headers":{
"title":"chat ",
"keys":"chat,asd ,
"description":" website"
},
"languages":{
"ru":"russian",
"ar":"arabic",
"en":"English"
},
"defaultLanguage":"ru",
"colors":{
"mainColor":"red",
"primary":"green",
"chatBackGround":"white"
}
}
I want to make my website easy to edit after publishing it, but after I build my app, I can't find that settings.json file there in build directory.
I find out that files in public directory actually get included to build folder, I tried to put my settings.JSON in public,
but react won't let me import anything outside of src directory
I found other solutions like this one but didn't work
https://github.com/facebook/create-react-app/issues/5378
Also I tried to create in index.html a global var like (window.JSON_DATA={}), and attach a JS object to it and import it to App.js, but still didn't work.
How can I make a settings JSON file, and have the ability to edit it after publishing the app?
Add your settings.json to the public folder. React will copy the file to the root of build. Then load it with fetch where you need it to be used. For example if you need to load setting.json to the App.js then do the next:
function App() {
const [state, setState] = useState({settings: null});
useEffect(()=>{
fetch('settings.json').then(response => {
response.json().then(settings => {
// instead of setting state you can use it any other way
setState({settings: settings});
})
})
})
}
If you use class-components then do the same in componentDidMount:
class CustomComponent extends React.Component {
constructor(props) {
super(props);
this.state = {settings: null};
}
componentDidMount() {
fetch('settings.json').then(response => {
response.json().then(settings => {
this.setState({settings: settings});
})
})
}
}
Then you can use it in render (or any other places of your component):
function App() {
...
return (
{this.state.settings && this.state.settings.value}
)
}
The easiest way would be to require() the file on the server during server side rendering of the html page and then inline the json in the html payload in a global var like you mentioned window.JSON_DATA={}. Then in your js code you can just reference that global var instead of trying to use import.
Of course this approach would require you to restart your server every time you make a change to the json file, so that it get's picked up. If that is not an option then you'll need to make an api call on the server instead of using require().
You may want to look at using npm react-scripts (https://www.npmjs.com/package/react-scripts) to produce your react application and build. This will package will create a template that you can put your existing code into and then give you a pre-configure build option that you can modify if you would like. The pre-configured build option will package your .json files as well. Check out their getting started section (https://create-react-app.dev/docs/getting-started/)
If you don't want to go that route, and are just looking for quick fix, then I would suggest change your json files to a JS file, export the JS object and import it in the files you need it since you seem to be able to do that.
//src/sampledata.js
module.exports = {
sample: 'data'
}
//src/example.jsx (this can also be .js)
const sampledata = require('./sampledata');
console.log(sampledata.sample); // 'data'
you can use 'Fetch Data from a JSON File'
according to link
https://www.pluralsight.com/guides/fetch-data-from-a-json-file-in-a-react-app
example

meteor: import directory - modular import of methods which are needed

I'm migrating my meteor application to the import-function of meteor 1.3.
But I think this is not quite the best way it should be done. Isn't it possible to load/import just the method which is really needed?
I mean, right now just all methods are loaded by importing the the methods.js. But I would like to do that in a modular way. So if the form .fomNewElement is used in the app, the method insertArticle will be imported and so on. Not just loading everything...
Below you can see my folder structure for /imports and some content of the files. Is there anything more I could improve in the structure itself?
Also it would be great if the import would depend on user roles. Is this possible?
imports/api/article/client/article.js
import { Articles } from '../';
import { insertArticle, updateArticle } from '../methods.js';
Template.Articles.helpers({
// some helpers
});
Template.Artilces.onCreated(function() {
// some code
});
Template.Artilces.onRendered(function() {
// some code
});
Template.Articles.events({
'submit .formNewElement': function(event) {
event.preventDefault();
var title = event.target.title.value.trim();
insertArticle.call({
title: title
});
},
'click .anything': function() {}
});
As you can see, I put into that js-file all helpers, events and onCreated/onRendered code. Hope this is 'correct'... Please give me some hint, if this isn't very smart.
imports/api/article/index.js
export const Articles = new Mongo.Collection('articles');
imports/api/article/methods.js
import { Articles } from './';
export const insertArticle = new ValidatedMethod({
name: 'article.insert',
validate: new SimpleSchema({
title: { type: String }
}).validator(),
run( document ) {
Articles.insert( document );
}
});
export const updateArticle = new ValidatedMethod({
name: 'article.update',
validate: new SimpleSchema({
_id: { type: String },
'update.title': { type: String }
}).validator(),
run( { _id, update } ) {
Articles.update( _id, { $set: update } );
}
});
And the other files:
imports/startup/client/index.js
import '../../api/redactor-article/client';
imports/startup/server/index.js
import '../../api/redactor-article/server/publications.js';
import '../../api/redactor-article/methods.js';
imports/api/article/client/index.js
import './article.html';
import './article.sass';
import './article.js';
Filestructure
/imports
/api
/article
/client
article.html
article.js
article.sass
index.js
/server
publications.js
index.js
methods.js
Update
Maybe it would be a better way to structure an import module like this:
imports/
api/
articles/
publication.js
methods.js
collection.js
ui/
articles/
article.html
article.css
article.js // contains helpers, events and onCreated/onRendered
Then I have to import the files in startup/client (-> all ui files of this module AND all api files) and startup/server (-> just all api files)...
Right?
A few points:
You've put everything under imports/api. That directory is designed for collections, methods, helpers, 'business logic' and public API (e.g. if you expose a REST API, you'd do it from within that directory). Use imports/ui for your templates (including their styles and associated .js files).
You don't need to differentiate between client and server directories within imports. Just import the files you need from the respective main entry points (i.e. client/main.js and server/main.js). This point is a little more complex than I suggest here, see the link to 'structure' in the Meteor Guide, below.
index.js doesn't seem like a logical place to put your Articles collection. I'd make a file at /imports/api/articles/articles.js for it. See http://guide.meteor.com/structure.html for a good overview about where to put things and why.
Also, in the interests of following best-practices, use a default export for your Articles collection: http://guide.meteor.com/code-style.html#collections
To answer your question about how much of the file is exported (i.e. which functions), there's not much you can do about everything being loaded. The bundler needs to read the entire JS file anyway (imagine you exported an object and then changed it further down in the same file– not the best practice, but possible). If you're not using a function though, by all means, don't import it! And you can always split up your methods into seperate files if they get unmanageable.
Regarding your question about only importing bits for certain user roles: always avoid using imports or other types of obfuscation for security. The ideal way to do security on Meteor is to assume ANYTHING is accessible on the client (it pretty much is) and code your server-side code accordingly. That means, if you have an admin area, assume that anyone can access it. You can do checks in server methods and publications for this.userId and do a database lookup there to ensure the user has the correct privileges. Again, the guide has more info about this: http://guide.meteor.com/security.html
A final note about imports/exports: the idea behind them is not to reduce code size, but to provide a graph of what is actually being used (and leaving out the files that aren't) to make hot code reloading faster for a better development experience. They also make for cleaner application code that is easier to understand, because you don't have random magical globals swimming around that could have come from anywhere, and help to keep logically distinct pieces of code separate.
Best of luck :)

Categories

Resources