In SAPUI5, you can often bind resource bundle properties to items in several ways. I'm attempting to do it in JavaScript, using data provided by an Odata service, but the methods I've found here so far haven't worked. I've tried two different ways:
How to Use Internalization i18n in a Controller in SAPUI5?
binding issue with sap.m.List and model configured in manifest.json
And neither of these have worked. I feel this is because I'm inside a items list, inside of a dialog that's causing my issue:
My code is:
var resourceBundle = this.getView().getModel("i18n");
if (!this.resizableDialog) {
this.resizableDialog = new Dialog({
title: 'Title',
contentWidth: "550px",
contentHeight: "300px",
resizable: true,
content: new List({
items: {
path: path,
template: new StandardListItem({
title: resourceBundle.getProperty("{Label}"),//"{ path : 'Label', fomatter : '.getI18nValue' }",
description: "{Value}"
})
}
}),
beginButton: new Button({
text: 'Close',
press: function () {
this.resizableDialog.close();
}.bind(this)
})
});
getI18nValue : function(sKey) {
return this.getView().getModel("i18n").getProperty(sKey);
},
Using the 2nd method above, never calls the JavaScript method. I didn't think it would work, as it's more JSON based. The first method, loads the data fine, but instead of showing the resource bundle text, it shows just the text found inside of the {Label} value.
The value found inside of {Label} matches the key found inside of the resouce bundle i.e. without the i18n> in front, like you would have in a view.
Anyone have any suggestions?
Use of formatter will solve your problem, but the way you're doing it is wrong. Try this, it will solve your problem.
var resourceBundle = this.getView().getModel("i18n");
if (!this.resizableDialog) {
this.resizableDialog = new Dialog({
title: 'Title',
contentWidth: "550px",
contentHeight: "300px",
resizable: true,
content: new List({
items: {
path: path,
template: new StandardListItem({
title: {
parts: [{ path: "Label" }],
formatter: this.getI18nValue.bind(this)
},
description: "{Value}"
})
}
}),
beginButton: new Button({
text: 'Close',
press: function () {
this.resizableDialog.close();
}.bind(this)
})
});
}
And the formatter function getI18nValue will be like this,
getI18nValue: function(sKey) {
return this.getView().getModel("i18n").getResourceBundle().getText(sKey);
},
Related
I have one big request for you. I am a high-school student and I want to create an app for students with my friend. In the begining we wanted to use React for our reactive components, but then we saw Vue and it looked really good. But because of the fact, that we already have a big part of the app written in twig, we didn't want to use Vue.js standalone, because we would have to change a lot of our code, especially my friend, which is writing backend in Sympfony. So we use the runtime only version, which does not have a template option, so i have to write render functions for our components. And i am stucked with one particular problem.
I am writing a file-manager, and i need to render layer for every folder. Code is better then million words, so, take a look please :
var data = {
name: 'My Tree',
children: [
{
name: 'hello',
isFolder: false,
},
{
name: 'works',
isFolder: true,
children: [
{
name: 'child2',
isFolder: true,
},
{
name: 'child3',
isFolder: false,
},
]
}
]
}
Vue.component('layer', {
render: function renderChild (createElement) {
if(data.children.length){
return createElement('ul', data.children.map(function(child){
return createElement('li', {
'class' : {
isFolder: child.isFolder,
isFile: !child.isFolder
},
attrs: {
id: "baa"
},
domProps: {
innerHTML: child.name,
},
on:{
click: function(){
console.log("yes");
},
dblclick: function(){
console.log("doubleclicked");
if(child.children.length){
// if this has children array, create whole "layer" component again.
}
}
}}
)
}))
}
},
props: {
level: {
type: Number,
required: true
},
name: {
type: String,
}
}
})
new Vue({
el: '#fileManagerContainer',
data: data,
render (h) {
return (
<layer level={1} name={"pseudo"}>
</layer>
)
}
})
My question is, how to write that recursive call, which will render the whole Layer component on the doubleclick event, if the element has children array.
Thank you in advance for any reactions, suggestions or answers :)
I know this is a very old question, so my answer won't be useful to the OP, but I wanted to drop in to answer because I found myself with the very same problem yesterday.
The answer to writing these recursive render functions is to not try to recurse the render function itself at all.
For my example, I had a set of structured text (ish) - An array of objects which represent content - which can be nested, like so:
[
// each array item (object) maps to an html tag
{
tag: 'h3',
classes: 'w-full md:w-4/5 lg:w-full xl:w-3/4 mx-auto font-black text-2xl lg:text-3xl',
content: 'This is a paragraph of text'
},
{
tag: 'img',
classes: 'w-2/3 md:w-1/2 xl:w-2/5 block mx-auto mt-8',
attrs: {
src: `${process.env.GLOBAL_CSS_URI}imgsrc.svg`,
alt: 'image'
}
},
{
tag: 'p',
classes: 'mt-8 text-xl w-4/5 mx-auto',
content: [
{
tag: 'strong',
content: 'This is a nested <strong> tag'
},
{
content: ' '
},
{
tag: 'a',
classes: 'underline ml-2',
content: 'This is a link within a <p> tag',
attrs: {
href: '#'
}
},
{
content: '.'
}
]
}
]
Note the nested elements - These would need recursion to render properly.
The solution was to move the actual rendering work out to a method as follows:
export default {
name: 'block',
props: {
block: {
type: Object,
required: true
}
},
methods: {
renderBlock (h, block) {
// handle plain text without a tag
if (!block.tag) return this._v(block.content || '')
if (Array.isArray(block.content)) {
return h(block.tag, { class: block.classes }, block.content.map(childBlock => this.renderBlock(h, childBlock)))
}
// return an html tag with classes attached and content inside
return h(block.tag, { class: block.classes, attrs: block.attrs, on: block.on }, block.content)
}
},
render: function(h) {
return this.renderBlock(h, this.block)
}
}
So the render function calls the renderBlock method, and that renderBlock method is what calls itself over and over if it needs to - The recursion happens within the method call. You'll see that the method has a check to see whether the content property is of an Array type - At this point it performs the same render task, but rather than passing the content as-is, it passes it as an Array map, calling the same render method for each item in the array.
This means that, no matter how deeply the content is nested, it will keep calling itself until it has reached all the way to the "bottom" of the stack.
I hope this helps save someone some time in the future - I certainly wish I'd had an example like this yesterday - I would have saved myself a solid couple of hours!
I use jQuery datatable and Export buttons.
I change the filename and the title.
My problem is that I want the title to be dynamic, based on the filters and custom filters.
My problem is that The file name configured on the first load only
.withButtons([
{
extend: "excelHtml5",
filename :$scope.getFileName(false),
title:$scope.getFileName(true),
exportOptions: {
columns: ':visible'
},
//CharSet: "utf8",
exportData: { decodeEntities: true}
},
Of course $scope.getFileName() is only called once, or the number of times you intentionally call it. Go the other way around : Use the init() function along with a $watch :
.withButtons([{
extend: "excelHtml5",
//filename: $scope.getFileName(false),
//title: $scope.getFileName(true),
exportOptions: {
columns: ':visible'
},
//CharSet: "utf8",
exportData: {
decodeEntities: true
},
init: function(dt, node, config) {
$scope.watch('fileName', function()
//config.filename is in fact config.title + config.extension
config.title = $scope.fileName.title;
config.extension = $scope.fileName.extension;
})
}
}])
You are not explaining why you need to use a $scope.getFileName() despite you are using angular where such things is easily automated. With the above you can now update a fileName struct on $scope whenever it is needed, and the export filename will change accordingly
$scope.fileName = {
extension: '.xls',
title: 'my-table-export'
}
Ok I'm pretty sure I know exactly what I need to do here but I'm not sure how to do it. Basically I have a grid that I want to make a key column bind to an array of key/values, which I've done before with kendo (not using Angular) and I know that when I'm creating my key/value array asynchronously then that needs to complete before I can get them show-up with kendo, which I have done using promises before.
So here I have the same issue only angular is also involved. I need to fetch and format an array of data into the format in which a kendo grid column can digest it, so no problem here is my controller code:
var realm = kendo.data.Model.define({
id: 'realmID',
fields: {
realmID: { editable: false, nullable: true }
realmType: { type: 'string', validation: { required: true } }
}
})
var ds1 = kendoHelpers.dataSourceFactory('realms', realm, 'realmID')
var realmType = kendo.data.Model.define({
id: 'realmTypeID',
fields: {
realmTypeID: { editable: false, nullable: true },
name: { type: 'string', validation: { required: true } }
}
})
var ds2 = kendoHelpers.dataSourceFactory('realms/types', realmType, 'realmTypeID')
$scope.mainGridOptions = {
dataSource: ds1,
editable: true,
navigatable: true,
autoBind:false,
toolbar: [
{ name: "create" },
{ name: 'save' },
{ name: 'cancel' }
],
columns: [
{ field: 'realmID', title: 'ID' }
{ field: 'realmTypeID', title: 'Realm Type', editor: realmTypesDDL, values: $scope.realmTypeValues },
{ command: "destroy" }
]
}
$scope.secondGridOptions = {
dataSource: ds2,
editable: true,
navigatable: true,
toolbar: [
{ name: "create" },
{ name: 'save' },
{ name: 'cancel' }
],
columns: [
{ field: 'realmTypeID', title: 'ID' },
{ field: 'name', title: 'Name' }
{ command: "destroy" }
]
}
ds2.fetch(function () {
$scope.realmTypeValues = [{ text: 'Test', value: "24bc2e62-f761-4e70-804c-bc36fdeced3d" }];
//this.data().map(function (v, i) {
// $scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID})
//});
//$scope.mainGridOptions.ds1.read()
});
function realmTypesDDL(container, options) {
$('<input />')
.appendTo(container)
.kendoDropDownList({
dataSource: ds2,
dataTextField: 'name',
dataValueField: 'realmTypeID'
});
}
I made this dataSourceFatory helper method above to return me a basic CRUD kendo dataSource that uses transport and also injects an authorization header which is working fine so don't get hung up on that, ultimately I'm going to be using this data in another grid as well as for reference values for the main grid, but I've hard coded some values that I can use to test with in the ds2.fetch callback.
My HTML is pretty plain:
<div>
<h2>Realms</h2>
<kendo-grid options="mainGridOptions"></kendo-grid>
<h2>Realm Types</h2>
<kendo-grid options="secondGridOptions"></kendo-grid>
</div>
This all works fine and well except I am only seeing the GUID of the realmTypeID in the grid, I click it and the editor is populated correctly so that's good but I want the text value to be displayed instead of the GUID. I'm sure the issue is that the array of values is empty whenever angular is binding to the grid options. My questions are:
How do I either delay this bind operation or manually rebind it after the fetch call?
Is there a better way to handle a situation like this? I try not to expend finite resources for no reason (IE making server calls when unnecessary)
Note: When I move the creation of the text/value array to happen before the grid options, I get the desired behavior I am after
EDIT A work around is to not use the directive to create the grid and instead defer the grid creation until the callback of whatever data your column is dependent on, I was hoping for a more elegant solution but this is better than nothing. So your HTML becomes something like
<h2>Realms</h2>
<div id="realms"></div>
<h2>Realm Types</h2>
<kendo-grid options="secondGridOptions"></kendo-grid>
Then you can create the grid in the fetch callback for example:
ds2.fetch(function () {this.data().map(function (v, i) {
$scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID})
});
$('#realms').kendoGrid($scope.mainGridOptions);
$scope.mainGridOptions.dataSource.fetch()
});
But this doesn't feel very angularish so I'm really hoping for a better solution!
Ok...well I think I hacked this enough and without another suggestion I'm going to go forward with this approach. I'm just going to move the binding logic to the requestEnd event of the second grid so that the values array can be populated right before the binding even. I'm also reworking the values array in this method. It is a bit weird though, I think there is some kendo black magic going on with this array because I can't just set it to a new empty array without it breaking completely...which is why I'm poping everything out prior to repopulating the array. That way when something is deleted or edited in the second grid, the DDL in the first grid is updated in the callback.
function requestEnd(e) {
for (var i = $scope.realmTypeValues.length; i >= 0; i--) $scope.realmTypeValues.pop();
var data;
if (e.type == "read")
data = e.response;
else
data = e.sender.data();
data.map(function (v, i) { $scope.realmTypeValues.push({ text: v.name, value: v.realmTypeID }); });
if ($('#realms').data('kendoGrid') == undefined) {
$('#realms').kendoGrid($scope.mainGridOptions);
}
else
$('#realms').data('kendoGrid').columns[4].values = $scope.realmTypeValues;
}
ds2.bind('requestEnd', requestEnd);
So I'm going to accept my own answer unless anyone has a better approach!
I am new to ExtJs, just stepped into some basic things and found that its very hard to get started as a beginner.
Below are the two ways of implementing Ext button:
Sample1:
var nextBtn = new Ext.Button({
text: 'Next >>',
handler: function() {
Ext.getDom('form_main').submit();
},
id: 'next',
renderTo: 'next'
});
Sample2:
Ext.widget('button', {
text: 'some long title of my cool button',
scale: 'large',
cls: 'my-button',
width: 100,
renderTo: 'output'
});
My guess is beacuse of the version, it has changed. Please let me know what is the difference between these two codes.
Regards,
There are many ways to instantiate a class in ExtJS.
Take this definition as example:
Ext.define ('Ext.button.Button', {
alias: 'widget.button' ,
// here other properties and methods ...
});
Then you can chose one of these ways to instantiate Ext.button.Button:
First: javascript style
var button = new Ext.button.Button ({
// props and methods
});
Second: ExtJS style with Ext.create method
var button = Ext.create ('Ext.button.Button', {
// props and methods
});
Third: ExtJS style with Ext.widget method (it uses alias property)
var button = Ext.widget ('button', {
// props and methods
});
I suggest you to use the second or the third way because they use ExtJS dynamic loader: here's the documentation
I am used to ExtJS 3.X, but am struggling with ExtJS 4.
I want to create an extension of a grid and be able to use an instance of the grid with the xtype. As far as im aware, I have to set the alias as widget.xtypename but its not working for me.
var MyGrid = Ext.define('mygrid', {
extend:'Ext.grid.Panel',
alias: 'widget.mygrid',
// rest of grid...
});
Ext.create('Ext.window.Window', {
title:'My Window',
items:[{
xtype:'mygrid'
}]
})
The Error I am getting in Chrome console is Cannot create an instance of unrecognized alias: widget.mygrid
Some help would be much appretiated
Ext.define('MyApp.Grid',{
extend: 'Ext.grid.GridPanel',
alias: 'widget.mygrid',
.......
.......
}
Now you can use as xtype:'mygrid'
The problem may be that you are attempting to instantiate an object that uses your new class, immediately following the call to Ext.define. Remember that Ext.define is an asynchronous process. Anything that needs to instantiate components should be in an onReady handler, or in Ext.application (launch), or in initComponent in a component class, or in init in a controller class, for these locations are guaranteed to be called only after all the defines have completed.
Specifying an alias beginning with "widget." will allow you to use it wherever xtype is expected. In your simple example, you might try doing the following:
var MyGrid = Ext.define('mygrid', {
extend:'Ext.grid.Panel',
alias: 'widget.mygrid',
// rest of grid...
}, function() {
Ext.create('Ext.window.Window', {
title:'My Window',
items:[{
xtype:'mygrid'
}]
});
});
This will instantiate your window within the callback after the define completes.
If you are using working on a MVC application, you can fix this by adding the view information to your controller. In your controller you need to specify the view in an array named views.. Here is an example:
Ext.define('MyApp.controller.Users', {
extend: 'Ext.app.Controller',
views: ['users.List'],
...
In your case you may need to define views:['mygrid'].
If you are not using MVC architecture, you will need to use the Ext.require and specify your grid class exists.
I believe you need to add a xtype to your config:
var MyGrid = Ext.define('mygrid', {
extend:'Ext.grid.Panel',
alias: 'widget.mygrid',
xtype: 'mygrid',
// rest of grid...
});
After researching more, I would expect the alias to be all you need. Are you defining an initComponent function? Below is an example from Sencha:
Ext.define('App.BookGrid', {
extend: 'Ext.grid.Panel',
// This will associate an string representation of a class
// (called an xtype) with the Component Manager
// It allows you to support lazy instantiation of your components
alias: 'widget.bookgrid',
// override
initComponent : function() {
// Pass in a column model definition
// Note that the DetailPageURL was defined in the record definition but is not used
// here. That is okay.
this.columns = [
{text: "Author", width: 120, dataIndex: 'Author', sortable: true},
{text: "Title", flex: 1, dataIndex: 'Title', sortable: true},
{text: "Manufacturer", width: 115, dataIndex: 'Manufacturer', sortable: true},
{text: "Product Group", width: 100, dataIndex: 'ProductGroup', sortable: true}
];
// Note the use of a storeId, this will register thisStore
// with the StoreManager and allow us to retrieve it very easily.
this.store = new App.BookStore({
storeId: 'gridBookStore',
url: 'sheldon.xml'
});
// finally call the superclasses implementation
App.BookGrid.superclass.initComponent.call(this);
}
});
This one also works:
Ext.define('Path.to.ClassUsingSubcomponent', {
...
requires: ['Path.to.YourSubcomponent'],
...
}