Community Tip - Stay updated on what is happening on the PTC Community by subscribing to PTC Community Announcements. X
I am trying to adjust the TW textbox widget so that its ReadOnly property can be a binding target. I want some logic to be evaluated, and that evaluation will determine if it is ReadOnly.
I have adjusted the properties and such so that in the IDE it can have a variable bind to its ReadOnly property.
However, when i go to the Runtime screen, the textbox's ReadOnly property does not seem to update.
I am new to Thingworx and am trying to wade through the OOTB components and documentation to figure this out, but I am stuck. Here is my runtime code...do I need to adjust something in my Runtime or my IDE to make this happen?
Runtime:
TW.Runtime.Widgets.textbox_hails = function () {
var thisWidget = this;
var defaultValue = undefined;
var onInputChange = function () {
thisWidget.setProperty('Text', thisWidget.jqElement.find('input').val());
// fire my bindable event so listeners know something happened
thisWidget.jqElement.triggerHandler('Changed');
};
this.runtimeProperties = function () {
return {
'needsError': true
};
};
this.renderHtml = function () {
defaultValue = thisWidget.getProperty('Text');
var formatResult = TW.getStyleFromStyleDefinition(thisWidget.getProperty('Style', 'DefaultTextBoxStyle'));
var border = TW.getStyleCssBorderFromStyle(formatResult);
var textSizeClass = 'textsize-normal ';
var textAlignClass = thisWidget.getProperty('TextAlign');
if (thisWidget.getProperty('Style') !== undefined) {
textSizeClass = TW.getTextSizeClassName(formatResult.textSize);
}
var cssInfo = TW.getStyleCssTextualFromStyle(formatResult);
var cssTextBoxText = TW.getStyleCssTextualNoBackgroundFromStyle(formatResult);
var inputType = 'text';
if (this.getProperty('MaskInputCharacters') === true) {
inputType = 'password';
}
var placeholderText = thisWidget.getProperty('PlaceholderText');
var html = '<div class="widget-content widget-textbox"><table ' + (thisWidget.getProperty('InnerShadow') === false ? '' : 'class="shadow" ') + ' border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" style="' + cssInfo + '"><tr><td valign="middle"><input type="' + inputType + '" class="widget-textbox-box ' + textSizeClass + ' ' + textAlignClass + '" tabindex="' + thisWidget.getProperty('TabSequence') + '" style="' + cssTextBoxText + '" ' + (thisWidget.getProperty('ReadOnly') === true ? 'disabled="disabled" ' : '') + ' value="' + (thisWidget.getProperty('Text') === undefined ? '' : thisWidget.getProperty('Text').replace(/"/g, """)) + '" placeholder="' + (placeholderText === undefined ? '' : placeholderText) + '"></input></td></tr></table></div>';
return html;
};
this.afterRender = function () {
// notice when "text" changes and update the 'Text' property
var TextboxLabelStyle = TW.getStyleFromStyleDefinition(thisWidget.getProperty('TextboxLabelStyle', 'DefaultWidgetLabelStyle'));
var textboxFocusStyle = TW.getStyleFromStyleDefinition(thisWidget.getProperty('DefaultTextboxFocusStyle', 'DefaultFocusStyle'));
var TextboxLabel = TW.getStyleCssTextualNoBackgroundFromStyle(TextboxLabelStyle);
var TextboxLabelSize = TW.getTextSize(TextboxLabelStyle.textSize);
var TextboxLabelAlignment = this.getProperty('LabelAlignment', 'left');
var cssTextboxFocusBorder = TW.getStyleCssBorderFromStyle(textboxFocusStyle);
var TextboxHeight = thisWidget.getProperty('Height');
var formatResult = TW.getStyleFromStyleDefinition(thisWidget.getProperty('Style', 'DefaultTextBoxStyle'));
var border = TW.getStyleCssBorderFromStyle(formatResult);
if (thisWidget.getProperty('TextboxLabelStyle', 'DefaultWidgetLabelStyle') === 'DefaultWidgetLabelStyle'
&& thisWidget.getProperty('DefaultTextboxFocusStyle', 'DefaultFocusStyle') === 'DefaultFocusStyle') {
if (!addedDefaultStyles) {
addedDefaultStyles = true;
var defaultStyles = ' .runtime-widget-label { ' + TextboxLabel + TextboxLabelSize + ' text-align: ' + TextboxLabelAlignment + '; }' +
' .widget-textbox table { ' + border + ' }' +
' .widget-textbox.focus table { ' + cssTextboxFocusBorder + ' }' +
' .widget-textbox-box { height: ' + TextboxHeight + 'px; }';
$.rule(defaultStyles).appendTo(TW.Runtime.globalWidgetStyleEl);
}
} else {
var styleBlock =
'<style>' +
'#' + thisWidget.jqElementId + ' { ' + TextboxLabel + ' }' +
'#' + thisWidget.jqElementId + '-bounding-box .runtime-widget-label { ' + TextboxLabel + TextboxLabelSize + ' text-align: ' + TextboxLabelAlignment + '; }' +
'#' + thisWidget.jqElementId + ' table { ' + border + ' }' +
'#' + thisWidget.jqElementId + '.focus table { ' + cssTextboxFocusBorder + ' }' +
'</style>';
$(styleBlock).prependTo(thisWidget.jqElement);
}
var textboxSelector = '#' + thisWidget.jqElementId + ' .widget-textbox-box';
var textboxContainer = '#' + thisWidget.jqElementId + '.widget-textbox';
$(textboxSelector).on('focus', function () {
$(textboxContainer).addClass('focus');
});
var inBlurHandlingInIe8 = false;
$(textboxSelector).on('blur', function (e) {
$(textboxContainer).removeClass('focus');
try {
if (e.target.selectionStart !== undefined) {
thisWidget.setProperty('CursorPosition', e.target.selectionStart);
} else {
// ie8
if( inBlurHandlingInIe8 ) {
return;
}
inBlurHandlingInIe8 = true;
e.target.focus();
var el = e.target;
var range = document.selection.createRange();
if (range && range.parentElement() == el) {
var len = el.value.length;
var normalizedValue = el.value.replace(/\r\n/g, "\n");
// Create a working TextRange that lives only in the input
var textInputRange = el.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());
// Check if the start and end of the selection are at the very end
// of the input, since moveStart/moveEnd doesn't return what we want
// in those cases
var endRange = el.createTextRange();
endRange.collapse(false);
var start, end;
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split("\n").length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
}
thisWidget.setProperty('CursorPosition', start);
}
e.target.blur();
setTimeout(function() {
// keep ie8 from infinite loop
inBlurHandlingInIe8 = false;
},1);
}
} catch(err) {
TW.log.error('error trying to get cursor position on blur',err);
}
});
thisWidget.jqElement.find('input').bind('change', onInputChange);
};
this.updateProperty = function (updatePropertyInfo) {
console.log(updatePropertyInfo.TargetProperty);
console.log(typeof updatePropertyInfo.SinglePropertyValue);
console.log(thisWidget.jqElement.find('input').val(updatePropertyInfo.SinglePropertyValue));
if (updatePropertyInfo.TargetProperty === 'Text') {
thisWidget.jqElement.find('input').val(updatePropertyInfo.SinglePropertyValue);
thisWidget.setProperty('Text', updatePropertyInfo.SinglePropertyValue);
}
if (updatePropertyInfo.TargetProperty === 'ReadOnly') {
thisWidget.jqElement.find('input').val(updatePropertyInfo.SinglePropertyValue);
thisWidget.setProperty('ReadOnly', updatePropertyInfo.SinglePropertyValue);
}
};
this.resetInputToDefault = function () {
thisWidget.jqElement.find('input').val(defaultValue === undefined ? '' : defaultValue);
thisWidget.setProperty('Text', defaultValue);
};
this.getProperty_Text = function () {
if (thisWidget.jqElement !== undefined) {
return thisWidget.jqElement.find('input').val();
} else {
return this.properties['Text'];
}
};
this.beforeDestroy = function () {
thisWidget.jqElement.find('input').unbind();
};
};
IDE:
TW.IDE.Widgets.textbox_hails = function () {
var thisWidget = this;
this.widgetProperties = function () {
return {
'name': 'Textbox_Hails',
'description': 'Enables the user to enter text',
'category': ['Common'],
'defaultBindingTargetProperty': 'ReadOnly',
'supportsLabel': true,
'supportsResetInputToDefault': true,
'properties': {
'Text': {
'isBindingTarget': true,
'isBindingSource': true,
'defaultValue': '',
'baseType': 'STRING',
'warnIfNotBoundAsSource': false
},
'Width': {
'defaultValue': 200
},
'Height': {
'defaultValue': 24,
'isEditable': true
},
'TextAlign': {
'defaultValue': 'left',
'baseType': 'STRING',
'description': 'The positioning of the text.',
'selectOptions': [
{ value: 'left', text: 'Left' },
{ value: 'right', text: 'Right' },
{ value: 'center', text: 'Center' }
]
},
'LabelAlignment': {
'baseType': 'STRING',
'defaultValue': 'left',
'selectOptions': [
{ value: 'left', text: 'Left' },
{ value: 'right', text: 'Right' },
{ value: 'center', text: 'Center' }
]
},
'PlaceholderText': {
'defaultValue': '',
'baseType': 'STRING',
'description': 'Textbox placeholder text. Not supported in IE9 and earlier versions.'
},
'ReadOnly': {
'description': 'Is the textbox read only',
'baseType': 'BOOLEAN',
'defaultValue': false,
'isBindingTarget': true
},
'MaskInputCharacters': {
'description': 'Do not show contents of entry at runtime',
'baseType': 'BOOLEAN',
'defaultValue': false
},
'TabSequence': {
'description': 'Tab sequence index',
'baseType': 'NUMBER',
'defaultValue': 0
},
'InnerShadow': {
'description': 'Adds inner shadow effect to the inside of the textbox',
'baseType': 'BOOLEAN',
'defaultValue': true
},
'CursorPosition': {
'isBindingSource': true,
'defaultValue': 0,
'baseType': 'NUMBER'
},
'Style': {
'baseType': 'STYLEDEFINITION',
'defaultValue' : 'DefaultTextBoxStyle'
},
'TextboxLabelStyle': {
'baseType': 'STYLEDEFINITION',
'defaultValue': 'DefaultWidgetLabelStyle'
},
'DefaultTextboxFocusStyle': {
'baseType': 'STYLEDEFINITION',
'defaultValue': 'DefaultFocusStyle'
},
'Editable':{
'baseType':'BOOLEAN',
'defaultValue':true
}
}
};
};
this.renderHtml = function () {
var formatResult = TW.getStyleFromStyleDefinition(this.getProperty('Style'));
var textSizeClass = 'textsize-normal';
var textAlignClass = this.getProperty('TextAlign');
if (this.getProperty('Style') !== undefined) {
textSizeClass = TW.getTextSizeClassName(formatResult.textSize);
}
var cssInfo = TW.getStyleCssTextualFromStyle(formatResult);
var cssTextBoxText = TW.getStyleCssTextualNoBackgroundFromStyle(formatResult);
var TextboxStyleBorder = TW.getStyleCssBorderFromStyle(formatResult);
var html = '';
html += '<div class="widget-content widget-textbox '+ textAlignClass +'"><div class="widget-textbox-wrapper" style="' + TextboxStyleBorder + '"><span class="widget-textbox-text-icon"></span><table ' + (thisWidget.getProperty('InnerShadow') === false ? '' : 'class="shadow"') + ' border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" style="'+ cssInfo +'"><tr><td valign="middle"><span class="widget-textbox-text ' + textSizeClass + '" style="' + cssTextBoxText +'">' + ((this.getProperty('Text') === undefined) ? 'Textbox' : Encoder.htmlEncode(this.getProperty('Text'))) + '</span></td></tr></table></div></div>';
return html;
};
this.afterRender = function () {
//TW.log.info('valuedisplay this.afterRender');
var TextboxLabelStyle = TW.getStyleFromStyleDefinition(thisWidget.getProperty('TextboxLabelStyle'));
var TextboxLabel = TW.getStyleCssTextualNoBackgroundFromStyle(TextboxLabelStyle);
var TextboxLabelSize = TW.getTextSize(TextboxLabelStyle.textSize);
var TextboxLabelAlignment = this.getProperty('LabelAlignment', 'left');
var TextboxHeight = this.getProperty('Height');
var resource = TW.IDE.getMashupResource();
var widgetStyles = '#' + thisWidget.jqElementId + '-bounding-box .builder-widget-label { '+ TextboxLabel + TextboxLabelSize + ' text-align: ' + TextboxLabelAlignment + '; }' +
'#' + thisWidget.jqElementId + ' .widget-textbox-wrapper { height: ' + TextboxHeight + 'px; }';
resource.styles.append(widgetStyles);
};
this.widgetEvents = function () {
return {
'Changed': {}
}
};
this.afterSetProperty = function (name, value) {
var result = false;
switch (name) {
case 'Style':
case 'TextAlign':
case 'TextboxLabelStyle':
case 'InnerShadow':
case 'LabelAlignment':
result = true;
break;
case 'Text':
thisWidget.jqElement.find('.widget-textbox-text').text(value);
break;
default:
break;
}
return result;
};
this.validate = function () {
var result = [];
var isReadOnly = this.getProperty('ReadOnly');
if( isReadOnly !== true ) {
if (!this.isPropertyBoundAsSource('Text')) {
result.push({ severity: 'warning', message: '{target-id}: Text is not bound to any target. If you check ReadOnly you will not receive this warning.' });
}
}
return result;
}
};
Not sure if you have access to the actual Thingworx instance, but I always recommend just looking at an existing Widget that has an example of a bindable property.
you can get to the widget code by going to Tomcat folder/webapps/Thingworx/common/thingworx/widgets
Hi Pai...I do have access to the Thingworx instance, and I have been tirelessly looking through all of the JS files. I know how to make a property bindable...that has been completed in the code above.
Here is an example of what is happening and what my main question is:
1. ReadOnly is bindable property
2. The 'Output' of an expression (output value type is Boolean, and the expression is 1>2) is mapped to ReadOnly
3. I have added in some Logs, and they verify that the UpdatePropertyInfo.TargetProperty is ReadOnly when i press the button to evaluate
4. UpdatePropertyInfo.TargetProperty.SinglePropertyValue, however, is being logged as string instead of boolean
5. When I log the value of ReadOnly to the console after the update, it returns false (this is correct)
6. The textbox is not re-rendered as a ReadOnly textbox
I do not know how to re-render the page using the properties that have since been updated. Is this something that needs to be defined in an Event Trigger? Is there a call that i need to make to re-render? Does logic need to be specified in my afterRender() function?
The TW widgets have logic written into their renderHTML() functions that puts in the disabled:disabled style attribute, but I am not able to figure out how it handles dynamic properties (isVisible).
I have been facing the same issue for over a week now and would appreciate as specific of an answer as possible. My team has faith in this products ability, but a lack of support and/or guidance on how to make the simple changes we need will render this product useless.
Thank you in advance for your help!
I've found a much easier solution is to have two widgets, one being disabled/read-only; and then overlap the two widgets and bind the 'Visible' state of each.
Before Buttons had bindable 'Disabled' state, I had a normal button and a hidden button styled to look disabled, both in the same position; then on whatever condition, I'd hide the working button and reveal the 'disabled' button.