3.0 beta 2

Issues with composite nested controls in subclassed objects

Background

About a year ago, I needed a calendar widget to use as a date picker. I couldn't find exactly what I wanted so I wrote my own, modelling it on the features I wanted from other ones.

At one point I thought why not write it in the AW framework rather than pure Javascript. Although I didn't have the time to do it then, I revisited this recently.

It took me a little longer than I expected (mainly to get all the CSS rules correct between the 3 browsers I tested against plus dealing with the different style issues between strict and quirks mode).

Design took me a while as I tried various things out before I settled on doing it in a way similar to Carlos' approach (using horizontal panels).

The overall code, however, was much shorter and easier.

I've put the code up as it currently stand here (for strict mode) -

http://www.users.on.net/~asm/calendar/

with the quirks versions here -

http://www.users.on.net/~asm/calendar/quirks.html

I also wrote a custom SQL Date format subclass to support it. The code for that is based on Steve Levithan's date/time format routines, found here -

http://blog.stevenlevithan.com/archives/date-time-format

My date formats are based on Sybase ASE's date styles (I have comments on Steve's page with links to Sybase's docs). The formats can easily be changed/extended to support MS SQL Server. (Not sure about Oracle and other databases though.)

Apart from formatting dates, it can also validate an input date against a specified style.

The Issues

The main problems I have are -

1) No matter what I try, I just can't get the grid cells to refresh when changing the month/year. I've tried every suggestion mentioned in the forum here. The DOM gets updated correctly, just not the displayed HTML.

2) There is a big problem with the IDs of controls/templates. If I use controls, they get unique IDs and work - except when you try to use more than one widget. Then they conflict with each other.

The controls should get their IDs from the parent but they don't. I can't work out why.

If I use templates, their IDs are incomplete. This has been reported before but the suggested solutions don't help.

As it stands, I've put controls in the top horizontal panel and templates in the bottom horizontal panel to illustrate the problems. There are some comments in the code to help identify them.

Similar problems reported in the past were -

/datagrid.12675/input-getting-renamed.html
/datagrid.13500/error-with-button-inside-panel.html

Alex mentions nested controls here -

/datagrid.17531/input-box-in-panel-with.html

but it appears that its still a problem.

This problem is also related -

/datagrid.8453/object-serialization-not-working.html

Any solutions to these 2 problems would be appreciated but I suspect the second one may need to wait for a future version.

Finally, there are a few issues with the AW styles between strict and quirks mode. Strict seems to add left and right padding of 4px to most objects.

I had to write a lot of CSS rules to manage this in strict mode to try and keep both versions looking the same. A side-by-side examination of both shows that the combo and input at the top of the calendar widget aren't laid out exactly the same. IE, in particular, has a problem truncating some text in the combo in quirks mode (just happens with the string "September").

The combo button isn't offset correctly in strict mode with both Google Chrome and IE.

However, the most unusual thing I found was with the grid rows in IE in quirks mode. It appears that the rows aren't offset correctly. I had to add a specific rule to set the top to -1px for IE (see CSS .aw-hpanel-middle rule).

Tooltips in Google Chrome are annoying too. They appear too soon and remain for too long. Any mouse movement over the same element brings the tooltip back. However, this isn't an AW issue and I probably won't leave the tooltips in.
Anthony
September 24,
Anthony,
I remember trying same thing (clicking a button/combo on top panel) for my calendar, but I was unable to refresh data too, and that was the main reason because I use column-headers as buttons.

I spent several hours on that, so I found a past testing dummy sample
that reproduces it well.
The curious thing is that data is refreshed if you do enough horizontal scroll ( manually) [except column 5 ?? ] or click a header to sort.
On the other hand if virtualMode is set to false sorting works (but not manually scrolling).

About the second issue, I would suggest setting ID's for new objects to avoid conflicting each other... i.e:
var cal3 = new AW.UI.Cal
cal4.setId('cal3ID');
.....
var cal4 = new AW.UI.Cal
cal4.setId('cal4ID');
....
But doing so in your sample makes the second not responding (instead of mixed events) that's a good synthom, but needs to be investigated more.

