12

A new website and the power of social media

by MrSoundless 21. September 2011 18:00

I've recently launched my new website. It's my personal website which has links to my personal pages and portfolio. I've hired someone on vWorker to do the design and that worked out great.

The website is built in ASP.NET MVC 3 . There was actually no need to do it in MVC at all, I just did it because I wanted to. The portfolio items are loaded from XML files. So if I want to add a new item, I can just copy an XML file and edit the values. I've chosen not to add a CMS because I felt it would be overkill for something like this. Especially because new items won't be added that often and even if they would be added often, copying and editing an XML file is not much work. Especially if it just contains a couple of fields.

The power of social media

What I did notice, is the power of social media. This website is not one which I was expecting many people would visit. Especially because it doesn't contain anything that's interesting to anyone but employees or customers. I had decided to put a link on my facebook to at least show my friends and spread the word about what I do. After checking google analytics, I was shocked to see that after just 1 day I had 68 visits from which 62 were unique! I have 239 friends and around 28 of them don't even know Dutch. This means that 29% of the contacts which do speak the language, have at least clicked the link on my share.

This is a huge amount and it really shows how effective social media can be. So next time you release a product, make sure you post a link with a very short description on the social websites!

Tags: , , ,

General

2

Filtering a CGridView with an ajax-button

by MrSoundless 7. June 2011 16:08

On this post, the Yii blog demo, which comes with the Yii download, will be used as an example.

There is a question I heard a couple of times on the Yii channel @ FreeNode. The question we're talking about is: 'How do I add a button (preferably an AJAX-button) to my page which filters the CGridView?'. I'll be answering this question in this post.

Scenario

When we open up 'index.php?r=post/admin' in our blog demo, we'll see that we have a CGridView with a bunch of posts in it. What we want to do is, get rid of the Post Status dropdown and add 'Draft', 'Published', 'Archived' buttons above our CGridView. When we click 'Draft', only drafts will be displayed. The same goes for 'Published' and 'Archived'. We don't want the complete page to be refreshed while doing this.

STEP 1: Adding an ID to the CGridView

We'll start by adding an ID to our CGridView. This will make things much easier later on.

$this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'post-grid', // This line is added!
	'dataProvider'=>$model->search(),
	'filter'=>$model,
	...

STEP 2: Adding the buttons

Next we'll add the buttons that will do the filtering. You can place them anywhere. I decided to put them above the CGridView. The buttons won't do anything for now. 

echo CHtml::button('Draft');
echo CHtml::button('Published');
echo CHtml::button('Archieved');

Add those lines above your CGridView, refresh and you'll see the 3 buttons. When you click them nothing will happen.

STEP 3: Adding the filtering functionality

In this step we'll be make the buttons filter the CGridView results. For this function to work, I've written a small JavaScript function which I register by calling 'CClientScript::registerScript' :

