Thursday, December 30, 2010

MAC Apache MySQL PHP

MAMP in short, is what most php developer is looking for on MAC.
First, get macport installed.
Then, follow this instruction here:

Make sure to disable the web sharing of MAC, which is default enabled.
And to start it, you will need to to run the daemons in /opt/local/etc/LaunchDaemons.
Example:
/opt/local/etc/LaunchDaemons/org.macports.apache2/apache2.wrapper start

DocumentRoot is located @ /opt/local/apache2/htdocs
Configuration @ /opt/local/apache2/conf


MAC and repository

MAC was originated from Darwin BSD, a varient of unix made by open source community.
But Apple did a well job, making the user interface suitable for desktop use.

Alike linux such as ubuntu, it has a *nix like file structure.
Most installer packages are in extension .dmg or .pkg.
Or you may install things from source code itself.

There are some way to make repository like installer, alike apt-get or yum in linux.
One of it is macports, macports.org
Installing MacPorts requires XCode to be installed. It can be found on apple website.
XCode with the iOS sdk is pretty huge, total up to 3+Gb.

Mac paths is different from most linux.
The application is mostly installed under /Application.
While the user home directory is under /Users/your_user_name
Other than that, its pretty much the same file structure with linux.

Keyboard wise, its pretty much confusing. There are no direct home or end key.
The function key needs the Fn function to press together to make it work.
But you may change this setting in the System preference, found on the upper left with a small Apple icon.
As for home button, pressing the "Command" + "Left arrow" gives you the home, while "Command" + "Right arrow" gives you the End.
The "Command" key is pretty much alike the control key in windows or linux.
But in terminal, the Control+c of linux have to be "Control"+c in mac.

What is really awesome in MAC is their touch pad. Its multi touch, and it detect 4 fingers!
Using Four finger and dragging down, give the overview of all the tasks in thumbnail.
Using 3 fingers and dragging left gives the "Go back", while dragging right gives the "Go Forward" maneuver. Touching the pad with 2 fingers give the "Right" click trigger. And as usual, using 2 fingers to drag will give the scrolling maneuver, alike the iphone or ipad.
Multi tasking in MAC is pretty much more smoother compared to ubuntu 10.10. But there is a new patch done in the recent kernel hacking which seems to solve this issue on X Windows, and hopefully this will give the more consistant multi-tasking feel on linux.

More on MAC keyboard:

Hope this give you a head start of understand MAC.
Cheers~ :)

Wednesday, December 29, 2010

jquery.form onsubmit

jQuery has a plugin for form which does alike mootool .load method for form..
If you need to submit the form through javascript, you may not use
document.formname.submit(), as this will bypass the onsubmit event.

To make it work, use $("#formid").submit();


Reference:

Monday, December 27, 2010

External MX record / Mail server

It is possible to set external mail on server by setting MX record of the server dns to point to external IP. Another method will be removing the dns of the domain from the server and let the DNS be resolved on the external managed DNS.

If you have Postfix configured for your smtp, you will need to set the virtual-transport of the domain to another place.
Example:
domainname: exmaple.com, transport: smtp:ASPMX.L.GOOGLE.com:25

If you are running virtual domain support with mysql for postfix, you may find the configuration in /etc/postfix/mysql-virtual_transports.cf , stating the table in mysql to change.

More info:

Thursday, December 23, 2010

Debugging PHP with XDebug and Netbeans

On ubuntu:
sudo apt-get install php5-xdebug

Edit xdebug.ini in /etc/php5/conf.d/xdebug.ini
#Add this line:
xdebug.remote_enable=on

Restart apache:
sudo /etc/init.d/apache2 restart

In firefox, add extension "easy XDebug".
Restart firefox.

In netbeans, right click on project, "properties".
Go to "Run configuration", "Advanced"
Check on "Dot not open browser", "okay".
Click on "Debug", "Debug project"

