Custom sap.ui.core.messagebundle texts - internationalization

How can we use custom translations for sap.ui.core.messagebundle? We'd like to provide our own validation messages (e.g., for String.Search)?

var oBundle;
defaultLocale = "en-EN";
var countryLocale = "de-FR";
oBundle= jQuery.sap.resources({url : "i18n/labels-en.properties", locale: defaultLocale});
//If need to use any other language have a condition to define accordingly.
//oBundle= jQuery.sap.resources({url :"i18n/labels-fr.properties", locale: countryLocale });
And in your controls just below to setText
oBundle.getText("label.title");

The texts in the types are hard-coded and bound to the libraries bundle. Therefore you can not change them. The only approach to do that would be to inherit from a UI5 type and extend it to your needs.
Fortunately you can always use the Control events validationError and validationSuccess to handle the validation yourself and use your own texts there.

Related

CKEditor 5: How can I load to/save from model instead of view?

My application uses CKEditor 5 to allow users to edit rich text data. These texts support some application-specific custom elements (Web Components), and I want to extend CKEditor with custom plugins that support inserting such custom elements. I seem to be almost there, but I'm having some difficulties getting these custom elements into and out of the CKEditor instance properly.
Current plugin implementations
I mainly followed the Implementing an inline widget tutorial from the CKEditor 5 documentation. As an example, I would like to support a custom element like <product-info product-id="123"></product-info>, which in CKEditor should be rendered as a simple <span> with a specific class for some styling.
In my editing plugin, I first define the extension to the schema:
const schema = this.editor.model.schema;
schema.register('product-info', {
allowWhere: '$text',
isInline: true,
isObject: true,
allowAttributes: [ 'product-id' ]
});
I then define the upcast and downcast converters, closely sticking to the tutorial code:
const conversion = this.editor.conversion;
conversion.for('upcast').elementToElement({
view: {
name: 'span',
classes: [ 'product-info' ]
},
model: (viewElement, { writer: modelWriter }) => {
const id = viewElement.getChild(0).data.slice(1, -1);
return modelWriter.createElement('product-info', { 'product-id': id });
}
});
conversion.for('editingDowncast').elementToElement({
model: 'product-info',
view: (modelItem, { writer: viewWriter }) => {
const widgetElement = createProductInfoView(modelItem, viewWriter);
return toWidget(widgetElement, viewWriter);
}
});
conversion.for('dataDowncast').elementToElement({
model: 'product-info',
view: (modelItem, { writer: viewWriter }) => createProductInfoView(modelItem, viewWriter)
});
function createProductInfoView(modelItem, viewWriter) {
const id = modelItem.getAttribute('product-id');
const productInfoView = viewWriter.createContainerElement(
'span',
{ class: 'product-info' },
{ isAllowedInsideAttributeElement: true }
);
viewWriter.insert(
viewWriter.createPositionAt(productInfoView, 0),
viewWriter.createText(id)
);
return productInfoView;
}
Expected behavior
The idea behind all this is that I need to support the custom <product-info> elements stored in user data in the backend. CKEditor, which is used by users to edit that data, should load these custom elements and transform them into a styled <span> for display purposes while editing. These should be treated as inline widgets since they should only be able to be inserted, moved, copied, pasted, deleted as a whole unit. A CKEditor plugin should allow the user to create new such elements to be inserted into the text, which will then also be <span>s in the editing view, but <product-info>s in the model, which should also be written back to the backend database.
In other words, I expected this to ensure a direct mapping between element <product-info product-id="123"></product-info> in the model, and <span class="product-info">123</span> in the view, to support inserting and moving of <product-info> elements by the user.
Actual result
In short, I seem to be unable to get CKEditor to load data containing <product-info> elements, and unable to retrieve the model representation of these custom elements for backend storage. All operations to insert data to CKEditor from source, or to retrieve CKEditor data for sending to the backend, seem to operate on the view.
For example, if I preload CKEditor contents either by setting the inner content of the element that is replaced with the editor instance, or inserting it like this:
const viewFragment = editor.data.processor.toView(someHtml);
const modelFragment = editor.data.toModel(viewFragment);
editor.model.insertContent(modelFragment);
I see the following behavior (verified using CKEditor Inspector):
When inserting the custom element, i.e. <product-info product-id="123"></product-info>, the element is stripped. It's not present in either the model nor the view.
When inserting the view representation, i.e. <span class="product-info">123</span> I get the representation that I want, i.e. that same markup in CKEditor's view, and the <product-info product-id="123"></product-info> tag in the model.
This is exactly the opposite of what I want! In my backend, I don't want to store the view representation that I created for editing purposes, I want to store the actual custom element. Additionally:
My UI plugin to insert new product info elements, uses a command that does the following:
execute({ value }) {
this.editor.model.change( writer => {
const productInfo = writer.createElement('product-info', {
'product-id': value
});
this.editor.model.insertContent(productInfo);
writer.setSelection(productInfo, 'on');
});
}
which also works as I want it to, i.e. it generates the product-info tag for the model and the span for the view. But, of course, when loading an entire source text when initialising the editor with data from the backend, I can't use this createElement method.
Conversely, in order to retrieve the data from CKEditor for saving, my application uses this.editor.getData(). There, these proper pairs of <product-info> model elements and <span> view elements get read out in their view representation, instead of their model representation – not what I want for storing this data back!
The question
My question is: what do I need to change to be able to load the data into CKEditor, and get it back out of the CKEditor, using the custom element, rather than the transformed element I want to show only for editing purposes? Put differently: how can I make sure the content I insert into CKEditor is treated as the model representation, and how do I read out the model representation from my application?
I'm confused about this because if the model representation is something that is only supposed to be used internally by CKEditor, and not being able to be set or retrieved from outside – then what is the purpose of defining the schema and these transformations in the first place? It will only ever be visible to CKEditor, or someone loading up the CKEditor Inspector, but of no use to the application actually integrating the editor.
Sidenote: an alternative approach
For a different approach, I tried to forgo the transformation to <span>s entirely, and just use the custom element <product-info>, unchanged, in both the model and the view, by using the General HTML Support functionality. This worked fine, since this time no transformation was needed, all I had to do was to set the schema in order for CKEditor to accept and pass through the custom elements.
The reason I can't go with this approach is that in my application, these custom components are handled using Angular Elements, so they will actually be Angular components. The DOM manipulation seems to interfere with CKEditor, the elements are no longer treated as widgets, and there are all manner of bugs and side effects that come with it. Elements show up fine in CKEditor at first, but things start falling apart when trying to select or move them. Hence my realisation that I probably need to create a custom representation for them in the CKEditor view, so they're not handled by Angular and preventing these issues.

