In Java FX 2.x TableView is one of the controls you can't set a tooltip directly. You must set tooltips for TableCell or TableRow instead. The technique used in this turorial is very similar for both controls. Let's take a look!

Tooltips on TableCell

myTableColumn.setCellFactory(new Callback<TableColumn<DataModel, Object>, TableCell<DataModel, Object>>() {
	@Override
	public TableCell<DataModel, Object> call(TableColumn<DataModel, Object> p) {
		return new TableCell<DataModel, Object>() {
			@Override
			public void updateItem(Object t, boolean empty) {
				super.updateItem(t, empty);
				if (t == null) {
					setTooltip(null);
					setText(null);
				} else {
					Tooltip tooltip = new Tooltip();
					DataModel myModel = getTableView().getItems().get(getTableRow().getIndex());
					tooltip.setText(myModel.getTip());
					setTooltip(tooltip);
					setText(t.toString());
				}
			}
		};
	}
});

To add tooltips to column cells we have to set a custom cell factory for the target column. In our example DataModel is just the TableView's data model, change that to your model's name. Also this example's model returns Object so that's one more thing you will probably need to change over the casts. The getTip() method is there to also show you how to get the TableRow Data Item.

Tooltips on TableRow

myTable.setRowFactory(new Callback<TableView, TableRow>() {
	@Override
	public TableRow call(final TableView p) {
		return new TableRow() {
			@Override
			public void updateItem(DataModel item, boolean empty) {
				super.updateItem(item, empty);
				if (item == null) {
					setTooltip(null);
				} else {
					Tooltip tooltip = new Tooltip();
					tooltip.setText(getItem().getTip());
					setTooltip(tooltip);
				}
			}
		};
	}
});

Notice that for rows we need to set our custom row factory to the target table not column! Also it's much simpler to get the DataModel when working on rows, you only need to call getItem() method. 

Recently i started working on a JavaFX 2.x project and i needed a lot of TableViews for one of my stages. The most common and best practice to do this, is to implement a class that defines the data model and provides methods and fields to further work with the table. In my case defining 8++ Data Models seemed a little tedious so i started looking for a dynamic way to populate TableViews. I came accross this solution but that too seemed complicated. 

My solution is to create a generic Data Model which can by extended to add as many columns as you want, so not 100% dynamic then. The constructor works with a variant number of parameters so you don't have to create as many constructors as your fields, but you have to create as many getters as your fields. The second part, can be avoided but as long as you can auto generate them in any decent IDE there is no reason to go mad with reflections.

