Quantcast
Channel: Bryntum
Viewing all 370 articles
Browse latest View live

Using Ext Scheduler with Angular 2

$
0
0

One of the most popular javascript frameworks nowadays is Angular. Angular gives you the ability to make HTML content dynamic by using templates, components and services. However Angular does not have a suite of high-level UI components as ExtJS does.

In this blog post we are going to create an Angular component for our Ext Scheduler, which can be used in Angular templates as <scheduler> tags. After following the steps in this blog post, you will have a basic example with interaction between Ext Scheduler and Angular. Feel free to extend the example to suite your needs.

angular2

Prerequisites

This guide uses Angular with Typescript, but you should be able to follow it for the most part even if you use Dart or JavaScript.

1. Please check https://angular.io/docs/ts/latest/quickstart.html for Angular prerequisites (at least node v5.x.x and npm 3.x.x).
2. Make sure you have typescript installed, tsc -v. Otherwise install it with npm install -g typescript.

Create Angular project

We are going to follow a few steps from Angular Quickstart to get started quickly :)  Please follow the steps below before continuing:

Step 1: Create and configure the project

Step 2: Our first Angular component

Step 3: Add main.ts

Step 4: Add index.html

If everything went well you should now have a project structure like this:

app
  app.component.ts
  main.ts
node_modules
  [many folders]
typings
  globals
  index.d.ts
index.html
package.json
systemjs.config.js
tsconfig.json
typings.json

If the typings folder is missing then you can run:
./node_modules/.bin/typings install

To automatically track changes in your project files you can run:
npm start
This command will start a webserver and a browser, compile changes on the fly and update the browser.

Create the Angular component

Angular components (a directive with a template) can be used as tags in templates to add functionality. We want to use Ext Scheduler in Angular and are thus going to create a component for it. It will be called SchedulerComponent and have a corresponding <scheduler> tag. Attributes in the tag will be used as settings for the scheduler and we will also enable event binding.

Start by adding a file named scheduler.component.ts to your app folder. Add the lines listed below:

import {Component} from '@angular/core';

