David J McClelland

Experiences at the Intersection of Programming & Design

Archive for the ‘Tools’ Category

Adding truncation feature to Axis Tick Labels in chart widget extension required the addition of dynamic properties.

Solution Phase One:

Use D3 to set titles on tick labels exceeding a user-configurable length:

if (thisWidget.truncateX !== 0) {
    d3.selectAll(widgetContainerId + '-chart .nv-x .nv-axis .tick text')
        .each(function (i) {
            var tick = d3.select(this);
            var fullText = tick.text();
            var truncText = fullText;
            // standard truncation with ellipses
            if (fullText.length > thisWidget.truncateX) {
                truncText = fullText.substring(0, thisWidget.truncateX) + '...';
            }
            // preceding truncation with ellipses
            if (thisWidget.truncateX < 0){
                truncText = '...' + fullText.substring(Math.abs(thisWidget.truncateX), fullText.length);
            }
            tick.text(truncText);
            tick
                .append("svg:title")
                .text(fullText)
    });
}

I Considered using an algorithm based on available space but this was complicated by text tilt option and the fact that it would have to calculate every label individually. Instead I added a property for X and Y axis truncation to the widget IDE. Note that using a negative number performs truncation at the start of the label string instead of the end. This is handy when labels suffer from an overuse of Hungarian notation.

Negative value leaves n characters at the end of the label and places a preceding ellipses

Standard truncation replaces limits to the first n characters and replaces the rest with an ellipses

I Used the standard Thingworx platform Tooltip library

…with the Widget chart extension to apply a tooltip to truncated text label’s title.

// qtip jquery extension over titles in d3 axis
$('title').each(function () { // Grab all elements with a title element,and set "this"
    $(this).parent().qtip({
        id : thisWidget.jqElementId,
        content: {
            text: $(this).text()
        },
        position: {
            target: 'mouse',
            adjust: {
                x: 10,
                y: 10
            }
        },
        style: {
            classes: 'widget-barChart-tooltipStyle' + customTooltipStyleName,
            widget: false, // Use the jQuery UI widget classes
            def: false // Remove the default styling (usually a good idea, see below)
        }
    });
    // remove title element
    $(this).remove();
});

I Removed the titles when I noticed browser tooltips poking through since titles are divs and not attributes in D3.

-12 truncation result

12 truncation result

 

I Used Qtip class name attribute to style tooltips according to widget property

This was the most confusing aspect of integrating the tooltips. Qtip lets you add a classname to the Qtip element that it puts on the Body DOM so you can create a style and have it apply to it (in style object in snippet above). Sounds simple! But QTip applies its own default class names to each tooltip on its own, and sometimes they superseded my custom classes. In the snippet above, classes is where a classname is defined, and widget and def are false to prevent the other classnames from being added by Qtip. The additional customToooltipStyleName is appended when the widget has a custom style applied to it. In this way, that classname matches the specific widget id, making it apply the custom style to that widget alone.

Default platform tooltip style

custom widget tooltip style

custom widget tooltip result

 

Solution Phase Two:

When multiple services provide data to Chart Widget, which one provides the label text?

I had sidestepped this issue in the original chart widget design by leaving out X-Axis Tick Labels. This was intentional: we released an MVP and wanted feedback from customers about what was important to them to include in a more elaborate solution. I added labels soon after release, but they only worked on single-service bound charts. Adding truncation to labels forced the issue that this effort should not be wasted on a feature that didn’t really work in every case.

Generate a service picker

Adding a property to the widget IDE to list off bound services seemed like it would be trivial, but this was not the case. There is no ready-made property that will display a drop-down list of data sources currently bound to a widget. There is no ready way to persist this information in the widget entity outside of the internal bindings definition, which is used by the Connections tab in the IDE and by Runtime to retrieve the data and update the widget display. This is not directly exposed in the widget API.

So, how to roll our own Bound Service Picker? I looked at how Collections widget used JSON to create and persist bindings and update the IDE properties from a service definition. I took this concept a significant step further by hiding the underlying JSON and using it to create a dropdown picker.

