Qooxdoo--garbage?

D

David Mark

Downloaded the qooxdoo SDK. Only 20MB; that's a bargain! Of course,
it is mostly the graphics used by the widgets. And if the low-level
code turns out to be the usual gobbledygook found in such frameworks,
the rest of it is just a waste of time and space.

Those wishing to save their own time should skip to the last
sentence. There's nothing new here. ;)

The source starts in \framework\source\class\qx. This folder contains
7 scripts. Bootstrap, Class, Interface, Mixin, Part, Theme and the
very oddly named __init__.js. My guess is that that last one is the
entry point.

/**
* This is the framework's top-level namespace or "package".
*
* It contains some fundamental classes, while the rest of the class
hierarchy
* is available in the corresponding sub packages. Please see the
descriptions
* below. The little **+** preceding a name offers more descriptive
text when
* you click on it.
*
* All packages are structured in the same way. Use either the
hyperlinks in
* the listing, or the tree structure to navigate to the corresponding
packages
* and their documentation. You can also use the __Search__ button to
find
* certain class and method names, and the __Legend__ button for
information
* about the symbols used throughout this reference. Use the toolbar
buttons to
* to the right to tailor the amount of information that is displayed
with each
* class, e.g. you can enable display of inherited or protected class
members.
*/

Well, not it isn't. It's just a comment. I sense Dojo-itis has
afflicted this project where everything is sliced and diced into as
many files as possible, so as to make it impossible to find anything.
Such granularity might be useful if (unlike Dojo) the many files
mirror the product's modularity.

Moving on to Bootstrap, assuming it will be the real entry point.

/**
* Create namespace
*/
if (!window.qx) {
window.qx = {};
}

Could really end the review right there. The first line of code
augments a host object for no reason other than ignorance. JFTR, that
should have read:-

var qx;

if (!qx) {
qx = {};
}

....or:-

var qx;

qx = qx || {};

The fact that they botched a global variable declaration doesn't bode
well for the remaining umpteen thousand lines of code.

__classToTypeMap :
{
"[object String]": "String",
"[object Array]": "Array",
"[object Object]": "Object",
"[object RegExp]": "RegExp",
"[object Number]": "Number",
"[object Boolean]": "Boolean",
"[object Date]": "Date",
"[object Function]": "Function",
"[object Error]": "Error"
},

There's a waste of space.

bind : function(func, self, varargs)
{
var fixedArgs = Array.prototype.slice.call(arguments, 2,
arguments.length);


Well, clearly they haven't yet mastered slice.


return function() {
var args = Array.prototype.slice.call(arguments, 0,
arguments.length);


Again.


return func.apply(self, fixedArgs.concat(args));
}
},

firstUp : function(str) {
return str.charAt(0).toUpperCase() + str.substr(1);
},

The substr method is non-standard.

http://mdn.beonex.com/en/Core_JavaScript_1.5_Reference/Global_Objects/String/substr#Summary

They should have used slice, but then they are pretty clumsy with that
one. It's interesting to note that they've been working on this for
*years*, but still haven't learned the basics of the language.
Recently Ajaxian described this thing as a "mature" framework, which I
assume referred to the amount of wasted time (and perhaps the huge
collection of graphics).

/**
* Get the internal class of the value. See
* http://thinkweb2.com/projects/proto...red-harmful-or-how-to-write-a-robust-isarray/
* for details.

So they've read Kangax' blog, but apparently not closely. There is
never a good reason to write an isArray function. And it's impossible
anyway. Other than the userAgent property (which will likely pop up
soon), there's really no greater indicator of incompetence than an
isArray function.

Typically such things are used for "overloading" schemes that must
differentiate between Object and Array objects (and sometimes even
host objects. Don't use such designs as they are pinned on something
that is impossible.

*
* @param value {var} value to get the class for
* @return {String} the internal class of the value
*/
getClass : function(value)
{
var classString = Object.prototype.toString.call(value);
return (
qx.Bootstrap.__classToTypeMap[classString] ||
classString.slice(8, -1)
);
},