import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TableRow {

    private Object one;
    private Object two;
    private Object three;
    private Object four;
    private Object five;
    private Object six;

    public TableRow(Object... args) {
        Field[] fields = getClass().getDeclaredFields();
        int i = 0;
        for (Object arg : args) {
            try {
                fields[i++].set(this, arg);
            } catch (IllegalArgumentException | IllegalAccessException ex) {
                Logger.getLogger(TableRow.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * @return the one
     */
    public Object getOne() {
        return one;
    }

    /**
     * @return the two
     */
    public Object getTwo() {
        return two;
    }

    /**
     * @return the three
     */
    public Object getThree() {
        return three;
    }

    /**
     * @return the four
     */
    public Object getFour() {
        return four;
    }

    /**
     * @return the five
     */
    public Object getFive() {
        return five;
    }

    /**
     * @return the six
     */
    public Object getSix() {
        return six;
    }
}

Then use the model just like in every other tutorial arround the web and the Oracled Docs. In my case it was something like this. Simple, generic and not overly complicated.

private TableView<TableRow> myTable;
@FXML
private TableColumn<TableRow, Object> myTableColumnOne;
@FXML
private TableColumn<TableRow, Object> myTableColumnTwo;

public void populateMyTable()
{
	myTableColumnOne.setCellValueFactory(new PropertyValueFactory<TableRow, Object>("one"));
	myTableColumnTwo.setCellValueFactory(new PropertyValueFactory<TableRow, Object>("two"));

	ObservableList data = FXCollections.observableArrayList();
	for(Map.Entry<String, Integer> entry: myHashMap.entrySet())
	{
		data.add(new TableRow(entry.getKey(), entry.getValue()));
	}
	myTable.getItems().setAll(data);
}

I hope, you will benefit from this solution, leave a comment if you have an idea on how to improve it even further.

javaFXTableViews.png

Edit: 4/25/20013
--------------------------------------------------

I took it on step further to remove the reflection in the constructor.


import java.util.ArrayList;
import java.util.Arrays;

/**
 * Generic data model is a wrapper for ArrayList to easily populate tableViews
 * and other javafx controls. This class would be perfect if java reflection
 * supported magic getters and setters like php or if there was a nice way to
 * dynamically add them during runtime.
 */
public class GenericModel {

    /**
     * The indexed list of objects
     */
    private ArrayList data;

    /**
     * Public constructor with variable number of parameters.
     *
     * @param args
     */
    public GenericModel(Object... args) {
        data = new ArrayList<>(Arrays.asList(args));
    }

    public Object get0() {
        return data.get(0);
    }

    public Object get1() {
        return data.get(1);
    }

    public Object get2() {
        return data.get(2);
    }

    public Object get3() {
        return data.get(3);
    }

    public Object get4() {
        return data.get(4);
    }

    public Object get5() {
        return data.get(5);
    }
}

banner-772x250.png

screenshot-1.pngSummy for wordpress generates excerpts for your posts by applying various algorithms for automatic summarization extraction. It scores your text's sentences, based on extended configuration options, and returns the highest ranked. WP-Summy is based on the Sum+my and was created in an attempt to further develop the Core Library through your feedback.

Important Notes

  • Currently only English & Greek languages are supported.
  • PHP 5.3 is required in order to use this plugin
  • It was developed and tested under Wordpress 3.5
  • Please report if it's compatible with older versions of wordpress

How To Use

  • Write your blog post as you normally do
  • Make sure excerpt and summy blocks are on screen
  • Experiment with all the options and hit Summarize

Installation

  1. Upload the entire folder `summy` to the `/wp-content/plugins/` directory
  2. Activate the plugin through the 'Plugins' menu in WordPress

 

Download WP-Summy v1.0.1

Flattr this

Apparently komposta.net become famous today, who needs google analytics and webmaster tools when you wake up in the morning and u see that ~300 comments have been posted overnight (lol). Since September 2012 ~900 comments have been submitted. Only 14 of them are not spam, 4 of them are mine ;) and 300 were posted yesterday. A month ago i added a honeypot solution to the komposta comments system and it's working great but it's obvious some time limit catch has to be added to prevent this aggressive spam behavior. Some other fan factor they only target posts with popular keywords like twitter and zend.

spamAttack.png

Bots found me pretty fast because i set a redirection from my old site to komposta.net for about 2 months until t3-design.com domain expired. I kinda regret it now...

-----Edit: 12 hours later
It's still on, i wonder what will happen if i turn comments off for these 3-4 targeted posts... Next update tomorrow

spamAttack2.gif

-----Edit12 hours later
Turning commenting off for the targeted posts did the trick, i am gonna turn it back on to see if they gave up...

I 've been to alot of interviews lately and i always promote myself as a php programmer. The truth is i don't like people who fill their resume with a huge list of programming languages just because they happened to use them briefly at some point in their life either as a hobby or as part of their studies. Apparently and as pointed by friends, i am stupid i don't show all aspects of my skills. So here is the very first application i have build which isn't written in php.

Cakebox is an archives manager for optical media. I am a hoarder when it comes to tv series, animes and movies, i don't like to delete stuff and since hdds used (???) to be super expensive i wrote a simple application to easily find cd's/dvd's in mountains of cakeboxes. The application started its life as a Java desktop application named `CakeBox Collector` after the movie `Bone Collector`. Basically it's a database application with categories and entries with title and files list, with search and all the basic crud forms. It used the excellent H2 database and was build upon the swing framework. 

Last year and after 4 years i first started Cakebox i decided to port the application to C# for various reasons. Netbeans dropped support for the swign framework, i was really bored and because i thought the whole task would later help me with mobile applications. So after a slight name change and only about a week later Cavebox was born. I used the very cool alternative to Visual Studio, SharpDevelop and for database i used SQLite and the ADO.Net adapter. I tried to change as little as possible in the UI because i thought it would be cool to see them side by side and i really liked the original compact design. Below there are two animated gifs showing both applications can you spot the differences ;)

cavebox.gif cakebox.gif

I don't want to get into the fight which is better but still I was impressed with the low memory footprint and startup times. Of course there were a few things C# doesn't have and i missed them during the port procedure but there were also a lot of things that made C# much easier to live with. Overall i think both languages have their merits and despite the recent hatred toward Java and Oracle nobody can dispute the fact that pretty much everyone has used or is using with or without their knowledge a device/application working with Java.

Cakebox and Cavebox will find their way into github pretty soon...

Lithium models are very flexible, schema can be lazy loaded by the first call to Model::schema(), unless it has been manually defined in the model subclass. Below there is an example of a model with predefined schema 

class UserGroups extends \lithium\data\Model
{
	protected $_schema = array(
		'id' => array('type' => 'id', 'length' => 10, 'null' => false, 'default' => null),
		'title' => array('type' => 'string', 'length' => 64, 'null' => false, 'default' => null),
		'description' => array('type' => 'string', 'length' => 255, 'null' => false, 'default' => null),
		'admin' => array('type' => 'integer', 'length' => 1, 'null' => false, 'default' => 0),
		'resources' => array('type' => 'string', 'length' => null, 'null' => false, 'default' => null),
	);

	protected $_meta = array(
		'key' => 'id',
		'locked' => true,
		'source' => 'user_groups'
	);
}

If you leave the schema configuration empty,lithium will retrieve the information directly from the data source. Which is nice until you understand that this extra step will be taken every time the model is initialized, which is a no no. That's why I am going to show you how to cache the model's schema. Firstly we need to set up a connection and the cache storage, here is some basic configuration:

use lithium\data\Connections;
use lithium\storage\Cache;

Cache::config(array(
	'default' => array(
		'adapter' => 'File',
		'strategies' => array('Serializer')))
);

Connections::add('default', array(
	'type' => 'database',
	'adapter' => 'MySql',
	'host' => 'localhost',
	'login' => 'root',
	'password' => '',
	'database' => 'database',
	'encoding' => 'UTF-8'
));

You probably have those two already in your application's bootstrap files, here is the piece of code that does the schema caching

Connections::get('default')->applyFilter('describe', function($self, $params, $chain) {
	if(!Environment::is('production')) {
		return $chain->next($self, $params, $chain);
	}

	$key = 'schema_' . md5(serialize($params['meta']));
	if(!$result = Cache::read('default', $key)) {
		$result = $chain->next($self, $params, $chain);
		Cache::write('default', $key, $result, '+1 month');
	}
	return $result;
});

By using Lithium's filters we can hook into the data source describe method to intercept default behavior. Firstly there is a check to skip caching if we are not in production environment. Then the system checks if schema has  already been in cache by using the model's meta as a unique key. If not, we allow the default procedure to run and we write the result to cache. 

Some notes
Recently Lithium's model and pretty much all data classes have been heavily updated, in many cases breaking backward compatibility. One of them is the model schema, when fully initialized is now an object instead of a simple arrray. The above snippet was written for the latest version in the dev branch but it will probably work for previous versions as well.

The best practice when writing a model is to manually define the schema. If you follow this principle I am not sure the above snippet will improve  much your app's performance. Initialize object vs retrieve object from cache. Maybe with a more advance cache adapter like APC???

Let me know how this worked for you!

I have mixed feelings about 2012, it wasn't all bad but it certainly wasn't all fun fun fun. Here are some highlights. Very early i was super happy because after a period of three years of laziness  i was finally through university and i was preparing myself to serve mandatory military the coming spring as every other young male in Greece. Unfortunately the following months until May i had a serious health issue which sent me to hospital a couple of times and my precious 4 months of vacations turned into a very stressful period for me.

Anyway May came and my military service started. I have to admit it was better than i thought, living 9 months with no serious worries except what's for dinner and what's my next work duty isn't bad especially if you consider the alternative, which was looking for a job in the worst possible period in Greece going through the worst part of the economical crisis. Unfortunately that came to an end pretty fast as well as i broke my leg. I guess that was my bad since i am not exactly fit. After 2.5 months i was early discharged from military which some people found fortunate, but then again i couldn't walk at all from early    June to late August, so yeah crutches in summer time in Greece, lucky me. Actually i went to the beach many times for rehabilitation, it's not fun trying to walk with crutches on hot sand barefoot. I guess it was if you were watching me! I almost forgot my favorite moment with crutches, my university graduation ceremony which i would have missed if i hadn't broken my leg. Now try to imagine, a nice summer hot morning me with my crutches trying to climb the stairs to get to the stage to receive my degree. I am laughing my ass off right now but trust me i never felt more uncomfortable in my life.

It was about August 15th when i decided to get up from bed and do some actual work. The result, is what you see komposta.net and komposta cmf. On September i started looking for a job as a web developer. I am still looking if you are wondering. I got a little depressed while doing that, because there are not many job offers for all the demand out there and some of them are complete scam. I remember going for an interview for a PHP Developer and they started asking me if i know Ruby. After a while i politely asked them, if you want someone who knows how to code in Ruby why do you ask for a senior PHP Programmer in the job description. The answer was complete BS and the real reason was that they wanted a programmer who could be trained and of course he could be paid much less than a senior programmer. Even though after the interview i politely denied the offer they kept calling me for days to accept the job. :)

Fortunately i took some freelance works and that's what i have been doing for the last 3 months. It's not much but i am not completely broke and i managed to make it until now. I am sure 2013 will be a much better period for job hunting in Greece, so yeah i am little happy i managed to avoid taking up a job i don't really like but then again i am still living with my parents. Even when i was free i tried to stay busy as much as possible with personal projects like komposta and Sum+my.

2012 was tough for many people around the world, i truly believe next year will be much better. Here is my resolutions for 2013

  1. Advance as a php freelance programmer
  2. Get a job i actually like
  3. Do both 1 & 2
  4. Make komposta.net popular
  5. Publish komposta cmf on github
  6. Travel as much as possible
  7. Combine 8 with an F1 race
  8. Run without fear about my broken leg
  9. Do 6 & 7 again
  10. Improve my writting skills in english (thx a lot for reminding me T4Co!)

 I wish you all a very Happy New Year 2013!!

FMScout: Twitter Spotlight is another project for fmscout.com i recently finished. The purpose of the plugin is to show status updates from multiple twitter users in a quick and fancy way. The plugin is built to work with the new Twitter Api1.1 rate limits and with the proper configuration it's possible to actually show tweets from a big number of accounts without worrying about getting banned from twitter. Currently fmscout.com is only fetching data from ~15 accounts with ~40 to be the ultimate goal but going tripple digits is possible with longer Cache TTL policy.

tweets0.png tweets1.png

It involved a lot of javascript code, and the js plugin is based on JQuery and JsRender to render all the on the fly html code. The tweets appear in multiple tiles which get flipped randomly every x seconds. All tiles and twitter accounts get the same "spotlight" by shuffling them like a deck of cards instead of randomly picking one every time. My javascript was a little rusty and this project was a lot of fun to build and see in action.

Tweets: Twitter Spotlight

  • PHP: Cotonti Plugin
  • Javascript: JQuery Plugin
  • Site: www.fmscout.com
  • Finished in 4 days after the work request

I have already introduced you to this application, and i already told you about the porting process to Zend Framework 2 but that's not all. Since i finished the first version which only worked for Greek documents, i always wanted to add support for the English language and extend it further more.

So after a few weeks of work and research here it is with a brand new name, Sum+my, which stands for Summarization Methodology Yardstick.

summy_home.png summy_admin_documents.png summy_admin_terms.png  

 

Changelog v2.0

  • Brand New Appearance based on Twitter Bootstrap
  • Ported Application to Zend Framework 2.0
  • Added Support for English Language
  • Added Stemmer Test Page
  • Fixed typos in the Greek Stemmer, improved accuracy a lot!
  • Summaries are now cached, to avoid double posting, the links are by no means permanent!

 summy_admin_home.png summy_stemmer_en.png summy_changelog.png

It's probably the work i am most proud because it's so out of my element, so give it a try you might  actually find it a great tool for everyday tasks and let me know if you have any ideas to improve it further or why not start adding support for more languages. For that you might also want to check the docs of the Sum+my

 

I finished yesterday porting summy.komposta.net to zf2, it will be online after some new features and further development. The process wasn't very hard and you don't have to master zf2 before you begin or even to finish the task. There are already a lot of resources about zf2 and to be able to see code from real life applications from the modules.zendframework.com is a huge bonus. In my case it was a very small application with only 5-6 controllers with a few extra controller plugins, acl + auth + the 15 classes based on Zend\Filter, and it took about 5 hours of work, mostly reading and copy/paste.

First of all performance impressions, out of the box with zero optimization, zf2 is a little friendlier on memory but a little worse on speed. The differences are small but noticeable, i can't give you excact numbers cause i couldn't bother with extensive tests so take my opinion or leave it.

Secondly some tips:

  1. If you are building multi-modular applications avoid doublicate configurations, otherwise you will often be stuck wondering why some changes don't take effect.
  2. The code hasn't changed that much, zf2 hasn't been rewritten from scratch with a few exceptions, so you will often end up wondering what's up with all the fuss about zf2 being hard to start with 
    $error = false;
    $return = $this->params()->fromQuery('return');
    if($this->getRequest()->isPost())
    {
    	$username = $this->params()->fromPost('username');
    	$password = $this->params()->fromPost('password');
    	$return = $this->params()->fromPost('return');
    
    	if($username != '' AND $password != '')
    	{
    		$adapter = $auth->getAdapter();
    		$adapter->setIdentity($username)
    				->setCredential(hash('SHA256', $password));
    
    		$result = $adapter->authenticate();
    		if($result->isValid())
    		{
    			$auth->getStorage()->write($adapter->getResultRowObject(null, 'password'));
    			if($return)
    			{
    				return $this->redirect()->toUrl(base64_decode($return));
    			}
    			return $this->redirect()->toRoute('default', array('controller' => 'index', 'action' => 'index'));
    		}
    	}
    	$error = true;
    }
    
    zf2 vs zf1
    if($this->getRequest()->isPost())
    {
    	$username = $this->_request->getParam('username');
    	$password = $this->_request->getParam('password');
    
    	if($username != '' AND $password != '')
    	{
    		$adapter = new Zend_Auth_Adapter_DbTable(Zend_Db_Table::getDefaultAdapter());
    		$adapter->setTableName('user')
    				->setIdentityColumn('username')
    				->setCredentialColumn('password')
    				->setIdentity($username)
    				->setCredential(hash('SHA256', $password));
    
    		$auth = Zend_Auth::getInstance();
    		$result = $auth->authenticate($adapter);
    		if($result->isValid())
    		{
    			$auth->getStorage()->write($adapter->getResultRowObject(null, 'password'));
    			$returnUri = $this->_getParam('returnUri');
    			$this->_helper->redirector->gotoUrlAndExit($returnUri);
    		}
    	}
    	$this->view->loginFailed = true;
    	$this->view->returnUri = $this->_getParam('returnUri');
    }
  3. The default route you will find in the skeleton app is very basic, so to at least achieve the main function of zf1 default route /controller/action/id use the bellow route, it also supports extra query params, if you call the url helper with route name 'default/query'
    'default' => array(
    	'type' => 'Segment',
    	'options' => array(
    		'route' => '/[:controller[/:action]][/:id]',
    		'constraints' => array(
    			'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
    			'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
    			'id' => '([0-9]+)?'
    		),
    		'defaults' => array(
    			'__NAMESPACE__' => 'Application\Controller',
    			'controller' => 'Application\Controller\Index',
    			'action' => 'index',
    			'id' => false
    		),
    	),
    	'may_terminate' => true,
    	'child_routes' => array(
    		'query' => array(
    			'type' => 'Query',
    		),
    	),
    ),
  4. If you stepped on a bug there is a good possibility you are not the first one to discover it, check the zf2 github as much as possible.
  5. Use the zf2 classmap generator to make autoloading a hell of a lot faster.

 

That's all for now, i will continue posting about ZF2, my opinion about it has improved a lot since i started porting one of my old zf1 applications and check back maybe i will start posting some very interesting tutorials about Zend Framework 2

 

 
Page Generated in: 0.3061s, Memory Usage: 8.1429MB, Queries: 3