Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
lordofthestack
Explorer

Getting Started - Introduction

I recently walked through creating custom controls

Now I'm going to also walk through extending the standard library controls (without destroying things  )

Today I'm going to extend the Switch control (because I need to anyway, everybody wins!)

In this case, I am adding the editable property that many other sap.m controls already have (like sap.m.Input)

This is important for accessibility compliance (because disabling input controls causes them to grey out, which fails contrast colour tests, and can also cause screen readers to miss the control entirely). Also it looks much nicer when you switch a form between edit and read modes.

So instead of disabling the Switch I'll be able to set editable to false, which will make its State render as plain text (just like sap.m.Input)

I'm going to start with a standard sap.m.Switch in my xml view as a point of reference


<mvc:View xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:dalraeContainers="dalrae.ui.containers"
xmlns:core="sap.ui.core"
xmlns:f="sap.ui.layout.form"
controllerName="dalrae.doco.Page">
    <Page title="D'Alrae UI5 Library" enableScrolling="true">
    <headerContent>
    </headerContent>
    <content>
        <dalraeContainers:ShadowBox width="150px">
            <f:SimpleForm>
                <Label text="Are you ok?" />
                <Switch state="false"
                        customTextOn="Yes"
                        customTextOff="No"
                    />
            </f:SimpleForm>
        </dalraeContainers:ShadowBox>
    </content>
    </Page>
</mvc:View>





Extending the Control

So now I want to extend this control and (hopefully) have the view look exactly the same as it did with the original sap.m.Switch
When we create new controls we extend the blank sap.ui.core.Control class, but in this case we extend sap.m.Switch
But the concept is the same, the only difference being that we need to use the existing renderer of sap.m.Switch

Here's what that looks like


sap.ui.define(
    ['sap/m/Switch'],
    function(Switch) {
        return Switch.extend("dalrae.ui.controls.Switch",{
            metadata: {
                properties: {
                    editable: {
                        type: "boolean",
                        defaultValue: true
                    }
                }
            },
            renderer: function(oRm,oControl){
                sap.m.SwitchRenderer.render(oRm,oControl); //use supercass renderer routine
    
            },
        });
    }
);





The unintuitive thing here is that, the renderer ends up in its own class SwitchRenderer, instead of Switch.prototype.renderer, tough to figure out without seeing an example.

Now that I have my own Switch control, I can just change the namespace in my xml, and because I'm calling the correct renderer, the control will still work correctly. (I've also set the state to true so that I can tell I'm looking at the latest code)


<mvc:View xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:dalraeContainers="dalrae.ui.containers"
xmlns:dalrae="dalrae.ui.controls"
xmlns:core="sap.ui.core"
xmlns:f="sap.ui.layout.form"
controllerName="dalrae.doco.Page">
    <Page title="D'Alrae UI5 Library" enableScrolling="true">
    <headerContent>
    </headerContent>
    <content>
        <dalraeContainers:ShadowBox width="150px">
            <f:SimpleForm>
                <Label text="Are you ok?" />
                <dalrae:Switch state="true"
                        customTextOn="Yes"
                        customTextOff="No"
                    />
            </f:SimpleForm>
        </dalraeContainers:ShadowBox>
    </content>
    </Page>
</mvc:View>





And upon retest... hooray! my Switch is still working

Extending the Control - Two Renderers

Now the real work, to toggle between the default renderer and my own renderer.

When editable is false, the render will be custom


sap.ui.define(
    ['sap/m/Switch'],
    function(Switch) {
        return Switch.extend("dalrae.ui.controls.Switch",{
            metadata: {
                properties: {
                    editable: {
                        type: "boolean",
                        defaultValue: true
                    }
                }
            },
            renderer: function(oRm,oControl){
                if(oControl.getEditable())
                    sap.m.SwitchRenderer.render(oRm,oControl); //use supercass renderer routine
                else {
                    //render control as simple text
                    var txt = (oControl.getState() ?
                                oControl.getCustomTextOn() : oControl.getCustomTextOff()); //determine the text based on on/off state
                    oRm.write("<span tabindex=\"0\""); //tabindex allows keyboard navigation for screen readers
                    oRm.writeControlData(oControl); //ui5 trackings data, outputs sId, absolutely mandatory
                    oRm.writeClasses(oControl); //allows the class="" attribute to work correctly
                    oRm.write(">");
                    oRm.write( jQuery.sap.encodeHTML( txt ) ); //always use encodeHTML when dealing with dynamic strings
                    oRm.write("</span>");
                }
            },
        });
    }
);





And a slight change to the xml to set editable to false


                <dalrae:Switch state="true"
                        customTextOn="Yes"
                        customTextOff="No"
                        editable="false"
                    />




And look at that, nailed it!

My Switch is now not editable, and displays in a universally accessible way!

Extending the Control - Check for bugs, read the source

Unfortunately though, you may have already figured out a scenario where this code bugs out...
If I take out the customTextOn and customTextOff properties... my render is blank


                <dalrae:Switch state="true"
                        editable="false"
                    />