Hopping Alex can find a solution for it soon, cause your idea is an excellent feature for next AW apps., and also hopping giving you new test/debug ideas.

Tanks

<script> 
AW.UI.Cal = AW.UI.Grid.subclass()
AW.UI.Cal.create = function(){

var obj = this.prototype; 

obj.setCellText(function(i, j){return j + "." + i});
obj.setHeaderText("header");

obj.setColumnCount(10);
obj.setRowCount(100);

//obj.setVirtualMode(false);

var toolbar = new AW.UI.Button;
toolbar.setId("thebutton");

toolbar.onControlClicked = function(){
obj.setCellText(function(i, j){return  j + "." + i});
// obj.getScrollTemplate().refresh();
//obj.getLayoutTemplate().getPanelTemplate('center').refresh();
}

var toolbar2 = new AW.UI.Button;
toolbar2.setId("thebutton22");

toolbar2.onControlClicked = function(){
  obj.setCellText(function(i, j){return '---'+ j + "." + '+++' + i});
// obj.getScrollTemplate().refresh();
//obj.getLayoutTemplate().getPanelTemplate('center').refresh();
 }

obj.defineTemplate("topLine", function(){ return toolbar } );
obj.defineTemplate("bottomLine", function(){ return toolbar2 } );

var panel = new AW.Panels.Horizontal;
obj.setLayoutTemplate(panel);

obj.setPanelTemplate(function(i){
    switch(i){
        case "top": return this.getTopLineTemplate();
       case "center": return this.getScrollTemplate();
        case "bottom": return this.getBottomLineTemplate();
    }
});

obj.onPanelWidthChanged = function(width, panel){
obj.getLayoutTemplate().changePanelWidth(width, panel);
};

obj.onPanelHeightChanged = function(height, panel){
obj.getLayoutTemplate().changePanelHeight(height, panel);
};

obj.setPanelHeight(23, "top"); //bottom line 
obj.setPanelHeight(23, "bottom"); //bottom line 
} 

var composite2 = new AW.UI.Cal; 
composite2.setId('aaaaa');
document.write(composite2); 

var composite3 = new AW.UI.Cal; 
composite3.setId('bbbbbbbb');
document.write(composite3); 
</script>
Carlos
September 27,
Hi Carlos,

> I remember trying same thing (clicking a button/combo on top panel)

Ah, that explains your use of the header.

> The curious thing is that data is refreshed if you do enough horizontal scroll
> On the other hand if virtualMode is set to false sorting works

I've disabled scrolling but I did try it with it on to see if it worked - with no luck.

> would suggest setting ID's for new objects to avoid conflicting each other
> But doing so in your sample makes the second not responding

The issue appears to be an AW bug. If you look at my example with a DOM inspector or a debugger, the default ID of the first calendar is aw66. Objects within it inherit that ID except for the objects in the top panel or templates in the bottom panel.

Objects in the top panel get IDs of aw41, aw53 etc. and the templates in the bottom panel get IDs like -today. That's the crux of the problem. These objects and templates don't inherit their IDs from their parent.

I suspect the AW code doesn't check if the panels contain objects that require their IDs to be inherited from the parent (hence the templates with IDs like -today and -clear). Although this doesn't explain the other objects getting unique IDs (that get the same for all instance of the parent object).

However, at one point, the date I display in the bottom panel was a templete rather than an object. I changed it because it wouldn't display as a template (because of its incomplete ID) but I did notice in the DOM that its ID did get fixed (changed to aw66-date after I updated the value) but I couldn't get it to refresh and display.

As is, I'll need to wait for comments from Alex. If its due to limitations in the AW code, I'll have to wait for the next release.
Anthony
September 28,
Hi Anthony,
For a similar situation ( on auto id assignment for more than one subclassed instance ). I ended up solving the issue in two differet ways:

- A second argument, passed , and used as prefix for nested objects' id setting. ie: button1.setId(arg+'01')

- A static ID prefix (like 'A'/'B' different on each nested object) followed by a random number defined in a function returned by a property, (to avoid the small risk of duplication).

The only way I found to refresh data properly is a nested grid subclass used as 'center' layout template of the main subclass.