/**
* Whether the value is a string.
*
* @param value {var} Value to check.


And what is this value allowed to be?


* @return {Boolean} Whether the value is a string.
*/
isString : function(value)
{
// Added "value !== null" because IE throws an exception "Object
expected"
// by executing "value instanceof Array" if value is a DOM
element that
// doesn't exist. It seems that there is a internal different
between a
// JavaScript null and a null returned from calling DOM.
// e.q. by document.getElementById("ReturnedNull").


As noted in another thread, that's about as confused as it gets. And
apparently they are fine with passing host objects to these
functions. In other words, the whole thing is doomed from the start.


return (
value !== null && (
typeof value === "string" ||
qx.Bootstrap.getClass(value) == "String" ||
value instanceof String ||
(!!value && !!value.$$isString))
);
},

Disallow String objects (which shouldn't be used anyway) and you only
need the typeof check. The rest of the tacked on nonsense (and
accompanying confused comment) is simply flailing to try to make an
impossible design "work". God only knows what that last check is
supposed to be.

isArray : function(value)
{
// Added "value !== null" because IE throws an exception "Object
expected"
// by executing "value instanceof Array" if value is a DOM
element that
// doesn't exist. It seems that there is a internal different
between a
// JavaScript null and a null returned from calling DOM.
// e.q. by document.getElementById("ReturnedNull").


Deja vu.


return (
value !== null && (
value instanceof Array ||
(value && qx.data && qx.data.IListData &&
qx.Bootstrap.hasInterface(value.constructor, qx.data.IListData) ) ||
qx.Bootstrap.getClass(value) == "Array" ||
(!!value && !!value.$$isArray))
);
},


Odd rearrangement of the previous function's logic. They are trying
desperately to make this ill-advised design work cross-frame/window,
but are 0-2 at this point. They aren't even close.

Prediction: somebody will show up here and assert that these functions
pass their unit tests and demand to know where they fail. Make no
mistake that they can be demonstrated to fail, but that's actually
beside the point.

/**
* Whether the value is an object. Note that built-in types like
Window are
* not reported to be objects.


Built-in types like Window?


*
* @param value {var} Value to check.


Again, what is this value allowed to be? Is this really meant to
handle host objects? Considering the previous comment, it seems
unlikely they know what host objects are.


* @return {Boolean} Whether the value is an object.
*/
isObject : function(value) {
return (
value !== undefined &&
value !== null &&
qx.Bootstrap.getClass(value) == "Object"
);
},

/**
* Whether the value is a function.
*
* @param value {var} Value to check.
* @return {Boolean} Whether the value is a function.
*/
isFunction : function(value) {
return qx.Bootstrap.getClass(value) == "Function";
},


I have to wonder why they haven't "decorated" this one with the hacks
seen in the previous functions. Also have to wonder what on earth
they would use this for.

And if host objects (and RegExp objects) are excluded (as they
certainly should be), a typeof check would suffice. And, as with the
isString, a function that is nothing more than an typeof operation is
a wasted call (just use the typeof operator!) As the documentation is
simply "Whether the value is a function", there's no telling what they
are trying to accomplish with this. An attempt at an explanation
would certainly be welcome.

Moving on to Class:-

#require(qx.Interface)
#require(qx.Mixin)


There it is. What are the odds that those two scripts serve any
useful purpose on their own? Can you really build a qooxdoo project
without this Class script. If not, then there was no need to break
the them up.


// Normalize include to array
if (config.include && !(config.include instanceof Array)) {
config.include = [config.include];
}


Oops. Why didn't they call their "perfect" isArray function? Can't
help but think they might have noticed this if the code wasn't
scattered among multiple files.

So enough with the low-level language machinations. It's apparent
they are very new to JS (and have never heard of browser scripting).

bom/Selector.js:-

This class contains code based on the following work:

* Sizzle CSS Selector Engine - v1.0

http://sizzlejs.com/
http://groups.google.com/group/sizzlejs
http://github.com/jeresig/sizzle/tree

Snapshot from Oct 29 2009
commit b363fde6c7b55d43777b28eeb7ede5827e899ec9
Copyright:
(c) 2009, The Dojo Foundation

License:
MIT: http://www.opensource.org/licenses/mit-license.php


----------------------------------------------------------------------

Copyright (c) 2009 John Resig


The kiss of death. :(

bom/Viewport.js:-


This class contains code based on the following work:

* Yahoo! UI Library
http://developer.yahoo.com/yui
Version 2.2.0

Copyright:
(c) 2007, Yahoo! Inc.

License:
BSD: http://developer.yahoo.com/yui/license.txt


YUI is clueless to this day, but YUI circa 2007?

qx.Class.define("qx.bom.Viewport",
{
statics :
{
/**
* Returns the current width of the viewport (excluding a
eventually visible scrollbar).
*
* <code>clientWidth</code> is the inner width of an element in
pixels. It includes padding
* but not the vertical scrollbar (if present, if rendered),
border or margin.
*
* The property <code>innerWidth</code> is not useable as defined
by the standard as it includes the scrollbars
* which is not the indented behavior of this method. We can
decrement the size by the scrollbar
* size but there are easier possibilities to work around this.
*
* Safari 2 and 3 beta (3.0.2) do not correctly implement
<code>clientWidth</code> on documentElement/body,
* but <code>innerWidth</code> works there. Interesting is that
webkit do not correctly implement
* <code>innerWidth</code>, too. It calculates the size excluding
the scroll bars and this
* differs from the behavior of all other browsers - but this is
exactly what we want to have
* in this case.
*
* Opera less then 9.50 only works well using
<code>body.clientWidth</code>.
*
* Verified to correctly work with:
*
* * Mozilla Firefox 2.0.0.4
* * Opera 9.2.1
* * Safari 3.0 beta (3.0.2)
* * Internet Explorer 7.0


LOL. What about Opera 9.50?


*
* @signature function(win)
* @param win {Window?window} The window to query
* @return {Integer} The width of the viewable area of the page
(excludes scrollbars).
*/
getWidth : qx.core.Variant.select("qx.client",
{
"opera" : function(win) {
if (qx.bom.client.Engine.VERSION < 9.5) {
return (win||window).document.body.clientWidth;
}
else
{
var doc = (win||window).document;
return qx.bom.Document.isStandardMode(win) ?
doc.documentElement.clientWidth : doc.body.clientWidth;
}
},

"webkit" : function(win) {
if (qx.bom.client.Engine.VERSION < 523.15) { // Version <
3.0.4
return (win||window).innerWidth;
}
else
{
var doc = (win||window).document;
return qx.bom.Document.isStandardMode(win) ?
doc.documentElement.clientWidth : doc.body.clientWidth;
}
},

"default" : function(win)
{
var doc = (win||window).document;
return qx.bom.Document.isStandardMode(win) ?
doc.documentElement.clientWidth : doc.body.clientWidth;
}
}),


All pinned on dated observations (of four browsers, one of which was
in Beta at the time).

http://www.cinsoft.net/viewport.asp

/**
* Returns the scroll position of the viewport
*
* All clients except MSHTML supports the non-standard property
<code>pageXOffset</code>.


All clients?! It's admittedly a non-standard property.


* As this is easier to evaluate we prefer this property over
<code>scrollLeft</code>.
*
* For MSHTML the access method differs between standard and
quirks mode;
* as this can differ from document to document this test must be
made on
* each query.
*
* Verified to correctly work with:
*
* * Mozilla Firefox 2.0.0.4
* * Opera 9.2.1
* * Safari 3.0 beta (3.0.2)
* * Internet Explorer 7.0


Does that lot represent all clients?


*
* @signature function(win)
* @param win {Window?window} The window to query
* @return {Integer} Scroll position from left edge, always a
positive integer
*/
getScrollLeft : qx.core.Variant.select("qx.client",
{
"mshtml" : function(win)
{
var doc = (win||window).document;
return doc.documentElement.scrollLeft || doc.body.scrollLeft;
},

"default" : function(win) {
return (win||window).pageXOffset;
}
}),


Let's see about this browser sniffing.

bom/client/Browser.js

This class contains code from:

Copyright:
2009 Deutsche Telekom AG, Germany, http://telekom.com

I suppose a phone company is as good a source as any for browser
scripting (can't do much worse than jQuery and YUI).


defer : qx.core.Variant.select("qx.client",
{
"webkit" : function(statics)
{
// Safari should be the last one to check, because some other
Webkit-based browsers
// use this identifier together with their own one.
// "Version" is used in Safari 4 to define the Safari version.
After "Safari" they place the
// Webkit version instead. Silly.


No, what is silly is parsing the UA string in the first place. The
very nature of these comments should make that obvious.

http://www.jibbering.com/faq/notes/detect-browser/


// Palm Pre uses both Safari (contains Webkit version) and
"Version" contains the "Pre" version. But
// as "Version" is not Safari here, we better detect this as the
Pre-Browser version. So place
// "Pre" in front of both "Version" and "Safari".


Well, glad we got all of that sorted out. :) For today (or whenever
this was released) anyway. Clearly this code, on which the rest of
this framework hangs, will need to be rewritten constantly.


statics.__detect("AdobeAIR|Titanium|Fluid|Chrome|Android|
Epiphany|Konqueror|iCab|OmniWeb|Maxthon|Pre|Mobile Safari|Safari");
},

"gecko" : function(statics)
{
// Better security by keeping Firefox the last one to match


Better security?


statics.__detect("prism|Fennec|Camino|Kmeleon|Galeon|Netscape|
SeaMonkey|Firefox");
},

"mshtml" : function(statics)
{
// No idea what other browsers based on IE's engine


Aha! :)


statics.__detect("IEMobile|Maxthon|MSIE");


And no idea of the hundreds (if not thousands) of non-IE agents that
have "MSIE" in their UA string.


},

"opera" : function(statics)
{
// Keep "Opera" the last one to correctly prefer/match the
mobile clients
statics.__detect("Opera Mini|Opera Mobi|Opera");
}
})
});


bom/client/Multimedia.js

/**
* Internal initialize helper
*
* @return {void}
* @signature function()
*/
__init : qx.core.Variant.select("qx.client",
{
"mshtml" : function()
{
var control = window.ActiveXObject;
if (!control) {
return;
}


Basically, they rely on those ridiculous UA inferences (borrowed from
a phone company) for everything. In 2010. And such code would have
been unworkable (and laughable) in 2001. Forget it.
 
G

Garrett Smith

Downloaded the qooxdoo SDK. Only 20MB; that's a bargain! Of course,
it is mostly the graphics used by the widgets. And if the low-level
code turns out to be the usual gobbledygook found in such frameworks,
the rest of it is just a waste of time and space.

Those wishing to save their own time should skip to the last
sentence. There's nothing new here. ;)

I see some loose inferences that resemble those in Mootools. Basically,
it is the idea of using something other than navigator.userAgent to
detect a browser, where that information can later be used to address
yet another situation.

Incidentally, HTML 5 draft is standardizing the navigator object.

| userAgent
|
| Must return the string used for the value of the "User-Agent" header
| in HTTP requests, or the empty string if no such header is ever sent.
|

This is something that HTML 5 got right. Trying to nail down specifics
of formatting would have been foolish.

navigator.platform is also specified:

| platform
|
| Must return either the empty string or a string representing the
| platform on which the browser is executing, e.g. "MacIntel", "Win32",
| "FreeBSD i386", "WebTV OS"
The source starts in \framework\source\class\qx. This folder contains
7 scripts. Bootstrap, Class, Interface, Mixin, Part, Theme and the
very oddly named __init__.js. My guess is that that last one is the
entry point.

/**
* This is the framework's top-level namespace or "package".
*
* It contains some fundamental classes, while the rest of the class
hierarchy
* is available in the corresponding sub packages. Please see the
descriptions

The code is wrapping. It is not executable as transmitted and is harder
to read than it would be, had it been formatted to < 72 character width.

http://jibbering.com/faq/#postCode
http://jibbering.com/faq/notes/posting/#ps1Lw
http://jibbering.com/faq/notes/code-guidelines/#formatting

Comments or corrections to those documents are welcome -- I'll try to
fix them if they can be pointed out.

Garrett
 
R

Ry Nohryb

/**
 * Create namespace
 */
if (!window.qx) {
  window.qx = {};

}

Could really end the review right there.  The first line of code
augments a host object for no reason other than ignorance.

"window" is not a host object. It's an alias of the global object:

(function () {return this})().k= 27;
window.k
--> 27

That Microsoft's Internet Explorers got -even- "window" botched -as
well-, does not prove anything wrt this.
 
D

David Mark

"window" is not a host object.

It most assuredly is a host object, provided by browsers.

It's an alias of the global object:

There's no proof of that whatsoever. Nor is there any standard that
dictates such.
(function () {return this})().k= 27;
window.k
--> 27

And there's no proof, right there. :) And regardless, would you
seriously consider creating a global property by setting a property of
window?
That Microsoft's Internet Explorers got -even- "window" botched -as
well-, does not prove anything wrt this.

There's no way to botch a host object as there are no rules for host
objects.

http://www.cinsoft.net/host.html
 
T

Thomas 'PointedEars' Lahn

Garrett said:
The code is wrapping.

That is not in itself a Bad Thing.
It is not executable as transmitted

I would submit that code reviews do not need to be executable as
transmitted, nor would that be wanted (think of the amount of information to
be transmitted but _not_ being commented upon!).

Therefore, AISB, it would be a good idea that code being reviewed should be
marked as quotation if possible, to clearly distinguish it from comments and
code suggestions; if the original code has not been discussed in the thread
previously, then as third-party quotation with `|'. Many newsreaders
provide the capability to do that automatically. (And no, I do not think
that the limited capabilities of the buggy Web interface of the Google
Groups archive should define the posting guidelines for any newsgroup.)
and is harder to read than it would be, had it been formatted to < 72
character width.

Let x be the length of the longest line in a posting except the new-line
character (sequence), that which you mislabeled "character width". Then
insisting on x < 72 is utter nonsense, far from reality: very few people
would write code like this (not even you), that would waste a lot of disk
space and add bogus LOCs. 72 <= x <= 80 is reasonable. x <= 80 is
necessary since a newsgroup, especially a technical one, may be read from a
standard issue virtual terminal, and there is of course the basic
readability aspect of not having too many characters in one line (think of
newspapers and books).

Further, you cannot reasonably blame David (or anyone else) for not
rewrapping each and every LOC in *other's* code when making their *review*.
The (qooxdoo) authors should have wrapped earlier and used proper (multi-
line documentation) comments in the first place; IMHO, that the code is
wrapped when posted to a newsgroup is good indication that it has never been
seriously peer-reviewed before.

When I had quoted from qooxdoo code¹, I had to trim the indentation to that
it would fit into a line of max. 76 characters (that is where I draw the
line in Usenet, as it has served me well in the past). Still, I needed to
place one word in a comment on the next line and add the `//'. While that
makes it easier visible that this line belongs to the comment, it also moves
the rest of the lines one forward, thereby changing the line numbers to
something different then used in a possible source code reference. And that
was only one occurrence.

This should be taken into consideration when defining posting guidelines for
this newsgroup. (It should be obvious that such rules cannot be enforced
here, only lived by regulars providing a good example.)
Comments or corrections to those documents are welcome -- I'll try to
fix them if they can be pointed out.

[x] done


HTH

PointedEars
___________
¹ <--
realism: HTML 4.01 Strict
evangelism: XHTML 1.0 Strict
madness: XHTML 1.1 as application/xhtml+xml
-- Bjoern Hoehrmann
 
T

Thomas 'PointedEars' Lahn

Gabriel said:
No it isn't. It is a property of the global object.

And a host-defined one at that, as specified in ES3F, section 10.1.5, second
item, first sentence, and ES5, section 15.1, last paragraph, first sentence.
(Given how many times the last sentence has been misunderstood, probably it
should have never been included in the Specification in the first place.)


PointedEars
 
G

Garrett Smith

Garrett said:
David Mark wrote:
[...]

Therefore, AISB, it would be a good idea that code being reviewed should be
marked as quotation if possible, to clearly distinguish it from comments and
code suggestions; if the original code has not been discussed in the thread
previously, then as third-party quotation with `|'. Many newsreaders
provide the capability to do that automatically. (And no, I do not think
that the limited capabilities of the buggy Web interface of the Google
Groups archive should define the posting guidelines for any newsgroup.)
and is harder to read than it would be, had it been formatted to< 72
character width.

Let x be the length of the longest line in a posting except the new-line
character (sequence), that which you mislabeled "character width". Then
insisting on x< 72 is utter nonsense, far from reality: very few people
would write code like this (not even you), that would waste a lot of disk
space and add bogus LOCs. 72<= x<= 80 is reasonable. x<= 80 is
necessary since a newsgroup, especially a technical one, may be read from a
standard issue virtual terminal, and there is of course the basic
readability aspect of not having too many characters in one line (think of
newspapers and books).

SO you're saying that this:-

| Code should be formatted to a maximul line length. For newsgroup
| code, it should not exceed 72 chars.

- should be changed to 80 chars?

Will any newsreaders automatically rewrap the lines to fewer characters?
What about when replying? For example, I am concerned about:

this.is.a.very.looooong.code.line(/asdfqw[\b\n\u0020asdfgzxc13579]?/);

Becomes:
this.is.a.very.looooong.code.line(/asdfqw[\b\n\u0020asdfgzxc13579]?
?/)

Becomes eventually:
this.is.a.very.looooong.code.line(/asdfqwer[\b
\u0020asdfgzxc13579]
)?/)

If the suggestion is to format to 72-char width, the code is less likely
to be wrapped by a reader.

Further, you cannot reasonably blame David (or anyone else) for not
rewrapping each and every LOC in *other's* code when making their *review*.

Code should be formatted before posting to the NG.

When the code is obfuscated from the beginning or when it uses tabs or
very long lines, it makes it hard to read. Reformatting being painful
provides motivation for the person doing reformatting to want to
reformat the least amount necessary and if everything posted is
reformatted, it can have the effect or reducing the amount of code
posted. This in turn has the effect of making the reader focus on just
that chunk.

When code has been reformatted, the one posting the code should mention
that it was reformatted.

Reformatting can have the effect of making poster look at the code again
before posting.
The (qooxdoo) authors should have wrapped earlier and used proper (multi-
line documentation) comments in the first place; IMHO, that the code is
wrapped when posted to a newsgroup is good indication that it has never been
seriously peer-reviewed before.

It is their choice to use whatever formatting they like and to even
publish code minified or with tabs, if they want.

Code that has no spaces, as minified code found on google groups, or
code that has been formatted with tabs, such as jQuery, should be
reformatted before posting.
When I had quoted from qooxdoo code¹, I had to trim the indentation to that
it would fit into a line of max. 76 characters (that is where I draw the
line in Usenet, as it has served me well in the past). Still, I needed to
place one word in a comment on the next line and add the `//'. While that
makes it easier visible that this line belongs to the comment, it also moves
the rest of the lines one forward, thereby changing the line numbers to
something different then used in a possible source code reference. And that
was only one occurrence.

This should be taken into consideration when defining posting guidelines for
this newsgroup. (It should be obvious that such rules cannot be enforced
here, only lived by regulars providing a good example.)

The formatting section is a formalized summary of suggested conventions
for posting code. It's intended to help communication by improving the
quality of the post.

[...]

Garrett
 
D

Dr J R Stockton

In comp.lang.javascript message <[email protected]
september.org>, Mon, 14 Jun 2010 16:47:23, Garrett Smith
SO you're saying that this:-

| Code should be formatted to a maximul line length. For newsgroup
| code, it should not exceed 72 chars.

- should be changed to 80 chars?

Ignore the oaf.

Seventy-two characters is the Usenet standard for ordinary text, to
allow for multiple quoting within 80 characters, which is the limit for
signatures.

Code and data for Usenet should normally be written within 72
characters, but may need to be wider. It is then important that the
posting agent does not wrap it unintelligently. A well-written posting
agent will allow lines (but not by default) up to the Usenet maximum,
which is at least large.

Will any newsreaders automatically rewrap the lines to fewer
characters? What about when replying?

Good newsreaders will allow their users the readers to adjust such
things, per article, while reading.

For example, I am concerned about:

this.is.a.very.looooong.code.line(/asdfqw[\b\n\u0020asdfgzxc13579]?/);

Becomes:
this.is.a.very.looooong.code.line(/asdfqw[\b\n\u0020asdfgzxc13579]?
?/)

Becomes eventually:
this.is.a.very.looooong.code.line(/asdfqwer[\b
\u0020asdfgzxc13579]
)?/)


In many cases, simple changes to the code will reduce line length :

T = this.is.a.very.looooong.code.line // no more
T(/asdfqw[\b\n\u0020asdfgzxc13579]?/);

If the suggestion is to format to 72-char width, the code is less
likely to be wrapped by a reader.

After quoting, code is not executable without repair anyway, so wrapping
after considerable quoting is unimportant. One can generally locate for
use the original posting of the code.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top