Kendo Ui AutoComplete - How to change default filter "startWith" to "contains"?

My application built with AngularJs and KendoUI controls. I used AutoComplete Text Box so many places in the application. Now client wants that search should be with "Contains" filter. for the same i need to put filter: 'contains' everywhere AutoComplete control used.
I want to change default filter 'startWith' to 'contains' at beginning of the application. So that i can escape to make change every html file.
can anyone knows how to do the same?
I guess you need to update your auto complete filter property at least once for all controls to support dynamic property binding and bind to some root configuration, like:
<input kendo-auto-complete k-filter="config.autoComplete.defaultFilter" />
So will be able to change default filter in future by updating only config value.
Another approach - is to override default "setOptions" behavior for "AutoComplete" component to use correct filter by default somewhere on app start:
var nativeSetOptions = window.kendo.ui.AutoComplete.prototype.setOptions;
window.kendo.ui.AutoComplete.prototype.setOptions = function(options) {
options.filter = 'contains';
nativeSetOptions.call(this, options);
}
You can use k-options attribute:
<input kendo-auto-complete ng-model="yourModel" k-data-source="yourDataSource" style="width: 100%;" k-options="autocompleteOptions"/>
and then in your controller:
$scope.autocompleteOptions = {
filter:"contains"
}

Dynamically adding custom elements to DOM Aurelia [duplicate]

