valueStateText not defined for SAPUI5 custom control - javascript

I have below custom control created by extending sap.m.Input that lets the user enter only numbers. However, when there is actually an error the state of control changes to Error with red borders but the valueStateText is not displayed when it has focus. How can I get the valueStateText for my custom control? Shouldn't it inherit from sap.m.Input?
Custom Control code:
sap.ui.define([
"sap/m/Input"
], function (Control) {
"use strict";
return Control.extend("sample.control.NumericInput", {
metadata: {
properties: {},
aggregations: {},
events: {}
},
init: function () {
if (sap.ui.core.Control.prototype.onInit) {
sap.ui.core.Control.prototype.onInit.apply(this, arguments);
}
this.attachLiveChange(this.onLiveChange);
},
renderer: function (oRM, oControl) {
sap.m.InputRenderer.render(oRM, oControl);
},
onLiveChange: function (e) {
var _oInput = e.getSource();
var val = _oInput.getValue();
val = val.replace(/[^\d]/g, "");
_oInput.setValue(val);
}
});
});
XML code:
<hd:NumericInput value="{path:'payload>/MyNumber',type:'sap.ui.model.type.String',constraints:{minLength:1,maxLength:10}}" valueStateText="My Number must not be empty. Maximum 10 characters."/>

In your init override you need to call the init of the parent control (not onInit of sap.ui.core.Control). The init of the sap.m.InputBase (sap.m.Input's parent class) sets up the valuestate initial values and rendering so it's missing all that code out and not working correctly as you've found.
Check out this example based on your code:
sap.ui.define([
"sap/m/Input"
], function (Control) {
"use strict";
return Control.extend("sample.control.NumericInput", {
metadata: {
properties: {},
aggregations: {},
events: {}
},
init: function () {
Control.prototype.init.apply(this, arguments);
this.attachLiveChange(this.onLiveChange);
},
renderer: function (oRM, oControl) {
sap.m.InputRenderer.render(oRM, oControl);
},
onLiveChange: function (e) {
var _oInput = e.getSource();
var val = _oInput.getValue();
val = val.replace(/[^\d]/g, "");
_oInput.setValue(val);
}
});
});
// Small model
const model = new sap.ui.model.json.JSONModel({
MyNumber: "10000000000000000000",
});
// Create an example of the control (JS not XML but idea is the same)
const input = new sample.control.NumericInput({
valueState: "Error",
valueStateText:"My Number must not be empty. Maximum 10 characters.",
value: {
path:'/MyNumber',
type:'sap.ui.model.type.String',
constraints:{minLength:1,maxLength:10}
}
});
input.setModel(model);
input.placeAt('content');
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-libs="sap.m"></script>
</head>
<body class="sapUiBody sapUiSizeCompact">
<div id='content'></div>
</body>
</html>

you can greatly reduce your code to
Input.extend('NumericInput', {
renderer: {
},
onAfterRendering: function () {
if (Input.prototype.onAfterRendering) {
Input.prototype.onAfterRendering.apply(this, arguments);
}
this.$().find("INPUT").each(function(i, input) {
$(input).on("keypress keyup blur", function(event) {
$(this).val($(this).val().replace(/[^\d].+/, ""));
if ((event.which < 48 || event.which > 57)) {
event.preventDefault();
}
});
});
},
});
https://jsbin.com/muberid/1/edit?js,output

Related

Is there a way to write ts modules as native code in the JavaScript data file when compiling?

I want to divide an app in a TypeScript development environment into function files - so that each file contains only one function.
I would like to realise this with TS modules. In the compiled JavaScript file, however, these modules should not get imported at runtime, but compiled as native code.
For example, from this app.ts
type ArbitraryAttribute = any //can refer to any value valid at runtime
declare interface App {
get? (key: string): ArbitraryAttribute | void,
set? (key: string, val: ArbitraryAttribute): void,
helper?: AppHelper,
}
declare interface AppHelper {
deepGetter? (key: string): ArbitraryAttribute | void,
deepSetter? (key: string, val: ArbitraryAttribute): void,
}
import { get } from "./get";
import { set } from "./set";
import { helper } from "./helper/index";
const app:App = {
get,
set,
helper,
}
this app.js is to be generated:
var app = {
get: function (key) {
if (app.helper && app.helper.deepGetter) {
return app.helper.deepGetter(key);
};
},
set: function (key, val) {
if (app.helper && app.helper.deepSetter) {
app.helper.deepSetter(key, val);
};
},
helper: {
deepGetter: function (key) {
// get anything
},
deepSetter: function (key, val) {
// set anything
},
},
};
Neither in the TypeScript configuration nor in webpack have I found a solution for this.
This should be feasible, right? Does anyone know a solution or a library that solves this problem?
As #Dimava mentions, via tsconfig there is the possibility to merge a number of typescript files into a single js file, but the result for my aproach is really messy. The previosly postet js file will look like this:
System.register("get", [], function (exports_1, context_1) {
"use strict";
var get;
var __moduleName = context_1 && context_1.id;
return {
setters: [],
execute: function () {
exports_1("get", get = function (key) {
if (app.helper && app.helper.deepGetter) {
return app.helper.deepGetter(key);
}
;
});
}
};
});
System.register("set", [], function (exports_2, context_2) {
"use strict";
var set;
var __moduleName = context_2 && context_2.id;
return {
setters: [],
execute: function () {
exports_2("set", set = function (key, val) {
if (app.helper && app.helper.deepSetter) {
return app.helper.deepSetter(key, val);
}
});
}
};
});
System.register("helper/deepGetter", [], function (exports_3, context_3) {
"use strict";
var deepGetter;
var __moduleName = context_3 && context_3.id;
return {
setters: [],
execute: function () {
exports_3("deepGetter", deepGetter = function (key) {
// get anything
});
}
};
});
System.register("helper/deepSetter", [], function (exports_4, context_4) {
"use strict";
var deepSetter;
var __moduleName = context_4 && context_4.id;
return {
setters: [],
execute: function () {
exports_4("deepSetter", deepSetter = function (key, val) {
// set anything
});
}
};
});
System.register("helper/index", ["helper/deepGetter", "helper/deepSetter"], function (exports_5, context_5) {
"use strict";
var deepGetter_1, deepSetter_1, helper;
var __moduleName = context_5 && context_5.id;
return {
setters: [
function (deepGetter_1_1) {
deepGetter_1 = deepGetter_1_1;
},
function (deepSetter_1_1) {
deepSetter_1 = deepSetter_1_1;
}
],
execute: function () {
exports_5("helper", helper = {
deepGetter: deepGetter_1.deepGetter,
deepSetter: deepSetter_1.deepSetter,
});
}
};
});
System.register("index", ["get", "set", "helper/index"], function (exports_6, context_6) {
"use strict";
var get_1, set_1, index_1, app;
var __moduleName = context_6 && context_6.id;
return {
setters: [
function (get_1_1) {
get_1 = get_1_1;
},
function (set_1_1) {
set_1 = set_1_1;
},
function (index_1_1) {
index_1 = index_1_1;
}
],
execute: function () {
app = {
get: get_1.get,
set: set_1.set,
helper: index_1.helper,
};
}
};
});
I haven't get it working for "--module es2015 --moduleResolution classic",
only for for "--module system --moduleResolution node".
And the file weighs almost six and a half times as much!

VueJS detecting if a button was clicked in Watch method

I am creating undo/redo functionality in VueJS. I watch the settings and add a new element to an array of changes when the settings change. I also have a method for undo when the undo button is clicked.
However, when the button is clicked and the last setting is reverted, the settings are changed and the watch is fired again.
How can I prevent a new element being added to the array of changes if the settings changed but it was because the Undo button was clicked?
(function () {
var Admin = {};
Admin.init = function () {
};
var appData = {
settings: {
has_border: true,
leave_reviews: true,
has_questions: true
},
mutations: [],
mutationIndex: null,
undoDisabled: true,
redoDisabled: true
};
var app = new Vue({
el: '#app',
data: appData,
methods: {
undo: function() {
if (this.mutations[this.mutationIndex - 1]) {
let settings = JSON.parse(this.mutations[this.mutationIndex - 1]);
this.settings = settings;
this.mutationIndex = this.mutations.length - 1;
console.log (settings);
}
},
redo: function() {
}
},
computed: {
border_class: {
get: function () {
return this.settings.has_border ? ' rp-pwb' : ''
}
},
undo_class: {
get: function () {
return this.undoDisabled ? ' disabled' : ''
}
},
redo_class: {
get: function () {
return this.redoDisabled ? ' disabled' : ''
}
}
},
watch: {
undoDisabled: function () {
return this.mutations.length;
},
redoDisabled: function () {
return this.mutations.length;
},
settings: {
handler: function () {
let mutation = JSON.stringify(this.settings),
prevMutation = JSON.stringify(this.mutations[this.mutations.length-1]);
if (mutation !== prevMutation) {
this.mutations.push(mutation);
this.mutationIndex = this.mutations.length - 1;
this.undoDisabled = false;
}
},
deep: true
}
}
});
Admin.init();
})();
Since you make the changes with a button click, you can create a method to achieve your goal instead of using watchers.
methods: {
settings() {
// call this method from undo and redo methods if the conditions are met.
// move the watcher code here.
}
}
BTW,
If you don't use setter in computed properties, you don't need getters, so that is enough:
border_class() {
return this.settings.has_border ? ' rp-pwb' : ''
},
These watchers codes look belong to computed:
undoDisabled() {
return this.mutations.length;
},
redoDisabled() {
return this.mutations.length;
},

Unable to get property of undefined or null reference UWP

I have the following code in my visual studio and it works perfectly well au a UWP app on my desktop win10, albeit it does not work on my windows phone as a UWP app. I also tried running my simple webapp as from a webserver and loading it in the Edge and it works perfectly.
What should be the problem?
My code looks like this. I omitted some parts:
var model = {
db: {},
goalsobj: {},
goals: [],
init: function() {
var openReq = window.indexedDB.open("GoalsDB");
openReq.onupgradeneeded = function (event) {
model.db = event.target.result;
var objectStore = model.db.createObjectStore("Goals", { keyPath: "id" });
objectStore.createIndex("id","id", {unique:true});
};
openReq.onsuccess = function (event) {
model.db = event.target.result
model.db.transaction("Goals", "readonly").objectStore("Goals").count().onsuccess = function (event) {
if (event.target.result == 0) {
console.log("indexeddb empty");
var goalstemplate = {
id: "idee",
goals: [{ "name": "Task1" }, { "name": "Task2" }, { "name": "Task3" }]
}
var addReq = model.db.transaction("Goals", "readwrite").objectStore("Goals").add(goalstemplate);
} else {
model.db.transaction("Goals", "readonly").objectStore("Goals").get("idee").onsuccess = function (e) {
model.goalsobj = e.target.result;
//console.log(e.target.result);
model.goals = model.goalsobj.goals;
goalfunc.makeList(); //model should not talk to view, but this case it is amust, because if I remove this, it does not render at boot.
}
}
}
openReq.onerror = function (event) {
console.log("Operation failed");
}
}
},
add: function(goalname) {
model.goals.push(
{
"name": goalname
});
model.savedb();
},
move: function (id,updown) {
if (updown == "up") {
model.goals.splice((id-1), 0, model.goals.splice(id, 1)[0]);
};
if (updown == "down") {
model.goals.splice((id+1), 0, model.goals.splice(id, 1)[0]);
};
},
savedb: function(){
//console.log(goals);
var update = model.db.transaction("Goals", "readwrite").objectStore("Goals").put(model.goalsobj);
update.onerror = function (event) {
console.log(event);
};
},
};
Now When I rund this cond on my device it sais:
Unhandled exception at line 28, column 25 in ms-appx-web://1318f74a-397e-4958-aa6b-c8d11b7c5dce/js/main.js
0x800a138f - JavaScript runtime error: Unable to get property 'goals' of undefined or null reference
I have tested your code in my device (Device: Microsoft RM-1118 OSVersion:WindowsMobile 14393). It is working fine. As you can see I placed a button on the html page. The action of button click will execute model.init(), and then I set a break-point at model.goals = model.goalsobj.goals;. When click button the second time and model.goals will be set right value.
So I think the issue may happen in your target device or your GoalsDB was destroyed. Because the cause of Unable to get property 'goals' of undefined or null reference is that model.goalsobj was not set right value. Please check whether those operations have changed your database structure, such as moving operation. You can show more detail about your target device, and I will help you.
(function () {
document.getElementById("createDatabase").addEventListener("click", createDB, false);
function createDB() {
model.init();
}
})();
var model = {
db: {},
goalsobj: {},
goals: [],
init: function () {
var openReq = window.indexedDB.open("GoalsDB");
openReq.onupgradeneeded = function (event) {
model.db = event.target.result;
var objectStore = model.db.createObjectStore("Goals", { keyPath: "id" });
objectStore.createIndex("id", "id", { unique: true });
};
openReq.onsuccess = function (event) {
model.db = event.target.result
model.db.transaction("Goals", "readonly").objectStore("Goals").count().onsuccess = function (event) {
if (event.target.result == 0) {
console.log("indexeddb empty");
var goalstemplate = {
id: "idee",
goals: [{ "name": "Task1" }, { "name": "Task2" }, { "name": "Task3" }]
}
model.db.transaction("Goals", "readwrite").objectStore("Goals").add(goalstemplate);
} else {
model.db.transaction("Goals", "readonly").objectStore("Goals").get("idee").onsuccess = function (e) {
model.goalsobj = e.target.result;
//console.log(e.target.result);
if (model.goalsobj.goals != undefined) {
model.goals = model.goalsobj.goals;
} else {
console.log(e.target.result);
}
//goalfunc.makeList(); //model should not talk to view, but this case it is amust, because if I remove this, it does not render at
}
}
}
openReq.onerror = function (event) {
console.log("Operation failed");
}
}
},
add: function (goalname) {
model.goals.push(
{
"name": goalname
});
model.savedb();
},
move: function (id, updown) {
if (updown == "up") {
model.goals.splice((id - 1), 0, model.goals.splice(id, 1)[0]);
};
if (updown == "down") {
model.goals.splice((id + 1), 0, model.goals.splice(id, 1)[0]);
};
},
savedb: function () {
//console.log(goals);
var update = model.db.transaction("Goals", "readwrite").objectStore("Goals").put(model.goalsobj);
update.onerror = function (event) {
console.log(event);
};
}
};

Starting with module pattern

I'm trying to start using moddule pattern in my JS code from the beginning but I have problems to understand how to perform this kind of code design.
This is a simple event:
$('#docTable').on('dblclick', 'tbody tr.line', function (event) {
$('#modal1').modal({ keyboard: false, backdrop: "static", dismiss: "modal" });
$('#modal1').modal('show');
});
I've created a couple of JS files. View.js:
var task = window.task || {};
task.View = (function () {
function View(rootElement) {
var dom = {
table: $('#docTable'),
},
callbacks = {
onsubmit: undefined
};
return {
};
}
return View;
}());
and Controller.js:
$(document).on('ready', function () {
var View = task.View(document);
});
but I have no idea how to continue and catch the dblclick event.
Could anybody please help me?
Thanks in advance.
You can create 'class' View and add event binding to its prototype. After that you can use it on multiple tables. If you want to have access to element in table you can add classes to them and find them in defineDOM method:
View.js
var task = window.task || {};
task.View = function (table) {
this.$table = $(table);
this.init();
};
task.View.prototype ={
init: function () {
this.defineDOM();
this.bindEvents();
},
defineDOM: function() {
// Search for DOM elements in context of table element
this.$button = $('.docButton', this.$table);
this.$links = $('.docLinks', this.$table);
},
bindEvents: function () {
this.$table.on('dblclick', 'tbody tr.line', this.onDblClick.bind(this))
},
onDblClick: function () {
$('#modal1').modal({ keyboard: false, backdrop: "static", dismiss: "modal" });
$('#modal1').modal('show');
}
}
Usage
$(document).on('ready', function () {
new task.View('#docTable');
});

Knockoutjs select value change doesn't update observable

I have a select option that gets its initial value from EmailField and its options from allEmailFields:
<select data-bind="options: $parent.allEmailFields, value: EmailField()"></select>
When I change the value of the select my model doesn't get updated. Isn't this something two way binding should take care of? Or I need to write handler for the change event?
Module is here:
define('mods/fieldmapping', ["knockout", "libs/knockout.mapping", "datacontext", "mods/campaigner", "text!templates/fieldmapping.html", "text!styles/fieldmapping.css"],
function (ko, mapping, datacontext, campaigner, html, css) {
'use strict';
var
fieldMappingItem = function (data) {
var self = this;
self.CrmField = ko.observable(data.CrmField);
self.EmailField = ko.observable(data.EmailField);
},
dataMappingOptions = {
key: function (data) {
return data.PlatformFieldName;
},
create: function (options) {
return new fieldMappingItem(options.data);
}
},
fieldMappingViewModel = {
contentLoading: ko.observable(false)
},
showFieldMapping = function () {
campaigner.addStylesToHead(css);
campaigner.addModalInnerPanel(html);
},
init = function (connectionId, connectionName) {
fieldMappingViewModel.fieldMappings = mapping.fromJS([]);
fieldMappingViewModel.allEmailFields = mapping.fromJS([]);
fieldMappingViewModel.originatorConnectionName = ko.observable();
fieldMappingViewModel.originatorConnectionName(connectionName);
fieldMappingViewModel.saveFieldMappings = function () {
console.log(ko.toJSON(fieldMappingViewModel.fieldMappings));
amplify.request("updateExistingFieldMappings",
{
cid: connectionId,
RequestEntity: ko.toJSON(fieldMappingViewModel.fieldMappings)
},
function (data) {
console.log(data);
});
};
showFieldMapping();
amplify.request('getExistingFieldMappings', { cid: connectionId }, function (data) {
amplify.request("getCampaignerFields", function (breezerData) {
mapping.fromJS(breezerData.ResponseEntity, fieldMappingViewModel.allEmailFields);
});
mapping.fromJS(data.ResponseEntity, dataMappingOptions, fieldMappingViewModel.fieldMappings);
ko.applyBindings(fieldMappingViewModel, $('#fieldMapping')[0]);
});
};
return {
init: init,
fieldMappingViewModel: fieldMappingViewModel,
html: html,
css : css
}
});
Replace:
<select data-bind="options: $parent.allEmailFields, value: EmailField()"></select>
With:
<select data-bind="options: $parent.allEmailFields, value: EmailField"></select>
if you want to create bi-derectinonal dependency, so you should pass into binding observable.
P.S.: http://knockoutjs.com/documentation/observables.html#observables

Categories