Here is a very usefull trick to get more details on the error:
SHOW ENGINE INNODB STATUS
it will display more details on the last innoDB error.
Here is a very usefull trick to get more details on the error:
SHOW ENGINE INNODB STATUS
it will display more details on the last innoDB error.
I recently hlooked for a free tool to manage a todo list. As I have a LAMPP server at home, I obviously looked for a php-mysql solution that can manage hierarchy or tasks. After som times hanging around, i found what was the closest to my need: tarsks-jr that can be downloaded here
Installation was quite easy, and it is really user-friendly, but 2 features are missing for this free version:
As I had to exchange my task list with customers in a read-only way, I decided to write a pdf export plugin for tasks.
well, in fact, application is not designed for plugins, so as I can't just give you a zip file, I'll explain what I did. Let's start.
Well, software achitecture is easy to understand, you've got a frontend page that creates the menu and dispatches requests.
Considering this, first task is obviously to add a menu entry for PDF export, so I inserted this at line 253:
<?php $uri = 'export_pdf.php'.($screen=='focus' ? '?screen=focus&root='.(int)$_GET['root'] : '') ?> <div class="menuHR"><img src="images/clear.gif" height="1" width="1"></div> <table width="100%" cellpadding="0" cellspacing="4" class="hand" onclick="window.open('<?php echo $uri; ?>');" title="export pdf"> <tr> <td width="16"><img src="images/icon_pdf.png" width="16" height="16"></td> <td><span class="menuText">export pdf</span></td> </tr> </table>
I know this is dirty and non standard complyant, but the whole application is coded this way, so I addapted my coding style.
So, I just added a link that open in a new window and open the export_pdf.php page.
the $uri variable is used to be able to select only a task and its subtasks to export as pdf.
Now, to have everything work fine, let's create our export_pdf.php file, and leave it empty. As we are creating files, let's add a library file that we'll call pdf_functions.php.
We're now ready to start real work !
As we want to be able to create several type of PDF files (all tasks and subtasks view and a single task and its sub tasks), we need to implement a front controller. I decided to do it the same way as the initial one was made: quick and dirty.
first, let's include libraries:
<? ini_set('short_open_tag', '1'); // suppress Notice messages error_reporting(E_ALL); $timer = microtime(); // start page timer // make config changes in "config.php" // includes require_once('objects.php'); require_once('database.php'); require_once('functions.php'); require_once('pdf_functions.php'); require_once('compatibility.php'); require_once('config.php'); ob_start(); // hack to keep some weird char? from starting output in spanish.php require_once('languages/'.$custom->language.'.php'); ob_end_clean(); // set page variables $page = new page; $task = new task; require_once('set_up.php'); // session session_start(); if (!empty($_REQUEST["home_sort_order"])) { $_SESSION["home_sort_order"] = $_REQUEST["home_sort_order"]; } if (empty($_SESSION["mode"])) { $_SESSION["mode"] = "standard"; } if (!isset($_SESSION["messages"])) { $_SESSION["messages"] = array(); } $debug = 0; // set to 1 to display debug messages $current_page = "index.php?current_page=1"; // initialize string $reload_task = 1; // when this gets set to 0, it stops the save from happening // not mobile? we'll use the standard interface $_SESSION["mode"] = "standard"; if ($debug == 1) { print_r($_REQUEST); } require_once('engine.php');
I just copied the code from the original index.php and added our library. By the way, I set the error level to E_ALL for debugging.
Now, we're ready for the PDF generation.
I'm using FPDF library to create the document.
To install it, just download the library from the website and copy font folder and fpdf.php to a folder you name fpdf.
As told in the library manual, I extended original class to add my own generation functions. As documentation is included I give the code as is, without more explanations, you just have to know that the function used from front controller is called printTask.
<?php require_once("fpdf/fpdf.php"); class tasksPDF extends FPDF{ /** * the task level */ private $level=0; /** * the subtask offset width for rendering */ private $offset = 5; /** * headers numbers */ private $numbers = array(0); /** * automatically calculates the cell height from the fontsize */ function AutoCell($w, $text, $border=0, $align='J', $fill=false) { $this->MultiCell($w, $this->FontSize+2, $text, $border, $align, $fill); } /** * */ function MarginCell($text, $border=0, $align='J', $fill=false) { if($this->level>=2) { $this->Cell(($this->level-1)*$this->offset, $this->FontSize+2, '', 0, 0); } if($this->level>=1) { $this->Cell($this->offset, $this->FontSize+2, '', '', 0); } $this->AutoCell(0,$text, $border, $align, $fill); } function printTask($task, $show_completed_tasks = 0, $levels = 0) { //increase the task number $this->numbers[$this->level]++; //prepare for title $this->SetFont("Arial","B",12); $this->SetFillColor(204,204,204); $y = $this->GetY(); $this->MarginCell(' '.implode($this->numbers, '.').' '.$task->title, 'TLR', 'L',true); $this->Image('images/icon_priority_'.$task->priority.'.gif', $this->level*$this->offset + $this->lMargin + 1, $y+1, 4,4); //insert body text $this->SetFont("Arial","",10); $this->SetFillColor(255,255,255); $this->MarginCell($task->notes, 'LR'); //get childrens according to configuration if ($show_completed_tasks == 1) { $displayed_children = $task->children; } else { $displayed_children = $task->open_children; } // if some, then insert linebreak if(count($displayed_children)) { $y = $this->GetY(); $this->Cell(0,2,' ','R',2); if($y>$this->GetY()) $y = $this->tMargin; $this->Line($this->level * $this->offset + $this->lMargin, $y, $this->level*$this->offset + $this->lMargin, $this->GetY()); } // let's go for childrens $this->level++; // level up (identation up ;) ) array_push($this->numbers, 0); // prepare numerotation foreach($displayed_children as $child) { //get actual position $y = $this->GetY(); //get the task $child_task = new task; $child_task->retrieve($child); $this->printTask($child_task, $show_completed_tasks); // did we reach the end of page ? if($y>$this->GetY()) $y = $this->tMargin; //draw vertical line to act as a box $this->Line(($this->level-1) * $this->offset + $this->lMargin, $y, ($this->level-1)*$this->offset + $this->lMargin, $this->GetY()); } // back to normal $this->level--; //back to previous numerotation array_pop($this->numbers); //if childrens displayed, then a line break if(count($displayed_children)) { $y = $this->GetY(); //close the last box $this->Line( ($this->level + 1) * $this->offset + $this->lMargin, $y, $this->w - $this->rMargin, $y); //add a blank line $this->Cell(0,2,' ','R',2); // did we reach the end of the page ? if($y>$this->GetY()) $y = $this->tMargin; $this->Line($this->level * $this->offset + $this->lMargin, $y, $this->level*$this->offset + $this->lMargin, $this->GetY()); } // close the box $this->Line($this->level * $this->offset + $this->lMargin, $this->GetY(), $this->w - $this->rMargin, $this->GetY()); } }
This is not very complete, but in the actual state, it feets my needs... I let you adapt this code for your need.
Now we've got everything, let's finish our front controller by adding following code at the end:
<?php // for syntax higlight, when copying to the end of the front controller you don't need it $PDF = new tasksPDF(); $PDF->AddPage(); switch ($screen) { case "home": $PDF->setTitle('PDF export of all tasks'); foreach ($tasks as $temp) { $task = new task; $task->retrieve($temp); $PDF->printTask($task, $page->check_flag("show_completed_tasks")); } break; case "focus": $PDF->setTitle('PDF export of task: '+$task->title); $PDF->SetFont("Arial","B",14); $PDF->AutoCell(0,"Project: ".$task->title, 0, 'C'); $PDF->SetFont("Arial","",12); $PDF->AutoCell(0,"\n".$task->notes."\n"); //only show subtasks $show_completed_tasks = $page->check_flag("show_completed_tasks"); if ($show_completed_tasks == 1) { $displayed_children = $task->children; } else { $displayed_children = $task->open_children; } foreach($displayed_children as $child) { $child_task = new task; $child_task->retrieve($child); $PDF->printTask($child_task, $show_completed_tasks); } break; default: die('unknown action !'); } $database->disconnect(); header("Content-Type: application/pdf; charset=".$language->charset); $PDF->Output();
you can customize it as you want, it is very easy and
Unlike usual, this article isn't very detailed, but it's late, and this blog is about javascript, not php ;)
I hope it gonna be usefull for somebody, and at least it allowed me to learn the FPDF great library !
That's all folk !
As you may know, i've been working on Vista sidebar's gadgets for a while now, and i had to deal with a lot of specifics problems. Indeed, gadget development is very similar to IE web browser programing, but Microsoft thought it was smart to add some specificities, just as if they had no way to modify the IE7 rendering engine nor the win32 API to fit the gadget needs.
Well, now it is the way it is, the only solution is to deal with the given platform.
If you ever tried to create a partially transparent background gadget, you may have experienced one of following problem:
If you didn't, you're lucky, if you did and didn't find a workaround, i'll try to give a method in this article.
To have a single part background, you'll need an empty gadget and a png image representing your gadget background. I assume you already have all this.
First to show your gadget background, you need to define an HTML element to receive the image. This element is a Microsoft specific tag called g:background. You can find the specifications and documentation in this page.
Be carefull, as we use the g:background tag, don't use a doctype and especialy no xhtml doctype !
So, now you page should look like this:
<html> <head> </head> <body style="width:150px;height:200px;" > <g:background id="background" align="center" style="z-index:999;position:absolute;top:0;left:0;width:150px;height:200px;" src="./path/to/your/background" /> <div id="content"> </div> </body> </html>
This should be enough to have it working.
You may have note that it I fixed the background and the body sizes, it is mandatory to avoid any unwanted effect I mentioned in the introduction. Please make sure that those sizes are the same as your background image.
If you added some content, you should have noticed that the result isn't the expected one. That's because we didn't position the content correctly. let's add some rules in the css stylesheet:
#content{ width:100%; height:100%; padding:0; margin:0; position:absolute; top:0; left:0; }
Well, it is now exactly as we expected, isn't it ?
NO ? Damn, why does the text over the transparent area has a pink border ?
Well, I don't know, and the only way I found to add some text on the transparent area is to use the g:background addTextObject method. Otherwise, just try not to write anything in the transparent area :).
Ok, we now have a fixed size and single part background, but what can I do if I want to resize my gadget ? should I embed a background image per size ? let's go further to play around a bit.
I assume that if you reached this paragraph you want extensible transparent background, so let's dive into the subject.
First of all, you'll need a blanc gadget with the same code as previous paragraph.
As the background image isn't the same anymore, let's replace the g:background source with a transparent image of 1px square. You may now have following index html file:
<html> <head> <script type="text/javascript">//<![CDATA[ /** * @description * gadget initialization function */ function init() { /* your initialization code */ } /** * @description * changes the background size and images * * @param {Number} height the new gadget height * @param {String} headerUrl the new gadget header image url * @param {String} midleSliceUrl the new gadget middle repeat slice 1px height * @param {String} footerUrl the new gadget footer image url */ function setBackground( height, headerUrl, midleSliceUrl, footerUrl ) { /* code given in the article will come here */ } //]]></script> </head> <body onload="init()"> <g:background id="background" align="center" style="z-index:999;position:absolute;top:0;left:0;width:150px;height:200px;" src="./path/to/transparent.png" /> <div id="content"> </div> </body> </html>
So, let's see how to fill in those functions.
To achieve our goal, we'll separate the background into 3 parts, the top one, a repeating slice and the bottom. An improvement (for speed reason) could be to introduce a 4th image, that is a 10 or 20 pixel height image for the middle slice you will repeat, and for the last part use the 1px height slice. I'll let you do this improvement.
So, everything is ready to code, let's go.
First of all we'll code the resizing part, as we've seen before the body and the background has to be the same size, and most important the same size as the images.
// resize part // parses the height as integer, just in case var _nHeight = parseInt(height, 10); var _body = document.body || document.documentElement; var _background = document.getElementById('background'); _body.style.height = _nHeight + 'px'; _background.style.height= _nHeight + 'px';
So, now we've got the gadget at the right size, next step is to add the images to fill the background:
// fill the background image //load the images to get the sizes var _headerHeight, _sliceHeight, _footerHeight, _headerImg = new Image(), _sliceImg = new Image(), _footerImg = new Image(); _headerImg.onerror=function(){System.Debug.outputString('header not loaded')}; _sliceImg.onerror=function(){System.Debug.outputString('slice not loaded')}; _footerImg.onerror=function(){System.Debug.outputString('footer not loaded')}; _headerImg.src= headerUrl; _sliceImg.src= midleSliceUrl; _footerImg.src= footerUrl; // set the sizes _headerHeight = _headerImg.height; _sliceHeight = _sliceImg.height; _footerHeight = _footerImg.height; var _top = 0; // add the header _background.addImageObject(headerUrl, 0, _top); _top+= _headerHeight; //calculate the middle part height var _middlepartHeight = _nHeight - _headerHeight - _footerHeight; // and append the slice as many time as needed for(var _i=0; _i<_middlepartHeight; _i+=_sliceHeight) { _background.addImageObject(headerUrl, 0, _top); _top+= _sliceHeight; } // now add the footer _background.addImageObject(footerUrl, 0, _top); // That's all folk !
Here we are, we've got our resize function ! let's do the call into the init function:
setBackground( 300, './path/to/header.png', './path/to/middleSlice.png', './path/to/footer.png')
As usual i'll give some documentation link for fast find:
And also an hint, add a bigger slice (for instance 30px) to avoid too much iteration, and use the 1px slice to fill the difference. have fun !
This arcticle studies Extjs in 2.0.2 version.
I recently had to use Ext that is by the way a wonderfull library, no matter the licencing issues that occured lately. Ext.util.MixedCollection is a class allowing to store any type of data and to add, remove, filter and sort it.
Adding or removing data is quite easy, you just have to read the documentation, to filter, just give the field you want to filter and the pattern you want to match, but when it comes to sort, the documentation isn't really helpfull.
We'll first have a look to the code and then we'll give a simple sort example.
Let's find the sort method :
sort : function(dir, fn){ this._sort("value", dir, fn); }
As we can see, it is really short, it just calls a private function named _sort. Let's go deeper into the layers and study the _sort method ;)
_sort : function(property, dir, fn){ var dsc = String(dir).toUpperCase() == "DESC" ? -1 : 1; fn = fn || function(a, b){ return a-b; }; var c = [], k = this.keys, items = this.items; for(var i = 0, len = items.length; i < len; i++){ c[c.length] = {key: k[i], value: items[i], index: i}; } c.sort(function(a, b){ var v = fn(a[property], b[property]) * dsc; if(v == 0){ v = (a.index < b.index ? -1 : 1); } return v; }); for(var i = 0, len = c.length; i < len; i++){ items[i] = c[i].value; k[i] = c[i].key; } this.fireEvent("sort", this); }
As we can see, the default sort callback is defined as follows : return a - b ;, and then this callback is passed to the array sorter. So, the function format is the same as for the Array.prototype.sort function.
Let's define the environement (I will not include a test directly in the blog because of Extjs weight):
//creation var characters = new OWT.util.MixedCollection(); //populate characters.addAll([ {id: 1, first_name: 'Kyle', last_name: 'Broflovski', age: '8', phone: '555-14569'}, {id: 2, first_name: 'Eric', last_name: 'Cartman', age: '8', phone: '555-96541'}, {id: 3, first_name: 'Stanley', last_name: 'March', age: '8', phone: '555-12478'}, {id: 4, first_name: 'Kenny', last_name: 'McCormick', age: '8', phone: '555-78954'}, {id: 5, first_name: 'Leopold', last_name: 'Stotch', age: '8', phone: '555-63215'}, {id: 6, first_name: 'Jesus', last_name: 'Christ', age: '2008', phone: '555-12479'} ]);
If you want to sort this collection easyly, just use following function:
function sort_characters( asFieldName, asOrder) { var _sFieldName = asFieldName, _fSorter = function(obj1, obj2){ if( isFinite( obj1[sFieldName] - obj2[sFieldName] ) ) return obj1[sFieldName] - obj2[sFieldName] ; return obj1[asFieldName] > obj2[asFieldName] ? 1:-1 } characters.sort(asOrder || 'DESC', _fSorter); }
Here we are !
See you soon
As I said in this article, I recently used JST. After playing around with it for 2 or 3 days, I came up with the conclusion that an abstraction layer is needed, except if you want to rewrite the whole code every time !
JST documentation about the templateObject.process function says :
Note that the '''contextObject''' can contain any JavaScript object, including strings, numbers, date, objects and functions. So, calling ${groupCalender(new Date())} would call contextObject.groupCalender(new Date()). Of course, you would have to supply the groupCalender() function, which should return a string value.
So, we've got a way to define our helpers !
Let's try it :
// As usual our namespace var App = App || {}; /** * a little binding utility relying on closure. * * This function can be improved by handling the * merge of given parameters on binding and given * parameters on call so that : * <pre> * var myBindedFunc = App.bind( myFunc, myContext, param1, param2 ); * myBindedFunc ( param3 ); * <pre> * results being the same as : * <pre> * var myBindedFunc = App.bind( myFunc, myContext ); * myBindedFunc ( param1, param2, param3 ); * <pre> * or, using the call utility : * <pre> * myFunc.call( myContext, param1, param2, param3 ); * <pre> * * @param {Function} afCallback function to apply * @param {Object} aoContext the context to apply the function in * * @return {Function} */ App.bind = function( afCallback, aoContext ){ /* make a copy */ var _fCallback = afCallback; var _oContext = aoContext; /* create the closure */ return function(){ return _fCallback.apply(_oContext, arguments); }; } //let's define our helpers App.Helpers = { /** * used as an helper, writes 'foo' */ test:function(){ return 'foo'; }, /** * increments a value, to demonstrate * the binding necessity for helpers * that needs to access main context */ count:App.bind(function(){ App._counter = App._counter || 0; return App._counter++; },window) }; /* parse and process */ function parse_and_process(){ var dest = document.getElementById('jst_add_helpers_template_test_1_dest'); var tpl = document.getElementById('jst_add_helpers_template_test_1_template'); dest.innerHTML = TrimPath.parseTemplate( tpl.innerHTML).process( App.Helpers ); }
Ready to test ?
So, It seems that works, but why the hell did I use a binding ?
Well, it's all about contexts, the template is executed in process function's first parameter context. That means you are enclosed in this context, so you can't access the other variables ( such as App in the count helper's case ).
So, whenever we need to create an helper that has to access other namespaces, it is necessary to bind it to needed context. Application field of such a technique can be:
Well, That's cool, we've done with helpers definition understanding, but it can be exhausting to select helpers every-time you call a template, let's see how to make it simple.
As usual, automation means a new layer. We will need as usual a main namespace, and following sub-namespace.
The parser wrapper needs first to keep a trace of our parsed templates, so let's have a storage facility :
/* namespace */ var App; App.Templates = App.Templates || {}; /* utilities */ /** * copy all the properties from aoSource to aoDestination * @param {Object} aoDestination * @param {Object} aoSource * @return {Object} */ App.extend = function(aoDestination, aoSource){ for (var property in aoSource) aoDestination[property] = aoSource[property]; return aoDestination; }; /* storage utilities */ App.Templates.stored = {}; /** * stores a template * * @param {String} asTemplate the template name * @param {OGF.Templates.templateObject} asTemplate the parsed template * * @return {OGF.Templates.templateObject} */ App.Templates.store = function( asTemplateName, aoTemplate ){ return App.Templates.stored[asTemplateName] = aoTemplate; }; /** * reads a template * * @param {String} asTemplate the template name * * @return {OGF.Templates.templateObject} */ App.Templates.find = function( asTemplateName ){ return App.Templates.stored[asTemplateName]; };
Easy isn't it ? Now the parsing layer :
/* parser */ /** * parses a template * this function is a wrapper for the template. * * @param {String} asTemplate the template string to parse * * @return {OGF.Templates.templateObject} */ App.Templates.parse = function( asTemplate ){ return new App.Templates.templateObject( TrimPath.parseTemplate(asTemplate) ); }; /** * parses a template and store it. * This function main goal is to be binded on the request return. * * @param {String} asTemplate the template string to parse * * @return {OGF.Templates.templateObject} */ App.Templates.parseAndStore = function( asName, asTemplate ){ App.Template.store( asName, App.Templates.parse( asTemplate ) ); };
Well, you maybe noticed that we used an undeclared class called App.Templates.templateObject. Let's declare it.
This class is our abstraction layer over the TrimPath template object.
It just adds our helpers definition to the evaluation context.
/** * @constructor * @classDescription the template object wrapper for OGF * @param {templateObject} aoTemplate the template object */ App.Templates.templateObject = function(aoTemplate){ this.initialize(aoTemplate); } App.Templates.templateObject.prototype = { /** * the template object reference * @var {templateObject} */ _template:null, /** * initializes the object * @param {templateObject} aoTemplate the template object */ initialize:function(aoTemplate){ this._template = aoTemplate; }, process:function( aoDatas ){ var _oDatas = {}; if(App.Templates.Helpers) { _oDatas = App.extend( _oDatas, App.Templates.Helpers); } _oDatas = App.extend( _oDatas, aoDatas || {}); return this._template.process(_oDatas); } };
Well, to define helpers you just need to add them to the App.Templates.Helpers namespace. Here are some of mine :
App.Templates.Helpers = { /** * include an already compiled template * * @param {Object} asTplName * @param {Object} context */ include:(function( asTplName, aoContext){ return App.Templates.find(asTplName).process(aoContext); }).bind(window), /** * write a tag opening with the given tagname and the given properties * example : * ${%open_tag('div', {id:"foo",class:"bar",style:{width:'50%',background:'#000',color:'#fff'}})%} * I'm currently looking for a solution to make it more readable */ open_tag:function( asTagName, aoProperties ){ var _oProperties = aoProperties || {}; var _aProperties = [asTagName]; for( var _i in _oProperties) { var _val = ''; if(typeof(_oProperties[_i]) == 'object') { _val = []; for(var _j in _oProperties[_i] ) { _val.push(_j+':'+_oProperties[_i][_j]); } _val = _val.join(';'); } else { _val = _oProperties[_i]; } _aProperties.push(_i+'="'+_val+'"'); } return '<'+_aProperties.join(" ")+'>'; } }
To conclude, I'll say that I use this for the binding when I include libraries into a closure (As I do for every code in this blog) to avoid polluting the main namespace with utilities functions or prototype extensions (just as used in the JST code with the array prototype for IE5 bugfix)
Some improvements can be done, such as utilities functions to register new helpers or a function to update the element with template result (and optionally evaluate scripts).
You can also allow some options to determines which modifiers to use, to auto-load them...
It can sometimes be hard to make cross platform applications because of the different implementations. Attaching an event on a object can become hell if you don't use an abstraction layer. First of all, let's see the normal cross-browser approach :
function myEventFunc ( aeEvent ) { // just get the event if we are using IE var _eEvent = aeEvent || window.event ; // now do what we want to do /* ... */ } /* now attach this function on the elements */ if( document.getElementById ) { var _eElt = document.getElementById ('myElement'); if( _eElt.addEventListener ) // W3C (Gecko, webkit, Opera) { _eElt.addEventListener( "myEventName", myEventFunc, false ); } else if( _eElt.attachEvent ) // MSIE { _eElt.attachEvent ( "myEventName", myEventFunc, false ); } else { throw new Error('Your browser is very old ! please upgrade.'); } } else { throw new Error('Your browser is very old ! please upgrade.'); }
Well, let's see what solutions we have to avoid such a big amount of code.
Every library used a way to get rid of such compatibility problems, and are using a single interface to do stuff.
First thing that comes to mind to add new behavior to an existing class is the prototype extension. Even if it can pose problems sometimes (such as for arrays), it can be a nice way to achieve our goal. The HTML elements class is supposed to be HTMLElement, so extending the HTMLElement.prototype should do the trick.
If quite every browser allows this extension, Internet Explorer doesn't like to make web developer's life easy, so this can do the stuff in quite every case, except for the major browser.
You can find more informations and workaround at following locations :
As the prototype extension isn't supported natively for every browser, let's see another way. We still need to extend elements, so when will be the perfect time ? When using them of course. Which process is always used before using an HTMLElement ? selection !
To extend elements we'll need a namespace with all our functions and a selection function. Let's have a piece of code to illustrate :
/** * the application namespace */ var App = {}; /** * the element functions namespace */ App.HTMLElement = { addEvent: function(aElement, asEvent, acCallback, abBubbles){ var _bBubbles = abBubbles || false; if(aElement.addEventListener ) // W3C (Gecko, webkit, Opera) { aElement.addEventListener( asEvent, acCallback, _bBubbles ); } else if( aElement.attachEvent ) // MSIE { aElement.attachEvent ( 'on'+asEvent, acCallback, _bBubbles ); } else { throw new Error('Your browser is very old ! please upgrade.'); } } }; /** * our simple selector * @param {String} asElementId the id of the element * @return {HTMLElement} */ App.get = function(asElementId){ var _eElement = document.getElementById(asElementId); if( ! _eElement.extended ) { for(var _i in App.HTMLElement) { _eElement[_i] = (function(aMethod,eElement){ var _method = aMethod; var _elt = eElement; return function(){ /* transforms the arguments into an array */ var _args = [_elt]; for(var _j in arguments) { _args.push( arguments[ _j ] ); } /* forces the context */ return _method.apply( _elt, _args ); } })(App.HTMLElement[_i], _eElement); } _eElement.extended = true; } return _eElement; };
Well, this piece of code should work, let's try it :
Here is the code I used to set this behavior up
/** * load event to initialize the div */ function extends_elts_test1_init(){ App.get('extend_elts_test_1').addEvent('mouseout', extends_elts_test1_out).addEvent('mouseover',extends_elts_test1_over); } /** * function attached to the mouseout event of the element */ function extends_elts_test1_out(){ App.get('extend_elts_test_1').style.backgroundColor = "#ff0"; } /** * function attached to the mouseover event of the element */ function extends_elts_test1_over(){ App.get('extend_elts_test_1').style.backgroundColor = "#0ff"; } /* now register the load event */ if(window.addEventListener) { window.addEventListener('load',extends_elts_test1_init,false); } else if(window.attachEvent) { window.attachEvent('onload',extends_elts_test1_init,false); }
To conclude on the elements extension, let's say that's prototype's way of doing. You can improve this code by mixing it with the HTMLElement.prototype extension. Just call the namespace HTMLElement, declare it as a new object and extend the prototype if it isn't natively done.
Well, this method is nice, but making a closure for every function every time an object is extended seems to be a bit a heavy way isn't it ? let's see what else can be done.
Previous approach was based on selection, let's keep it, but this time we'll build a complete wrapper around it, a wrapper that references our element and implements our new methods.
/** * our namespace, as usual */ var App = {}; /** * the elements methods */ App.Element = function( anElement ){ this._element = anElement ; }; App.Element.prototype = { /** * the element instance * @var {HTMLElement} */ _element : null, /** * cross-platform event observer * @param {String} asEvent the event name * @param {Function} acCallback the callback * @param {Boolean} abBubbles the bubbling flag [optionnal] * * @return {App2.Element} */ addEvent: function( asEvent, acCallback, abBubbles){ var _bBubbles = abBubbles || false; if(this._element.addEventListener ) // W3C (Gecko, webkit, Opera) { this._element.addEventListener( asEvent, acCallback, _bBubbles ); } else if( this._element.attachEvent ) // MSIE { this._element.attachEvent ( 'on'+asEvent, acCallback, _bBubbles ); } else { throw new Error('Your browser is very old ! please upgrade.'); } return this; }, /** * set a style property. needed for the demo. * @param {String} asProperty the property name * @param {String} asValue the new property value * * @return {App2.Element} */ setStyle:function( asProperty, asValue ){ this._element.style[asProperty] = asValue ; return this; } }; /** * once again the selection function * @param {String} asElementId the element to select id * @return {App.Element} */ App.get = function( asElementId ){ var _Element = document.getElementById( asElementId ); return new App.Element( _Element ); }
Want a try ? let's go using quite the same code as earlier, except for the style property setting, I used the newly created wrapper.
This way is cleaner, but needs more code to be done, because you have to implement every method, none are given by default !
This approach is the one chose by Ext framework, and seems to be the fastest at execution time ( I'm still impressed with the result they achieved with this framework ).
Any method you choose, first improvement will be to add a method for registering new "native" methods. Whatever, except if you are embarrassed with some library functionalities (such as prototype Array extension), or if application is so specific that no library fit your needs, it is always better to rely on existing maintained code.