Carlos
September 29,
Hi Carlos,
Yes, I think I'd have to pass an ID prefix for each object and use that. I need to work out when the AW code sets the ID for an object. Perhaps I can use that instead if its set for the core object before my init function is called. Haven't checked setId() yet, so I'll test that as well.

Can you explain what you mean by "a nested grid subclass used as 'center' layout template of the main subclass"? Do you mean the center panel of a horizontal panel grid?
Anthony
September 30,
Yes, sorry ,I mean center-Panel ,
You can add a grid object ( width&height 100%) in main subclass and replace the 'center' panel with a simple "return objnested.toString();" instead of the standard "return this.getScrollTemplate".

Note1- in the main subclass It isn't necessary to define anything ( i mean for data,rows,count etc.. cause the scroll template is supplanted)
Note2- remember to apply same ID technique to the grid as rest obects.

You can also define a 'complex' subclassed grid instead ( a new subclass into main one) to get all inherited properties of the main. ( but I believe is not needed in most cases)

Now, the bad news, you need to make some new properties in the main to call properties in the nested grid,
And also make calls from events on the nested to properties of main.
(all this depends on code structure of course, and on the choice... 1st or 2nd solution)


Carlos
September 30,
I made some mods to set the ids. The object's initial ID is available by the time the init() function is called, so I used that to set the ids of the nested objects. It didn't work for the templates, so I changed them to objects.

A few problems with this. Setting an ID externally appears to fire after the init() call and, with two or more objects created, the nested objects now appear to be defunct (looks that way in the DOM) for all instances except the last one. So its basically no different to the original scenario where all the nested objects had the same ID in each instance.

Also found something odd with the bottom panel template. I was calling setContent() for it in the order of the objects contained within it (left to right) but the "Today" button just wouldn't fire/respond. When I moved it to be the second one added, it did work. I'm not sure why that is since it works fine for the top panel.

Anyway, the page in strict mode has the new version and the quirks mode page remains as the old version.
Anthony
October 1,
I think I found it!
The line causing all issues here is...
new AW.Panels.Horizontal;
Because it gets always the same ID for all instances of the subclass.
It's probably also the issue with the ScrollTemplate not refreshig, and the BottomPanel locking.
I'd tried to change it's ID (to be unique across any subclass) in many different ways, with no luck.
Anthony, for now you need to create a new subclass for each new calendar object.
Alex, How can we fix this?
Carlos
October 4,
Hi Carlos,

> Because it gets always the same ID for all instances of the subclass.

That seems to happen to all objects that aren't templates. However, the panel one seems to work as either a control object or a template.

This one -

http://www.users.on.net/~asm/calendar/broken.html

has the panel defined as a template (and two grids to show how they affect each other). It ends with the same result.

Since control objects seem to get static IDs, I think using templates is probably the correct approach. The AW code appears to use their template names to create an ID but somehow fails when they are encapsulated within another template.

I'll try replacing all the control objects with templates and set their IDs as I'm doing now to see if that makes a difference.
Anthony
October 5,
Hi Anthony,
Please check this dummy sample ( with a full grid nested object supplanting the scroll template), it seems that works as it should.

Still unsure if the main cause is:
- the original scroll template insertion method
- a complex " multiple-objects into top/bottom horiz-panel.
- the order of code ( objects declared into top/bottom templates after grid panel construction)
- other causes..

I will do some more testing the next few days.