Go to firefox, click on "Start XDebug session" icon, then open the URL to your local project.
Example: http://localhost/myprojectnamefolder/

You are done, you will notice debugging buttons on the top toolbar, on the right.
You may continue / step in / other action.





MODx 2.0.6 update

MODx team have finally made it right :)
Every logged in session now have to be in
$_SESSION["modx.user.contextTokens"]

Example:
$_SESSION["modx.user.contextTokens"]["web"]=UserId#


And $modx->executeProcessor($options) now get $scriptProperties from $options, no longer $_POST.



Wednesday, December 22, 2010

MODx 2 Plugin OnBeforeDocFormSave

Here is an example to make a tv to be a required field:
----------------------
<?php
/*
* $mode: "new/upd"
* $resource: modResource object
* $id: resource id
* Calling $modx->event->output("..."); will output error and cancel save
*/


if (empty($_REQUEST["tv4"])) {
$modx->event->output("Field 'XXX' is required");
}


Tuesday, December 21, 2010

MODx 2 Resource Create/Update Cancel redirect

If you need to create your own resource grid,
you may find it difficult to control the resource create / update redirect on after save or when cancel button is clicked.
One way to do this is to add your own startupclientscript like this:
----------------------
setCancelButton();

function setCancelButton() {
if (!Ext.getCmp("modx-action-buttons")) {
setTimeout("setCancelButton()", 1000);
return;
}
Ext.getCmp("modx-action-buttons").actions.cancel = 90;//your action number
for(var c=0; c Ext.getCmp("modx-action-buttons").items.items.length; c++) {
if (Ext.getCmp("modx-action-buttons").items.items[c].process == "cancel") {
Ext.getCmp("modx-action-buttons").items.items[c].handler = cancelHandler;
}
}
}

var cancelHandler = function (btn,e) {
var fp = Ext.getCmp(this.config.formpanel);
if (fp && fp.isDirty()) {
Ext.Msg.confirm(_('warning'),_('resource_cancel_dirty_confirm'),function(e) {
if (e == 'yes') {
MODx.releaseLock(MODx.request.id);
MODx.sleep(400);
location.href = '?a=90'; //your url or action here
}
},this);
} else {
MODx.releaseLock(MODx.request.id);
location.href = '?a=90'; //your url or action here
}
}


Sunday, December 19, 2010

DHTMLX Grid reloading data

Handy function to reload data in grid

mygrid.reloadPage = function() {
this.load(this.xmlFileUrl + getUrlSymbol(this.xmlFileUrl) + "posStart=" +
((this.currentPage-1)*this.rowsBufferOutSize) + "&count=" + this.rowsBufferOutSize, this._data_type);
}

DHTMLX Grid attachHeader and attachFooter

DHTMLX Grid is very strict on height setting.
If you ever attachHeader or attachFooter, always remember to set the height of the column you are adding using style="height:30px".

After setting, running:
grid.setSizes();
grid.callEvent("onGridReconstructed", []);

This above also applies to subgrid

Saturday, December 18, 2010

DHTMLXGrid subgrid paging

To create paging in subgrid, one must create a row in the table.
The example below put up paging as footer.
The enablePaging should only be call when loading data.
This is because the grid is not created yet, therefore the div did not exists yet.


mygrid.attachEvent("onSubGridCreated",function(subgrid, id){
//collapse all other subgrid
subgrid.setHeader("#master_checkbox,Id,Name,Age");
subgrid.setColTypes("ch,ro,ro,ro");
subgrid.setInitWidths("40,50,100,100");
subgrid.init();
subgrid.enableAutoHeight(true);
//notice the div is given the unique id
//the trailing #cspan to set colspan of the other column to merge with 1st column
// therefore, the #cspan have to match your column count-1
subgrid.attachFooter("<div id="subgrid_h" + id + "" style="width:100%;"></div><div id="subgrid_f" + siteid + "" style="width:100%; height: 20px;"></div>,#cspan,#cspan,#cspan");

subgrid.load("http://example.com/path_tojson?count=10&posStart=0&siteid=" + siteid,function() {
subgrid.enablePaging(true, 10, 10, "subgrid_h" + siteid, true, "subgrid_f" + siteid);
subgrid.setPagingSkin("bricks");
subgrid.setSizes();
subgrid.callEvent("onGridReconstructed", []);
}, "json");

return false; // block default behavior
});