It seems Aurelia is not aware when I create and append an element in javascript and set a custom attribute (unless I am doing something wrong). For example,
const e = document.createElement('div');
e.setAttribute('custom-attr', 'some value');
body.appendChild(e);
Is there a way to make Aurelia aware of this custom attribute when it gets appended?
A little background: I am creating an app where the user can select their element type (e.g. input, select, checkbox etc.) and drag it around (the dragging is done in the custom attribute). I thought about creating a wrapper <div custom-attr repeat.for="e of elements"></div> and somehow render the elements array, but this seemed inefficient since the repeater will go through all the elements everytime I push a new one and I didn't not want to create a wrapper around something as simple as a text input that might be created.
You would have to manually trigger the Aurelia's enhance method for it to register the custom attributes or anything Aurelia related really. And you also have to pass in a ViewResources object containing the custom attribute.
Since this isn't as straight forward as you might think, I'll explain it a bit.
The enhance method requires the following parameters for this scenario:
Your HTML as plain text (string)
The binding context (in our scenario, it's just this)
A ViewResources object that has the required custom attribute
One way to get access to the ViewResources object that meets our requirements, is to require the custom attribute into your parent view and then use the parent view's ViewResources. To do that, require the view inside the parent view's HTML and then implement the created(owningView, thisView) callback in the controller. When it's fired, thisView will have a resources property, which is a ViewResources object that contains the require-d custom attribute.
Since I am HORRIBLE at explaining, please look into the example provided below.
Here is an example how to:
app.js
import { TemplatingEngine } from 'aurelia-framework';
export class App {
static inject = [TemplatingEngine];
message = 'Hello World!';
constructor(templatingEngine, viewResources) {
this._templatingEngine = templatingEngine;
}
created(owningView, thisView) {
this._viewResources = thisView.resources;
}
bind() {
this.createEnhanceAppend();
}
createEnhanceAppend() {
const span = document.createElement('span');
span.innerHTML = "<h5 example.bind=\"message\"></h5>";
this._templatingEngine.enhance({ element: span, bindingContext: this, resources: this._viewResources });
this.view.appendChild(span);
}
}
app.html
<template>
<require from="./example-custom-attribute"></require>
<div ref="view"></div>
</template>
Gist.run:
https://gist.run/?id=7b80d2498ed17bcb88f17b17c6f73fb9
Additional resources
Dwayne Charrington has written an excellent tutorial on this topic:
https://ilikekillnerds.com/2016/01/enhancing-at-will-using-aurelias-templating-engine-enhance-api/

Can I replace $ by $('form')?

I use this https://jsfiddle.net/rz0zk5u6/2/ to validate 2 forms on the same page.
To differentiate the forms, can I replace
$.validator.setDefaults({});
by
$("#loginForm").validator.setDefaults({});
?
$.validator.setDefaults({});
is for setting the global defaults. If you wish to set per-form options, use:
var loginForm = $('#loginForm').validate({
// options go here
});
as described in the docs. Then you can use:
loginForm.form();
to perform the validation as described here.
Yes, jquery plugins will apply to elements in a selection/jquery object. If you select multiple forms, it will apply the validator to all of them. The following all do the same thing; Apply the validator to multiple forms.
$('form').validator.setDefaults({});
$('#loginForm, #anotherForm').validator.setDefaults({});
var forms = $('form');
forms.validator.setDefaults({});

Manually load kendo mobile view

I'm working on a Kendo Mobile project with a number of:
Kendo Views (external to root html)
Modal Views (in the root html).
The external files load on demand and everything works fine. But i'd like to have the same load on demand behavior for some of the modal views, because the root/based html file is becoming too large and not manageable.
Is there a way to either:
Store a modal view in an external file? If so is it possible to load via javascript syntax (app.navigate()) rather than the declarative syntax (href='externalmodal').
Manually pre-load an external view without navigating to it first.
This code lets you manually create a view:
var viewUrl = 'blahblahblah';
var element = $.parseHTML('<div data-role=view>test</div>')[0];
element.style.display = 'none';
$(document.body).append(element);
var options = $.extend({}, kendo.parseOptions(element, kendo.mobile.ui.View.fn.options));
var view = new kendo.mobile.ui.View(element, options);
view.element[0].setAttribute('data-url', viewUrl);
kendo.mobile.application.navigate(viewUrl, '');
Depending on what features you use, you may need to instead use code similar that that used for ModalView below so that Kendo creates the subclass (changes: substitute View for ModalView, substitute view for modalview, add data-url, remove call to show(), maybe check that view not already created by checking for element with matching data-url). We haven't tested setting roles.view this way, but we did something similar while testing this stuff out and it worked.
Don't try settings the options - Kendo got confused (at least trying to set useNativeScrolling didn't work, also don't try setting the options object on the subclass unless you really know what you are doing).
Caveat: This was using browserHistory:false (which disables routing) when the kendo.mobile.Application was created. The technique should still work when using browser history if you use a valid url fragment (same as would be created by Kendo for the pushstate/hashchange url).
This is a also way to cleanly subclass kendo.mobile.ui.View that works well - although you must still use data-role=view even though your subclass is a "different" component. Note that you can't just use you cant use your own subclassed component with its own name like role=myview to subclass a view because there are hard-coded checks specifically for data-role=view in the kendo codebase. Same if you wish to subclass: layout modalview drawer splitview page (amongst other hard-coded kendo ui component names - search kendo code for kendo.roleSelector - ugly). e.g.
MyView = kendo.mobile.ui.View.extend({
init: function(element, options) {
kendo.mobile.ui.View.prototype.init.apply(this, arguments);
...
var myView = new MyView('<div data-role=view>test</div>');
Why it works: The relevant function in the Kendo source code is _findViewElement which does element = this.container.children("[" + attr("url") + "='" + urlPath + "']"); to see if the view already exists for a url, before creating a new one. A unique init function is always required as it ends up being the constructor function.
If you want to subclass a modalview, you need to do something different due to the way kendo works:
var MyModalView = kendo.mobile.ui.ModalView.extend({
html: '<div data-role=modalview style="width:90%;display:none;">Foobar</div>',
init: function() {
kendo.mobile.ui.ModalView.prototype.init.apply(this, arguments);
}
});
function makeModalView() {
$(document.body).append($.parseHTML(MyModalView.prototype.html));
var roles = $.extend({}, kendo.mobile.ui.roles);
roles.modalview = MyModalView;
var modalView = kendo.initWidget($(element), {}, roles);
modalView.open();
return modalView;
}

Resources