<script> 
var Cal = AW.UI.Grid.subclass()
Cal.create = function(){

var obj = this.prototype; 
    var _super = this.superclass.prototype; 
    // constructor 
    obj.init = function(arg){ 
// calling parent constructor 
      _super.init.call(this); 

var id = this.getId();

obj.setVirtualMode(false);

obj.defineModel("My");
obj.defineMyProperty('whatever', function(){
return OBJ22.getControlWhatever();
})

var OBJ22 = new AW.UI.Grid;

OBJ22.setId(id + 'grid_01');
OBJ22.setCellText(function(i, j){return j + "." + i});
OBJ22.setHeaderText("header");
OBJ22.setColumnCount(11);
OBJ22.setRowCount(10);
OBJ22.setVirtualMode(false); 
OBJ22.setStyle('height', '100%');
OBJ22.setStyle('width', '100%');
OBJ22.setStyle('border', 'none');
//OBJ22.setScrollBars("none");
//OBJ22.onScrollBarsChanged  = function(){this.setScrollBars("none");return 1}; // cancel change 
OBJ22.defineControlProperty('whatever', function(){
return 'pppp_' ;
})

 var Bt_01 = new AW.UI.Button;
Bt_01.setId(id+"but_01");

Bt_01.onControlClicked = function(){
alert('panel.ID= '+panel.getId()+'   '+ 'subclass.ID= '+id);
OBJ22.setCellText(function(i, j){return  j + "." + i});
OBJ22.getRowsTemplate().refresh();
 }

var Bt_02 = new AW.UI.Button;
Bt_02.setId(id+"but_02");

Bt_02.onControlClicked = function(){
alert(panel.getId());
OBJ22.setCellText(function(i, j){return '---'+ j + "." + '+++' + i});
OBJ22.getRowsTemplate().refresh();
 }

obj.defineTemplate("topLine", function(){ return Bt_01.toString()  } );
obj.defineTemplate("bottomLine", function(){ return Bt_02.toString() } );

var panel = new AW.Panels.Horizontal;
panel.setId(id+'000');
obj.setLayoutTemplate(panel);

obj.setPanelTemplate(function(i){
    switch(i){
        case "top": return this.getTopLineTemplate();
       case "center": return OBJ22.toString();
        case "bottom": return this.getBottomLineTemplate();
    }
});

obj.onPanelWidthChanged = function(width, panel){
obj.getLayoutTemplate().changePanelWidth(width, panel);
};

obj.onPanelHeightChanged = function(height, panel){
obj.getLayoutTemplate().changePanelHeight(height, panel);
};

obj.setPanelHeight(23, "top"); //bottom line 
//obj.setPanelHeight(266, "center"); //bottom line 
obj.setPanelHeight(23, "bottom"); //bottom line 
} 
}
//////****************************//////

var composite2 = new Cal; 
composite2.setId('aaaaa');
document.write(composite2);

var composite3 = new Cal; 
composite3.setId('bbbb');
document.write(composite3); 


var Bt_04 = new AW.UI.Button;
Bt_04.setId('ccccc');

Bt_04.onControlClicked = function(){
alert(composite2.getMyWhatever() )
}
document.write(Bt_04); 
</script>
Carlos
October 5,
Anthony,
I give up with this, seems that I was close when I though a probably cause was - original scroll template insertion method -

in my last test I put:
var panel = ....
var ST = this.getScrollTemplate();
and then:
case "center": return ST;

FF error console then say "this.getScrollTemplate() is not a function", so
I ended up thinking the a panel/layout does not permit a scrolltemplate insertion in this manner ( well, at least for multiple instances, or even a later refresh in a single instance).
Maybe as you said is just because nested templates are losing hook, or conflicting each other.

Any way I will use a second nested grid as the solution for now until Alex check it and find (as usual) one of his magical fixes. ;-)

Carlos
October 6,
Hi Carlos,

Your idea is interesting but I can't get it to work in FF2 (works in IE8 and GC though). I don't get the grid cells. FireBug shows them as disabled (although they are there). If you uncomment your disable scrollbar lines, the code goes into an infinite loop too.

Its curious that you do all this in the init() function. I did a test with my code before and defined an object in the init() function and got an error. I might look into that further.

As far as your other comments go -

>- the order of code

I've played around with that a fair bit and it makes little difference to most of the code.

>- a complex " multiple-objects into top/bottom horiz-panel.

Your buttons work on their own. Of course, you need to set their IDs.

About the only other things I haven't tried are -

1) Try constructing a box model for the top and bottom templates like the AW code does for most things. Not sure about this though.

2) Try defining the elements in the top and bottom templates as separate objects/templates and then add them as such to the calendar object. My hope here is that it will fix the ID issue. However, that will add more complexity and other issues.

Oh, one other thing I found yesterday - setId() doesn't work for a template in init(). It works for objects, obviously. Not sure why this is. Another one for Alex to check, I suppose.
Anthony
October 6,
Hey Carlos,