Yii::app()->clientScript->registerScript('updateGridView', '
$.updateGridView = function(gridID, name, value) {
	$("#"+gridID+" input[name="+name+"], #"+gridID+" select[name="+name+"]).val(value);
	$.fn.yiiGridView.update(gridID, {data: $.param($("#"+gridID+" .filters input, #"+gridID+" .filters select"))});
}
', CClientScript::POS_READY);

Basically, what it does is: Fill in the value in the right filter and refresh the CGridView.

Now change your buttons code to this:

echo CHtml::button('Draft', array(
	'onclick'=>
		'$.updateGridView("post-grid", "Post\[PostStatus\]", "'.Post::STATUS_DRAFT.'"); '
));
echo CHtml::button('Published', array(
	'onclick'=>
		'$.updateGridView("post-grid", "Post\[PostStatus\]", "'.Post::STATUS_PUBLISHED.'"); '
));
echo CHtml::button('Archived', array(
	'onclick'=>
		'$.updateGridView("post-grid", "Post\[PostStatus\]", "'.Post::STATUS_ARCHIVED.'"); '
));

Here we simply call the updateGridView function with the right parameters. Save, refresh and you'll see the buttons do it's job.

If you will keep the filter for the PostStatus inside your CGridView, you'll be done now and you won't have to follow the next steps. Else, continue to step 4.

STEP 4: Removing the filter from the CGridView

In this step we'll be removing the Status filter from the CGridView without breaking the filtering functionality we just added. If we simply remove the filter without doing anything else, the buttons we just created will throw a JavaScript error when you try to use them. This makes sense, because when we click the button, it will try to set the value of the dropdown. But since the dropdown is not there anymore you'll get that error. To avoid this from happening we have to do a couple of things. First we'll start by adding a hidden field to the filters. This hidden field will keep the value for the filter. And so instead of changing the value of the dropdown, we'll be changing the value of the hidden field. We do this by adding a new JavaScript function and calling it immediately:

Yii::app()->clientScript->registerScript('updateGridView', '
$.appendFilter = function(name) {
	$("#post-grid .filters").append("<input type=\"hidden\" name=\""+name+"\" />");
}
$.appendFilter("Post[status]");
', CClientScript::POS_READY);

Save. Refresh. Now fire up firebug. Browse to TD with the filter class inside your CGridView. You'll see that at the end of this TD, a hidden inputfield is added. Now click on one of the 3 buttons and watch carefully what happens: the input field is gone!!! The reason why this happens is because as soon as we call $.fn.yiiGridView.update , the complete gridview is renewed. Which means our filter is not there because we didn't add it after updating. To get this right, we'll have to make use of the afterAjaxUpdate event on the CGridView. We'll be doing that like this:

$this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'post-grid',
	'afterAjaxUpdate'=>'function(slash, care) { $.appendFilter("Post[status]"); }',

Save. Refresh. Fire up firebug, browse to your new hidden filter. Now click one of the filter buttons. You'll notice that the value of the input field is empty after we update. This happens because after an ajax call we add a new input field. So if you click the Archived button now, you shouldn't see any posts. But when you enter 'T' in the input box for the post title, you'll see a post popping up. Because it forgets the value of the input box, it will search in all posts. What we want in this case, is to just search inside the 'Archived' posts. We can do this by simply remembering the value of the filter box we added.

We'll have to change our javascript functions/calls for this:

Yii::app()->clientScript->registerScript('appendFilter', '
$.postFilterStatus = "";
$.appendFilter = function(name, varName) {
	var val = eval("$."+varName);
	$("#post-grid .filters").append("<input id=\""+varName+"\" type=\"hidden\" name=\""+name+"\" value=\""+val+"\" />");
}
$.updateGridView = function(gridID, varName, value) {
	$("#"+varName).val(value);
	eval("$."+varName+"=value;");
	$.fn.yiiGridView.update(gridID, {data: $.param($("#"+gridID+"	.filters input, #"+gridID+" .filters select"))});
}
$.appendFilter("Post[status]", "postFilterStatus");
', CClientScript::POS_READY);

We're now keeping a jQuery variable named 'postFilterStatus'. This variable basically stores the value of the input field. The appendFilter function is adjusted to take the name of that variable. The updateGridView is also adjusted to take the name of that variable, but it also populates that variable when it's called.

We now change our buttons to:

echo CHtml::button('Draft', array(
	'onclick'=>'
		$.updateGridView("post-grid", "postFilterStatus", "'.Post::STATUS_DRAFT.'");
'));
echo CHtml::button('Published', array(
	'onclick'=>'
		$.updateGridView("post-grid", "postFilterStatus", "'.Post::STATUS_PUBLISHED.'");
'));
echo CHtml::button('Archived', array(
	'onclick'=>'
		$.updateGridView("post-grid", "postFilterStatus", "'.Post::STATUS_ARCHIVED.'");
'));

...and our afterAjaxUpdate to:

'afterAjaxUpdate'=>'function(slash, care) { $.appendFilter("Post[status]", "postFilterStatus"); }',

Save. Refresh. And you got it!

Now if your CGridView always has at least 1 default filter, you can stop here. If you don't always have 1 default filter or if you're not sure, move on to step 5.

STEP 5: Adding the ability to use filter buttons without any default filters

In this step I'll explain how to get the buttons to work in all times. So after this step, your buttons will be able to filter no matter if the CGridView has it's default filters or not.

We'll start by adding 2 divs that will keep the hidden fields in it:

<div id="filtersParent" style="display:none;"><div class="filters"></div></div>

We'll start by changing the appendFilter function so that it takes the id of the parent of the filters div.

$.appendFilter = function(filterParentID, name, varName) {
	var val  = eval("$."+varName);
	var stringToAppend = "<input id=\""+varName+"\" type=\"hidden\" name=\""+name+"\" value=\""+val+"\" />";
	$("#"+filterParentID+" .filters").append(stringToAppend);
}

We also need to update the updateGridView function so that it also takes a parent ID:

$.updateGridView = function(gridID, filterParentID, varName, value) {
	$("#"+varName).val(value);
	eval("$."+varName+"=value;");
	$.fn.yiiGridView.update(gridID, {data: $.param($(
		"#"+grid+" .filters input, "+
		"#"+grid+" .filters select, "+
		"#"+filterParentID+" .filters input, "+
		"#"+filterParentID+" .filters select, "
	))});
}

Now update the calls to appendFilter to:

$.appendFilter("filtersParent", "Post[status]", "postFilterStatus");

...and update the buttons to use the new updateGridView function:

echo CHtml::button('Draft', array(
	'onclick'=>'
		$.updateGridView("post-grid", "filtersParent", "postFilterStatus", "'.Post::STATUS_DRAFT.'");
'));
echo CHtml::button('Published', array(
	'onclick'=>'
		$.updateGridView("post-grid", "filtersParent", "postFilterStatus", "'.Post::STATUS_PUBLISHED.'");
'));
echo CHtml::button('Archived', array(
	'onclick'=>'
		$.updateGridView("post-grid", "filtersParent", "postFilterStatus", "'.Post::STATUS_ARCHIVED.'");
'));

Also change the afterAjaxUpdate to look like this:

...
'afterAjaxUpdate'=>'
	function(slash, care) {
		if ($("#postFilterStatus").length == 0)
			$.appendFilter("filtersParent", "Post[status]", "postFilterStatus");
	}',
...

Last but not least, disable default filter to proof that it works:

...
'filter'=>null,
...

Now save, refresh, enjoy Yii!

Tags: , , , ,

PHP | Yii

0

Translating text by extending the System.String class

by MrSoundless 18. May 2011 21:44

In this post I'm going to explain how one could translate text in a .NET application.

I've been asked this question and when I looked around the web, I figured there are many unclear explanations on how to get this to work. So I decided I should make something cool and make a blogpost about it.

Create a resource file

To be able to translate texts, you probably want to store all your text first. This can be done in a resource (.resx) file. You can create a resource file by selecting 'Resource File' on the 'New File' dialog. You'll see 3 columns: Name, Value and Comment. Name is the keyword which points to the value. The value is the translated text. The comment column is just what it says, a comment.

So here's an example on how it could look:

NameValueComment
introText This is an intro text.  

You can name the resource file whatever you want. We'll name it 'Translations.resx' .

The ResourceHandler class

So we filled the resource file. What now? To read out the resource file we could write a class which handles this for us. I decided to write the next, feel free to use it:

using System;
using System.Globalization;
using System.Reflection;
using System.Resources;

/// <summary>
/// Handles resources with different languages.
/// </summary>
public static class ResourceHandler
{
    private static ResourceManager _resourceManager;
    private static string _baseName;

    private static ResourceManager ResourceManager
    {
        get 
        {
            if (_resourceManager == null)
                throw new Exception("Basename has to be set before using any translation methods.");

            return _resourceManager;
        }
    }
    /// <summary>
    /// The base name of the resource file.
    /// </summary>
    public static string BaseName
    {
        get { return _baseName; }
        set
        {
            _baseName = value;
            _resourceManager = new ResourceManager(_baseName, Assembly.GetCallingAssembly());
        }
    }

    /// <summary>
    /// Returns the string of the searched key back
    /// </summary>
    /// <param name="key"></param>
    /// <returns>The String vlaue of the searched key</returns>
    public static String Translate(this String key)
    {
        string lstrValue = ResourceManager.GetString(key, System.Threading.Thread.CurrentThread.CurrentCulture);
        if (lstrValue == null)
        {
            lstrValue = key;
        }
        return lstrValue;
    }

    /// <summary>
    /// Returns the string of the searched key back
    /// </summary>
    /// <param name="key"></param>
    /// <param name="culture"></param>
    /// <returns>The String vlaue of the searched key</returns>
    public static String Translate(this String key, CultureInfo culture)
    {
        string lstrValue = ResourceManager.GetString(key, culture);
        if (lstrValue == null)
        {
            lstrValue = key;
        }
        return lstrValue;
    }
}

So I put a bunch of comments around the functions + property to clear things up.

Example of the usage of the ResourceHandler

Now you're kinda done. All you have to do is use the ResourceHandler.

You can do this by first setting the BaseName of the resource file. This is pretty much the name of our resource file.

ResourceHandler.BaseName = "Translations";

After doing this, you can simply do:

"introText".Translate();

The output of this will be the value you entered in your resource file. So in our case that will be "This is an intro text.".

 If the name "introText" doesn't exist in your resource file, "introText" will be returned.

Translate to another language

Now the point of all this is to be able to translate text to another language and we didn't really do that. To be able to translate to another language, we need to create another resource file. The name for this resource file is very important. It should look something like this: filename.language.resx OR filename.language-culture.resx . So if we would want to have Dutch translations we would have to create a resource file named Translations.nl.resx or Translations.nl-NL.resx .

Now to be able to use this file all we have to do is set the current CultureInfo.

Thread.CurrentThread.CurrentCulture = new CultureInfo("nl-NL");
"introText".Translate();

We could also pass the culture on to the translate method:

"introText".Translate(new CultureInfo("nl-NL"));

The first method would be used if you want to translate complete pages. The second method is there if you want to translate a couple of string values.

Tags: , , ,

C# | .NET

23

Searching and sorting a column from a related table in a CGridView

by MrSoundless 9. May 2011 20:49

In this post I'm going to explain how to search/sort a column from a related table in a CGridView by adding a comment list to the Yii Blog demo.

First we'll start with the CommentController which we'll add an action to. We'll name it actionList. The action will look like this:

public function actionList()
{
    $model=new Comment('search');
    $model->unsetAttributes();
    if(isset($_GET['Comment']))
        $model->attributes=$_GET['Comment'];
	
    $this->render('list',array(
        'model'=>$model,
    ));
}

This is nothing special. It looks exactly like the admin.php file that is created when you use the CRUD creation.

Now we'll create the view. Create a file named list.php in /protected/views/comment/ and paste this code in it.

<?php
$this->breadcrumbs=array(
	'Comments',
);
?>

<h1>Comment List</h1>

<?php $this->widget('zii.widgets.grid.CGridView', array(
	'dataProvider'=>$model->search(),
	'filter'=>$model,
	'pager'=>array('pageSize'=>25),
	'columns'=>array(
		'content',
		'status',
		'author',
	),
)); ?>

This is a basic CGridView which just views the 'content', 'status' and 'author', columns of the comments.

Let's say we want to add the title of the post to which the comment belongs to this list. We'll just add post.title to the list to do that.

'columns'=>array(
    'content',
    'post.title',
    'status',
    'author',
),

Now if you run the page and check it out, you'll notice that the post title is visible for each comment.

The problem

If you look at the page again you'll notice that you're not able to sort by the post title. You're also not able to search for the related column. This is basically because the CGridView checks if there is a '.' inside the given column name and if there is, it just makes sure the filter for that column is not displayed.

The solution

To fix this problem we have to do a couple of things. First we'll have to add a getter and a setter to the Comment model. This could look something like this:

private $_postTitle = null;
public function getPostTitle()
{
    if ($this->_postTitle === null && $this->post !== null)
    {
        $this->_postTitle = $this->post->title;
    }
    return $this->_postTitle;
}
public function setPostTitle($value)
{
    $this->_postTitle = $value;
}

Next we'll add the property to the rules function:

public function rules()
{
    // NOTE: you should only define rules for those attributes that
    // will receive user inputs.
    return array(
        array('content, author, email', 'required'),
        array('author, email, url', 'length', 'max'=>128),
        array('email','email'),
        array('url','url')

        array('content, postTitle, status, author', 'safe', 'on'=>'search'),
    );
}

The biggest change will probably happen in our search function. First we'll add the criteria:

$criteria=new CDbCriteria;
$criteria->with = "post"; // Make sure you query with the post table.

$criteria->compare('t.content',$this->content,true);
$criteria->compare('t.status',$this->status);
$criteria->compare('t.author',$this->author,true);
$criteria->compare('post.title', $this->postTitle,true);

Next we'll add the sorting:

$sort = new CSort();
$sort->attributes = array(
    'defaultOrder'=>'t.create_time DESC',
    'content'=>array(
        'asc'=>'t.content',
        'desc'=>'t.content desc',
    ),
    'status'=>array(
        'asc'=>'t.status',
        'desc'=>'t.status desc',
    ),
    'author'=>array(
        'asc'=>'t.author',
        'desc'=>'t.author desc',
    ),
    'postTitle'=>array(
        'asc'=>'post.title',
        'desc'=>'post.title desc',
    ),
);

You might have noticed that I'm using the full 'tablename'.'columnname' syntax. I'm doing this because this way we'll avoid mysql throwing a 'column is ambigious error'.

To make sure this all works, we have to pass on the CSort instance and the CDbCriteria instance to the CActiveDataProvider like this:

return new CActiveDataProvider('Comment', array(
    'criteria'=>$criteria,
    'sort'=>$sort
));

Now all we have to do is change our view so that it takes the property name inside the CGridView and we're done:

'columns'=>array(
    'content',
    'postTitle',
    'status',
    'author',
),

This should do the trick. Feel free to share this with all your friends!

The blog demo can be found in the Yii download which can be found here.

Tags: , , , ,

PHP | Yii

2

Joined a new Open Source project: LSLEditor

by MrSoundless 3. May 2011 22:55

Some readers might know that I learned programming on Second Life. Second Life has it's own scripting language named LSL which is a pretty simple language. I learned 99% of the basics on Second Life by scripting simple stuff and offering my help in return for people's patience. Scripting always happened in Second Life for me. You just create a script in game, fill it with some code, save it and run it. The problem with this is that saving/compiling huge scripts takes a while. Also, sometimes, the grid decides to have huge lag which sometimes makes it impossible to save scripts.

So about 1.5 years ago, I decided I wanted to be able to script LSL offline. I looked around and found this great editor named LSLEditor. I could actually run scripts in this editor which meant I didn't have to log on Second Life just to test my scripts. So a couple of days ago I noticed that the LSLEditor project went open source and since I totally enjoyed using LSLEditor, I decided to contribute to it. I also asked if I could join the project and yesterday I have officially been accepted as an LSLEditor developer.

So from now on, you can expect posts about (almost) each release of LSLEditor.

You can find the project on http://lsleditor.sourceforge.net/.

Please understand that I did NOT build this project. I'm just contributing to it. So all credits for the development of LSLEditor, goes to the original developer (Alphons) and the admins of the project who have kept this project alive.

Tags: ,

LSLEditor

22

PHPExcel in Yii

by MrSoundless 23. April 2011 23:35

A little while, I had to build a .xls export function into a Yii application. I decided to use PHPExcel for this, since I had used it before. It's also probably the best Excel importer/exporter for PHP. I soon figured that this didn't go as smooth as I expected.

The problem

Yii kept throwing an Exception about it not being able to find the file to import. Of course it couldn't find the file because PHPExcel's importer was supposed to handle that. I searched around and found a couple of posts like this one. This failed for me because between the spl_autoload_unregister and spl_autoload_register calls, I had to get info from a Model through another Model. When you try to reach a related Model, Yii actually tries to autoload that Model. But since we just unregistered the yii autoloader by calling spl_autoload_unregister, the Yii import was never called and so autoloading the Model failed.

The solution

So I went on and figured out that the reason they unregistered the Yii autoloader, was because the Yii autoloader was called before the PHPExcel autoloader. Since the Yii autoloader throws an Exception when it can't find a file and PHPExcel doesn't, the solution was simple: switch the order in which the autoloaders get registered. I decided to do this by changing the PHPExcel Autoloader's register method from this:

 

<?php
	public static function Register() {
		return spl_autoload_register(array('PHPExcel_Autoloader', 'Load'));
	}	//	function Register()

 

To this:

 

<?php
	public static function Register() {
		$functions = spl_autoload_functions();
		
		foreach($functions as $function)
			spl_autoload_unregister($function);
		
		$functions=array_merge(array(array('PHPExcel_Autoloader', 'Load')), $functions);
		
		foreach($functions as $function)
			$x = spl_autoload_register($function);
		
		return $x;
	}	//	function Register()

 

So this is what happens:

  1. All registered autoload functions are saved in $functions.
  2. Then they all are unregistered.
  3. Next we put PHPExcel_Autoloader in front of array we named $functions.
  4. We register all of them in that same order.
  5. And we return the $functions array

The thing with this is, you can use this everywhere. So if you change the PHPExcel autoloader and you decide to use it later in a project which is not built with Yii, it'll still work fine! From now on PHPExcel's AutoLoader will never be a problem again, no matter which framework you use!

All I have to do now is create a patch and submit it to http://phpexcel.codeplex.com/ so no one will ever have that problem again. Or maybe I should just wait till I have at least 100 comments before I do that Wink

 

Click the link below to download the fixed Autoloader.php for PHPExcel 1.7.3c

Autoloader for PHPExcel 1.7.3c Autoloader.php (1.84 kb)

Autoloader for PHPExcel 1.7.6  Autoloader.php (2.15 kb)

Tags: , ,

PHP | Yii

1

Changed theme

by MrSoundless 20. April 2011 14:17

In my last post I mentioned all the problems with the blog. I decided to switch theme's until everything in that theme is fixed. Actually I like this theme. I might keep it.

My next post will be much more useful then this one Tongue out

Tags:

Blog

0

I'm blogging!!

by MrSoundless 18. April 2011 19:00

Hi everyone,

First of all I want to welcome you all to my new blog. Let me introduce myself: I'm Fatih Kalyon, another developer from the Netherlands. I'm currently 23 years old and I currently work at CyberNed in Amsterdam as a PHP-developer.

So there I am, sharing stuff on a new blog. I have to say it's been hard to decide which blog engine to use. I first wanted to use WordPress but I didn't want to go through all the pain that comes with it when setting it up on an IIS server. Next I looked at Umbraco. Looked fine, but I couldn't find a quick way to implement theme's. Either there are no useful docs on this, or they are just really hard to find. So I decided to move on. Next I checked out BlogEngine.net. I kinda felt like this could work. So I installed it, checked out the whole cms part, figured out how to build a theme on it, build the theme and last but not least: deployed it to my server. So this means this blog is currently running on BlogEngine.Net. I have to say I'm still not 100% satisfied. So I might be building my own blog engine some time later.

Also I'm still not done with the theme. It might look pretty nice, but I'm still missing quite a lot:

  • 'About Me'-page
  • Sidebar
  • Category list
  • Tag cloud
  • Search box
  • Comments don't work for some unknown reason.

I'll try to add/fix those as soon as I can.


So for now I want to welcome all of you and I hope you'll enjoy my posts. If you have any questions, you're out of luck because the Contact page probably won't work either Tongue out

Anyway enjoy!!

Tags:

Blog

Powered by BlogEngine.NET 2.0.0.36
Original Design by Laptop Geek, Adapted by onesoft