So I need to handle this scenario and get the default On/Off text rendering
Now I *could* just hardcode this... but let's not mess with something that SAP have probably done smarter already
I'm sure I can find that text somewhere, so I'm going to just google for the sap.m.Switch source code and see what's going on, and here it is: https://searchcode.com/codesearch/view/92988873/

(you can also use debug mode in your fiori app to find the -dbg file, i find googling faster in most cases)

And it didn't take me long to find this little snippet in there...


sap.m.Switch.prototype.onBeforeRendering = function() {
var Swt = sap.m.Switch;
this._sOn = this.getCustomTextOn() || Swt._oRb.getText("SWITCH_ON");
this._sOff = this.getCustomTextOff() || Swt._oRb.getText("SWITCH_OFF");




So there we go, as suspected, there's some intelligence going on there (possibly for language definitions)
Reusing that should be as simple as using those variables they've set (this._sOn and this._sOff) which occurs before the render

New code:


sap.ui.define(
    ['sap/m/Switch'],
    function(Switch) {
        return Switch.extend("dalrae.ui.controls.Switch",{
            metadata: {
                properties: {
                    editable: {
                        type: "boolean",
                        defaultValue: true
                    }
                }
            },
            renderer: function(oRm,oControl){
                if(oControl.getEditable())
                    sap.m.SwitchRenderer.render(oRm,oControl); //use supercass renderer routine
                else {
                    //render control as simple text
                    var txt = (oControl.getState() ?
                                oControl._sOn : oControl._sOff); //use the super classes existing variables for on/off text
                    oRm.write("<span tabindex=\"0\""); //tabindex allows keyboard navigation for screen readers
                    oRm.writeControlData(oControl); //ui5 trackings data, outputs sId, absolutely mandatory
                    oRm.writeClasses(oControl); //allows the class="" attribute to work correctly
                    oRm.write(">");
                    oRm.write( jQuery.sap.encodeHTML( txt ) ); //always use encodeHTML when dealing with dynamic strings
                    oRm.write("</span>");
                }
            },
        });
    }
);




And upon retest... there's my default "On" text

And there we go, bug fixed, it's always a good idea to check the original controls source code before banging in your own code, which could yield unpredictable results.

Before Release - Do some regression testing

I've made a number of changes, so it's time to do a regression test... to ensure the custom text still works (which we know it will...)


                <dalrae:Switch state="true"
                        editable="false"
                        customTextOn="Yeah!"
                    />




And another test.. setting editable to true to ensure the default render still functions...(I know it will, but retest it anyway)


                <dalrae:Switch state="true"
                        editable="true"
                        customTextOn="Yeah!"
                    />




Sure does

All done! Switch control extended to have an editable property

[Update 1.1, 19/09/2016]

When I actually used this control I did come across two things not working as I had hoped, I have updated the code to fix them as follows

- changing between editable false/true was not rerendering the dom

- clicking on the uneditable label was still changing the value because the underlying classes methods were not suppressed

new code below is commented with the changes


sap.ui.define(
    ['sap/m/Switch'],
    function(Switch) {
       
        return Switch.extend("dalrae.ui.controls.Switch",{
            metadata: {
                properties: {
                    editable: {
                        type: "boolean",
                        defaultValue: true
                    }
                }
            },
           
            renderer: function(oRm,oControl){
                if(oControl.getEditable())
                    sap.m.SwitchRenderer.render(oRm,oControl); //use supercass renderer routine
                else {
                    //render control as simple text
                    var txt = (oControl.getState() ?
                                oControl._sOn : oControl._sOff); //use the super classes existing variables for on/off text
                    oRm.write("<span tabindex=\"0\"");
                    oRm.writeControlData(oControl); //ui5 trackings data, outputs sId, absolutely mandatory
                    oRm.writeClasses(oControl); //allows the class="" attribute to work correctly
                    oRm.write(">");
                    oRm.write( jQuery.sap.encodeHTML( txt ) ); //always use encodeHTML when dealing with dynamic strings
                    oRm.write("</span>");
                }
            },
           
  /* UPDATE 1.1 a few bugfixes for issues I found when actually usuing this control in a project */
  //force a rerender instead of writing logic to update the DOM
            _setDomState: function(p) {
                this.rerender();
            },
          
            //don't allow the input events to fire in editable=false mode
            ontouchstart: function(e) {
                if(this.getEditable())
                    sap.m.Switch.prototype.ontouchstart.call(this,e);
            },
            ontouchmove:  function(e) {
                if(this.getEditable())
                    sap.m.Switch.prototype.ontouchmove.call(this,e);
            },
            ontouchend: function(e) {
                if(this.getEditable())
                    sap.m.Switch.prototype.ontouchend.call(this,e);
            },
            ontouchcancel: function(e) {
                if(this.getEditable())
                    sap.m.Switch.prototype.ontouchcancel.call(this,e);
            },
            onsapselect:  function(e) {
                if(this.getEditable())
                    sap.m.Switch.prototype.onsapselect.call(this,e);
            },
          
        });
    }
);

See you later...

This was a continuation of my blog How to create a custom UI5 control
Next up, I will expand on this topic and demonstrate

  • How to add a fully accessible "press" event
4 Comments