this.afterAddBindingSource = function (bindingInfo) { ...
this.afterRemoveBindingSource = function (bindingInfo) {...

These functions were the key to managing a list of binding: Every time anything is bound to the widget or unbound, one of these functions is called. The bindingInfo provides the name of the binding target property, and this enables selectively adding and removing dataSource names to a hidden string property that is constructed as a serialized array. The length of the array determines whether the DataSource picker is displayed in the IDE (since it is only required when 2 or more services are bound). The array also updates the selectOptions of the DataSource picker, which are the selections it displays in the dropdown.

Number of bound dataSources is reflected in properties. Each datasource has different labels

Short Labels from DataSource1

Long labels from DataSource2

Generate a Field picker

The same technique was used to populate a Field Picker. The contents of the field picker only needed to be retrieved when the first data source was bound, because every service bound to the Chart Widget has to share a common Data Shape. And the selectOptions of the Field Picker are the Field Definitions from the service Data Shape.

var dataShape = thisWidget.getInfotableMetadataForProperty(property) || {};

This function returns the fieldnames of a service when it is bound if it is called from within afterAddBindingSource() as value names. These are put in an array used to populate a Field Names dropdown property in the Widget IDE and serialized and put in a hidden string property for persistence.

The selected item in the pickers is persisted in the widget entity model and restored when the Mashup containing the widget Extension is reopened in Mashup Builder. If that were not the case, the selections can be monitored and set to hidden string properties as well. I set it up that way and then verified this additional step wasn’t needed.

Every Data Source has the same DataShape which in the example consists of these two field names.

Managing the Data Source and Field Name pickers

    /**
     * Invoked by the platform whenever the user binds a data source to a property on this widget.
     * @param bindingInfo <Object>        An object containing the newly created binding's properties.
     */

    this.afterAddBindingSource = function (bindingInfo) {
        var thisWidget = this;
        var property = bindingInfo.targetProperty;
        var properties = thisWidget.allWidgetProperties().properties;
var seriesNum = thisWidget.getProperty('NumberOfSeries');
        if( seriesNum === dataSources.length && seriesNum < thisWidget.MAX_SERIES){
            thisWidget.setProperty('NumberOfSeries', thisWidget.getProperty('NumberOfSeries') + 1);
        }
        // minimum binding requirement is met
        if (property.indexOf('Data') > -1) {
            this.jqElement.find(".configuration-warning").remove();
        }
        //adding multiple data services to populate chart - only need one dataShape - they all must be same
        if (property.indexOf('DataSource') > -1) {
            thisWidget.setProperty('SingleDataSource', false);
            var dataShape = thisWidget.getInfotableMetadataForProperty(property) || {};

            // build up an array of datashape names and types to allow user to pick dataSource to 
            // provide x-axis label field
            dataSourceList.push({'value': property, 'text' : property});
            // also provide a simple list of data sources to runtime to match them to the dataSource 
            //x-axis label field
            dataSources.push(property);

            // build field names list if it is not already defined
            if(String(thisWidget.getProperty('_dataShape')).length === 0) {
                for (var key in dataShape) {
                    // check also if property is not inherited from prototype
                    if (dataShape.hasOwnProperty(key)) {
                        var value = dataShape[key];
                        multiServiceDataShape.push({'value': value.name, 'text': value.name});
                        dataShapeList.push(value.name);
                    }
                }
            }

            properties['X-AxisLabelField']['selectOptions'] = multiServiceDataShape;
            properties['X-AxisLabelField']['isEditable'] = true;
            properties['X-AxisLabelField']['isVisible'] = true;

            thisWidget.setProperty('_boundDataSources', dataSources.join(','));
            thisWidget.setProperty('_dataShape', dataShapeList.join(','));

            properties['X-AxisLabelDataSource']['selectOptions'] = dataSourceList;
            properties['X-AxisLabelDataSource']['isEditable'] = true;
            properties['X-AxisLabelDataSource']['isVisible'] = true;

            this.setSeriesProperties(this.getProperty('NumberOfSeries'),
            this.getProperty('SingleDataSource'));
            this.updatedProperties();
        }
    };

    /**
     * Invoked by the platform whenever the user unbinds a data source to a property on this widget.
     * @param bindingInfo <Object>        An object containing the newly removed binding's properties.
     */

    this.afterRemoveBindingSource = function (bindingInfo) {
        var thisWidget = this;
        var property = bindingInfo.targetProperty;
        var properties = thisWidget.allWidgetProperties().properties;

        // remove the datasource that was unbound
        for(var i = 0; i < dataSourceList.length; i++){
            if(dataSourceList[i].value === property){
                dataSourceList.splice(i, 1);
            }
        }

        // dataSources is not in same order as dataSourceList so splice separately
        for(var i = 0; i < dataSources.length; i++){
            if(dataSources[i] === property){
                dataSources.splice(i, 1);
            }
        }

        thisWidget.setProperty('_boundDataSources', dataSources.join(','));

        if (property.indexOf('DataSource') > -1) {

            // the X-AxisLabelField doesn't provide any functionality when there is one binding
            if (dataSources.length <= 1){
                properties['X-AxisLabelField']['isVisible'] = false;
                properties['X-AxisLabelDataSource']['isVisible'] = false;
                this.updatedProperties();
            }
        }

        //all data bindings were removed - sound the alarm!
        if (dataSources.length === 0 && property.indexOf('Data') > -1) {
            this.addNoBindingWarning();
        }

        this.updatedProperties();
    };

What is a “Thing” in Thingworx Composer?

thingworx-ptc-logo

What is  a “Thing”?

A Thing is defined by Thingworx as a representation of a business process in terms of properties and business logic. For example, a locomotive would be represented not in terms of static information such as dimensions, color and weight but in terms of dynamic business-relevant such as its current location, pulling capacity and maintenance status.

What Makes Things Interesting?

Things are only interesting if they have characteristics that interest us. To extend the locomotive analogy, a train that is running on time and about to reach my station is probably more interesting than one still an hour away. It is even more interesting if it is going to my destination. These characteristics are called Properties in Thingworx.

How Do Things Talk?

A train is famous for blowing it’s horn or whistle. The interest level of a train whistle’s can vary by distance or whether you are waiting for a train or standing on the tracks. But there is no indication in a whistle that indicates which train the whistle comes from or necessarily why it was blown. Things address these limitations by providing a rich system of events, alerts and subscriptions.

Events: When Things Happen To Things

A Thing Property can have an Event defined. This is a business rule where an action can be taken when a property changes in a certain way. Such as a train that is more than 5 minutes behind schedule.

Alerts: We Care When Certain Things Happens

Events can trigger a message to the next station which could display an updated arrival time on a schedule display. They can also be used (along with additional rules and location data) to calculate what speed increase is required to catch up before arriving at the next station and supress sending a late arrival message.

Subscriptions: Only Some of Us Care

Telling a system what alerts are wanted ensures that only information of interest is sent. Instead of standing by the train arrival display, commuters use an app to subscribe to updated arrival times so they can sit in the coffee shop and wait for a jingle when they have a couple minutes left to board. This requires subscribing to alerts for your train at your planned departure time.

When to use:

  • Where a specific representation of an instance of an asset that combines properties, processes and business logic is needed.
  • Things are also often used to provide a single instance of internal application services, such as a utility function holder.

A Thing Example

Thingworx Thing Icon

A Thing is represented by this default icon in the Composer Editor Home Menu. The most basic Thing is one based on GenericThing. This is as basic a Thing as you can define. It inherits the properties and services that are required for any Thing, much like an interface would define required properties and methods of a Class.

thingDetails4

Extending a Thing

Things are only useful when you define specific properties, services, alerts and alarms on them. You can do this using built-in capabilities inherited from the Generic Thing.

To clearly illustrate how the these features interact to provide functionality I will show how to exercise all aspects of a Thing using only custom features:

  • Create a Thing Property
  • Create a Thing Service
  • Create a Service Event
  • Create a Thing Subscription
  • Integrate a Thing into a Mashup

Custom Property

To add a custom property to a Thing, create a new Thing that inherits Generic Thing. Click the Properties link from the Left menu. Refer to the screen shot below. We will add a boolean property because it is a simple switch.

Thing Custom Property

Thing Custom Property

Thing Service

A Service is an implementation of a rule in a Thing that can manipulate data. Rules are written in Javascript by default. See the screenshot for how to program a service that switches an input boolean value to the opposite of its current value. The data can come from a Thing property.

Thing Service

Thing Service

Subscriptions

Subscriptions are used to respond to particular events. In this example I subscribe to an data change event on my Thing when the property value is flipped to false.

Thing Subscription

Thing Subscription

Using a Thing in a Mashup

There isn’t a “Thing Widget” that can be dragged onto a layout in Thingworx Composer. Specific aspects of a Thing can be added to a Mashup via a combination of widgets and data services. This can add up to a selective rendition of the aspects of a thing that focuses on the aspects of it that are important for a particular viewer.

How Thing Appears in Mashup

How Thing Appears in Mashup

Wiring Mashup Events

Wiring Mashup Events

Using data to set Widget

Using data to set Widget

Setting Alerts

Alerts can be added to each property of a Thing. They can be set to fire when a condition is met. In this case, when macProperty is false.

Add Alerts to Properties

Add Alerts to Properties

Alerts Manager

Alerts Manager

Viewing Alerts

Alerts can be subscribed to in order to set other properties, send emails, SMS, and other communications. The following screens show the event Monitor tab recording the alerts fired when macProperty was set to false by clicking the checkbox in the Mashup.

An Alert Displayed in the Alert Monitor

An Alert Displayed in the Alert Monitor

An Unacknowledged Alert

An Unacknowledged Alert

No Acknowledged Alert Yet

No Acknowledged Alert Yet

alertsTabAcknowledge

Acknowledge The Alert

Alert Acknowledgement Confirmed

Alert Acknowledgement Confirmed

Acknowledged Alert

Acknowledged Alert

New Alert

A New Alert

Alert History

Alert History

  • 2 Comments
  • Filed under: Tools
  • Intellij IdeaCygWingitorange@2x

    I didn’t find much existing basic documentation on how to get started with scripting in earnest, so I decided to fill the gap with my own experience. I had never been an avid user of command windows or terminals or scripting, and I admit to complaining that Cygwin is be a confusing heap. But that hasn’t stopped me from realizing their benefits, and I am ready to give them the respect they deserve since they have saved me time and drudgery.

    Solving Script Amnesia

    A successful script automates some tedious activity on the OS and is soon forgotten. That’s my problem: when I need to write or edit a script its hard to pick up where I left off or remember arcane syntax that I don’t use anywhere else. I don’t write scripts all day, so I often don’t remember where I put it, what environment I edited in, let alone some arcane syntax and other context information.

    I should have realized a long time ago that if a script is worth writing, it is worth safeguarding and properly maintaining like any source code. But script editing happens in spurts as a means to an end – it’s hard to justify taking the time to build up an infrastructure and become more proficient. This post documents how I set up a script repository and IDE using Git, Bitbucket, IntelliJ, Cygwin and some assorted environmental variables. So this documents my effort to get a a scripting environment set up that I can get familiar with and rely on. I’ll know it’s successful if I start writing more scripts because I feel more confident that I will get a longer return from the effort.

    Before I set up my scripting environment I asked my self some questions to help determine what tools and techniques I would need, and I encourage you to do answer these as well:

    Scripting Environment Questions:

    • Portability: How portable does my script need to be?
      • What OS(s) will it run on? My case: Win/Mac/Linux
      • What user(s) will use it? Me/Coworkers/My boss/Continuous Integration
    • IDE? Can I edit my scripts in the same IDE I use for everything else? Yes (detailed follow)
    • Extensibility: If my script can’t do everything natively, is there a strategy to add functionality? (Yes, details follow)
      • For example, if your script needs to do a REST call, do you: script a browser? import a cURL library?
    • Conventions: Is there a convention I can follow to put my scripts where both the OS and I can find them without tweaking? (Yes, requires some care)
      • For example: home/user/bin

    My answers lead me to a create a setup that prioritizes flexibility across OS and users and tries to follow general conventions. So, it should work for anybody, but you can sacrifice some of the generalization for your own convenience if you want to.

    Portability

    Once you combine Windows with another OS you are probably going to run into Cygwin as your go-to solution because you can write bash scripts that can run on Windows, MacOS and Linux. It is still very possible/maybe necessary to write Windows-only scripts in Cygwin. All my scripts are .sh except some narrow Windows cases.

    Portability also pertains to editing and running scripts on multiple machines in multiple user accounts. It means creating a project definition that can be put under version control to allow me to edit and run scripts on any machine as any user. To do this I followed conventions regarding where to house scripts (see Conventions below) and set my cygwin user folder as the project root and committed this to BitBucket using Git.

    IDE Support

    Maybe it should have been obvious, but it took me awhile to decide that I should strive to use my regular editor to edit scripts. I use IntelliJ, and I found a plugin called BashSupport that supports syntax highlighting, rename refactoring, documentation lookup, inspections, quickfixes and such. I can also run scripts from run configurations within IntelliJ- not sure if that is due to BashSupport or not honestly. Before that I used Notepad++, or simply Notepad. Another big advantage of using an IDE: it creates a project definition so you can reopen the project and see your scripts! Again, should have thought of this earlier, but my scripts were all small and scattered and this seemed like a heavyweight tank cracking a nut.

    Extensibility

    Cygwin has cultivated a large library of plugins that scratch the itch of various developer needs. It is likely to cover any requirement I will have. Install Cygwin to C:\ and don’t install any optional components. Save the installer (setup-x86_64.exe in my case) in the cygwin folder inside a folder you create called installer. Cygwin will create a cache of the plugins library in there. You will need the installer if you ever decide to install a plugin, so its a good idea to keep it with the project folder instead of losing in in the downloads folder like I have done.

    cygwinAlwaysRunAsAdmin

    Tip: In Cygwin Shortcut select Preferences: Shortcut Pane: Click Run as Administrator

    Conventions

    Cygwin tries to follow conventions whenever possible. It puts a directory structure that mimics Linux root, but it is located inside the Cygwin installation folder. Instead of accepting the default “Program files” location, install cygwin at C:\ to avoid spaces in any paths. My user dir is therefore: C:\cygwin64\home\[windows username]\bin. I added bin to .bashrc so I can run scripts from anywhere in Cygwin.

    Running Scripts

    From Intellij: As I mentioned, you can run shell scripts from within Intellij using run configurations. This seems like a lot of work after getting past the cool idea factor. It doesn’t open up any debugging options as far as I can tell. It’s a good idea to check the “Show this page” box to fill in any necessary arguments before running a script.

    cygwinRunConfiguration

    Intellij Script Run Configuration

    scriptRunInIDE

    Script output in Run Output of Intellij

    From Cygwin: cd bin, then ./script.sh to execute. Or add bin to .bashrc and execute script.sh from any location. Add Cygwin to context menu in Windows.

    From Git Bash: Open it from your bin directory to run a script on a Windows dev box lacking Cygwin. Otherwise useless because it is not extensible and conventional.

    You can run Cygwin in the IntelliJ terminal or in a terminal emulator like Conemu as well – Google for details.

    Polymer Data Models/Templates in Custom Elements

    A custom element can contain it’s own data source and be its own data model, as this example shows. The script provides a JavaScript object “salutations” containing a list of 4 objects with properties “what” and “who”.

    The inner template sets a repeat property that processes each “who/what” object “s” in salutations. The contents of the template is a mix of html and mustachioed object notations that results in a repeater containing labels and text inputs as shown in the inset from the resulting page.

    Source Code Explainer

    customElementDataModelTemplate

    My Feature Branch

    Polymer.org Example

    Live example

  • 0 Comments
  • Filed under: Practice, Tools
  • How to Add a Custom Element to a Polymer Project

    polymerLogo

    Starting with a project build with Yeoman (Yo Polymer), I add a custom element and display it in the index file. In addition, how to set up a project to build with Vulcanize and why, and how custom elements relate to the data model in Polymer is discussed.

    polymerSandboxDirectory

    polymerSandbox with djm-nameTag element

    I made a copy of one of the existing custom elements in the Yo-Polymer base project. I replaced the contents with a nametag example element from the Polymer data binding topic on their website. I named it with djm- prefix in order to satisfy the Polymer requirement that custom element names contain a dash while also adding a namespace to it by referring to my initials.

    djm_nameTag

    <link rel="import" href="../../bower_components/polymer/polymer.html">
    
    <polymer-element name="djm-nameTag" attributes="">
    
      <template>
        This is <b>{{owner}}</b>'s djm-nameTag element.
      </template>
    
      <script>
        Polymer('djm-nameTag', {
          // initialize the element's model
          ready: function() {
            this.owner = 'Rafael';
          }
        });
      </script>
    </polymer-element>

    Then I added the element to the index.html file body.

    <djm-nameTag></djm-nameTag>

    I expected to have to add an html import to the index file but there were no imports for the other elements. I realized that they were being imported via the vulcanize library method.

    <!-- build:vulcanized elements/elements.vulcanized.html -->
    <link rel="import" href="elements/elements.html">

    I added a djm-nameTag entry to elements.html

    Then when I ran

    grunt build

    grunt cleaned and rebuilt the dist directory and added djm-nameTag element to the elements.vulcanized.html file.

    polymerSandboxDistDirectory

    polymerSandbox dist directory including elements.vulcanized.html

     

  • 0 Comments
  • Filed under: Techniques, Tools
  • Building a Polymer Project From Scratch

     

     nodeJs + npmLogo + bower + yo + gruntLogo =polymerLogo

    1. Download and install NodeJs
    2. Make sure nodeJs is on the system PATH.
    3. Open a command window and enter node -v
    4. Install Node Package Manager (NPM): Enter npm install npm -g
    5. npm install -g grunt-cli
    6. npm install -g bower
    7. npm install -g yo
    8. npm install -g generator-polymer
    9. Create a project directory and navigate inside it from command: mkdir my-new-project cd $_
    10. To build a polymer skeleton project: yo polymer

    More on yo polymer scaffolding generator

    yoPolymerDirectory

    Scaffolded Polymer Project Directory after yo polymer

     

    yoPolymerDirectoryAfterBuild

    dist directory contains Polymer site root after grunt build

    Run server:

    grunt serve

    yoPolymerServed

    HTML outputs to default browser automatically on port 9000

    Test (Local):

    grunt test

    yoPolymerTested

    HTML outputs to default browser automatically

  • 0 Comments
  • Filed under: Techniques, Tools
  • Getting Started with Polymer

    I have been trying out a few frameworks and tools for HTML development lately. So far, Polymer is the only one with something new and interesting about it that might be worth capturing here. This is a great place to try it out and find out what it is.

    bower

    The first step for trying Polymer for me was getting serious about using Bower. I had kicked it around while working with Angular but it didn’t work well with Intellij and I kinda dropped it. Intellij had a nice workaround and I didn’t have many dependencies, but polymer recommends it and has a ton of dependencies. So yeah, that means you will also need nodejs since Bower runs on it.

    To get rolling I tried the recommended bower installation approach. I got a directory with some files in it that I assume have something to do with Polymer. Whatever, not what I was looking for. Next, I downloaded the zip file from Your first Polymer app. It contains a set of tutorial files nicely staged out so that you can compare your incomplete solution to a correct final one – nice.

    Once I had the starter project laid out I was supposed to start running it in a web server – polymer doesn’t work with file:// URLs. Since I have nodejs on my path I could start a command window at the root of the starter project and enter

    start httpserver

    The tutorial recommends starting up a python server but too many complications on Windows, I don’t use Python etc. My server started up on port 8080, which was fine because that’s a port I use for throwaways.

    Why not 'Polymer in 10 Minutes'?

    Because I found it required too much foreknowledge about Polymer to learn anything basic. For example, Step 1 is “Load the Polymer core” -what? with a link that goes to the API reference. I tried loading the plunker examples (which refuse to open in a new tab) but sadly plunker was down. Later it says to “use an HTML Import to load the polymer.html dependency” but never shows the import statement, identifies what file it goes in, shows what it looks like, or where it goes in whatever unidentified file. This was became clear after I downloaded the tutorial, however.

  • 0 Comments
  • Filed under: Tools
  • Learn-along Maven 3: Integrating Selenium

    maven  Intellij Idea selenium

    In Learn-along Maven 3: Adding Dependencies I shared the way I add dependencies to a project to enable frameworks and libraries via Maven Project Object Model (POM).

    The dependencies satisfied the requirements for working with Java Server Pages, Struts 1.x, JBoss, testNg and Selenium. Together these will work with Java and JBoss Application Server to provide a testable web application.

    Since I started with the Hello World archetype I have some work to do to enable serving web pages with my application. All  the application can do from the start is print “Hello World!” out in the console, and verify that it works via a test class using jUnit.

    The Java Server Pages dependency provides a ready-made framework for delivering web pages via JBoss. Following the greased path outlined by the documentation, I added a web folder at the same level as src, and created a web page within it named “index.jsp”. This filename is the default for jsp/jBoss and will open when the directory containing it is reached in a browser. Then I added a folder within web named WEB_INF, and created a web.xml file in it which is used to define the jsp configuration for a j2ee compliant appllication server such as jBoss.

    To do this I need to create a web page within the project. I will  configure it within a standard web application format that JBoss can process and then deliver at a URL on my local computer. Then I will write a Selenium test that uses TestNg annotations and assertions to automatically open and verify that the page is launched in a specific browser and contains the content that I included when I created the web page.

    The Web Page

    <html>
    <head>
        <title>Welcome to simplest-struts</title>
    </head>
    <body>
    <p>simplest struts</p>
    </body>
    </html>

    Create a folder named “web” and save this html as “index.jsp” in it.

    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    		  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
               version="2.5">
        <display-name>Simplest-Struts-1</display-name>
    
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
    
        <servlet>
            <servlet-name>action</servlet-name>
            <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
            <init-param>
                <param-name>config</param-name>
                <param-value>/WEB-INF/struts-config.xml</param-value>
            </init-param>
            <load-on-startup>2</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>action</servlet-name>
            <url-pattern>*.do</url-pattern>
        </servlet-mapping>
    
    </web-app>

    SeleniumTest.java

    package com.davidjmcclelland;
    
    import com.thoughtworks.selenium.SeleneseTestBase;
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.firefox.FirefoxDriver;
    import org.testng.annotations.BeforeClass;
    import org.testng.annotations.Test;
    import org.testng.annotations.AfterClass;
    
    import org.apache.log4j.Logger;
    
    import static org.testng.Assert.assertTrue;
    
    public class SeleniumTest extends SeleneseTestBase {
        public static WebDriver driver = new FirefoxDriver();
        private final Logger logger = Logger.getLogger(SeleniumTest.class);
    
        public SeleniumTest(){
            super();
        }
    
        @BeforeClass
        public void setup(){
            logger.info("Setup class: " + this.getClass().getName());
            driver.get("http://localhost:8080/simplest_struts_1_Web_exploded/");
        }
    
        @Test
        public void titleTest(){
            assertTrue(driver.getPageSource().contains("simplest struts"));
        }
    
        @AfterClass
        public void tearDown(){
            driver.close();
            driver.quit();
        }
    
    }

    The Selenium test running from IntelliJ

  • 0 Comments
  • Filed under: Techniques, Tools
  • Learn-along Maven 3: Adding Dependencies

    maven  Intellij Idea 

    In Learn-along Maven 3: Hello World Tests Compared I compared running tests on a Hello World Java app using Intellij and various Maven approaches.

    In this post I will compare methods for adding libraries/dependencies. Where I am going with this is to take the Hello World archetype and make it into a webapp project and then test it with Selenium.

    • Add Java Server Pages
    • Add Struts 1.x
    • Add Selenium
    • Test locally in browser
    • Test in browser from Jenkins

    Libraries vs Dependencies...

    Adding libraries via the Intellij 12 module tab in project settings is not the same as adding Maven dependencies. I guess that seems obvious as I write this, but I would argue that if you create a Maven project it should behave consistently in a Maven-centric way. But when I add apache-log4j in the Project Structure dialog and select Library->Via Maven, a dependency is not added to the project POM file. The dependency storage format dropdown is a clue: the choices are IntelliJ or Eclipse formats, not Maven POM. The junit library that I added to the POM file manually is listed in Dependencies and has the prefix Maven: in front of it to indicate it is in the POM.

    I was hoping to find an integrated method for adding dependencies to the POM using IntelliJ 12 or 13, but so far I still find that pasting in a dependency block from Nexus Central repository is the best and easiest method.

    For several reasons:

    1. I am able to have a good look at the origin and version of the resource I am getting
    2. I am certain the dependency gets in the POM
    3. The resource is stored in the .m2 directory in the package where it belongs.
    4. I don’t have to manually figure out where to put the resource in the .m2 directory.
    5. I can update the resource by editing the POM entry and Intellij will download and install the update in the background

    Nexus Central vs. Intellij

    Nexus Central Repository shows resources in context, and rolled up in far fewer choices than a dropdown.

    A dropdown is insufficient when there are over 200 valid selections for “junit”

    A Library downloaded from a Maven Repo should default to saving in the appropriate package in the .m2 directory. I don’t want to guess where to put it when the correct answer should be in the artifact’s POM.

    Check the setting to Import Maven projects automatically

    Shortcomings/Challenges

    This aspect of developing in Java/Intellij is clunky compared to other stacks I have used. For example, to move a project from a simple “Hello World” command line test to a Struts WebApp required a lot of digging around to find the required libraries and versions- I finally “cheated” and opened the POM of another project and grabbed the entries form it to save time. There is no “Make this struts” button.

    A veteran Java developer would counter that “you just need to know what you’re doing.” That is true, of course. I know I can create a Struts project from an Archetype and get started. A wise developer needs to know what they are not doing. I need to focus my effort on developing what makes my project different and important enough to be funded. A strong library feature is a critical aspect of this.

    Once I found what I thought were the correct libraries/versions for Selenium, testNg, Struts and JSP were added to the POM there were several problems. Not having a real user interface to handle dependencies meant spending a lot of time Googling and comparing to other project to figure out. Eventually I got it sorted out and currently can run a test that opens a browser window to the page served by the webapp project.

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    
    xmlns="http://java.sun.com/xml/ns/javaee"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    		  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
               version="2.5">
        <display-name>Simplest-Struts-1</display-name>
    
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
    
        <servlet>
            <servlet-name>action</servlet-name>
            <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
            <init-param>
                <param-name>config</param-name>
                <param-value>/WEB-INF/struts-config.xml</param-value>
            </init-param>
            <load-on-startup>2</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>action</servlet-name>
            <url-pattern>*.do</url-pattern>
        </servlet-mapping>
        <jsp-config>
        <taglib>
            <taglib-uri>/tags/lab4-converter</taglib-uri>
            <taglib-location>/WEB-INF/lab4-converter.tld</taglib-location>
        </taglib>
        </jsp-config>
    
    </web-app>
  • 0 Comments
  • Filed under: Techniques, Tools
  • Learn-along Maven 3: Hello World Tests Compared

    maven  Intellij Idea console2Logo

    In Learn-along Maven 3: IDE vs CLI I compared creating a Hello World Java app via Maven using command line vs with IntelliJ using a Maven extension. The result was pretty much identical, but the process was slightly different between the two. Both projects ran green on Jenkins, although they really don’t do much and the test class is a bogus assertTrue(true).

    Now I will I will change the test to evaluate that the “Hello World” String is created as an accessible String and compare running the test

    • within Intellij,
    • within Intellij via Maven plugin,
    • with Maven via command line, and
    • via Jenkins.

    Change Main class and test assertion...

    Original archetype Hello World Class

    Testable Hello World class now has a variable message

    Replace the test assertion with this:

    assertTrue( App.message.equals(EXPECTED_STRING) );

    Test in IntelliJ...

    Build and run the test in Intellij by right-clicking the test class window tab or file icon.

    The target directory contains the build.

    Maven Test in IntelliJ...

    Maven Intellij plugin expands from the Right side by default, click Test in lifecycle…

    Test via Maven does not put empty annotation folders in target, otherwise the output looks the same as running the test from Intellij right-click menu, but its important to note that it is *not* the same thing.

    Maven Test from cmd.exe...

    mvn clean test

    Cmd shows the same information that is displayed in the Run log window in Intellij when you run a test via the Maven plugin.

    Output target directory was the same as running the maven plugin from within Intellij.

    Maven Test in Jenkins...

    Looks like the test runs great on Jenkins

    Wait something’s wrong: all Jenkins did was build an empty target folder structure. Nothing was run or tested at all. It’s a freestyle project – you have to enter the Maven commands.

    Make sure you enter “Invoke top-level Maven targets” which is the same thing as what was entered in the command line example.

    You should see test results at the bottom of the console log view.

    Maven’s workspace target directory now contains class files.

     

  • 0 Comments
  • Filed under: Techniques, Tools
  • Categories





    Finger Lakes, NY

    United States of America

    Subscribe via Email

    Enter your email address to subscribe to this blog and receive notifications of new posts by email.