UNPKG

@twobirds/microcomponents

Version:

Micro Components Organization Class

437 lines (317 loc) 15.4 kB
## History ### Content - [Before 2006](#before-2006) - [DOM and Javascript in 2006](#dom-and-javascript-in-2006) - [How it started](#how-it-started) - [Initial Specification](#initial-specification) - [Unforeseen Problems](#unforeseen-problems) - [The Result](#the-result) - [Release on Ajaxian](#release-on-ajaxian) - [First SPA ever](#first-spa-ever) - [Some Code Snippets](#some-code-snippets) - [The Fallout](#the-fallout) ## Before 2006 Starting 1980, I programmed client-server or standalone applications for DOS and MS Windows. Around 1995 I switched to web programming. Before 2006 I was working on server side modular components using PHP, with little client side automation using Javascript. Starting 2004 I developed twoBirds v1, vaguely centered around a web component idea. ## DOM and Javascript in 2006 The Internet has just existed for a couple of years for private use. Douglas Crockford had released the [JSON specification](https://en.wikipedia.org/wiki/JSON) in 2000. The [XMLHttpRequest](https://en.wikipedia.org/wiki/XMLHttpRequest) was specified by Microsoft in 2001. As of IE7 (2006) it was adopted by every browser, albeit buggy in IE(!). The Javascript engines and the DOM model in contemporary browsers slightly differed from each other then. John Resig had released [jQuery](https://en.wikipedia.org/wiki/JQuery). [Prototype](https://en.wikipedia.org/wiki/Prototype_JavaScript_Framework) was its main competitor then. Both of these libraries were immensely helpful, since they allowed for cross-browser access to the DOM and leveraged Javascript inconsistencies. I was a self-employed full-stack programmer then. **PHP** and **Javascript** knowledge was all it took in these lucky days to call yourself a full stack programmer. ## How it started With a call, obviously. The requirements were: convert an Excel sheet into a web application, so insurance company staff could plan customer appointments and the company could automate monthly travel expense refunds. Easy, of course I can do that. LAMP and jQuery will do it for me. Until it didn't. jQuery doesn't help you in any way structuring complex code. But my existing twoBirds v1 could. I saw the chance to let this project fund twoBirds v2. I called back the customer and explained. In essence we agreed to this: the customer will pay the same sum, but it will take double as long. The updated twoBirds v2 library will continue to be my royalty, but he is given a lifelong license to it. The project was a grossly underestimated, as I found out. Estimated at 6 month, it finally took 12 and most of this was twoBirds, especially the cross-browser part. Spoiler: it worked in the end. ## Initial Specification twoBirds was to fulfill these requirements: - **cross browser**. This led to some unforseen consequences, which I will describe below. - **on-demand loading** of Javascript files. Also on-demand loading of associated **HTML templates** and **CSS**. This must be **recursive**, so MCs can recursively load other MCs and their requirements. - **internal Micro Component repository** in the resulting application to avoid multiple loading for multiple instances of a MC in the page. - **leverage XMLHttpRequest inconsistencies**, especially the famous "closure bug" that led to memory leaks in IE. - **jQuery** as the selector engine. Thanks again John, without your code it would have been much harder. We all stand on the shoulders of giants. ## Unforseen Problems Browsers tested: - **IE5.5**+ - **Netscape Communicator**, later **Firefox** also - **Chrome** - **Opera** - **Safari** ( Safari for Windows at least, see below) - **Konqueror**, since I was working on OpenSUSE. This resulted in some problems, i.e. (list is not complete): ### No debuggers Firebug wasn't there yet. When it finally arrived as a firefox extension, I was really cooking. Other than that there was no reflection mechanism, so no runtime introspection for most browsers. The Proxy() class didn't exist yet. Microsofts skript debugger didnt help a lot with other browsers. ### Missing equipment Since I had no Apple hardware, I couldn't reliably test for Safari on MAC. Safari for Windows was slightly different from Safari on MAC. At least I got it working there but could not promise for MAC hardware. I was told by others it worked there as well, though. ### Meager internal documentation for browsers It took me month to find out the best way to on-demand load Javascript code, templates and CSS, and how to avoid FOUC. ### timeout() Cross-browser support in 2006 meant Konqueror also. But Konqueror v1 had a funny bug: it could only handle one (1) timeout via setTimeout() at a time. I had to come up with a workaround. So I implemented tb.timer() which used a single timeout to poll a list of timestamped callbacks every 100 ms. ```javascript tb.timer = { destroylist: [], addlist: [], list: [], ms: 100, // public add: function( pCallback, pTimeMs ) { var myDate = new Date(); var myIdentifier = '_to'+tb.misc.getid(); var myTime = myDate.getTime() + pTimeMs; var myArray = [ myTime, '( '+pCallback+' )', myIdentifier ]; tb.timer.addlist.push( myArray ); if (tb.timer.list.length === 0 ) { window.setTimeout( 'tb.timer.timeout()', 10); } return myIdentifier; }, abort: function( pIdentifier ) { tb.timer.destroylist.push( pIdentifier ); }, //private destroy: function( pIdentifier ) { var myNewlist = []; var i=0; for ( i in tb.timer.list ) { if ( tb.timer.list[i][2] !== pIdentifier ) { var myObj = tb.misc.cloneObj( tb.timer.list[i] ); myNewlist.push( myObj ); } } tb.timer.list = myNewlist; }, timeout: function() { while ( tb.timer.addlist.length > 0 ) { tb.timer.list.push( tb.timer.addlist.pop() ); } while ( tb.timer.destroylist.length > 0 ) { tb.timer.destroy( tb.timer.destroylist.pop() ); } if ( tb.timer.list.length < 1 ) { tb.timer.ms = 100; return; } else { var myNewList = []; var myMinVal = 0; while ( tb.timer.list.length > 0 ) { var myDate = new Date(); var myTime = myDate.getTime(); if ( tb.timer.list[0][0] <= myTime ) { var myEval = tb.timer.list[0][1]; eval( '( ' + myEval + ' )' ); } else { if ( tb.timer.list[0][1] < myMinVal || myMinVal === 0 ) { myMinVal = tb.timer.list[0][1]; } myNewList.push( tb.timer.list[0] ); } tb.timer.list.shift(); } tb.timer.list = myNewList; if ( myMinVal > 0 ) { myDate = new Date(); tb.timer.ms = myMinVal - myDate.getTime(); } else { tb.timer.ms = 100; } while ( tb.timer.addlist.length > 0 ) { tb.timer.list.push( tb.timer.addlist.pop() ); } window.setTimeout( 'tb.timer.timeout()', tb.timer.ms); } } }; window.setTimeout( 'tb.timer.timeout()', 0); ``` Then I implemented ... - **tb.timeout()** to use tb.timer(). - **tb.interval()** also, which used tb.timeout(). This I did with the added benefit that my implementation avoided runovers if the implemented functionality lasted longer than the interval. While I was at it, I also implemented... - **tb.wait()**, which used tb.interval() to wait for a condition to be true, and then called the callback. - **tb.observe()**, which was a tb.interval() implementation of tb.wait(). At this point I had a solution polling **Observables** in 2006. ## The Result So far I had a solution for **requirement loading**, **templating**, **observables** and **web components** as early as 2006. - **tb.require()** probably influenced [requireJs](https://requirejs.org/) and in turn the CommonJS specification. - **Templates** are now a coding standard and implemented in various ways using backtick strings ( `string with ${placeholder}` ) and the DOM template tag. - **Observables** are now a coding standard and implemented in various ways using getters/setters or the Proxy() object. - [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) initially contained a definition for user built in custom elements, which was a functional copy of twoBirds web components. Look at the screenshots attached at the end of this document. That is the application I developed, with a code example attached. ## Release on Ajaxian twoBirds v2 was open sourced and anounced on ajaxian.com on April 2nd 2007, you can find the original article on archive.org. ![Ajaxian Screenshot 1](./ajaxian-1.png) ![Ajaxian Screenshot 2](./ajaxian-2.png) Which led to this comment: ![Ajaxian Screenshot 3](./ajaxian-3.png) RTFM, man. Oh nevermind, Dean. I forget to add one. Autists... twoBirds as a framework will be dissolved into modular micro component based functionality. RIP. It served me well for 20 years. ## First SPA ever The first web component based single page application (SPA) that ever existed - to my knowledge. Prove me otherwise ;-). ![app1](./rnd0023-1.jpg); ![app2](./rnd0023-2.jpg); ![app3](./rnd0023-3.jpg); Yes, I contemplated programming a web based Excel, as in Google Calc. And web component based cockpit instruments for airplanes. As I think of it: **SpaceX** uses **Web Components** for cockpit instruments in their **Dragon Capsule**. ;-) ## Some Code Snippets This is the code for the left part of the table shown in the pictures above. ```javascript self.tourtable.leftcolumn = { // new object showcounter: 0, datecache: [], // cache für HTML code init: function (pDivId) { //tb.element.show( null, 'tourtable', 'leftcolumnline'); // preload line code tb.element.require( [ [ 'css', 'tourtable', 'leftcolumn'], [ 'tpl', 'tourtable', 'leftcolumn'] ], 'tourtable.leftcolumn.display("' + pDivId + '")', true ); }, display: function (pDivId) { if ( tourtable.middlecolumn.day == -1 ) { // clear HTML array tourtable.leftcolumn.datecache = []; } else { tourtable.leftcolumn.datecache[tourtable.middlecolumn.day] = ''; var pDay = tourtable.middlecolumn.day; } try { var myDiv = document.getElementById( pDivId ); } catch (e) { tb.timer.add( 'tourtable.leftcolumn.display("'+pDivId+'")', 100 ); return; } tourtable.leftcolumn.showcounter = 1; //tb.debug.add("<p>Element <font color='magenta'><b> display: </b></font> tourtable.leftcolumn in " + pDivId + "</p>"); var myHtml = tb.loader.tplget('tourtable','leftcolumn'); var myNewRegEx = new RegExp('%tourtableleftrows%'); myHtml = myHtml.replace(myNewRegEx, ''); tb.div.replace(pDivId,myHtml); myHtml = tb.loader.tplget('tourtable','leftcolumn'); var myDate = new tb.tbDate(); myDate.setDate(1); var myMonth = myDate.getMonth(); var myRows = ''; var myDateRows = ''; var myId = 1; var myLastDate=''; var createDate=''; //tb.debug.add('<p>leftcolumn <font color="blue">Act:</font> '+middlecolumn.getlength()+'</p>'); //var myDateList = ''; if ( tourtable.middlecolumn.getlength() > 0 ) for ( x=0; x < tourtable.middlecolumn.getlength(); x++ ) { var thisRecord = tourtable.middlecolumn.act.Data.data[0][x]; if ( tourtable.middlecolumn.currentId == null ) { tourtable.middlecolumn.currentId = thisRecord.id; } var myDate = new tb.tbDate(); myDate.setFromMysqlDateString( thisRecord.datum ); var thisDateIndex = myDate.getDate(); if ( ! tourtable.leftcolumn.datecache[thisDateIndex] ) { // create if necessary tourtable.leftcolumn.datecache[thisDateIndex] = ''; } if ( tourtable.leftcolumn.datecache[thisDateIndex] == '') { // create if necessary createDate = thisDateIndex; } var myNextDate = ( x == tourtable.middlecolumn.getlength()-1 || tourtable.middlecolumn.act.Data.data[0][x+1].datum != thisRecord.datum ) ? true : false; if ( !pDay || thisDateIndex == pDay || createDate == thisDateIndex ) { // pdate not given or this is the given date tourtable.leftcolumn.datecache[thisDateIndex] += tourtable.leftcolumnline.make( thisRecord.id, ( myLastDate == thisRecord.datum ? false : true ), myNextDate, myDate, thisRecord.ort ); } myLastDate = thisRecord.datum; } myRows = tourtable.leftcolumn.datecache.join(''); myNewRegEx = new RegExp('%tourtableleftrows%'); myHtml = myHtml.replace(myNewRegEx, myRows); //tb.div.replace(pDivId,myHtml); tb.misc.innerHTML(pDivId,myHtml); tourtable.leftcolumn.showcounter = 0; } }; ``` This is the example code they referred to on ajaxian.com: ```javascript index = {}; user = {}; tourtable = {}; index.index_body = { init: function (pDivId) { tb.element.require( [ [ 'css', 'index', 'index_body'], [ 'tpl', 'index', 'index_body'] ], 'index.index_body.display("' + pDivId + '")', true ); }, display: function (pDivId) { var myHtml = tb.loader.tplget('index','index_body'); tb.div.replace( pDivId, myHtml ); tb.element.require( [ [ 'js', 'tourtable', 'ddata'] , [ 'js', 'tourtable', 'rightcolumn'] , [ 'js', 'tourtable', 'leftcolumn'] , [ 'js', 'tourtable', 'leftcolumnline'] , [ 'js', 'tourtable', 'middlecolumn1line'] , [ 'js', 'tourtable', 'middlecolumn2line'] , [ 'js', 'tourtable', 'middlecolumn3line'] , [ 'js', 'tourtable', 'middlecolumn1'] , [ 'js', 'tourtable', 'middlecolumn2'] , [ 'js', 'tourtable', 'middlecolumn3'] , [ 'js', 'user', 'user_login'] , [ 'js', 'tb', 'pos'] , [ 'js', 'tb', 'md5'] , [ 'js', 'tb', 'tbDate'] , [ 'js', 'tb', 'tbCurrency'] , [ 'css', 'tourtable', 'middlecolumn'] , [ 'css', 'tourtable', 'rightcolumn'] , [ 'css', 'tourtable', 'leftcolumn'] , [ 'css', 'tourtable', 'leftcolumnline'] , [ 'css', 'tourtable', 'middlecolumn1'] , [ 'css', 'tourtable', 'middlecolumn2'] , [ 'css', 'tourtable', 'middlecolumn3'] , [ 'css', 'tourtable', 'middlecolumn1line'] , [ 'css', 'tourtable', 'middlecolumn2line'] , [ 'css', 'tourtable', 'middlecolumn3line'] , [ 'tpl', 'tourtable', 'rightcolumn'] , [ 'tpl', 'tourtable', 'leftcolumn'] , [ 'tpl', 'tourtable', 'leftcolumnline'] , [ 'tpl', 'tourtable', 'middlecolumn1line'] , [ 'tpl', 'tourtable', 'middlecolumn2line'] , [ 'tpl', 'tourtable', 'middlecolumn3line'] , [ 'tpl', 'tourtable', 'middlecolumn1'] , [ 'tpl', 'tourtable', 'middlecolumn2'] , [ 'tpl', 'tourtable', 'middlecolumn3'] , [ 'tpl', 'tourtable', 'middlecolumn'] , [ 'js', 'tourtable', 'middlecolumn'] ] ); tb.element.show( 'toprightcontainer', 'user', 'user_login' ); } }; ``` When I find the time I will turn into standalone web component using local storage, since it is a historic archievement IMHO. For now I have more pressing tasks to do. ## The Fallout I am an autist, which I didn't know back then BTW. Above all that means, if you focus on something, you keep focussed for life. And I focused on twoBirds, really. So I was burnt as a programmer, because I have a hard time working with the current standard frameworks. Now try to find a programmer job without using standard frameworks. In the end I lost my existence 3 times over the past 20 years, with all the consequences. No pity requested, I always pulled myself out of the mud. Thats just the way things go. But if feel tempted, you can [fund this](https://gofund.me/076c614c). I really want **Micro Components** to become a web standard also. I don't regret still working on the basic idea. As long as I can, I will continue doing so.