Making redui.net
Wednesday, May 7, 2014
Important change in the databinding mechanism
Imagine you have an object:
If you want to bind the textbox to it, previously you had 2 options:
1. Trivial case: bind to _id property
2. More interesting: bind to getid property
Support for binding to functions was added to support ko.observables. And since getId returns the constant value, it worked. However, binding to getActualId was not useful, because it is using 'this', but was called as a function.
Now you have an option number 3:
Because now to retrieve the value getActualId will be called as a method of customerData object.
As a reminder, only the last property the control binds to can be a function.
So given the object like this:
You can bind to "_address.getStreet", but you cannot bind to "getAddress.getStreet"
Hope it helps.
Sunday, May 4, 2014
Monthly Update: April
So, in April I got 115 visitors.
This is less than in March end even in February, however, I have a very good ratio of people staying on my site for longer than 30 seconds:
This means there were 48 people who actually were curious enough to go over the examples and understand a bit more about the library.
- What did make you interested?
- What is missing?
It would be great to hear from you, 48 people who stayed over 30 seconds.
Monday, April 28, 2014
Saturday, April 26, 2014
Some important internal methods
In case anyone is interested in creating new controls for redui, here is the description of the most important internal methods that the controls rely upon.
initializeControlFromModel
Creates and initializes any redui control from model.
Internally calls 'create' and 'initializeFromModel' control-specific handlers.
It's only necessary to directly call this method from the parent control that is not a generic container (i.e. does not extend IContainerControl), like ContextMenu, that does not allow to add any control type as a child, thus does not extend IContainerControl interface.
For the controls that are generic controls, like GroupBox, AccordionPanel etc, it's enough to call initializeInnerControls.
initializeInnerControls
Creates and initializes inner controls of a given control from model.
Used by container controls (GroupBox, AccordionPanel etc) to initialize the controls collection.
toHtml
Returns the HTML for a given control.
Relies on mustache template engine to render the HTML.
Templates are retrieved once and cached on the _templates object.
Templates are retrieved with redui version as a url query parameter, that forces the new version of template to be loaded.
localize
Localizes the label.
This function is used called from templates to localize every label/text defined in the model.
Localization only happens in Globalize.js is found.
bind
Standard behavior for data binding that is attached to every databound redui control.
Should only be called as a method of a redui control (requires 'this').
bindRecursively
Walks through the hierarchy of controls and binds them to the data.
Makes the data object (or it's property, in case the control is inside container that changes the context)
accessible from the bindingContext property.
If the property that the control is bound to is a function, calles the function.
If the function has a property 'subscribe' it is considered to be ko.observable, in which case the control
automatically subscribes to changes.
Also binds all the inner controls to the data. Normally it is not required to call this method directly.
updateElementValue
Updates the value of the rendered element, the value that is seen by user.
For that, relies to the control-specific handler.
Normally it is not required to call this method directly.
validateControl
Performs the control validation.
In the current implementation is aware of the control internal structure, so walks the inner controls on its own.
activateControl
Helper to call the control-specific activation handler.
Generic containers (GroupBox, AccordionPanel etc) don't have to call this method directly
and can activate their children by calling activateInnerControls.
activateInnerControls
Activates inner controls of the given control.
subscribeToChange
Subscribes the databound control to change event.
Change triggers binding update.
initializeControlFromModel
Creates and initializes any redui control from model.
Internally calls 'create' and 'initializeFromModel' control-specific handlers.
It's only necessary to directly call this method from the parent control that is not a generic container (i.e. does not extend IContainerControl), like ContextMenu, that does not allow to add any control type as a child, thus does not extend IContainerControl interface.
For the controls that are generic controls, like GroupBox, AccordionPanel etc, it's enough to call initializeInnerControls.
initializeInnerControls
Creates and initializes inner controls of a given control from model.
Used by container controls (GroupBox, AccordionPanel etc) to initialize the controls collection.
toHtml
Returns the HTML for a given control.
Relies on mustache template engine to render the HTML.
Templates are retrieved once and cached on the _templates object.
Templates are retrieved with redui version as a url query parameter, that forces the new version of template to be loaded.
localize
Localizes the label.
This function is used called from templates to localize every label/text defined in the model.
Localization only happens in Globalize.js is found.
bind
Standard behavior for data binding that is attached to every databound redui control.
Should only be called as a method of a redui control (requires 'this').
bindRecursively
Walks through the hierarchy of controls and binds them to the data.
Makes the data object (or it's property, in case the control is inside container that changes the context)
accessible from the bindingContext property.
If the property that the control is bound to is a function, calles the function.
If the function has a property 'subscribe' it is considered to be ko.observable, in which case the control
automatically subscribes to changes.
Also binds all the inner controls to the data. Normally it is not required to call this method directly.
updateElementValue
Updates the value of the rendered element, the value that is seen by user.
For that, relies to the control-specific handler.
Normally it is not required to call this method directly.
validateControl
Performs the control validation.
In the current implementation is aware of the control internal structure, so walks the inner controls on its own.
activateControl
Helper to call the control-specific activation handler.
Generic containers (GroupBox, AccordionPanel etc) don't have to call this method directly
and can activate their children by calling activateInnerControls.
activateInnerControls
Activates inner controls of the given control.
subscribeToChange
Subscribes the databound control to change event.
Change triggers binding update.
Thursday, April 24, 2014
How to create a pager in javascript
1. Take a piece of paper and draw
2. Test it on paper
3. Pseudo-code
// Check if there are pages at all if (pagesTotal < 1) { return; } // First visible page firstVisiblePageNo = pageNo - visiblePositions / 2 + 1 = 12 // Adjsut first visible page if (firstVisiblePageNo < 1) { firstVisiblePageNo = 1 } // Number of pages in sequence sequentialPages = visiblePositions lastPageNoInSequence = firstVisiblePageNo + visiblePositions - 1 = 17 // Adjust last page in sequence if (lastPageNoInSequence > pagesTotal) { lastPageNoInSequence = pagesTotal // If reached the last pages on the right, get more pages from the left if (lastPageNoInSequence - firstVisiblePageNo + 1 < visiblePositions) { firstVisiblePageNo = lastPageNoInSequence - visiblePositions + 1; } // Adjsut first visible page if (firstVisiblePageNo < 1) { firstVisiblePageNo = 1 } } if (lastPageNoInSequence < pagesTotal) { sequentialPages -= 2; showEllipsis = true } for (currentPageNo = firstVisiblePageNo; currentPageNo <= lastPageNoInSequence; currentPageNo++) { render page(currentPageNo) } if (showEllipsis) { render ellipsis render page(pagesTotal) }
4. Debug it on paper (or in notepad)
1 2 {3 4 [5] 6} 7 8 9 10 11 12 13 14 15 {1 [2] 3 4 5 6} 7 8 9 10 11 12 13 14 15 {1 [2] 3 4} 1 2 3 4 5 6 7 8 9 {10 11 12 13 [14] 15} 1 2 3 [4] 5 6 7 // Const visiblePositions = 6 // Given pageNo = 14 pagesTotal = 15
5. Code it
Do you like the result?
Feedback appreciated!
Wednesday, April 23, 2014
Anatomy of a Control Template
In this post I will explain some rules I use to create a template for a control.
Let's look at the template for a TextBox control.
What we see here?
This is the most important part of the template.
id - stores the unique id of the control, something like 'redui_control_123'. Unique id are assigned to every control upon its creation.
name - the name of the control, that you provide in the model.
for example, if you defined you TextBox as follows:
the name attribute will be filled with 'firstNameTextBox'.
data-name - the same as name attribute. Not every HTML tag allows name attribute, but data-name attribute is always available.
class - here we put the class that reflects the control type. It has prefix 'redui-' followed by the type that you use in the model. For instance, for TextBox it is 'redui-textbox'.
Label is optional, so when it is not set, this part of the template is not rendered. This part of the template is almost identical for every control, only class is different.
By convention, this div has an id that matches the control id with suffix '_template'.
Second, it has class that matches the control class with suffix '-outer', in our case, 'redui-textbox-outer'.
The most important, when you apply the custom CSS class in the model, it is set on this outer element.
So for example, if apply class 'myControl',
It will be set on the outer div.
Naturally, visibility ('redui-hidden' class) is also applied to the outer div.
By convention, this div has an id that matches the control id with suffix '_validationerrorbox'.
For controls that support validation this div is used to show the validation message.
Every control can have a context menu attached to it. To show it you need to add this section:
For the container controls you need to render inner controls. The following section takes care of it:
redui-hidden
Hides the control
redui-focusable
When the window is open, the focus goes to the first control that has redui-focusable class
redui-valid
Controls whether validation div should be visible
redui-activated
For some controls the special behavior is enabled upon activation. For example, GridViewColumn subscribes to click event to allow sorting. To avoid the double activation, the class redui-activated is set on such a control after the behavior is activated
redui-disabled
This class is used together with commands. When the command is not available (canExecute returns false), controls that are bound to this command get the class redui-disabled
Let's look at the template for a TextBox control.
<div id="{{id}}_template" class="redui-textbox-outer redui-valid{{#model.cssClass}} {{model.cssClass}}{{/model.cssClass}}{{#model.isHidden}} redui-hidden{{/model.isHidden}}"> {{#model.label}}<label for="{{id}}" class="redui-textbox-label"> <span>{{#_localize}}{{model.label}}{{/_localize}}: {{#model.isRequired}}*{{/model.isRequired}}</span> </label>{{/model.label}} <input id="{{id}}" name="{{name}}" data-name="{{name}}" class="redui-textbox redui-focusable" type="{{model.inputType}}" /> <div id="{{id}}_validationerrorbox" class="redui-validation-errorbox"></div> {{#contextMenu}}{{{_toHtml}}}{{/contextMenu}}</div>
What we see here?
1. Main tag
Every control has an HTML tag that it is build around. For the TextBox it is, natuarally, the input tag.<input id="{{id}}" name="{{name}}" data-name="{{name}}" class="redui-textbox redui-focusable" type="{{model.inputType}}" />
This is the most important part of the template.
id - stores the unique id of the control, something like 'redui_control_123'. Unique id are assigned to every control upon its creation.
name - the name of the control, that you provide in the model.
for example, if you defined you TextBox as follows:
{ "name": "firstNameTextBox", "type": "textbox", "label": "First Name", "bindsTo": "firstName" }
the name attribute will be filled with 'firstNameTextBox'.
data-name - the same as name attribute. Not every HTML tag allows name attribute, but data-name attribute is always available.
class - here we put the class that reflects the control type. It has prefix 'redui-' followed by the type that you use in the model. For instance, for TextBox it is 'redui-textbox'.
2. Label
Most of the controls have label{{#model.label}}<label for="{{id}}" class="redui-textbox-label"> <span>{{#_localize}}{{model.label}}{{/_localize}}: {{#model.isRequired}}*{{/model.isRequired}}</span> </label>{{/model.label}}
Label is optional, so when it is not set, this part of the template is not rendered. This part of the template is almost identical for every control, only class is different.
3. Outer div
One HTML tag is not enough even for the simplest control. In case of TextBox, it requires at least 2: input and label. To make it easier to target various tags with CSS-selectors they all are wrapped in one outer div.<div id="{{id}}_template" class="redui-textbox-outer redui-valid{{#model.cssClass}} {{model.cssClass}}{{/model.cssClass}}{{#model.isHidden}} redui-hidden{{/model.isHidden}}">
By convention, this div has an id that matches the control id with suffix '_template'.
Second, it has class that matches the control class with suffix '-outer', in our case, 'redui-textbox-outer'.
The most important, when you apply the custom CSS class in the model, it is set on this outer element.
So for example, if apply class 'myControl',
{ "name": "firstNameTextBox", "type": "textbox", "label": "First Name", "bindsTo": "firstName", "cssClass": "myControl" }
It will be set on the outer div.
Naturally, visibility ('redui-hidden' class) is also applied to the outer div.
4. Validation div
By convention, this div has an id that matches the control id with suffix '_validationerrorbox'.
For controls that support validation this div is used to show the validation message.
5. Context menu
Every control can have a context menu attached to it. To show it you need to add this section:
{{#contextMenu}}{{{_toHtml}}}{{/contextMenu}}
6. Inner controls
For the container controls you need to render inner controls. The following section takes care of it:
{{#controls}} {{{_toHtml}}} {{/controls}}
Finally, some important classes used in templates:
redui-hidden
Hides the control
redui-focusable
When the window is open, the focus goes to the first control that has redui-focusable class
redui-valid
Controls whether validation div should be visible
redui-activated
For some controls the special behavior is enabled upon activation. For example, GridViewColumn subscribes to click event to allow sorting. To avoid the double activation, the class redui-activated is set on such a control after the behavior is activated
redui-disabled
This class is used together with commands. When the command is not available (canExecute returns false), controls that are bound to this command get the class redui-disabled
Monday, April 21, 2014
GridView Customization: chosen approach
Visual decision I made in the end:
From the programming point of view, the standard context menu can be overriden. It will only be used if left empty, otherwise, the context menu that you explicitly specified will be used.
In addition to that, I will make customizeGridView the method of the IGridView interface, so it will be overridable too. (customizeGridView is called by the command bound to the "Select Columns..." menu item).
I feel that this gives a developer enough freedom and in the same time, if you don't do anything, it all just works.
From the programming point of view, the standard context menu can be overriden. It will only be used if left empty, otherwise, the context menu that you explicitly specified will be used.
In addition to that, I will make customizeGridView the method of the IGridView interface, so it will be overridable too. (customizeGridView is called by the command bound to the "Select Columns..." menu item).
I feel that this gives a developer enough freedom and in the same time, if you don't do anything, it all just works.
Subscribe to:
Posts (Atom)