For the plain text like introductions or short descriptions we use TextArea property. It could be useful for editor to see how many characters and words he already typed. It gives an overview of how much space for new value is required. That’s why I decided to implement property TextArea with statistics.
To implement new property I didn’t extend standard TextArea property, because it’s just an input widget with no wrapping DIV element. So there was no place to add another element with statistics. The new editor was created from scratch. It has own widget, template and styles.
In Alloy demo there is StringList editor. It also use textarea as editing widget, so it was a good starting point for my property.
The widget connects textarea with onChange, onKeyDown and onKeyUp events to calculate statistics. To count characters I simply took length of text and for words and lines I used regular expressions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
define([ "dojo/_base/declare", "dojo/_base/lang", "dijit/_Widget", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin", "epi/epi", "epi/shell/widget/_ValueRequiredMixin", "dojo/text!./template.html", "xstyle/css!./styles.css" ], function( declare, lang, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, epi, _ValueRequiredMixin, template ) { return declare([_Widget, _TemplatedMixin, _WidgetsInTemplateMixin, _ValueRequiredMixin], { templateString: template, intermediateChanges: false, value: null, postCreate: function() { this.inherited(arguments); this.textArea.set("intermediateChanges", this.intermediateChanges); this.connect(this.textArea, "onChange", this._onInputChange); this.connect(this.textArea, "onKeyDown", this._refreshStatistics); this.connect(this.textArea, "onKeyUp", this._refreshStatistics); }, _setValueAttr: function (value) { this._setValue(value, true); }, _setReadOnlyAttr: function (value) { this._set("readOnly", value); this.textArea.set("readOnly", value); }, _setIntermediateChangesAttr: function (value) { this.textArea.set("intermediateChanges", value); this._set("intermediateChanges", value); }, _onInputChange: function (value) { this._setValue(value); }, _setValue: function (value, updateTextarea) { if (this._started && epi.areEqual(this.value, value)) { return; } this._set("value", value); if (updateTextarea) { this.textArea.set("value", value); } this._refreshStatistics(); if (this._started && this.validate()) { this.onChange(value); } }, _refreshStatistics: function () { var value = this.textArea.get("value"); this.totalCharacters.innerHTML = value.length; if (value.trim().length > 0) { this.totalWords.innerHTML = value.trim().replace(/\s+/gi, ' ').split(' ').length; } else { this.totalWords.innerHTML = "0"; } this.totalLines.innerHTML = value.split(/\r*\n/).length; } }); }); |
Template use data-dojo-attach-point attributes to allow widget to access DOM nodes and update HTML.
1 2 3 4 5 6 7 8 9 10 |
<div class="dijitInline extended-textArea" tabindex="-1" role="presentation"> <div data-dojo-attach-point="stateNode, tooltipNode" class="textArea-wrapper"> <textarea class="textArea-default-wrapping" data-dojo-attach-point="textArea" data-dojo-type="dijit.form.Textarea"></textarea> </div> <div class="statistics"> <div>Characters: <span data-dojo-attach-point="totalCharacters">0</span></div> <div>Words: <span data-dojo-attach-point="totalWords">0</span></div> <div>Lines: <span data-dojo-attach-point="totalLines">1</span></div> </div> </div> |
There is also CSS styles file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.extended-textArea .textArea-wrapper { display: inline-block; } .extended-textArea textarea { width: 500px; } .extended-textArea .statistics { display: inline-block; vertical-align: top; color: #999; font-style: italic; } |
To use new property we need to set textAreaWithStatistics.textAreaWidget as editing class.
1 2 3 4 5 |
public class TestPage : SitePageData { [ClientEditor(ClientEditingClass = "textAreaWithStatistics.textAreaWidget")] public virtual string Teaser { get; set; } } |
The full source code is available on Gist.