I worked out what's wrong! I went back to the example Jim Hunter put up on his Friends of AW site (the example doesn't work but I cleaned it up a little) and played around with it. Have a look at this -

http://www.users.on.net/~asm/calendar/hunter.html

The green bordered object has broken IDs. The other two don't. So now I know what I need to do to fix things - use templates and use this or this.$owner to access the correct element.

(I'm not sure why the top input's control text can't be accessed. Its always an empty string. I don't know why that is, but its of no consequence.)
Anthony
October 8,
Carlos
October 8,
Hi Carlos,

Thanks for looking up those. I'm familiar with most of them while doing my own searches looking for any solutions to related problems but I have to admit I never read the first thread you listed because of its subject (I just assumed it was just a discussion about documentation).

In the last thread you cite, Alex mentions that $owner and _parent are private (and _parent appears to no longer exist). It would be nice if there was a public method available like getParent() to replace the use of $owner.

Anyway, the code is progressing now. The templates are working but I found that onControlClicked() doesn't work for a button template (not sure why because I can see it as a method in the DOM). Replaced it with onControlMouseUp() which works. Rather odd though.

I should have the new version up on my site by the weekend.
Anthony
October 8,
OK, I have it mostly working now. Have a look -
http://www.users.on.net/~asm/calendar/

Still haven't sorted out the use of a combo template correctly yet. Not sure about the correct method yet. Currently, the pop-up list isn't populated. However, I have found a way to duplicate the combo pop-up won't go away bug that gets reported from time to time.

Fire up a html inspector and watch as you click on the combo button. A div will appear. If you click elsewhere it will disappear. But if you click on the button instead, another div will be created. You can repeat this as many times as you like. You can only ever remove the top one. I'll raise a bug report for this later and suggest a fix.

The current code also has a problem with updating values set up with defineModel(). The problem happens in the changeMonth() and changeYear() functions when I call setCalCol() and setCalRow(). This doesn't seem to work or work consitently. To see the effect of the problem, click on a cell to select it then change the month or year. The same cell remains selected in the new calendar.

Also, I've fixed the problem I mentioned above with Jim Hunter's example. This now works -
http://www.users.on.net/~asm/calendar/hunter.html

However, the code to fix it was a bit of a hack. I get the value from the element's last child. getControlText() doesn't seem to work when an input object is defined as a template. Brian's solution isn't correct since he doesn't use templates (so multiple instances can't be created because the input fields will have the same IDs).
Anthony
October 11,
Good job Anthony!

You solved most issues ( or at least the hardest ones).

Haven't you tried it in IE ? . I see all templates ( except empty-cells duplicated) painted below, also see scrollbars on the ones containing data. ( but just tested on IE6)

FF3 and IE6 does not seems to suffer the combo-div issue you described, so I suppose it's just a FF2 behavior.

>change the month or year. The same cell remains selected

I remember fixing a similar issue on my calendar with: setSelectedRows([-1])

Anyway, it looks really nice. Great piece of code.
Carlos
October 11,
Or maybe both
this.setSelectedRows([-1]);
this.setSelectedColumns([-1]);
not sure;
Carlos
October 11,
Hi Carlos,

> You solved most issues ( or at least the hardest ones).

Yes, once I realised the problem with the way I was using this and changing it to this.$owner in the right places the refreshes all worked.

No sure about the combo issue. I tried to use AW.Templates.Combo instead but I suspect that might be only for grid cells. I'll have to keep experimenting.

> Haven't you tried it in IE ?

Just IE8 in standard and compatibility modes.

> FF3 and IE6 does not seems to suffer the combo-div issue you described

IE8 doesn't have it either. Clicking again makes it disappear.

> I remember fixing a similar issue on my calendar with: setSelectedRows([-1])

That might work. I'll have to test but I also track the selected row and col internally with my own code so I can deselect the previous one and mark the new one. I wrote a custom cell class routine to do this. It also tracks whether the date is within the month or outside.
Anthony
October 11,

This topic is archived.

See also:


Back to support forum

Search

Version 2

Looking for AW 2.6 ?
The old site is here.