@Component({
    selector: 'scheduler',
    template: '
' }) export class SchedulerComponent { }

This gives us a component with the <scheduler> tag (line 4). In the end this will yield an empty div tag (line 5), to which we will render the scheduler. Now we modify app.component.ts to import SchedulerComponent:

import {Component} from '@angular/core';
import {SchedulerComponent} from './scheduler.component';

@Component({
    selector: 'my-app',
    template: `
        

{{title}}

`, directives: [SchedulerComponent] }) export class AppComponent { title = 'Angular 2 demo'; }

An import-statement is added (line 2) to include the new component. The template is modified to include the component on page (line 8) and finally a directive is added to tell Angular which tags are valid (line 10).

Add the scheduler

Now we are going to include the required files for the scheduler and create an instance of it. Modify index.html and add the following to the <head> section:


In scheduler.component.ts we create the scheduler, but not before the template is applied. We do not yet know where to render the scheduler since our container element <div id="scheduler"> is not yet added to the DOM. We need to hook into a suitable moment in our component life cycle. Change scheduler.component.ts to match the lines below:

import {Component, AfterContentInit} from '@angular/core';

@Component({
    selector: 'scheduler',
    template: '
' }) export class SchedulerComponent implements AfterContentInit { ngAfterContentInit() { // this is where we will create an instance of Ext Scheduler } }

Because of TypeScript being more typesafe than JavaScript we need to declare a couple of global variables to handle the scheduler, or else we will get compilation errors. Add the following lines after the import statement:

// global variables for imported js libraries
declare var Ext:any;
declare var Sch:any;

The next step is to create an instance of the scheduler. Add the following lines to the ngAfterContentInit function:

var eventStore = Ext.create('Sch.data.EventStore', {
    model: 'Sch.model.Event',
    proxy: {
        type: 'memory'
    }
});

var resourceStore = Ext.create('Sch.data.ResourceStore')

var scheduler = Ext.create('Sch.panel.SchedulerGrid', {
    width: 1000,
    height: 600,
    renderTo: 'scheduler',
    columns: [
        {header: 'Name', sortable: true, width: 160, dataIndex: 'Name'}
    ],
    resourceStore: resourceStore,
    eventStore: eventStore
});

By now we have done most of the heavy lifting and have an empty scheduler displayed in your Angular project. But empty is kind of boring so let’s load some data.

Loading data

In a real world application we would load the data from a database, but in this sample we’ll just use dummy data from a JS file. Create a folder named data in the root of your project, add a file data.json with the following content:

{
    "events" : [
        {"Id" : "e10", "ResourceId" : "r1", "Name" : "Deal negotiation", "StartDate" : "2016-09-05", "EndDate" : "2016-09-08"},
        {"Id" : "e22", "ResourceId" : "r2", "Name" : "Attend software conference", "StartDate" : "2016-09-06", "EndDate" : "2016-09-09"},
        {"Id" : "e43", "ResourceId" : "r3", "Name" : "Visit customer", "StartDate" : "2016-09-05", "EndDate" : "2016-09-06"},
        {"Id" : "e210", "ResourceId" : "r4", "Name" : "Exhibition", "StartDate" : "2016-09-07", "EndDate" : "2016-09-11"},
        {"Id" : "e222", "ResourceId" : "r5", "Name" : "Meet customer X", "StartDate" : "2016-09-05", "EndDate" : "2016-09-06"},
        {"Id" : "e243", "ResourceId" : "r6", "Name" : "Prepare case studies", "StartDate" : "2016-09-06", "EndDate" : "2016-09-08"}
    ],

    "resources" : [
        {"Id" : "r1", "Name" : "Mike Anderson" },
        {"Id" : "r2", "Name" : "Kevin Larson" },
        {"Id" : "r3", "Name" : "Brett Hornbach" },
        {"Id" : "r4", "Name" : "Patrick Davis" },
        {"Id" : "r5", "Name" : "Jack Larson" },
        {"Id" : "r6", "Name" : "Dennis King" }
    ]
}

The file contains data for both the event-store and the resource-store. We can use Angulars http library to load it into the stores. Add two imports to schedule.component.ts below the other imports:

import {Http, HTTP_PROVIDERS} from '@angular/http';
import 'rxjs/add/operator/map';

Then add a provider to the @Component decorator:

@Component({
    selector: 'scheduler',
    template: '
', providers: [HTTP_PROVIDERS] })

Now inject the Http class into the SchedulerComponent class by adding a constructor:

export class SchedulerComponent implements AfterContentInit {

    constructor(private http:Http) { }

    ngAfterContentInit() {
        // existing code untouched...
    }
}

Finally, use http to load data.json and populate the stores by adding the following lines at the bottom of the ngAfterContentInit function:

this.http.get('data/data.json')
         .map(r => r.json())
         .subscribe(
             data => {
                 resourceStore.loadData(data.resources);
                 eventStore.proxy.data = data.events;
                 eventStore.load();
             },
             err => console.error(err)
          );

Add input property bindings

It would be nice to be able to configure the scheduler by using attributes in the <scheduler> tag, something that is called Property Binding. We will create an example binding for the title of the scheduler. This is done by adding fields to the SchedulerComponent class and decorating them with the @Input directive.

First, import the Input directive by changing the first import to:

import {Component, AfterContentInit, Input} from '@angular/core';

Secondly, add a field at the beginning of the SchedulerComponent class:

export class SchedulerComponent implements AfterContentInit {
    @Input() title:string;
    // existing code untouched

Third, alter the code that creates the scheduler in the ngAfterContentInit function:

var scheduler = Ext.create('Sch.panel.SchedulerGrid', {
    title: this.title,
    // existing code untouched
});

Finally, add a property binding to the <scheduler> tag in the template in app.component.ts:

@Component({
    selector: 'my-app',
    template: `
        

{{title}}

`, directives: [SchedulerComponent] })

Those are the basic steps for adding property bindings. If you download and look at the demo that belongs to this blog post you can see that we have added a few other useful properties. You can find the download link at the bottom of this post.

Add output property bindings

Angular supports output of values from a component to another components template, by decorating fields in the class with the @Output directive. We can use this to display information from the scheduler. For example we can display the number of loaded events in the main template.
First, import the Output directive by changing the first import to:

import {Component, AfterContentInit, Input, Output} from '@angular/core';

Secondly, add a field at the top of the SchedulerComponent class:

@Output() eventCount:number;

Third, assign a value when the data is loaded. Alter the http call in the ngAfterContentInit function:

resourceStore.loadData(data.resources);
eventStore.proxy.data = data.events;
eventStore.load();
//new line:
this.eventCount = eventStore.getCount();

Finally, modify the main template in app.component.ts to match the following lines:

@Component({
    selector: 'my-app',
    template: `
        

{{title}}

Number of events: {{scheduler.eventCount}}
`, directives: [SchedulerComponent] })

By specifying #scheduler on line 7 we assign a variable, called a template reference variable. This variable can be used in the template to access the output values. It is then used on line 5 to display the eventCount.

Add event bindings

Event bindings are not that different from property output bindings. We are going to add an event for when the user selects an event in the scheduler. For this we need an EventEmitter in the ScheduleComponent and then do event binding in the main template.

First, import EventEmitter by changing the first import to:

import {Component, AfterContentInit, Input, Output, EventEmitter} from '@angular/core';

Secondly, add an EventEmitter to the top of the SchedulerComponent class:

@Output() onEventSelect = new EventEmitter();

Then add a listener to the scheduler to catch the event and emit it. This listener is set in the config for the scheduler:

// last line of existing code in config object
eventStore: this.eventStore,
// add below
listeners: {
    eventselect: function(em, eventRecord) {
        this.onEventSelect.emit(eventRecord);
    },
    scope: this
}

Lines 4-9 contains the listeners configuration. Line 6 emits the event, enabling us to catch it outside the SchedulerComponent.

Now bind to the event in the main template. Alter the scheduler tag (in app.component.ts) to match the following lines:

@Component({
    selector: 'my-app',
    template: `
        

{{title}}

Selected event: {{eventTitle||'None'}}
`, directives: [SchedulerComponent] })

Line 5 will be used to display the title for the selected scheduler event. Line 8 is the event binding. When the scheduler emits an event the binding will call an onEventSelect function with the eventRecord passed as an argument. Add the onEventSelect function to the AppComponent class:

export class AppComponent {
    title      = 'Angular 2 demo';

    onEventSelect(eventRecord:any) {
        // event selected in scheduler, display its name
        this.eventTitle = eventRecord.get('Name');
    }
}

Accessing the scheduler from another class

In the section “Add output property bindings” above we created a template reference variable to access SchedulerComponent output in the template. Unfortunately we cannot use that variable to access the SchedulerComponent from the main class of the application (AppComponent). Instead we have to inject the SchedulerComponent as a ViewChild in AppComponent. Follow the steps below to modify app.component.ts:

First, import ViewChild by altering the first import to:

import {Component, ViewChild} from '@angular/core';

Secondly, add a @ViewChild field somewhere at the top of the AppComponent class:

@ViewChild(SchedulerComponent) schedulerComponent:SchedulerComponent;

Congratulations! If you reached this point you can access the scheduler by using this.schedulerComponent anywhere in AppComponent.

Download the example

View live example: http://www.bryntum.com/playpen/angular2
Download source: http://www.bryntum.com/playpen/angular2.zip

Code away!


Gantt Chart and Kanban Task Board Integration

$
0
0

We are happy to announce a new example which integrates the Ext Gantt with the Task Board component. This blog post will give a brief description of the example and highlight some interesting parts of the code. Feel free to try the example out, you can find it here. To browse the source files, just click Details on the right.

Start

Short description of the example

This example features Gantt tasks that have smaller tasks visualized in the Task Board. Each Gantt task can contain multiple Task Board tasks, shown as small colored thumbs inside the task bar:

Thumbs

Click on a thumb to highlight the corresponding Task Board task:

Click thumb

Or click on a Gantt task to filter the TaskBoard to only display related TaskBoard tasks:

Filter kanban

When the status of a Task Board task changes it is reflected immediately on the miniature. Dragging a Task Board task to Done also affects the Gantt task’s percentage field:

Percent done

Comments on the code

Below are some comments on the central parts of the code that makes the interaction possible. Task Board tasks are connected to Gantt tasks via a foreign key field on the model:

Ext.define('GanttTaskBoard.model.KanbanModel', {
    extend: 'Kanban.model.Task',

    // Add field to connect Kanban tasks to Gantt tasks
    fields: [
        {name: 'TaskId', type: 'int'}
    ]
});

The Task Board store handles getting related tasks:

Ext.define('GanttTaskBoard.store.KanbanStore', {
    extend  : 'Kanban.data.TaskStore',
    model   : 'GanttTaskBoard.model.KanbanModel',

    /**
     * Get all Kanban tasks that belongs to a specific Gantt task.
     * @param taskId Id of a Gantt task, matched to Kanban tasks field TaskId
     * @returns {*} Array of Kanban tasks
     */
    getKanbanTasksByTaskId: function (taskId) {
        return this.query('TaskId', taskId).items;
    }
});

Gantt task are given a shortcut to related Task Board tasks:

Ext.define('GanttTaskBoard.model.TaskModel', {
    extend : 'Gnt.model.Task',

    /**
     * Get related Kanban tasks (where KanbanTask.TaskId == Task.Id)
     * @returns {*} Array of related Kanban tasks
     */
    getKanbanTasks: function() {
        var me = this,
            kanbanStore = me.getTaskStore().kanbanStore;
        return kanbanStore.getKanbanTasksByTaskId(parseInt(this.getId(), 10));
    }
});

And when using custom data inside our Gantt task template, we also need to provide the relevant data to the template via the eventRenderer method. This is how the task miniatures are rendered:

Ext.define('GanttTaskBoard.view.Gantt', {
    extend    : 'Gnt.panel.Gantt',

    eventRenderer : function (taskRecord) {
        return {
            // Provide kanban tasks to the task body template
            kanbantasks : taskRecord.getKanbanTasks() || []
        };
    },

    // taskBody with an outer div (sch-gantt-task-inner) containing a progress bar and a div to hold kanban 
    // miniatures (.sch-gantt-kanban-bar)
    taskBodyTemplate : '<div style="width:{progressBarWidth}px;{progressBarStyle}"></div>' +
    '<div>' +
        // template used to render kanban tasks as miniatures into gantt tasks
        '<tpl for="kanbantasks">' +
                '<div data-qtip="{data.Name}" data-kanban-id="{data.Id}"></div>' +
        '</tpl>' +
    '</div>'
});

Additional resources

This sample can be found in the Gantt Pro edition and you can also run it online.

Using the MVVM pattern with Ext Scheduler

$
0
0

My name is Pavel and I recently joined the Bryntum development team. I have used Ext JS as my main JS framework for more than three years and today I’m going to show you how you can use the MVVM pattern in your application.

Let’s start by looking at what MVVM is and why we need it. As you may know, MVVM is the Model-View-ViewModel pattern. A Model holds data and a View is part of the UI. In our case, the ViewModel is a layer which is observing both the Model and the UI and helps to exchange data between them. All changes in the Model will reflect on our View instantly and the same is true in reverse. Immediately after changing data in the View, your Model will have updated values. The ViewModel allow us to control these changes and implement our custom logic, we don’t need to have extra event listeners doing manual work.

The picture below is from our WeekView example. Any changes to the edited Event record will be applied instantly to the Form. Any changes in the Form will be applied to the Event record. We can show this schematically:

ViewModel

The main focus of this post is the ViewModel and how it is used in our WeekView example. We will explore two ViewModel features called explicit binding and settable formulas, detailed information about these binding tools can be found in the Sencha docs. By using a ViewModel and these features we will workaround some flaws of the DateField and TimeField.

Creating an event form with ViewModel binding

We want to have a basic form to edit the details of a scheduled event. Something looking like the one below:

details

We also want the form to be bound to the ViewModel to automatically sync changes to and from the Model. For this we need to:

  • Create a Form Panel;
  • Specify ViewModel for the Form Panel;
    var form = new Ext.form.Panel({
    	viewModel: {}, 
            ...
    });
  • Set Event record to ViewModel data property (for example MyRecord);
    form.getViewModel().set('MyRecord', record);
  • Bind MyRecord data properties on Form Fields;
    // string notation
    {
    	xtype: 'field',
    	bind: '{MyRecord.Name}'
    }
    // is equal to object notation with value property
    {
    	xtype: 'field',
    	bind: {
    		value: '{MyRecord.Name}'
    	}
    }
    // you can bind to custom property but you have to be sure setter/getter exist.
    {
    	xtype: 'field',
    	bind : {
    		customName: '{MyRecord.Name}'
    	},
    	setCustomName : function(name) {
    		this.coolName = name + ' is cool!';
    	},
    	getCustomName : function(name) {
    		return this.coolName;
    	}
    }

Now we face a slight problem with the date and timefields that we want to fix using a ViewModel…

Datefield doesn’t keep time, timefield doesn’t keep date

Oops… Who could imagine that this is not basic functionality supported by Ext JS? Unfortunately we will have to take care of it manually, let’s dig deep into the Ext JS sources.

Lets start from Ext.form.field.Date component. There are two visual ways how you can change value of datefield: by typing and by using the datepicker. If you type, the field will keep the time info only if the format of date field contains time information, and the date picker doesn’t care even if the format contains time. If you look under the hood, you will find out that safeParse method of datefield checks if not utilDate.formatContainsHourInfo(format) then utilDate.clearTime(parsedDate); and setValue method of the DatePicker class clears time instantly this.value = Ext.Date.clearTime(value || new Date(), true);

Now let’s move to the Ext.form.field.Time component. It clears the date as soon as the setValue method of the TimeField is called. Only the time information is used for the value v = me.getInitDate(v.getHours(), v.getMinutes(), v.getSeconds()); and this getInitDate uses the initDateParts property that is equal to initDateParts: [2008, 0, 1]. TimeField inherits from ComboBox, so initComponent calls me.store = Ext.picker.Time.createStore(me.format, me.increment); to prepare the picker store. This createStore method of timepicker uses the initDate property that is equal to initDate: [2008,0,1]

It seems we located a few date and time field flaws which we want to work around in the demo using a ViewModel. Let’s continue by talking about how we can control getting and setting data using the ViewModel. The main tools that will help us is:

Explicit Binding and Settable Formulas

Formula is a very powerful tool that enables you to control get and set operations on a ViewModel. Formulas can be described with different approaches, let’s take a look at a few of them:

// Simple function notation. Implicit binding.
// ExtJS analyzes which data was gotten from viewModel
// and creates dependencies.
// Each time data is changed this formula is called.
formulas: {
	foo: function (get) {
		return get('bar_record.some_data_field');
	}
}
// Object notation with get function. The same as previous one.
formulas: {
	foo: {
		get: function (get) {
			return get('bar_record.some_data_field');
		}
	}
}
// Object notation with get and set functions.
// The same, but now also set method is called, before data is changed.
formulas: {
	foo: {
		get: function (get) {
			return get('bar_record.some_data_field');
		},
		set: function (value) {
			this.set('bar_record.some_data_field', value);
		}
	}
}
// Object notation with explicit binding, get and set functions.
// We bind on exact data property, so there is no analyzing.
// Also get function takes a value as a parameter instead of function.
formulas: {
	foo: {
		bind: '{bar_record.some_data_field}',
		get: function (value) {
			return value;
		},
		set: function (value) {
			this.set('bar_record.some_data_field', value);
		}
	}
}

Workaround using Formulas and Binding

Because of the date- and timefield limitations described above we need to use a formula each time a record field is changed. We will apply explicit binding for our Form. Here we will describe formulas only for Start Date, but the same is true for End Date. Do not forget to use clone method when you need to modify original date object.

formulas : {
	StartDate : {
		bind : '{MyRecord.StartDate}',
		get  : function (date) { return date; },
		set  : function (date) {
			var MyRecord = this.get('MyRecord'),
				original = MyRecord.get('StartDate');

			// Apply time from original record
			Sch.util.Date.copyTimeValues(date, original);
			MyRecord.set('StartDate', date);
		}
	},
	StartTime : {
		bind : '{MyRecord.StartDate}',
		get  : function (date) { return date; },
		set  : function (date) {
			var MyRecord = this.get('MyRecord'),
				// Make a clone of the date to avoid changes by reference
				original = Ext.Date.clone(MyRecord.get('StartDate'));

			// Apply time to original record
			Sch.util.Date.copyTimeValues(original, date);
			MyRecord.set('StartDate', original);
		}
	}
}

Now we need to bind form fields on formulas.

{ xtype : 'datefield', bind : '{StartDate}' },
{ xtype : 'timefield', bind : '{StartTime}' }

That’s it. You can use formulas and customize your app logic to be as flexible as you need. Also I want to warn you about Binding Timings, so if you need to execute some logic instantly you need manually call viewModel.notify();

I hope this article was helpful and that you are not tired of reading it :) . As a bonus you can check out our Weekview DEMO to see how MVVM pattern works in an Ext JS application.

Using Ext Gantt with React

$
0
0

React is a very popular library for front-end development. It makes it easy to build state driven views and it efficiently handles DOM updates. React is component-based and in this blog post we are going to explore how to wrap Ext Gantt as a reusable React component.

reactdone

Prerequisites

In this guide we will use React with JSX. We are going to load React and Babel from unpkg.com (a CDN) and Ext Gantt from bryntum.com (our trial version), so no installation is required.

Add index.html

We are going to create a React app and as stated in the Prerequisites above we need to load some external libraries. Start by creating an index.html file with the following contents:



    

    
    
    

    
    
    
    

    
    
    
    

    React demo


    

We now have an empty page that includes both React + Babel and ExtJS + Ext Gantt. It is time to wrap the Gantt chart as a React component, which will make it easy to use in React apps.

Create a React component

To keep the code clean we are going to create the component in a separate file. Create a file named gantt.jsx and include it in index.html below the div tag:

// existing line
// load the react component from gantt.jsx

Add the following to gantt.jsx:

// a wrapper for the gantt chart to simplify usage with react
window.Gantt = React.createClass({
    // called after reacts render, which yields a div
    // create a gantt chart and render it to that div
    componentDidMount : function() {
        var config = {

            // store that holds all tasks
            taskStore : {
                type     : 'gantt_taskstore',
                autoLoad : true,
                proxy    : {
                    type : 'ajax',
                    url  : 'data/tasks.json'
                }
            },

            // store that holds dependencies between tasks
            dependencyStore : {
                type     : 'gantt_dependencystore',
                autoLoad : true,
                proxy    : {
                    type : 'ajax',
                    url  : 'data/dependencies.json'
                }
            },

            // settings
            width            : 1000,
            height           : 400,
            eventBorderWidth : 0,
            split            : false,
            viewPreset       : 'weekAndDayLetter',
            rowHeight        : 30,

            // predefined columns
            columns: [
                { xtype: 'namecolumn', width: 150 }
            ],

            // renders to the div created in render() below
            renderTo : 'gantt'
        };

        // store the rendered gantt chart in state for later access
        this.setState({ widget : Ext.create('Gnt.panel.Gantt', config) });
    },

    // just renders a div that we use to do the actual gantt chart rendering
    render : function() {
        return (
            
); } });

The code above defines a React component called Gantt. It renders a div to the page (in the render() method which React calls to display the component). The div is then used as a container for our gantt chart, which is created in componentDidMount() (called by React after render).

The gantt chart includes some basic configurations for width, height etc. as well as also two stores which loads data from json files (in the zip if you download the example). In case we need to access the gantt chart later, it is stored in the React components state using setState().

Create a React app

Lets make a simple React app to try our new Gantt component out. Start by adding a script tag using Babel to index.html, below the script tag for gantt.jsx:

// existing lines
// new script tag for our React app

Now add the following lines to the script tag:

var GanttDemoApp = React.createClass({
    render : function() {
        return (
            
        );
    }
});

ReactDOM.render(
    ,
    document.getElementById('gantt-demo')
);

GanttDemoApp is a React component that at this point only contains a Gantt component (our gantt chart). It is rendered to the page using ReactDom.render(). If you open the index.html file in a browser on a web server, you should see the following:

reactpart

Configuration using Props

All settings in the Gantt component above are hard coded. In React you use Props to allow components to be configured from the outside. Let’s add a few simple Props to the Gantt component, you can of course add more later. First add the following to the top of the Gantt component, just below React.createClass():

propTypes : {
    id              : React.PropTypes.string.isRequired,
    taskUrl         : React.PropTypes.string.isRequired,
    dependenciesUrl : React.PropTypes.string.isRequired,
    preset          : React.PropTypes.string
},

The Props with isRequired at the end have to be specified by the user when using the component, others are optional. Now we use the Props as settings in the gantt chart by changing the following lines:

// in componentDidMount():
url : 'data/tasks.json'
url : 'data/dependencies.json'
viewPreset : 'weekAndDayLetter'
renderTo : 'gantt'
// in render():

To:

// in componentDidMount():
url : this.props.taskUrl
url : this.props.dependenciesUrl
viewPreset : this.props.preset
renderTo : this.props.id
// in render():

Props can now be set on our Gantt tag to easily configure the component. Change the tag to:

Interacting with the Gantt component

We are going to try two kinds of interactions:

  • Relaying events from the gantt chart to React
  • Exposing functions on our React component
Relaying events

Ext Gantt is based on ExtJS that has its own event system. We want to be able to take action on those events in our React app and therefore need to relay them somehow. A simple solution is to use callbacks as Props and then call them from an internal event listener. Lets try it out, add the following listener to the bottom of the config object in componentDidMount() in Gantt:

listeners : {
    taskclick : (gantt, taskRecord) => {
        this.props.onTaskSelect && this.props.onTaskSelect(taskRecord);
    }
}

What we added is an event listener for the gantt chart event taskclick. When the event is fired we check if we have a callback (onTaskSelect()) in Props and the call it with information about the task that was clicked. Now add the prop to the React apps Gantt tag (in bold below) and a function to handle the event to the app (also in bold):

var GanttDemoApp = React.createClass({
    handleTaskSelect: function(taskRecord) {
          // clicking a task in the gantt chart shows an alert with its name
          alert(taskRecord.get('Name'));
    },

    render : function() {
        return (
            onTaskSelect={this.handleTaskSelect} />
        );
    }
});

Exposing functions

When creating the Gantt component we stored the gantt chart in the React state, which we can access from the outside to manually reach Ext Gantt member functions. But a better approach is to add functions to the Gantt component which does that for us, making it easier to use. As an example let’s add a function to add new tasks, put the following code before render() in the Gantt component:

// can be called from parent component to add a new task
addTask : function() {
    this.state.widget.getTaskStore().getRoot().appendChild({
        Name: 'New task',
        leaf: true
    });
},

To be able to access it from our React app we are going to add a ref to the Gantt component. Refs give us easy access to child components. At the same time let’s add a button to trigger the function. Modify the tag to match this (new in bold):


ref="gantt1"
       taskUrl="data/tasks.json"
       dependenciesUrl="data/dependencies.json"
       preset="weekAndDayLetter"

        onTaskSelect={this.handleTaskSelect} />

Now add a handleAddClick() method to the React app, above the render method :

handleAddClick: function() {
    this.refs.gantt1.addTask();
},

That’s it, clicking the button calls handleAddClick() which uses refs to get the instance of the Gantt component and then calls addTask() on it.

Taking it further

Please download the example and check the source to see some more Props and also how to create a Column component used as a child to our Gantt component for configuring which columns to show. Feel free to extend it with the Props, events and functions you need for your app.

View live example: http://www.bryntum.com/playpen/react
Download source: http://www.bryntum.com/playpen/react/react.zip
Learn about React: http://facebook.github.io/react/

Happy coding!

Siesta 4.2.0 – Improved Recorder + Faster Testing

$
0
0

We’ve just released Siesta 4.2.0 – an important Siesta milestone and in this blog post we’ll highlight its most important features.

Introducing Ariadne – The New Query Finder

The internals of the Siesta event recorder have been completely rewritten (more specifically, the target finding algorithm). In previous versions, the recorder sometimes found either a very long CSS query or it could not figure out a stable CSS query at all. We decided that this feature was important enough to warrant a new implementation.

In this new release, we have moved the query finding functionality to a new project called Ariadne. Ariadne finds the “optimal” CSS query for any given DOM element and it handles Ext JS component queries just as well. Let’s check the key requirements we had for this task and design choices made during the development.

First of all, how does one define the “optimal” query, seen from the event recorder’s perspective?

The “optimal” query has to be stable – it should survive reasonable markup changes. This means the recorder should try to avoid all types of positional selectors such as: “>” (direct child), “nth-child()”, “nth-of-type()”. Those are very fragile and will break as soon as new element is added to the DOM, or when the position of an element changes. In the same time there are situations when it is not possible to build a unique query, without using those selectors. In such cases Ariadne falls back to a positional selector, but it uses them as little as possible. Ariadne prefers “nth-of-type(1)” selector over the “nth-child()”, since it survives addition of element with another tag name.

The “optimal” query also has to be short and readable. This means only specific CSS classes (or DOM attributes) should be included in the query, and as few as possible of them. This requirement also improves the stability of the query, since the bigger query is – the higher risk that a markup change will break the query.

And lastly of course, the finding query should be fast.

We have implemented a quite sophisticated algorithm to achieve all these requirements and here are some examples from ExtJS kitchensink (action targets are marked with red cross):

Example 1

image1

Old recorder:

Previous implementation finds some huge, unreadable CSS query:

CSS query:

#content-panel .kitchensink-example .x-panel.x-panel-default-framed.x-resizable.x-panel-resizable.x-panel-default-framed-resizable.x-border-box .x-panel-body.x-panel-body-default-framed.x-column-layout-ct.x-resizable.x-panel-body-resizable.x-panel-body-default-framed-resizable .x-container.x-column .x-form-layout-wrap.x-form-layout-auto-label .x-field.x-form-item.x-form-type-text.x-form-form-item .x-form-item-body.x-form-text-field-body .x-form-trigger-wrap .x-form-text-wrap .x-form-text

Component query:

>>#content-panel form-multicolumn[title=Multi Column Form] textfield[inputType=text]

Composite query:

#content-panel form-multicolumn[title=Multi Column Form] textfield[inputType=text] => .x-form-text

Ariadne:

Now the same recording repeated with Ariadne – resulting in a much shorter, more stable and more readable query:

CSS query:

#content-panel .x-container:nth-of-type(1) .x-field:nth-of-type(1) .x-form-text

Component query:

>>#content-panel textfield[fieldLabel=First Name]

Composite query:

#content-panel textfield[fieldLabel=First Name] => .x-form-text

 

Example 2

image2

Old recorder:

Previous implementation was not able to find any query and only generated coordinate: [ 546, 411 ]

Ariadne:

Ariadne demonstrates stable results:

CSS query:

#content-panel .x-container:nth-of-type(1) .x-field:nth-of-type(2) .x-form-text

Component query:

>>#content-panel textfield[fieldLabel=Last Name]

Composite query:

#content-panel textfield[fieldLabel=Last Name] => .x-form-text

Example 3

image3

Old recorder:

Previous implementation only found a component query: >>#content-panel layout-cardtabs tabbar tab[text=Tab 2]

Ariadne:

Ariadne finds all 3 types of queries, and the component query is shorter:

CSS query:

#content-panel .x-tab-inner:textEquals(Tab 2)

Component query:

>>#content-panel tab[text=Tab 2]

Composite query:

#content-panel tab[text=Tab 2] => .x-tab-inner

In general you should notice a significant improvement in the quality of generated queries.

Playback improvements

While improving the quality of the queries, we’ve also put some effort into improving the quality of the playback, so that recorded actions could be replayed 100% reliably. Notably Siesta now respects the caret position in text fields, supports HOME/END key caret navigation, properly simulates “click” events in certain edge cases. All these minor improvements should allow you to successfully replay what you’ve recorded.

SlimerJS upgrade

As you know Siesta includes SlimerJS as a headless test launcher. However as of version 0.10.0, SlimerJS is no longer headless and it requires Firefox to be installed in the OS along with a graphical server (xvfb or similar). We did not upgrade SlimerJS for a long time because of this reason, but now the Firefox version it implements is too old and outdated (39 and current is 49). So we had to upgrade SlimerJS for our users to get consistent results with the real Firefox browser. If you are using SlimerJS for running your tests in a server environment you will need to install xvfb and Firefox. It should be just a matter of running apt-get install firefox xvfb on Debian-like systems.

The good news is that once you have xvfb installed on your machine, you can significantly increase the test suite execution speed (see next section)

Xvfb – running several browser sessions simultaneously on local machine

As your test suite grows, execution time can become a bottleneck. The solution for that is parallelization – running your test suite in several parallel “threads” or “workers” (as it’s called in Siesta). Since version 4, Siesta supports the --max-workers command line config. However, when several browser windows are opened simultaneously on the same desktop, they start “focus fighting” – because focus can only be in one place by definition. Because of that, previously the primary use case for --max-workers config was in cloud testing providers, since on local machine it didn’t work reliably.

Starting from this release, Siesta supports a new command line switch --xvfb. It requires the presence of “xvfb” in the host system, which is basically a virtual desktop server. With this switch, Siesta will wrap every launched browser process with a virtual desktop, meaning the “focus fighting” problem goes away and one can safely specify reasonably big values for --max-workers config (limited by CPU mostly). Values between 5-10 works great for us:

Execution time for --xvfb --max-workers 1 ~ 13 min:

image2

Execution time for --xvfb --max-workers 7 ~ 3 min:

image1

This option is supported on Linux only for now. Support for MacOS may be added in future releases. Unfortunately we couldn’t find any xvfb-similar tool for Windows (those that exists don’t have command line interface), so Windows support is currently not feasible.

Conclusion

We encourage you to give this release a try and use the new features and improvements to speed up your test suite. Please also share your feedback in our forums.

Enjoy the Siesta!

Come meet us in Las Vegas at our Bryntum Happy Hour

$
0
0

SenchaCon 2016 in Las Vegas is only one week away we can’t wait to show you all the new cool things we’ve been working on this fall. If you’re in the Las Vegas area, you’re very welcome to come meet us on November 6th and have some beer and snacks. Please register at our Facebook event, and we hope to see you on Sunday at 5PM.

Happyhour

Disabling Animations In Your Siesta Tests

$
0
0

While testing your web application frontend, it’s key to have stable tests producing consistent results. This means rerunning a test multiple times under varying CPU load should always give a reliable predictable outcome. Animated CSS transitions and JS based animations are two things that could introduce instability for your UI tests such as race conditions while the mouse cursor is being moved.

Below is a simple example expanding an Ext.Panel (which uses JS animations) and after that a button is clicked which trigger a DIV to change size (using CSS transitions). Notice that the cursor is first waiting for the Ext.Panel to expand before moving the mouse to the next button. After the second click, the cursor tries to target the DIV while it’s in transition. Siesta detects this and retries the action after a short delay. It’s obviously better to not rely on this ‘retargeting’ technique, and instead have a stable app running with as little asynchronicity as possible.

anim

For maximum stability (and speed) in your tests, you can easily disable your CSS transitions by adding a simple extra preload:

preload : [
    'app.js',
    'app-all.css',
    {
        type    : 'css',
        content : '* { transition: none !important; }'
    }
]

Similarly you can turn off Ext JS animations by overriding your used Ext classes like this:

Ext.dd.DragSource.override({
        animRepair : false
    });

    Ext.panel.Panel.override({
        animCollapse : false
    });

See below the same test code as above run without animations enabled.

noanim

Hope you found these tips helpful, and happy testing!

Using Ext Gantt with Ruby on Rails

$
0
0

My name is Johan and I’ve just started working at Bryntum after working with Ext JS for about 8 years at my previous job. During my first week there was a forum post requesting a Ruby on Rails demo which I decided to build. This blog post contains a guide that explains the basics of integrating Ext Gantt with RoR. The guide shows step by step how to set up an application from scratch. At the end of the guide you should have an application looking something like this:
RoR-demo

Assumptions

This guide assumes that you already have Rails installed and know how to use it. If you need help getting started with Rails, there is an excellent guide over at http://guides.rubyonrails.org/getting_started.html.

Create an application

Open a terminal and type this in the folder where you want to create your application:

$ rails new gantt

You should now have a folder named gantt containing a brand new Rails application.

Create a data layer

Add models & data

Ext Gantt has five different kinds of stores: resources, assignments, dependencies, calendars and tasks. In this guide we are creating a basic application and will only be using tasks and dependencies. We need a model representing each of these entities. Use the Rails generator (in the root of the gantt folder) to create the models with fields as shown below:

$ bin/rails generate model Task Name:string StartDate:datetime Duration:integer DurationUnit:string ParentId:integer
$ bin/rails generate model Dependency From:integer To:integer Type:integer

Now let’s create the corresponding tables in the database, by running a migration:

$ bin/rake db:migrate

And finally we seed some data by pasting the following into db/seeds.rb:

Task.create([
    {Name: 'Investigation', StartDate: DateTime.parse('2017-01-01'), Duration: 8, DurationUnit: 'd'},
    {Name: 'Meetings', StartDate: DateTime.parse('2017-01-01'), Duration: 8, DurationUnit: 'd', ParentId: 1},
    {Name: 'Meeting 1', StartDate: DateTime.parse('2017-01-01'), Duration: 2, DurationUnit: 'd', ParentId: 2},
    {Name: 'Meeting 2', StartDate: DateTime.parse('2017-01-04'), Duration: 2, DurationUnit: 'd', ParentId: 2},
    {Name: 'Specs', StartDate: DateTime.parse('2017-01-06'), Duration: 4, DurationUnit: 'd', ParentId: 1},
    {Name: 'Implementation', StartDate: DateTime.parse('2017-01-12'), Duration: 10, DurationUnit: 'd'},
    {Name: 'Database', StartDate: DateTime.parse('2017-01-12'), Duration: 3, DurationUnit: 'd', ParentId: 6},
    {Name: 'Code', StartDate: DateTime.parse('2017-01-17'), Duration: 6, DurationUnit: 'd', ParentId: 6},
    {Name: 'Docs', StartDate: DateTime.parse('2017-01-25'), Duration: 1, DurationUnit: 'd', ParentId: 6},
])

Dependency.create([
    {From: 3, To: 4, Type: 2},
    {From: 4, To: 5, Type: 2},
    {From: 7, To: 8, Type: 2},
    {From: 8, To: 9, Type: 2}
])

And then seed by running:

$ bin/rake db:seed

Add controllers

The controllers will be called using AJAX from our JavaScript code to fetch data for Ext Gantt. The application created in this guide is read only, which means that we only need index  & show actions in the controllers. Generate a controller per model by running:

$ bin/rails generate controller Tasks index show
$ bin/rails generate controller Dependencies index

Implement models and controllers

When we generated the controllers, Rails also generated corresponding views for each controller and action. We want the controllers to supply data in JSON-format directly, we do not need the views. Remove app/views/tasks and app/views/dependencies.

Now let’s implement the models, starting with app/models/task.rb:

class Task < ActiveRecord::Base
  # a task can have many subtasks (children)
  has_many :children, :class_name => 'Task', :foreign_key => 'ParentId'
  # it might also belong to a parent task (if it is a subtask)
  belongs_to :parent, :class_name => 'Task', :foreign_key => 'ParentId'

  # root nodes (ParentId is null) as json tree
  def self.get_root_tree
    roots = Task.where('"tasks"."ParentId" IS NULL')
    {
        :success => true,
        :children => Task.json_tree(tasks)
    }
  end     

  # as json tree  
  def self.json_tree(tasks)    
    tasks.map do |task|      
    {          
      :Id => task.id,
      :Name => task.Name,
      :StartDate => task.StartDate,
      :Duration => task.Duration,
      :DurationUnit => task.DurationUnit,
      :leaf => task.children.count() == 0,
      :expanded => true,
      :children => task.children.count() == 0 ? nil : Task.json_tree(task.children)
     }
   end
 end
end

And then app/models/dependencies.rb:

class Dependency < ActiveRecord::Base
  # as json with desired fields
  def self.json(deps)
    deps.map do |dep|
      {
          :Id => dep.id,
          :From => dep.From,
          :To => dep.To,
          :Type => dep.Type
      }
    end
  end
end

This goes into TasksControllers index action  (app/controllers/tasks_controller.rb):

# fetch root tasks (ParentId is null) as json-tree
tree = Task.get_root_tree
# render (send to client)
render :json => tree

And this goes into show:

# ExtJS TreeStore requests root node as 'root'
if params[:id] == 'root'
  # act as if index was called
  index
else
  # fetch single task
  task = Task.find(params[:id])
  render :json => task.to_json
end

And in the index action of DependenciesController (app/controllers/dependencies_controller.rb):

# fetch all dependencies from database
dependencies = Dependency.all
# render as json (send to client)
render :json => Dependency.json(dependencies)

Almost ready to test our data layer, the only thing missing is routes. Add the following to routes.rb:

resources :tasks
resources :dependencies

Also remove any routes to views (get ‘tasks/index’ and get ‘dependencies/index’).

Test the data layer

If you haven’t done it already, start your web server (or use the Rails internal one, run bin/rails server). If you visit /tasks you should see the following JSON response:

{"success":true,"children":[{"Id":1,"Name":"Investigation","StartDate":"2017-01-01T00:00:00.000Z","Duration":8,"DurationUnit":"d","leaf":false,"expanded":true,"children":[{"Id":2,"Name":"Meetings","StartDate":"2017-01-01T00:00:00.000Z","Duration":8,"DurationUnit":"d","leaf":false,"expanded":true,"children":[{"Id":3,"Name":"Meeting 1","StartDate":"2017-01-01T00:00:00.000Z","Duration":2,"DurationUnit":"d","leaf":true,"expanded":true,"children":null},{"Id":4,"Name":"Meeting 2","StartDate":"2017-01-04T00:00:00.000Z","Duration":2,"DurationUnit":"d","leaf":true,"expanded":true,"children":null}]},{"Id":5,"Name":"Specs","StartDate":"2017-01-06T00:00:00.000Z","Duration":4,"DurationUnit":"d","leaf":true,"expanded":true,"children":null}]},{"Id":6,"Name":"Implementation","StartDate":"2017-01-12T00:00:00.000Z","Duration":10,"DurationUnit":"d","leaf":false,"expanded":true,"children":[{"Id":7,"Name":"Database","StartDate":"2017-01-12T00:00:00.000Z","Duration":3,"DurationUnit":"d","leaf":true,"expanded":true,"children":null},{"Id":8,"Name":"Code","StartDate":"2017-01-17T00:00:00.000Z","Duration":6,"DurationUnit":"d","leaf":true,"expanded":true,"children":null},{"Id":9,"Name":"Docs","StartDate":"2017-01-25T00:00:00.000Z","Duration":1,"DurationUnit":"d","leaf":true,"expanded":true,"children":null}]}]}

And /dependencies should yield:

[{"Id":1,"From":3,"To":4,"Type":2},{"Id":2,"From":4,"To":5,"Type":2},{"Id":3,"From":7,"To":8,"Type":2},{"Id":4,"From":8,"To":9,"Type":2}]

Create and serve Ext Gantt

Time for the JavaScript part of the example. We need to setup ExtJS and Ext Gantt, create a JavaScript application and have it served through rails. Lets begin by creating a start page for the application.

Create a start page

When Rails created our application it made a default ApplicationController which we can use to serve a start page. A layout was also generated at app/views/layout/application.html.erb. Edit it to contain the following:

<!DOCTYPE html>
<html>
<head>
  <title>RoR</title>
  <!--Ext and Gantt styles (using triton theme) -->
  <link href="https://www.bryntum.com/examples/extjs-6.0.1/build/classic/theme-triton/resources/theme-triton-all.css" rel="stylesheet" type="text/css"/>
  <link href="https://www.bryntum.com/examples/gantt-latest/resources/css/sch-gantt-triton-all.css?ver=4.2.1" rel="stylesheet"/>
  <!--Ext and Gantt scripts -->
  <script src="https://www.bryntum.com/examples/extjs-6.0.1/build/ext-all.js" crossorigin="anonymous" type="text/javascript"></script>
  <script src="https://www.bryntum.com/examples/extjs-6.0.1/build/classic/theme-triton/theme-triton.js" type="text/javascript"></script>
  <script src="https://www.bryntum.com/examples/gantt-latest/gnt-all-debug.js?ver=4.2.1" type="text/javascript"></script>
  <!-- Scripts and styles injected by Rails -->
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>

Add a view at app/views/application/index.html.erb. The HTML for our JavaScript application will be generated dynamically (by ExtJS and Ext Gantt), so leave the view empty :)

Now add a route (in routes.rb) to make it the start page:

root 'application#index'

Create the JavaScript application

Add a JavaScript file under app/assets/javascripts called gantt.js. Put the following code in it:

Ext.onReady(function () {
    // Create Gantt panel including stores
    var gantt = Ext.create('Gnt.panel.Gantt', {
        title           : 'Ruby on Rails',
        rowHeight       : 40,
        loadMask         : true,
        viewPreset       : 'weekAndDayLetter',
        readOnly: true,

        columns: [{ xtype: 'namecolumn', width: 200}],

        // Stores use rest-proxy to match RoR
        taskStore: Ext.create('Gnt.data.TaskStore', {
            proxy: { type: 'rest', url : '/tasks' }
        }),

        dependencyStore: Ext.create('Gnt.data.DependencyStore', {
            autoLoad: true,
            proxy   : { type: 'rest', url : '/dependencies' }
        })
    });

    // A viewport to fill the entire page
    var viewport = Ext.create('Ext.container.Viewport', {
        layout: 'fit',
        items : [ gantt ]
    });

    Ext.QuickTips.init();
});

Add some style

Almost done, we only need a tiny bit of css to make Ext Gantt look as intended. Add gantt.css to app/assets/stylesheets and enter the following:

.sch-ganttpanel .x-grid-cell {
    height : 35px;
}

That’s it

All done, visit the root of your webserver to see the example in action. You can also view the finished example here and download the source (in the Details panel to the right).

Happy hacking!


Using Ext Scheduler with Angular 2

$
0
0

One of the most popular javascript frameworks nowadays is Angular. Angular gives you the ability to make HTML content dynamic by using templates, components and services. However Angular does not have a suite of high-level UI components as ExtJS does.

In this blog post we are going to create an Angular component for our Ext Scheduler, which can be used in Angular templates as <scheduler> tags. After following the steps in this blog post, you will have a basic example with interaction between Ext Scheduler and Angular. Feel free to extend the example to suite your needs.

angular2

Prerequisites

This guide uses Angular with Typescript, but you should be able to follow it for the most part even if you use Dart or JavaScript.

1. Please check https://angular.io/docs/ts/latest/quickstart.html for Angular prerequisites (at least node v5.x.x and npm 3.x.x).
2. Make sure you have typescript installed, tsc -v. Otherwise install it with npm install -g typescript.

Create Angular project

We are going to follow a few steps from Angular Quickstart to get started quickly :)  Please follow the steps below before continuing:

Step 1: Create and configure the project

Step 2: Our first Angular component

Step 3: Add main.ts

Step 4: Add index.html

If everything went well you should now have a project structure like this:

app
  app.component.ts
  main.ts
node_modules
  [many folders]
typings
  globals
  index.d.ts
index.html
package.json
systemjs.config.js
tsconfig.json
typings.json

If the typings folder is missing then you can run:
./node_modules/.bin/typings install

To automatically track changes in your project files you can run:
npm start
This command will start a webserver and a browser, compile changes on the fly and update the browser.

Create the Angular component

Angular components (a directive with a template) can be used as tags in templates to add functionality. We want to use Ext Scheduler in Angular and are thus going to create a component for it. It will be called SchedulerComponent and have a corresponding <scheduler> tag. Attributes in the tag will be used as settings for the scheduler and we will also enable event binding.

Start by adding a file named scheduler.component.ts to your app folder. Add the lines listed below:

import {Component} from '@angular/core';

@Component({
    selector: 'scheduler',
    template: '
' }) export class SchedulerComponent { }

This gives us a component with the <scheduler> tag (line 4). In the end this will yield an empty div tag (line 5), to which we will render the scheduler. Now we modify app.component.ts to import SchedulerComponent:

import {Component} from '@angular/core';
import {SchedulerComponent} from './scheduler.component';

@Component({
    selector: 'my-app',
    template: `
        

{{title}}

`, directives: [SchedulerComponent] }) export class AppComponent { title = 'Angular 2 demo'; }

An import-statement is added (line 2) to include the new component. The template is modified to include the component on page (line 8) and finally a directive is added to tell Angular which tags are valid (line 10).

Add the scheduler

Now we are going to include the required files for the scheduler and create an instance of it. Modify index.html and add the following to the <head> section:


In scheduler.component.ts we create the scheduler, but not before the template is applied. We do not yet know where to render the scheduler since our container element <div id="scheduler"> is not yet added to the DOM. We need to hook into a suitable moment in our component life cycle. Change scheduler.component.ts to match the lines below:

import {Component, AfterContentInit} from '@angular/core';

@Component({
    selector: 'scheduler',
    template: '
' }) export class SchedulerComponent implements AfterContentInit { ngAfterContentInit() { // this is where we will create an instance of Ext Scheduler } }

Because of TypeScript being more typesafe than JavaScript we need to declare a couple of global variables to handle the scheduler, or else we will get compilation errors. Add the following lines after the import statement:

// global variables for imported js libraries
declare var Ext:any;
declare var Sch:any;

The next step is to create an instance of the scheduler. Add the following lines to the ngAfterContentInit function:

var eventStore = Ext.create('Sch.data.EventStore', {
    model: 'Sch.model.Event',
    proxy: {
        type: 'memory'
    }
});

var resourceStore = Ext.create('Sch.data.ResourceStore')

var scheduler = Ext.create('Sch.panel.SchedulerGrid', {
    width: 1000,
    height: 600,
    renderTo: 'scheduler',
    columns: [
        {header: 'Name', sortable: true, width: 160, dataIndex: 'Name'}
    ],
    resourceStore: resourceStore,
    eventStore: eventStore
});

By now we have done most of the heavy lifting and have an empty scheduler displayed in your Angular project. But empty is kind of boring so let’s load some data.

Loading data

In a real world application we would load the data from a database, but in this sample we’ll just use dummy data from a JS file. Create a folder named data in the root of your project, add a file data.json with the following content:

{
    "events" : [
        {"Id" : "e10", "ResourceId" : "r1", "Name" : "Deal negotiation", "StartDate" : "2016-09-05", "EndDate" : "2016-09-08"},
        {"Id" : "e22", "ResourceId" : "r2", "Name" : "Attend software conference", "StartDate" : "2016-09-06", "EndDate" : "2016-09-09"},
        {"Id" : "e43", "ResourceId" : "r3", "Name" : "Visit customer", "StartDate" : "2016-09-05", "EndDate" : "2016-09-06"},
        {"Id" : "e210", "ResourceId" : "r4", "Name" : "Exhibition", "StartDate" : "2016-09-07", "EndDate" : "2016-09-11"},
        {"Id" : "e222", "ResourceId" : "r5", "Name" : "Meet customer X", "StartDate" : "2016-09-05", "EndDate" : "2016-09-06"},
        {"Id" : "e243", "ResourceId" : "r6", "Name" : "Prepare case studies", "StartDate" : "2016-09-06", "EndDate" : "2016-09-08"}
    ],

    "resources" : [
        {"Id" : "r1", "Name" : "Mike Anderson" },
        {"Id" : "r2", "Name" : "Kevin Larson" },
        {"Id" : "r3", "Name" : "Brett Hornbach" },
        {"Id" : "r4", "Name" : "Patrick Davis" },
        {"Id" : "r5", "Name" : "Jack Larson" },
        {"Id" : "r6", "Name" : "Dennis King" }
    ]
}

The file contains data for both the event-store and the resource-store. We can use Angulars http library to load it into the stores. Add two imports to schedule.component.ts below the other imports:

import {Http, HTTP_PROVIDERS} from '@angular/http';
import 'rxjs/add/operator/map';

Then add a provider to the @Component decorator:

@Component({
    selector: 'scheduler',
    template: '
', providers: [HTTP_PROVIDERS] })

Now inject the Http class into the SchedulerComponent class by adding a constructor:

export class SchedulerComponent implements AfterContentInit {

    constructor(private http:Http) { }

    ngAfterContentInit() {
        // existing code untouched...
    }
}

Finally, use http to load data.json and populate the stores by adding the following lines at the bottom of the ngAfterContentInit function:

this.http.get('data/data.json')
         .map(r => r.json())
         .subscribe(
             data => {
                 resourceStore.loadData(data.resources);
                 eventStore.proxy.data = data.events;
                 eventStore.load();
             },
             err => console.error(err)
          );

Add input property bindings

It would be nice to be able to configure the scheduler by using attributes in the <scheduler> tag, something that is called Property Binding. We will create an example binding for the title of the scheduler. This is done by adding fields to the SchedulerComponent class and decorating them with the @Input directive.

First, import the Input directive by changing the first import to:

import {Component, AfterContentInit, Input} from '@angular/core';

Secondly, add a field at the beginning of the SchedulerComponent class:

export class SchedulerComponent implements AfterContentInit {
    @Input() title:string;
    // existing code untouched

Third, alter the code that creates the scheduler in the ngAfterContentInit function:

var scheduler = Ext.create('Sch.panel.SchedulerGrid', {
    title: this.title,
    // existing code untouched
});

Finally, add a property binding to the <scheduler> tag in the template in app.component.ts:

@Component({
    selector: 'my-app',
    template: `
        

{{title}}

`, directives: [SchedulerComponent] })

Those are the basic steps for adding property bindings. If you download and look at the demo that belongs to this blog post you can see that we have added a few other useful properties. You can find the download link at the bottom of this post.

Add output property bindings

Angular supports output of values from a component to another components template, by decorating fields in the class with the @Output directive. We can use this to display information from the scheduler. For example we can display the number of loaded events in the main template.
First, import the Output directive by changing the first import to:

import {Component, AfterContentInit, Input, Output} from '@angular/core';

Secondly, add a field at the top of the SchedulerComponent class:

@Output() eventCount:number;

Third, assign a value when the data is loaded. Alter the http call in the ngAfterContentInit function:

resourceStore.loadData(data.resources);
eventStore.proxy.data = data.events;
eventStore.load();
//new line:
this.eventCount = eventStore.getCount();

Finally, modify the main template in app.component.ts to match the following lines:

@Component({
    selector: 'my-app',
    template: `
        

{{title}}

Number of events: {{scheduler.eventCount}}
`, directives: [SchedulerComponent] })

By specifying #scheduler on line 7 we assign a variable, called a template reference variable. This variable can be used in the template to access the output values. It is then used on line 5 to display the eventCount.

Add event bindings

Event bindings are not that different from property output bindings. We are going to add an event for when the user selects an event in the scheduler. For this we need an EventEmitter in the ScheduleComponent and then do event binding in the main template.

First, import EventEmitter by changing the first import to:

import {Component, AfterContentInit, Input, Output, EventEmitter} from '@angular/core';

Secondly, add an EventEmitter to the top of the SchedulerComponent class:

@Output() onEventSelect = new EventEmitter();

Then add a listener to the scheduler to catch the event and emit it. This listener is set in the config for the scheduler:

// last line of existing code in config object
eventStore: this.eventStore,
// add below
listeners: {
    eventselect: function(em, eventRecord) {
        this.onEventSelect.emit(eventRecord);
    },
    scope: this
}

Lines 4-9 contains the listeners configuration. Line 6 emits the event, enabling us to catch it outside the SchedulerComponent.

Now bind to the event in the main template. Alter the scheduler tag (in app.component.ts) to match the following lines:

@Component({
    selector: 'my-app',
    template: `
        

{{title}}

Selected event: {{eventTitle||'None'}}
`, directives: [SchedulerComponent] })

Line 5 will be used to display the title for the selected scheduler event. Line 8 is the event binding. When the scheduler emits an event the binding will call an onEventSelect function with the eventRecord passed as an argument. Add the onEventSelect function to the AppComponent class:

export class AppComponent {
    title      = 'Angular 2 demo';

    onEventSelect(eventRecord:any) {
        // event selected in scheduler, display its name
        this.eventTitle = eventRecord.get('Name');
    }
}

Accessing the scheduler from another class

In the section “Add output property bindings” above we created a template reference variable to access SchedulerComponent output in the template. Unfortunately we cannot use that variable to access the SchedulerComponent from the main class of the application (AppComponent). Instead we have to inject the SchedulerComponent as a ViewChild in AppComponent. Follow the steps below to modify app.component.ts:

First, import ViewChild by altering the first import to:

import {Component, ViewChild} from '@angular/core';

Secondly, add a @ViewChild field somewhere at the top of the AppComponent class:

@ViewChild(SchedulerComponent) schedulerComponent:SchedulerComponent;

Congratulations! If you reached this point you can access the scheduler by using this.schedulerComponent anywhere in AppComponent.

Download the example

View live example: https://www.bryntum.com/playpen/angular2
Download source: https://www.bryntum.com/playpen/angular2.zip

Code away!

Gantt Chart and Kanban Task Board Integration

$
0
0

We are happy to announce a new example which integrates the Ext Gantt with the Task Board component. This blog post will give a brief description of the example and highlight some interesting parts of the code. Feel free to try the example out, you can find it here. To browse the source files, just click Details on the right.

Start

Short description of the example

This example features Gantt tasks that have smaller tasks visualized in the Task Board. Each Gantt task can contain multiple Task Board tasks, shown as small colored thumbs inside the task bar:

Thumbs

Click on a thumb to highlight the corresponding Task Board task:

Click thumb

Or click on a Gantt task to filter the TaskBoard to only display related TaskBoard tasks:

Filter kanban

When the status of a Task Board task changes it is reflected immediately on the miniature. Dragging a Task Board task to Done also affects the Gantt task’s percentage field:

Percent done

Comments on the code

Below are some comments on the central parts of the code that makes the interaction possible. Task Board tasks are connected to Gantt tasks via a foreign key field on the model:

Ext.define('GanttTaskBoard.model.KanbanModel', {
    extend: 'Kanban.model.Task',

    // Add field to connect Kanban tasks to Gantt tasks
    fields: [
        {name: 'TaskId', type: 'int'}
    ]
});

The Task Board store handles getting related tasks:

Ext.define('GanttTaskBoard.store.KanbanStore', {
    extend  : 'Kanban.data.TaskStore',
    model   : 'GanttTaskBoard.model.KanbanModel',

    /**
     * Get all Kanban tasks that belongs to a specific Gantt task.
     * @param taskId Id of a Gantt task, matched to Kanban tasks field TaskId
     * @returns {*} Array of Kanban tasks
     */
    getKanbanTasksByTaskId: function (taskId) {
        return this.query('TaskId', taskId).items;
    }
});

Gantt task are given a shortcut to related Task Board tasks:

Ext.define('GanttTaskBoard.model.TaskModel', {
    extend : 'Gnt.model.Task',

    /**
     * Get related Kanban tasks (where KanbanTask.TaskId == Task.Id)
     * @returns {*} Array of related Kanban tasks
     */
    getKanbanTasks: function() {
        var me = this,
            kanbanStore = me.getTaskStore().kanbanStore;
        return kanbanStore.getKanbanTasksByTaskId(parseInt(this.getId(), 10));
    }
});

And when using custom data inside our Gantt task template, we also need to provide the relevant data to the template via the eventRenderer method. This is how the task miniatures are rendered:

Ext.define('GanttTaskBoard.view.Gantt', {
    extend    : 'Gnt.panel.Gantt',

    eventRenderer : function (taskRecord) {
        return {
            // Provide kanban tasks to the task body template
            kanbantasks : taskRecord.getKanbanTasks() || []
        };
    },

    // taskBody with an outer div (sch-gantt-task-inner) containing a progress bar and a div to hold kanban
    // miniatures (.sch-gantt-kanban-bar)
    taskBodyTemplate : '<div style="width:{progressBarWidth}px;{progressBarStyle}"></div>' +
    '<div>' +
        // template used to render kanban tasks as miniatures into gantt tasks
        '<tpl for="kanbantasks">' +
                '<div data-qtip="{data.Name}" data-kanban-id="{data.Id}"></div>' +
        '</tpl>' +
    '</div>'
});

Additional resources

This sample can be found in the Gantt Pro edition and you can also run it online.

Using the MVVM pattern with Ext Scheduler

$
0
0

My name is Pavel and I recently joined the Bryntum development team. I have used Ext JS as my main JS framework for more than three years and today I’m going to show you how you can use the MVVM pattern in your application.

Let’s start by looking at what MVVM is and why we need it. As you may know, MVVM is the Model-View-ViewModel pattern. A Model holds data and a View is part of the UI. In our case, the ViewModel is a layer which is observing both the Model and the UI and helps to exchange data between them. All changes in the Model will reflect on our View instantly and the same is true in reverse. Immediately after changing data in the View, your Model will have updated values. The ViewModel allow us to control these changes and implement our custom logic, we don’t need to have extra event listeners doing manual work.

The picture below is from our WeekView example. Any changes to the edited Event record will be applied instantly to the Form. Any changes in the Form will be applied to the Event record. We can show this schematically:

ViewModel

The main focus of this post is the ViewModel and how it is used in our WeekView example. We will explore two ViewModel features called explicit binding and settable formulas, detailed information about these binding tools can be found in the Sencha docs. By using a ViewModel and these features we will workaround some flaws of the DateField and TimeField.

Creating an event form with ViewModel binding

We want to have a basic form to edit the details of a scheduled event. Something looking like the one below:

details

We also want the form to be bound to the ViewModel to automatically sync changes to and from the Model. For this we need to:

  • Create a Form Panel;
  • Specify ViewModel for the Form Panel;
    var form = new Ext.form.Panel({
    	viewModel: {},
            ...
    });
  • Set Event record to ViewModel data property (for example MyRecord);
    form.getViewModel().set('MyRecord', record);
  • Bind MyRecord data properties on Form Fields;
    // string notation
    {
    	xtype: 'field',
    	bind: '{MyRecord.Name}'
    }
    // is equal to object notation with value property
    {
    	xtype: 'field',
    	bind: {
    		value: '{MyRecord.Name}'
    	}
    }
    // you can bind to custom property but you have to be sure setter/getter exist.
    {
    	xtype: 'field',
    	bind : {
    		customName: '{MyRecord.Name}'
    	},
    	setCustomName : function(name) {
    		this.coolName = name + ' is cool!';
    	},
    	getCustomName : function(name) {
    		return this.coolName;
    	}
    }

Now we face a slight problem with the date and timefields that we want to fix using a ViewModel…

Datefield doesn’t keep time, timefield doesn’t keep date

Oops… Who could imagine that this is not basic functionality supported by Ext JS? Unfortunately we will have to take care of it manually, let’s dig deep into the Ext JS sources.

Lets start from Ext.form.field.Date component. There are two visual ways how you can change value of datefield: by typing and by using the datepicker. If you type, the field will keep the time info only if the format of date field contains time information, and the date picker doesn’t care even if the format contains time. If you look under the hood, you will find out that safeParse method of datefield checks if not utilDate.formatContainsHourInfo(format) then utilDate.clearTime(parsedDate); and setValue method of the DatePicker class clears time instantly this.value = Ext.Date.clearTime(value || new Date(), true);

Now let’s move to the Ext.form.field.Time component. It clears the date as soon as the setValue method of the TimeField is called. Only the time information is used for the value v = me.getInitDate(v.getHours(), v.getMinutes(), v.getSeconds()); and this getInitDate uses the initDateParts property that is equal to initDateParts: [2008, 0, 1]. TimeField inherits from ComboBox, so initComponent calls me.store = Ext.picker.Time.createStore(me.format, me.increment); to prepare the picker store. This createStore method of timepicker uses the initDate property that is equal to initDate: [2008,0,1]

It seems we located a few date and time field flaws which we want to work around in the demo using a ViewModel. Let’s continue by talking about how we can control getting and setting data using the ViewModel. The main tools that will help us is:

Explicit Binding and Settable Formulas

Formula is a very powerful tool that enables you to control get and set operations on a ViewModel. Formulas can be described with different approaches, let’s take a look at a few of them:

// Simple function notation. Implicit binding.
// ExtJS analyzes which data was gotten from viewModel
// and creates dependencies.
// Each time data is changed this formula is called.
formulas: {
	foo: function (get) {
		return get('bar_record.some_data_field');
	}
}
// Object notation with get function. The same as previous one.
formulas: {
	foo: {
		get: function (get) {
			return get('bar_record.some_data_field');
		}
	}
}
// Object notation with get and set functions.
// The same, but now also set method is called, before data is changed.
formulas: {
	foo: {
		get: function (get) {
			return get('bar_record.some_data_field');
		},
		set: function (value) {
			this.set('bar_record.some_data_field', value);
		}
	}
}
// Object notation with explicit binding, get and set functions.
// We bind on exact data property, so there is no analyzing.
// Also get function takes a value as a parameter instead of function.
formulas: {
	foo: {
		bind: '{bar_record.some_data_field}',
		get: function (value) {
			return value;
		},
		set: function (value) {
			this.set('bar_record.some_data_field', value);
		}
	}
}

Workaround using Formulas and Binding

Because of the date- and timefield limitations described above we need to use a formula each time a record field is changed. We will apply explicit binding for our Form. Here we will describe formulas only for Start Date, but the same is true for End Date. Do not forget to use clone method when you need to modify original date object.

formulas : {
	StartDate : {
		bind : '{MyRecord.StartDate}',
		get  : function (date) { return date; },
		set  : function (date) {
			var MyRecord = this.get('MyRecord'),
				original = MyRecord.get('StartDate');

			// Apply time from original record
			Sch.util.Date.copyTimeValues(date, original);
			MyRecord.set('StartDate', date);
		}
	},
	StartTime : {
		bind : '{MyRecord.StartDate}',
		get  : function (date) { return date; },
		set  : function (date) {
			var MyRecord = this.get('MyRecord'),
				// Make a clone of the date to avoid changes by reference
				original = Ext.Date.clone(MyRecord.get('StartDate'));

			// Apply time to original record
			Sch.util.Date.copyTimeValues(original, date);
			MyRecord.set('StartDate', original);
		}
	}
}

Now we need to bind form fields on formulas.

{ xtype : 'datefield', bind : '{StartDate}' },
{ xtype : 'timefield', bind : '{StartTime}' }

That’s it. You can use formulas and customize your app logic to be as flexible as you need. Also I want to warn you about Binding Timings, so if you need to execute some logic instantly you need manually call viewModel.notify();

I hope this article was helpful and that you are not tired of reading it :) . As a bonus you can check out our Weekview DEMO to see how MVVM pattern works in an Ext JS application.

Using Ext Gantt with React

$
0
0

React is a very popular library for front-end development. It makes it easy to build state driven views and it efficiently handles DOM updates. React is component-based and in this blog post we are going to explore how to wrap Ext Gantt as a reusable React component.

reactdone

Prerequisites

In this guide we will use React with JSX. We are going to load React and Babel from unpkg.com (a CDN) and Ext Gantt from bryntum.com (our trial version), so no installation is required.

Add index.html

We are going to create a React app and as stated in the Prerequisites above we need to load some external libraries. Start by creating an index.html file with the following contents:



    

    
    
    

    
    
    
    

    
    
    
    

    React demo


    

We now have an empty page that includes both React + Babel and ExtJS + Ext Gantt. It is time to wrap the Gantt chart as a React component, which will make it easy to use in React apps.

Create a React component

To keep the code clean we are going to create the component in a separate file. Create a file named gantt.jsx and include it in index.html below the div tag:

// existing line
// load the react component from gantt.jsx

Add the following to gantt.jsx:

// a wrapper for the gantt chart to simplify usage with react
window.Gantt = React.createClass({
    // called after reacts render, which yields a div
    // create a gantt chart and render it to that div
    componentDidMount : function() {
        var config = {

            // store that holds all tasks
            taskStore : {
                type     : 'gantt_taskstore',
                autoLoad : true,
                proxy    : {
                    type : 'ajax',
                    url  : 'data/tasks.json'
                }
            },

            // store that holds dependencies between tasks
            dependencyStore : {
                type     : 'gantt_dependencystore',
                autoLoad : true,
                proxy    : {
                    type : 'ajax',
                    url  : 'data/dependencies.json'
                }
            },

            // settings
            width            : 1000,
            height           : 400,
            eventBorderWidth : 0,
            split            : false,
            viewPreset       : 'weekAndDayLetter',
            rowHeight        : 30,

            // predefined columns
            columns: [
                { xtype: 'namecolumn', width: 150 }
            ],

            // renders to the div created in render() below
            renderTo : 'gantt'
        };

        // store the rendered gantt chart in state for later access
        this.setState({ widget : Ext.create('Gnt.panel.Gantt', config) });
    },

    // just renders a div that we use to do the actual gantt chart rendering
    render : function() {
        return (
            
); } });

The code above defines a React component called Gantt. It renders a div to the page (in the render() method which React calls to display the component). The div is then used as a container for our gantt chart, which is created in componentDidMount() (called by React after render).

The gantt chart includes some basic configurations for width, height etc. as well as also two stores which loads data from json files (in the zip if you download the example). In case we need to access the gantt chart later, it is stored in the React components state using setState().

Create a React app

Lets make a simple React app to try our new Gantt component out. Start by adding a script tag using Babel to index.html, below the script tag for gantt.jsx:

// existing lines
// new script tag for our React app

Now add the following lines to the script tag:

var GanttDemoApp = React.createClass({
    render : function() {
        return (
            
        );
    }
});

ReactDOM.render(
    ,
    document.getElementById('gantt-demo')
);

GanttDemoApp is a React component that at this point only contains a Gantt component (our gantt chart). It is rendered to the page using ReactDom.render(). If you open the index.html file in a browser on a web server, you should see the following:

reactpart

Configuration using Props

All settings in the Gantt component above are hard coded. In React you use Props to allow components to be configured from the outside. Let’s add a few simple Props to the Gantt component, you can of course add more later. First add the following to the top of the Gantt component, just below React.createClass():

propTypes : {
    id              : React.PropTypes.string.isRequired,
    taskUrl         : React.PropTypes.string.isRequired,
    dependenciesUrl : React.PropTypes.string.isRequired,
    preset          : React.PropTypes.string
},

The Props with isRequired at the end have to be specified by the user when using the component, others are optional. Now we use the Props as settings in the gantt chart by changing the following lines:

// in componentDidMount():
url : 'data/tasks.json'
url : 'data/dependencies.json'
viewPreset : 'weekAndDayLetter'
renderTo : 'gantt'
// in render():

To:

// in componentDidMount():
url : this.props.taskUrl
url : this.props.dependenciesUrl
viewPreset : this.props.preset
renderTo : this.props.id
// in render():

Props can now be set on our Gantt tag to easily configure the component. Change the tag to:

Interacting with the Gantt component

We are going to try two kinds of interactions:

  • Relaying events from the gantt chart to React
  • Exposing functions on our React component
Relaying events

Ext Gantt is based on ExtJS that has its own event system. We want to be able to take action on those events in our React app and therefore need to relay them somehow. A simple solution is to use callbacks as Props and then call them from an internal event listener. Lets try it out, add the following listener to the bottom of the config object in componentDidMount() in Gantt:

listeners : {
    taskclick : (gantt, taskRecord) => {
        this.props.onTaskSelect && this.props.onTaskSelect(taskRecord);
    }
}

What we added is an event listener for the gantt chart event taskclick. When the event is fired we check if we have a callback (onTaskSelect()) in Props and the call it with information about the task that was clicked. Now add the prop to the React apps Gantt tag (in bold below) and a function to handle the event to the app (also in bold):

var GanttDemoApp = React.createClass({
    handleTaskSelect: function(taskRecord) {
          // clicking a task in the gantt chart shows an alert with its name
          alert(taskRecord.get('Name'));
    },

    render : function() {
        return (
            onTaskSelect={this.handleTaskSelect} />
        );
    }
});

Exposing functions

When creating the Gantt component we stored the gantt chart in the React state, which we can access from the outside to manually reach Ext Gantt member functions. But a better approach is to add functions to the Gantt component which does that for us, making it easier to use. As an example let’s add a function to add new tasks, put the following code before render() in the Gantt component:

// can be called from parent component to add a new task
addTask : function() {
    this.state.widget.getTaskStore().getRoot().appendChild({
        Name: 'New task',
        leaf: true
    });
},

To be able to access it from our React app we are going to add a ref to the Gantt component. Refs give us easy access to child components. At the same time let’s add a button to trigger the function. Modify the tag to match this (new in bold):


ref="gantt1"
       taskUrl="data/tasks.json"
       dependenciesUrl="data/dependencies.json"
       preset="weekAndDayLetter"

        onTaskSelect={this.handleTaskSelect} />

Now add a handleAddClick() method to the React app, above the render method :

handleAddClick: function() {
    this.refs.gantt1.addTask();
},

That’s it, clicking the button calls handleAddClick() which uses refs to get the instance of the Gantt component and then calls addTask() on it.

Taking it further

Please download the example and check the source to see some more Props and also how to create a Column component used as a child to our Gantt component for configuring which columns to show. Feel free to extend it with the Props, events and functions you need for your app.

View live example: https://www.bryntum.com/playpen/react
Download source: https://www.bryntum.com/playpen/react/react.zip
Learn about React: http://facebook.github.io/react/

Happy coding!

Siesta 4.2.0 – Improved Recorder + Faster Testing

$
0
0

We’ve just released Siesta 4.2.0 – an important Siesta milestone and in this blog post we’ll highlight its most important features.

Introducing Ariadne – The New Query Finder

The internals of the Siesta event recorder have been completely rewritten (more specifically, the target finding algorithm). In previous versions, the recorder sometimes found either a very long CSS query or it could not figure out a stable CSS query at all. We decided that this feature was important enough to warrant a new implementation.

In this new release, we have moved the query finding functionality to a new project called Ariadne. Ariadne finds the “optimal” CSS query for any given DOM element and it handles Ext JS component queries just as well. Let’s check the key requirements we had for this task and design choices made during the development.

First of all, how does one define the “optimal” query, seen from the event recorder’s perspective?

The “optimal” query has to be stable – it should survive reasonable markup changes. This means the recorder should try to avoid all types of positional selectors such as: “>” (direct child), “nth-child()”, “nth-of-type()”. Those are very fragile and will break as soon as new element is added to the DOM, or when the position of an element changes. In the same time there are situations when it is not possible to build a unique query, without using those selectors. In such cases Ariadne falls back to a positional selector, but it uses them as little as possible. Ariadne prefers “nth-of-type(1)” selector over the “nth-child()”, since it survives addition of element with another tag name.

The “optimal” query also has to be short and readable. This means only specific CSS classes (or DOM attributes) should be included in the query, and as few as possible of them. This requirement also improves the stability of the query, since the bigger query is – the higher risk that a markup change will break the query.

And lastly of course, the finding query should be fast.

We have implemented a quite sophisticated algorithm to achieve all these requirements and here are some examples from ExtJS kitchensink (action targets are marked with red cross):

Example 1

image1

Old recorder:

Previous implementation finds some huge, unreadable CSS query:

CSS query:

#content-panel .kitchensink-example .x-panel.x-panel-default-framed.x-resizable.x-panel-resizable.x-panel-default-framed-resizable.x-border-box .x-panel-body.x-panel-body-default-framed.x-column-layout-ct.x-resizable.x-panel-body-resizable.x-panel-body-default-framed-resizable .x-container.x-column .x-form-layout-wrap.x-form-layout-auto-label .x-field.x-form-item.x-form-type-text.x-form-form-item .x-form-item-body.x-form-text-field-body .x-form-trigger-wrap .x-form-text-wrap .x-form-text

Component query:

>>#content-panel form-multicolumn[title=Multi Column Form] textfield[inputType=text]

Composite query:

#content-panel form-multicolumn[title=Multi Column Form] textfield[inputType=text] => .x-form-text

Ariadne:

Now the same recording repeated with Ariadne – resulting in a much shorter, more stable and more readable query:

CSS query:

#content-panel .x-container:nth-of-type(1) .x-field:nth-of-type(1) .x-form-text

Component query:

>>#content-panel textfield[fieldLabel=First Name]

Composite query:

#content-panel textfield[fieldLabel=First Name] => .x-form-text

 

Example 2

image2

Old recorder:

Previous implementation was not able to find any query and only generated coordinate: [ 546, 411 ]

Ariadne:

Ariadne demonstrates stable results:

CSS query:

#content-panel .x-container:nth-of-type(1) .x-field:nth-of-type(2) .x-form-text

Component query:

>>#content-panel textfield[fieldLabel=Last Name]

Composite query:

#content-panel textfield[fieldLabel=Last Name] => .x-form-text

Example 3

image3

Old recorder:

Previous implementation only found a component query: >>#content-panel layout-cardtabs tabbar tab[text=Tab 2]

Ariadne:

Ariadne finds all 3 types of queries, and the component query is shorter:

CSS query:

#content-panel .x-tab-inner:textEquals(Tab 2)

Component query:

>>#content-panel tab[text=Tab 2]

Composite query:

#content-panel tab[text=Tab 2] => .x-tab-inner

In general you should notice a significant improvement in the quality of generated queries.

Playback improvements

While improving the quality of the queries, we’ve also put some effort into improving the quality of the playback, so that recorded actions could be replayed 100% reliably. Notably Siesta now respects the caret position in text fields, supports HOME/END key caret navigation, properly simulates “click” events in certain edge cases. All these minor improvements should allow you to successfully replay what you’ve recorded.

SlimerJS upgrade

As you know Siesta includes SlimerJS as a headless test launcher. However as of version 0.10.0, SlimerJS is no longer headless and it requires Firefox to be installed in the OS along with a graphical server (xvfb or similar). We did not upgrade SlimerJS for a long time because of this reason, but now the Firefox version it implements is too old and outdated (39 and current is 49). So we had to upgrade SlimerJS for our users to get consistent results with the real Firefox browser. If you are using SlimerJS for running your tests in a server environment you will need to install xvfb and Firefox. It should be just a matter of running apt-get install firefox xvfb on Debian-like systems.

The good news is that once you have xvfb installed on your machine, you can significantly increase the test suite execution speed (see next section)

Xvfb – running several browser sessions simultaneously on local machine

As your test suite grows, execution time can become a bottleneck. The solution for that is parallelization – running your test suite in several parallel “threads” or “workers” (as it’s called in Siesta). Since version 4, Siesta supports the --max-workers command line config. However, when several browser windows are opened simultaneously on the same desktop, they start “focus fighting” – because focus can only be in one place by definition. Because of that, previously the primary use case for --max-workers config was in cloud testing providers, since on local machine it didn’t work reliably.

Starting from this release, Siesta supports a new command line switch --xvfb. It requires the presence of “xvfb” in the host system, which is basically a virtual desktop server. With this switch, Siesta will wrap every launched browser process with a virtual desktop, meaning the “focus fighting” problem goes away and one can safely specify reasonably big values for --max-workers config (limited by CPU mostly). Values between 5-10 works great for us:

Execution time for --xvfb --max-workers 1 ~ 13 min:

image2

Execution time for --xvfb --max-workers 7 ~ 3 min:

image1

This option is supported on Linux only for now. Support for MacOS may be added in future releases. Unfortunately we couldn’t find any xvfb-similar tool for Windows (those that exists don’t have command line interface), so Windows support is currently not feasible.

Conclusion

We encourage you to give this release a try and use the new features and improvements to speed up your test suite. Please also share your feedback in our forums.

Enjoy the Siesta!

Come meet us in Las Vegas at our Bryntum Happy Hour

$
0
0

SenchaCon 2016 in Las Vegas is only one week away we can’t wait to show you all the new cool things we’ve been working on this fall. If you’re in the Las Vegas area, you’re very welcome to come meet us on November 6th and have some beer and snacks. Please register at our Facebook event, and we hope to see you on Sunday at 5PM.

Happyhour

Debugging memory leaks in web applications using iframes

$
0
0

Many of our customers use popular third party javascript frameworks inside an iframe as a part of a larger application. Typically, each “sub-page” in this type of application is implemented in its own iframe that is part of the top HTML document. In most cases this works well, but in some cases it causes major memory leaks as iframes are added and removed repeatedly. We were recently asked to assist a customer with a memory leak problem in their application and below we are sharing what we learned along the way.

The problem

In the application we debugged, a common scenario was for users to open and close multiple iframe pages in the application over an extended period of time. Each time a user opened a view, a new iframe was created and when the view was closed the iframe was removed from the page. The problem was that sometimes after removing the iframe the memory used by it was still retained by the browser. After using the application for a couple of hours the browser would run out of memory and crash. It was apparent that in some way the browser was unable to release iframe memory. Our plan was to help our customer to find the leak by looking for known causes for iframe memory leaks using heap dumps and by doing code review.

Common causes of iframe memory leaks

Normally, removing an iframe allows the memory used by it to be garbage collected and released without any need of internal cleanup. But if an object outside of the iframe has a reference to an object inside the iframe, the iframe cannot be garbage collected. Common cases preventing garbage collection include:

The outer window has a reference to an object in the inner window, which might be set from the inside:

window.top.innerObject = someInsideObject

Or from the outside:

innerObject = iframeEl.contentWindow.someInsideObject

You could also add an event listener from the inner scope to the outer scope:

window.top.document.addEventLister(‘click’, function() { … });

This type of code is surprisingly sometimes even seen in framework code, as in JqWidgets.
 

Chrome Developer Tools to the rescue

Chrome has a few really nice tools built into it to aid in debugging memory leaks. First of all, by using the Chrome Task Manager you can quickly determine if there is a suspected memory leak. If the memory usage value keeps going up in your app, and never returns back to the initial memory footprint– you have an issue. Be sure to enable the JavaScript Memory column:

taskmanager

Console memory API

The values displayed under JavaScript Memory in the task manager can also be obtained through the console memory API. By default, this API does not provide any useful metrics. But if you start Chrome with the command line switches found below, it gives you the same numbers as the Task Manager:

--enable-precise-memory-info --js-flags="--expose-gc"

The second switch on the line enables the gc API, for forcing garbage collection programmatically. Below is a small snippet which shows the current memory usage in the top right corner of the browser window (very useful when debugging):

var div = document.createElement('div');
div.style.cssText = 'position:absolute;top:0;right:0;background:rgba(255,255,255,0.5);padding:10px;pointer-events:none;z-index:10000';
document.body.appendChild(div);
setInterval(function () {
    div.innerHTML = (Math.round(console.memory.usedJSHeapSize / 1024 / 1024 * 10) / 10) + ' / ' + (Math.round(console.memory.totalJSHeapSize / 1024 / 1024 * 10) / 10);
    gc();
}, 250);

 

Memory profiling

With the help of the Memory tab in Chrome Developer Tools (previously named Profiles in Chrome < 58) you can confirm whether it really is a leak or not. By recording an Allocation timeline you get a visual representation of when memory is allocated. It shows allocations as blue bars, and when the allocated memory is garbage collected the bar turns gray. Below is a recording with easily spotted memory leaks. An action is repeated three times, each time a new blue bar is displayed and it never turns gray:

snapshot

By clicking a bar Chrome displays the allocations made at that time. Be sure to force garbage collection by clicking on the trashcan icon, or else the blue bars might be memory that will be released.
It is also useful to capture and compare heap snapshots, which shows you everything on the heap at that point in time. For detailed information on how to debug memory issues in Chrome, there is a good article here https://developers.google.com/web/tools/chrome-devtools/memory-problems.

Our approach to finding the leaks

Every time we reopened an iframe, the memory usage grew and forcing garbage collection did not make it drop. Initial state memory footprint:

taskmanager2

After a few additional open/close actions:

taskmanager3

By capturing heap dumps we could also see that it was the iframe being retained. We looked for objects we knew belonged to the iframe, and when we found them in a heap dump captured after the iframe was removed we knew for certain that it was retained. Also, looking at the number of window instances in the dump indicates that the iframe is still there.
In the image below objectInsideIframe exists inside the iframe. Since it shows up in the heap dump taken it indicates that the iframe is retained:

leak

Our next step was to review the code to find all usages of window.top, window.parent and try to identify which of those was causing trouble. As it turned out, it was not an easy task.
The code base was very large and consisted of multiple third party frameworks. We managed to narrow the leak down to one specific function, but it was not obvious how it could cause the leak.

Looking deeper

We now knew how to reproduce the leak, but all attempts to use heap dumps and code review to discover why it was leaking had failed.
We needed another approach and decided to do a deep scan of the topmost window for any references to objects not created in its context. We suspected that references to such foreign-window objects were the root cause of the leak. To test our hypothesis, we wrote a small script that deeply verifies that each window property stems from the top window.Object constructor:

function scan(o) {

    Object.keys(o).forEach(function (key) {
        var val = o[key];

        // Stop if object was created in another window
        if (typeof val !== ‘string’ && typeof val !== ‘number’ && typeof val !== ‘boolean’ && !(val instanceof Object)) {
            debugger;
            console.log(key);
         }

         // Traverse the nested object hierarchy
    });
}

Running it on the page with the leak we saw in the heap dump above exposed the leak clearly:

console

The innerobject variable was not created in the top window context. Searching for innerobject in the code revealed a reference on the top level window to objectInsideIframe, which as we saw further up is an object inside the iframe.
Using this approach, we were able to identify why the iframe was retained. It turned out to be JQuery from the top context keeping references to objects inside the iframe. The offending code line looked like this:

window.top.jQuery.data(window.top.document.body, { foo : “bar” });

 

Fixing the leaks

In our case, the identified leak was quickly fixed by not using JQuery from the top context. If the “cross context” reference between objects is done on purpose and needed for the application to work as intended, you have a couple of choices:

  • Store a copy of the foreign object in the other window instead of a reference to it
  • Or be sure to set all “cross context” references to null when closing the iframe.
  • Also unregister any “cross context” DOM event listeners

But the best approach and our recommendation to our customer, is to decouple the iframe from the outer context completely. The iframe should ideally not directly reference anything in the parent and vice versa. This can be achieved using window.postMessage to send messages between the different windows.

Do you have any additional tips or tricks to share when it comes to debugging memory leaks in web apps? Please share your experience in the comments or our forums. Best of luck with avoiding iframe memory leaks in the future!


Introducing RootCause – Next Generation Error Handling For Web Apps

$
0
0

A few weeks ago we did a soft launch of our new javascript error monitoring and debugging tool called RootCause. As web developers, we face bug reports with very varying quality and content. Sometimes we receive bug reports saying “Feature X doesn’t work” and other times we get a nice error message and call stack. The time it takes for a developer to fix a bug is proportional to the quality of the bug report. If a clear test case is provided, it will be much faster than if the developer has to play detective and manually try to reproduce the bug.

 

First generation error monitoring tools

There has been multiple Error-Logging-As-A-Service tools around for a very long time. They are primitive and typically provide you with error message, call stack and some meta data about the error, user and application. This gives a product owners increased awareness of the health of a web application. But for developers, the information logged is rarely enough to be able to reproduce and fix an issue. Let’s consider a call stack of a real issue I just fixed in our Gantt chart code base:

With just the message, line, call stack it’s impossible to locate the source of this error.

Second generation tools

During 2016 we saw a few new tools being released offering additional debugging context, not just text information but also videos showing the error. Video recordings like this monitor the DOM for mutations which enable the tool to replay the error as it happened. This is pretty cool, and it definitely helps a developer to see the user’s actions to get ‘in context’. This approach will likely work fine in a basic web site but can severely affect performance in a large web application.

The RootCause approach

RootCause instead focuses on recording user, browser and console activity. With this information errors can be replayed in the Replay Studio, meaning you will have the exception live – ready to debug in your browser. This automates the tedious process of locating the error and trying to reproduce it. As developers, the ultimate scenario for fixing an unhandled exception is to see it as a live breakpoint in your own browser. The Maximum callstack exceeded bug mentioned above was reproduced in about one minute, and I was able to fix it in the next 10 minutes. This is really a paradigm shift, when you compare to manually searching for the error using only the call stack and message.

Resources

To learn more about RootCause and how it can help you debug errors more efficiently, please check out these links. Happy debugging!

Gantt 5.0 + Scheduler 5.0 + Calendar 0.9.0 Released

$
0
0

After a long development push during the past two months, we’re finally able to announce Scheduler 5.0.0 and Gantt 5.0.0 as well as the initial 0.9.0 release of our new Calendar. We also took some time to clean up and remove deprecated API members. We recommend you to go through the “Breaking Changes” section to see if there are any changes that could affect your code base. If you’re using an API that is marked as deprecated, please take the time to update/replace it according to the changelog. Let’s dig into the details to see what’s new!

Ext Scheduler 5.0

This release is the largest we ever made for our Scheduler product. To allow for easier browsing of our many examples, we made a new kitchen sink.

Screen Shot 2017-08-15 at 12.25.52

We also fixed numerous bugs and added some new important features:

  • New EditorWindow plugin which shows the event editor form in a window popup
  • Support for creating dependencies in the Scheduler, as supported previously only in the Gantt chart
  • New Resource name column, included by default showing the name of your resources
  • Styling is now SASS based as in Ext JS, making it much easier to theme the scheduler.
  • New split grid demo showing how you can split the scheduler into two vertical sections
  • New ‘render to div’ demo showing how to integrate the Scheduler into a basic HTML web page
  • New ‘scheduling suite’ demo showing how Scheduler, Task board and Calendar can share and visualize the same store data
  • Updated styling to be consistent for all samples

Below you can see the new experimental demo of the split section feature:

Screen Shot 2017-08-15 at 13.08.35

For a full list of changes, news and updates of the release, please refer to the changelog.

Ext Gantt 5.0

For Ext Gantt release is the largest we ever made for our Scheduler product. On a high level here’s what’s new:

  • As with Ext Scheduler, all our component styling is now SASS based.
  • New flag autoCalculateLag on the DependencyStore which calculates and set the lag of new task dependencies
  • New ´readOnly´ config available on Gnt.panel.Timeline. Set it to ´false´ to enable drag drop of tasks in the timeline (true by default)
  • New demo showing the Scheduler using the Gantt task store to schedule tasks and take calendars / dependencies into account.
  • Normalized styling of all samples

Below you can see the new combination demo of the Gantt task store used in Ext Scheduler:

Screen Shot 2017-08-15 at 13.23.42

For a full list of changes, news and updates of the release, please refer to the changelog.

Bryntum Calendar 0.9.0

Screen Shot 2017-08-15 at 13.50.05

We’re happy to announce the initial release of our latest UI component – the Bryntum Calendar. This was very often requested in the past and with this component, our scheduling suite gained a new vital piece. It contains the essential calendar day view, week view and month views (agenda + year view are both in progress). The data integration is very easy if you have worked with our other products or Ext JS data stores in the past. The Calendar includes various examples to get you started, and as with all our products it ships with its own Siesta test suite, so you can assert the quality of each release yourself.

With the arrival of the Calendar component, you can now schedule and manage tasks in any way you want, horizontal/vertical resource view, day/week/month view, Kanban view and Gantt view. The Calendar component integrates nicely with our other components, showcased in our new ´scheduling suite´ demo.

Screen Shot 2017-08-15 at 14.43.19

Summing up

If you want to try out or new releases, just head over to the sample pages of the respective product (listed below). We hope you’ll like our new releases and please let us know us of any feature or bug you would like to see prioritized. We’re trying hard to be as customer driven as possible. Happy scheduling!

Additional Resources

Welcoming Andrey Kozlov To The RootCause Team

$
0
0

Hello, everyone! I’m Andrey Kozlov, a full stack developer and I recently joined Bryntum to work on the RootCause product. In my past I’ve worked a great deal with javascript, and I’ve been using Ext JS since 2007.

Screen Shot 2017-09-20 at 13.37.57

In my past role as principal software developer, I’ve always seen a lot of value in logging javascript errors. Without a good monitoring system it’s just too hard to keep up with errors and helping users who report bugs is too time consuming. Having a good dashboard with overview, trends and the ability to mark a bug as resolved certainly helps developers to fight bugs in production. What is unique about RootCause is that we can fast forward the debugging process and reproduce a bug in a few minutes and know what the user did to trigger the error. This is very inspiring for any developer who is involved in the customer support process. No more manual tedious sending of emails or requesting screenshots.

I’m very inspired to get started and I’ll use my experience to improve the RootCause logger and dashboard and I already have many ideas of how we can make it even more powerful. If you have any questions for me, send me and email or find me in our forums!

Happy debugging! :)

/Andrey

Gantt + Scheduler 5.1.5 released

$
0
0

We are happy to announce the 5.1.5 release of our Ext Scheduler & Ext Gantt components. In this post we’ll outline the most notable changes.

Ext Scheduler 5.1.5 – Enchancements

The scheduling suite demo is our most advanced and fully featured demo, showing off the integration between our various UI components. This demo has now been restructured and simplified and it now also shows an Agenda view and a Year view, making it 8 (!) views sharing the exact same data set. Make a change in one view, all the rest are instantly updated automatically.


Screen Shot 2017-10-11 at 13.48.29

The tooltip UI shown when creating new dependencies between tasks has now been migrated into a proper Tooltip which makes it look consistent with the rest of the Scheduler / Gantt tooltips.

Screen Shot 2017-10-11 at 14.28.48

  • Scheduling suite demo now demonstrates new Agenda and Year views provided by the Bryntum Calendar component. Integrating the Calendar and Taskboard has become much easier.
  • The EventEditor widget has got a few new configuration options: “All day” checkbox, new nameFieldConfig and weekStartDay.
  • The EventEditor widget now behaves in a different way when the start date value is greater than the end date: 1) if it happens after the start date was edited, then the end date gets set to start date plus 1 day; 2) if it happens after the end date editing then the start date gets set to end date minus 1 day.
  • Sch.mixin.SchedulerView has got new aftereventresize and schedulelongpress events. Its aftereventdrop event now provides an array of event records as the 2nd argument.

Ext Scheduler 5.1.5 – Bugs fixed

  • Fixed #2938: Task tooltip and dependency tooltip should look the same
  • Fixed #3788: Dependency tooltip blocks access to target dependency connector
  • Fixed #3916: View scrolls after clicking schedule when editor is opened
  • Fixed #3804: Export doesn’t work when print plugin is also used
  • Fixed #4874: View scrolls to top when dragging event to the edge
  • Fixed #4875: Exporter excludes stylesheets and style tags in BODY

Ext Gantt 5.1.5 – Enhancements

  • The resource histogram now calculates a weighted average of allocation % in case this value changes in the middle of a day.

Ext Gantt 5.1.5 – Bugs fixed

  • Fixed #1620: % complete ignored in Milestone
  • Fixed #3819: Planning container is 100% done when it’s children are not all 100% done
  • Fixed #3936: Assignments not available if listening for ‘afteredit’
  • Fixed #4845: Histogram gap in bars when new task starts
  • Fixed #4847: Add new column not working as expected
  • Fixed #4857: Can’t sort resource assignment grid name column
  • Fixed #4866: Dependency lines missing
  • Fixed #4902: Changes in label editor are not saved on click on normal view

Detecting Broken Promises With Siesta And RootCause

$
0
0

In modern web development Promises are very useful when dealing with asynchronous code flows to avoid “callback hell”. A promise can be either resolved, pending (neither resolved/rejected) or rejected. When you are rejecting a promise, you need to have a catch method attached to it. See below:

async1(function(){ ... })
    .then(async2)
    .catch(function(e) {
        // Deal with any errors here
    })

Attaching this catch handler is very easy to forget however, and if forgotten an unhandledrejection event is fired on the window object.

Screen Shot 2017-10-11 at 16.50.10

Currently only Chrome fires this event, but that’s enough for us to be able to 1. detect such events fired in our test suite (with Siesta) and 2. detect when this event is fired in production (using RootCause). Sadly there is no stack trace available at this time but hopefully that will be added in the future. Let’s see how we can detect this event to improve our code!

1. Detecting unhandledrejection event in your Siesta tests

With Siesta you can now detect and fail tests automatically if unhandledrejection is fired, simply download our latest Siesta nightly build or wait for the next official release. That is all, this detection is enabled by default. After we updated Siesta company wide, we found an issue immediately (with no extra effort).

Screen Shot 2017-10-12 at 16.14.04

2. Detecting unhandled Promise rejections in your online production environment

To detect this in your production code, you can use our JS debugging service called RootCause. By default RootCause will log an error when unhandledrejection is fired, and you will be able to use either Live Replay or the Video Replay to figure out where you are missing a catch.

Screen Shot 2017-10-11 at 15.53.51

We hope this feature will help you find more bugs earlier in your code delivery process. The first hour of adding this feature to RootCause, we found a broken Promise in our code base (screenshot above) :) Happy debugging!

Viewing all 370 articles
Browse latest View live