Web Components Development Journal

A Web Components widget library development diary

Starting from today, I will write about all the stage of the development of the library.

Ajax data model

Web components may be used to create complex web services if used with ajax. So I create a web components to do ajax calls. The web component is called webf-ajax and here is how it can be used: <g-combo text="choose one" > <webf-ajax url="http://localhost:81/xtoolkit_json.php" action="list-tag"> <g-popup-item></g-popup-item> </webf-ajax> </g-combo> The ajax component can be use just to make ajax calls from javascript or to retrieve data dinamically. If the action attribute is set, the ajax call is performed when the component is created. in this case: http://localhost:81/xtoolkit_json.php?action=list-tag the expected data from the ajax call is { msg:"ok", code:0, data: [ ...items...] } then it creates one instance of its children (g-popup-item) for each data item and appends it to the parent web component.

Ajax feedbacks.

If the ajax component is inside a component implementing the WEBF_AJAXER feature, it uses the following methods from it: _showWait: function(){ /* show the waiting label */ }, _showMessage: function(msg){ /* show the message label */ }, _showError: function(jqXHR, textStatus){ /* show the error label */ }, _hideWait: function(){ /* hide messages */ }, So these methods should be implemented by the container component. <AJAXER> <g-combo text="choose one" > <webf-ajax url="http://localhost:81/xtoolkit_json.php" action="list-tag"> <g-popup-item></g-popup-item> </webf-ajax> </g-combo> </AJAXER>
You can also interact with this component via javascript: $("webf-ajax")[0].addListener("ajax",function(event_name,event){ // event.start=1 means before the ajax call // event.end=1 after the call (if it's ok) // event.error=1 after the call if there is an error }); $("webf-ajax")[0].doAjax( { /* data to be send to server (via post) **/ }, { action:"insert", // this parameter is set on the GET onFails: function(req,status){}, // if an error occurred onDone: function(res){ // it's ok res.data is the response from the server for (var i = 0; i < res.data.length; i++) { var obj = res.data[i]; } } });

Runtime data binding

Sometime is useful to completely separate code from presentation. look at this issues javascript object: var issueModel = { title: "issue title", data: { items: [] }, id:0, newItem: function(){ this.id++; return { id: this.id, title: "title"+this.id, text: "text"+this.id, status:{ author: { name: "admin" } }, tags: [], labels: [{title:'test'}] } }, // this is an action newItemAction: function(){ // define the model of dialog var dialog=this._hasDescendantFeature("WEBF_POPUP"); if (dialog){ dialog._runtime=this._runtime.newItem(); } }, // this is an action newItemOkAction: function(){ //webf_log("newItemOkAction",this); var issues=$("#issues")[0]; var dialog=this._hasAscendantFeature("WEBF_POPUP"); //webf_log("newItemOkAction dialog",dialog); if (dialog){ // do checks var model=dialog._runtime; model.labels[0].title=model.tags.value; issues._add_item(model); dialog._hide_popup(this); } }, newItemCancelAction: function(){ var dialog=this._hasAscendantFeature("WEBF_POPUP"); if (dialog){ dialog._hide_popup(this); } // do stuff }, addItem: function(item){ this.data.items.push(); } } this code manages a little issues service. It defines three actions, one to show a new item dialog, one to add the item and another to just close the dialog. All the management code is inside a single javascript object (issueModel). The actual web component is: <g-layout runtime="js:issueModel" is-example="true" def="[*,][{20px}][1]"> <g-button runtime="action:.newItemAction" text="New Issue" layout="0,1"> <g-dialog-3 anchor="min-width=400px top=bottom right=aright"> <g-layout def="[:authorL +10px,:author *][:titleL][:title 1][:textL][{100px} :text 1][{10px}][[:tags],*,[:ok,3px,:cancel]]"> <SPAN layout="authorL">Author:</span> <g-input-1 runtime="js:.status.author.name" layout="author"></g-input-1> <SPAN layout="titleL">Title:</span> <g-input-1 runtime="js:.title" layout="title"></g-input-1> <SPAN layout="textL">Text:</span> <g-textarea runtime="js:.text" layout="text"></g-textarea> <g-combo text="choose one" layout="tags" runtime="js:.tags" selection="multiple"> <g-popup-item label="WEBF">k</g-popup-item> <g-popup-item label="WEBF_CONTAINER">k</g-popup-item> <g-popup-item label="WEBF_CORE">k</g-popup-item> </g-combo> <g-button-1 runtime="action:.newItemOkAction" text="Add" layout="ok"></g-button-1> <g-button-1 runtime="action:.newItemCancelAction" text="Cancel" layout="cancel"></g-button-1> </g-layout> </g-dialog-3> </g-button> <issue-list id="issues" runtime="js:issueModel" layout="2,0"> </issue-list> </g-layout>
Notice the attribute runtime="js:issueModel" for the g-layout tag, it binds the web components with the global variable issueModel while the runtime="action:.newItemAction" attribute binds the button action with the issueModel newItemAction method.

When you bind a web component with an object, the web component will has a new property:

_runtime = object referenced by the attribute runtime

Runtime could be binded as follow:

  • webf:selector find a web components by the selector and binds it's _runtime
  • action:function Define the component's action with a global function
  • js:model Bind the component runtime with a global variable
  • action:.function Define the component's action with a method of a parent runtime
  • js:.model Bind the component runtime with a field of a parent runtime
The last two methods, uses parent runtime to bind. Take a look. newItemAction: function(){ // take the dialog var dialog=this._hasDescendantFeature("WEBF_POPUP"); if (dialog){ // I assign a new runtime to the dialog. dialog._runtime=this._runtime.newItem(); // since now all dialog's children input object reference are relative to the new created item object. // except for the actions (since the parent reference hasn't such a field) } },

Layout

The g-layout web component is enough good, but I would like to get more from it. Suppose you want a layout a from of this kind. (--label:)(---input---) (--label:)(---input---) (---------space-------) (-------)button-button The layout should be: [1/4,3/4] [1/4,3/4] [] [*,,3px,] I would like to:

  • -allow to define heights for cells
  • -allow to define labels for cells
  • -allow to define alignments to cells
  • -allow rows inside a cell.
so the new layout definition will be [:name_label right middle 1/4,:name 3/4] [:surname_label right middle 1/4,:surname 3/4] [{10px}] [*,[:ok,3px,:cancel]] the cell name_label will be right/middle aligned, and uses 33% of the space. {10px} means the height is 10 pixels. Now I can use it in the following way <g-layout def="[:name_label right middle 1/4,:name 3/4][:surname_label right middle 1/4,:surname 3/4][{10px}][*,[:ok,3px,:cancel]]"> <span layout="name_label">Name:</span> <g-input layout="name"/> <span layout="surname_label">Surname:</span> <g-input layout="surname"/> <g-button layout="ok" text="Ok"> <g-button layout="cancel" text="Cancel"> </g-layout> take a look: Name: Surname: It should be interesting to have a solution like [:label right middle 1/4 +3px,:input 3/4]+ [{10px}] [*,[:button +3px]+] <g-layout def="[:label right middle 1/4,:input 3/4]+[{10px}][*,[:button,3px]+]"> <span layout="label">Name:</span> <g-input layout="input"/> <span layout="label">Name:</span> <g-input layout="input"/> <g-button layout="button" text="Ok"> <g-button layout="button" text="Cancel"> </g-layout>

Layout

A simple web components to layout html nodes. Usage: <g-layout def="[*,][120px|*][1/2]"> <span layout="0,0">Row 0 Col 0 (fill with available space)</span> <span layout="0,1">Row 0 Col 1 (use node size)<gspan> <span layout="1,0">Row 1 Col 0 (Height 20px)</span> <span layout="2,0">Row 2 Col 0 (use half width)</span> </g-layout> take a look: Row 0 Col 0 (fill with available space) Row 0 Col 1 (use node size) Row 1 Col 0 (Height 120px) Row 2 Col 0 (use half width)

Data Model

Can a web component have a complex data model? And how to interact with? My library use differents approach to define datamodel for a web component. <g-tag model_field="model_value"/> <g-tag> <model_field>value</model_field> </g-tag> <g-tag> <script type="text/json">{ field: value }</script> this model is suitable for web component building, it is also stored into it (can be accessed via this._data.model). But how can I use it from implementation code? var data={ items:[ { href: "link1", clickCount: 0}, { href: "link2", clickCount: 0} ] } TAG: ...{ ... t.e(data.items,function(item){ t.p("A").att("href",item.href).p("") }); } apply_after_impl: ...{ var data=this._data.model; $("A",this._webfImpl).click(function(){ // which data.item I should use ? }); } Well I change the template builder so that it now has to more methods: map(list) // store the list and add a new attribute model_key=index into array matt(field name, field ref, list) // add field_name as attribute with value field_ref[field_name] // store field and list and // add a new attribute model_key=index into array so now i can use this mapping to access the datamodel. To be more confortable I add a new Feature called WEBF_MODEL_MAP. It stores the builder into the webcomponent (this._data.builder) and export new methods _getModelFor(node) // if the node has a model_key, return the stored array which shoud be [field,item{,model}{,...}] _applyModelFor(node,f) // if the node has a model_key, call f using the stored array as arguments. so now my event listener will be TAG: ...{ ... t.e(data.items,function(item){ t.p("A").matt("href",item).p("") }); } apply_after_impl: ...{ var self=this; $("A",this._webfImpl).click(function(){ var fRef = self._getModelFor(this); if (fRef){ var key=fRef[0]; var item=fRef[1]; item.clickCount++; } }); } or apply_after_impl: ...{ var self=this; $("A",this._webfImpl).click(function(){ var fRef = self._applyModelFor(this,function(name,item){ item[name].clickCount++; }); }); } Take a look:

Links

This are a simple link web components (click to open a popup) .

Tooltips

When I show a tooltip, none of the poup should be closed.

WEBF_ACTION revisited

This feature, manage actions on web components, Some time it conflict with the windowManager. For Example if there is an opened menu when I click a button, the menu is not closed because the Action stops the event propagation. To avoid this, I call the _close method of the windowManager inside the action. With this behaviour, now Accordion should use the action features, but the action should use a selector to manage the click node, so I add a private component data field "actionSelector" which is a string selector or a funtion. I use it to find the node on which a click fires the action. Now the Accordion web component could use actions to toggle the panel. Since the action click method now can be executed using a selector, the this could be different from the actual component, so the _action method is called with new parameters "self" is the component itself and "node" is the actual node. Now you can add a "href" to change page.

A CSS TRICK footer menu Web Component

Delegate Creation

When a web component is created, not necessarily it should also be implemented (inside html generated). In fact dialogs, menu etc. shouldn't. They could be created and destroyed as needed. I Add a new Feature called WEBF_DELEGATE_CREATION. A component with this feature will be created and destroyed as needed.

Parent-child relationship

I found a bug on the combo component. It didn't showed the popup menu. The combo dinamicaly creates a g-popup component. Since my template engine uses innerHTML to add code, The Inserted method was not called. I've to change a little in the WEBF_CONTAINER feature. Now it works: But i suppose i will work again on this feature.

Today I was thinking on how to let web components been stylezable.

Menu Bar

A Menu bar is almost like a popup except that every items are showed inline. So I add a property direction to the popup component, the item will use this property to show horizontal or vertical.

It works, but a menu bar couldn't be closed, so I create a new component, g-menu-bar I try to extend a list component but it didn't work, so I create a brand new one.

Ok, but sub menu are showed on the right of the item. So I use the anchor. Anchor sayes how popup is showed. When I create a popup I decide which anchor to use. Ex. a popup inside an item is showed on the right. I simply add a check saying that the anchor of a popup inside a WEBF_HORIZONTAL_LIST is "top=bottom left=left".

A Menu bar

A little bug

I found I little bug on parent child relationship. I know it will be. now it seems ok.

istlye

To customize internally a web component, i will use a special attribute called istyle it act like the standard html style except that it applies to the implementation root node.

A Menu bar at full width (istyle="width: 100%")

More on Menu bar

I hide the feature WEBF_SELECTION from the Menu bar, because the menu item shouldn't remain selected after a click.

Some tests

This are some strange cases Menu with other components has menu items Menu with a menu has menu items