DHTMLX Grid subgrid

Example of Grid with subgrid support with JSON data:

//=====Main grid======//
mygrid = new dhtmlXGridObject('divGridForm');
mygrid.setImagePath("imgs/");
//setting header row, notice 2nd column is a "check-all" checkbox
mygrid.setHeader(["","#master_checkbox","id","name");
mygrid.setInitWidths("30,40,50,100);
mygrid.setColAlign("center,center,left,right");
//expander symbol, checkbox, read-only field,...
mygrid.setColTypes("sub_row_grid,ch,ro,ro");
//setting sortable columns, notice 1st 2 columns is empty to disable sorting
mygrid.setColSorting(",,str,str");
mygrid.enablePaging(true, iLimit, 10, "pagingArea", true, "recinfoArea");
mygrid.setPagingSkin("bricks");
mygrid.load("http://example.com/pathtojson", "json");

//Main grid row double clickevent
mygrid.attachEvent("onRowDblClicked", function(rId,cInd){
//do something
});


//====Sub grid load on created====//
mygrid.attachEvent("onSubGridCreated",function(subgrid, id){
//collapse all other subgrid
//notice hiding column #2
subgrid.setHeader("#master_checkbox,,Industry,Advertiser");
//notice chekbox @ column #1
subgrid.setColTypes("ch,ro,ro");
subgrid.setInitWidths("40,0,100,100");
subgrid.init();
subgrid.load("http://example.com/path_tosubgrid_json" + id,function() {
//call to reload subgrid height
subgrid.setSizes();
subgrid.callEvent("onGridReconstructed", []);
}, "json");
return false; // block default behavior
});


//updates dec 19: this can be done by calling mygrid.getCheckedRows(0);
//overriding method to get all checkbox ticked in main grid
mygrid.getSelectedRows = function() {
var aResult = [];
this.forEachRow(function(id){
var cell=this.cells(id,0);
if (cell.isCheckbox() && cell.getValue()==1) {
aResult[aResult.length] = id;
}
});
return aResult;
}

//===to reload data of a page
mygrid.rowsBuffer = dhtmlxArray();
mygrid.changePage(this.currentPage);


//===to only allow expand of 1 subgrid
//if expand row
mygrid.attachEvent("onSubRowOpen", function(expandId, status) {
if (status) {
mygrid.forEachRow(function(id){
if (id!=expandId) {
mygrid.cells(id, 0).close();
}
});
}
});

//=== for custom sorting===//
mygrid.attachEvent("onBeforeSorting",function(ind,type,direction){
var iLimit = 10; //limit records / page
var sSortDir = direction;
var sSortBy = aRawCols[ind];
var iOffset = (mygrid.currentPage-1)*iLimit;
var sNewURL = "http://example.com/path_to_json_loader?&count=" + iLimit + "&posStart=" + iOffset + "&dir="+sSortDir+"&sort="+sSortBy;
mygrid.load(sNewURL, "json");
this.setSortImgState(true,ind,direction);//set a correct sorting image
return false;
});

Please note that, dhtmlxgrid loading behaviour is rather, "special case".
Most Grid will load data, and assume the data return is starting on index 0 from offset pass in,
but somehow, it skipped the records up to offset before collecting data for the new page.
even with posStart=2, your json data has to return postData of emty array of 2 before appending actual row of data.
Example:
[
[],[], {id:3, name: "xxx"},...
]


Grid with sub grid

Working with ExtJS grid was kind of cool. But if you need to have another grid within the grid, the Grid selection, checkbox and expand column become hair wire on ExtJS.
I've tried several ways, but some how given up on it. Hope fully ExtJS 3.4 or higher could see the light on this feature...

After searching around, DHTMLXGrid did serve the purpose pretty well.
But the documentation is a bit confusing, need a bit of digging on the doc and the code in order to configure things up to what you might need.

Reference:
+ http://docs.dhtmlx.com/doku.php?id=dhtmlxgrid:api_toc_alpha

Wednesday, December 15, 2010

MODx 2.0.5 form customization for profile

The new form customisation for MODx 2.0.5-pl seems to only cater for resource create/update.

If you need a form customization for user profile, you may do it by adding a plugin on initialized manager with event "OnManagerPageInit":
-----------
define("IS_PROFILE_MODULE", $_GET["a"] == 71? true: false);
if (IS_PROFILE_MODULE) {
$modx->regClientStartupScript("url to your file.js",true);
}
-----------

URL to your file.js:
-----------
Ext.onReady(function() {
doHideProfileTabs();
});

function doHideProfileTabs() {
if (!Ext.getCmp("modx-panel-profile-tabs")) {
setTimeout("doHideProfileTabs()", 1000);
return;
}

MODx.hideTab("modx-panel-profile-tabs","modx-panel-profile-update");
MODx.hideTab("modx-panel-profile-tabs","modx-profile-recent-docs");
//MODx.hideTab("modx-panel-profile-tabs","ext-comp-1020"); //change password
Ext.getCmp("modx-panel-profile-tabs").setActiveTab(2);
}
-----------
Please take note: The hidden tab is still in the tabs container, therefore setactivetab have to reflect the index of the original position.

MODx Updating to new version

files needed to copy:
core/* , except core/cache, core/config
manager/*
connectors/*
setup/*
index.php

Remember to avoid copying:
core/cache
core/config

Friday, December 10, 2010

Modx 2 getResources enhanced tvFilters

I've done some modification to getResources to accomodate more condition operators such as :
!=
EXISTS
NOTEXISTS
| : for OR operator

And changed that the condition will only effect after the main condition is meet, etc. published='1' and deleted='0'

Examples:
tvFilters=`mytv!=%xxx%|mytv==NOTEXISTS`
#setting condition to TV mytv does not contain tag "xxx" or no mytv variable exists

getResources2.php

Hope it helps you guys :)
Cheers~

Tuesday, December 7, 2010

Modx 2 file manager + upload image on 2.0.4-pl2 or lower

File manager works well when you plan to use only "web" context for your manager users.
But when it comes to using other context, you will realise that there are some permission issue related to context.

To fix it temporary, until modx 2.0.5 is release, here is a fix.
path:
model/modx/processors/browser:

*/directory/getlist.php , */directory/getfiles.php , */file/upload.php , */file/remove.php:
Adding this somewhere at the top:
$context = !isset($scriptProperties['ctx']) ? "web": $scriptProperties['ctx'];

Change from:
$root = $modx->fileHandler->getBasePath();
To:
$root = $modx->fileHandler->getBasePath(false, $context);
---------------
Change from:
$fileManagerUrl = $modx->fileHandler->getBaseUrl();
To:
$fileManagerUrl = $modx->fileHandler->getBaseUrl($context);
----------------

Wednesday, December 1, 2010

MODx 2 permissions for uploading image in resource editor

Minimum Requirement:
Context access:
Context: "mgr", min role: "member",
access policies: "file_upload, file_manager"

Context: "web/yourcontext", min role: "member",
access policies: "file_upload,file_list,file_update,file_remove"

Optional access policy:
directory_list
directory_update
directory_create
directory_remove
directory_chmod

javascript string replace all?

Javascript String "replace" function only replace 1 instance of the string.
If you need a replaceAll function, here is a handy function:

function replaceAll(source, find, replace) {
if (source == null) { return source; }
var aString = source.split(find);
return aString.join(replace);
}