PDA

View Full Version : Ext.extend : best way to handle configs?



jhorvath
26 Oct 2007, 10:00 PM
Ext.MessageBox.alert('Greetings','Hello world!'); :D

first off, let me say thanks to the Ext devs/support/doc writers/community et. al. for creating/contributing to all that is Ext. it is truly fun stuff.

I've been playing around for a few weeks now, reading the examples, and spending a lot of time lurking the forums and api docs.. and I've gotten to where I am starting to explore Ext.extend and creating (so far, very simple) custom components (mostly a collage of existing ones really).

I did, for instance, a simple LoginDialog. Not that I couldn't have created it without encapsulating the functionality in a separate reusable component, but more for experimentation. ^_^ And, while the code works, I just question if there is perhaps a more 'elegant' way of handling the configuration of said component, than I am currently doing.

example:



/**
* @fileoverview
* ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
* Ext.ux [user extension] - LoginDialog.js
*
* It is very simple in that it only provides the means to enter input and submission, via an XHR
* call to a URL you choose. Once the request has been received by the server, it will await a valid
* JSON response and respond accordingly with a failed login message, or redirecting the user furthur
* down the hole.
*
* #####################################################################################################
**
* @author Jeremy Horvath
* @version 1.0
*
**/

/**
* @class Ext.ux.loginDialog @inherits Ext.Window
* @constructor
*
* requires 2 parameters
*
* @param url [string] : the url we will POST to
* @param onSuccess [function] : the action which we will take if our request is responded to with a
* 'success' action
*
* ++ if no cfg.url is present, it will alert the user that
* its configuration is invalid and the POST is cancelled.
* ++ if no cfg.onSuccess() is present,.. nothing happens if the POST was successful
*/
Ext.ux.LoginDialog = function(config) {
var me = this;

var cfg = config || { }; cfg.form = cfg.form || { };
var def, ini = { };

/**
* @private member Ext.ux.loginDialog
*
* this function will submit the form to the URL we provided (default: '')
* and hookup our success/failure handlers to the supplied functions.
*
* a default failure handler is provided, which simply notifies you that,.. you fail.
*/
function auth() {
var form = this.findByType('form')[0].getForm();

if(form.url && form.url.trim()!=='') {
// me.hide();
form.submit({
failure : form.failure,
success : form.success,
waitMsg : form.waitMsg,
waitTitle : form.waitTitle
});
} else {
// no url
Ext.Msg.alert('Error ::', 'Invalid configuration.\nPlease contact an administrator.');
}
}

/**
* @private member Ext.ux.loginDialog
*
* provide defaults for some properties. (overridden by cfg, if present)
*/
def = {
animateTarget : Ext.getBody(),
bodyStyle : "padding: 5px;",
buttonText : 'Login',
height : 175,
title : ':: login',
width : 350,

form : {
baseParams : { async : true },
bodyStyle : "background-color: transparent; padding: 20px 0px;",
onFailure : function(f,a) {
if(a.failureType) {
switch(a.failureType) {
case Ext.form.Action.CLIENT_INVALID:
Ext.Msg.alert('Error [CLIENT_INVALID] ::', 'Please correct any errors and re-submit.'); break;
case Ext.form.Action.CONNECT_FAILURE:
Ext.Msg.alert('Error [CONNECT_FAILURE] ::', 'Unable to process your request.\nPlease try again later.'); break;
case Ext.form.Action.SERVER_INVALID:
Ext.Msg.alert('Error [SERVER_INVALID] ::', 'Unknown error.\nPlease contact an administrator.'); break;
}
} else {
Ext.Msg.alert('Error ::', 'Invalid username/password.');
}

// me.show();
},
onSuccess : function(f,a) { },
passwordLabel : 'Password',
passwordMax : 16,
passwordMin : 8,
passwordName : 'password',
url : '',
usernameLabel : 'Username',
usernameMax : 10,
usernameMin : 3,
usernameName : 'username',
waitMsg : 'Authenticating...',
waitTitle : 'Please wait'
}
};

/**
* @private member Ext.ux.loginDialog
*
* setup the initialization structure based on cfg/def.
*/
ini = {
animateTarget : cfg.animateTarget || def.animateTarget,
bodyStyle : cfg.bodyStyle || def.bodyStyle,
closable : false,
collapsible : false,
draggable : false,
height : cfg.height || def.height,
resizable : false,
title : cfg.title || def.title,
width : cfg.width || def.width,

items : new Ext.FormPanel({
baseParams : cfg.form.baseParams || def.form.baseParams,
bodyStyle : cfg.form.bodyStyle || def.form.bodyStyle,
border : false,
failure : cfg.form.onFailure || def.form.onFailure,
labelAlign : 'right',
method : 'POST',
success : cfg.form.onSuccess || def.form.onSuccess,
url : cfg.form.url || def.form.url,
waitMsg : cfg.form.waitMsg || def.form.waitMsg,
waitTitle : cfg.form.waitTitle || def.form.waitTitle,

items : [{
allowBlank : false,
fieldLabel : cfg.form.usernameLabel || def.form.usernameLabel,
maxLength : cfg.form.usernameMax || def.form.usernameMax,
minLength : cfg.form.usernameMin || def.form.usernameMin,
msgTarget : 'side',
name : cfg.form.usernameName || def.form.usernameName,
vtype : 'alpha',
xtype : 'textfield'
},{
allowBlank : false,
fieldLabel : cfg.form.passwordLabel || def.form.passwordLabel,
inputType : 'password',
maxLength : cfg.form.passwordMax || def.form.passwordMax,
minLength : cfg.form.passwordMin || def.form.passwordMin,
msgTarget : 'side',
name : cfg.form.passwordName || def.form.passwordName,
vtype : 'alphanum',
xtype : 'textfield'
}]
}),

buttons : [{ handler : auth, scope : this, text : cfg.buttonText || def.buttonText }]
};

/**
* call our parent contructor (Ext.Window.prototype) to initialize
* itself with our ini structure
*/
Ext.ux.LoginDialog.superclass.constructor.call(this,ini);

ini = null;
def = null;
cfg = null;
};

/**
* inheritance (Ext-style)
*/
Ext.extend(Ext.ux.LoginDialog, Ext.Window);


the general process I've adopted here is to have 3 config objects floating around.

config(cfg) = passed as parameter to the constructor
def = default properties/methods, all of which are over ridable
ini = final config applied to the superconstructor. this contains constants (not user modifiable) as well the cfg||def properties/methods depending on what the caller supplied.

i think that is an ok solution (i couldn't seem to conjure another decent way to handle it ^^). I guess my concern is really all the excess assignments i have going on during the ini{}. all of the 'cfg.blahbityblah||def.blahbityblah's is really (should be) uneccessary IMO.

that brings me to the question of the topic subject, is there a better way, one which i did not see?

i originally thought, `oooh, ill just use Ext.apply() and copy the whole thing over`. that would work fine if i was the only one using it, or i had no intention of learning enough to the point of creating something decent enough to share with others. but as it stands, the config passed to the constructor would be capable of overriding things i hadn't meant to be over ridable, or that there is just really no need for. =/

then i saw applyIf and thought my prayers were answered, but it seems that the function's name is false advertisement. ;)

applyIf should be applyIfNot, imho. the real applyIf should do the opposite that it does currently, but that would probably break some stuff if it were changed, im sure (even if easily remedied).

i could rip the applyIf out and rename it, and change the original to do the opposite (all in a separate namespace of course) and perhaps add recursion for the items[] buttons[] stuff. but then i have added a non-standard dependency. =/

what do/would you guys normally do? (and i pre-apologize if the answer to my question is answered in some example/doc/post, i did look, i promise :) )

thanks,
jeremy

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

Edit : decided against my anti-applyIf. while it saves me less typing, it is actually more cumbersome to create the object if i do happen to configure some things, especially with nested components.. doing



...{
items : {
items : {
property : 'value'
}
}
}


not real intuitive.

anyways, i rewrote it again, a little better i think. it's a learning experience ^^



/**
* @fileoverview
* ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
* Ext.ux [user extension] - LoginDialog.js
*
* It is very simple in that it only provides the means to enter input and submission, via an XHR
* call to a URL you choose. Once the request has been received by the server, it will await a valid
* JSON response and respond accordingly with a failed login message, or redirecting the user to another
* page (furthur down the hole).
*
* #####################################################################################################
**
* @author Jeremy Horvath
* @version 1.0
*
**/

/**
* @class Ext.ux.LoginDialog
* @singleton
*
* supported config options :
*
* <pre>
* option type desc
* -------------- -------------- ------------------------------------------------------
* animateTarget object/element target from which to animate from on show(). (default : Ext.getBody())
* autoClose boolean automatically removes the dialog from the DOM if true. (default : true)
* bodyStyle string additional CSS styles applied to the window's body. (default : 'padding: 5px')
* buttonText string text displayed on the login button. (default : 'Login')
* height number window height (in px). (default : 175)
* onFailure function function called if POST fails. (default : [basic failure function]
* onSuccess function function called if POST succeeds. (default : function(f,a) { })
* title string window title. (default : ':: login')
* url string url the form will POST to. (default : '')
* width number window width (in px). (default : 350)
*
* form.baseParams object parameters pre-pended to form contents during POST. (default : { async : true })
* form.bodyStyle string additional CSS styles applied to the form's body.
* (default : ''background-color: transparent; padding: 20px 0px;'')
* form.passwordLabel string label displayed next to the password field. (default : 'Password')
* form.passwordMax number maximum length for passwords. (default : 16)
* form.passwordMin number minimum length for passwords. (default : 8)
* form.passwordName string name of the password field sent during POST. (default : 'password')
* form.usernameLabel string label displayed next to the username field. (default : 'Username')
* form.usernameMax number maximum length for usernames. (default : 10)
* form.usernameMin number minimum length for usernames. (default : 3)
* form.usernameName string name of the username field sent during POST. (default : 'username')
*
* ^^^ note ^^^ passing form options is done by passing a form object, containing
* the options you wish to configure. eg:
*
* {
* bodyStyle : 'background-color: #fff;', // this affects the window's bodyStyle
* form : {
* bodyStyle : 'background-color: #000;' // this affects the form's bodyStyle
* }
* }
* </pre>
*/
Ext.ux.LoginDialog = function() {
var $public = { }; // used to expose all public methods / properties, cleanly
var hWnd = null; // hold our window handle

$public.STRINGS = { };

$public.STRINGS.err_T = 'Error ::';
$public.STRINGS.err_auth_fail = 'Invalid username/password.';
$public.STRINGS.err_auth_fail_T = $public.STRINGS.err_T;
$public.STRINGS.err_no_url = 'Invalid configuration.\nPlease contact an administrator.';
$public.STRINGS.err_no_url_T = $public.STRINGS.err_T;
$public.STRINGS.err_client_invalid = 'Please correct any errors and re-submit.';
$public.STRINGS.err_client_invalid_T = $public.STRINGS.err_T + ' [CLIENT_INVALID]';
$public.STRINGS.err_connect_fail = 'Unable to process your request.\nPlease try again later.';
$public.STRINGS.err_connect_fail_T = $public.STRINGS.err_T + ' [CONNECT_FAIL]';
$public.STRINGS.err_server_invalid = 'Unknown error.\nPlease contact an administrator.';
$public.STRINGS.err_server_invalid_T = $public.STRINGS.err_T + ' [SERVER_INVALID]';

$public.STRINGS.waitMsg = 'Authenticating. . .';
$public.STRINGS.waitTitle = 'Please wait. . .';

var autoClose = true;
var onFailure = function(f,a) {
if(a.failureType) {
switch(a.failureType) {
case Ext.form.Action.CLIENT_INVALID:
Ext.Msg.alert($public.STRINGS.err_client_invalid_T, $public.STRINGS.err_client_invalid);
break;
case Ext.form.Action.CONNECT_FAILURE:
Ext.Msg.alert($public.STRINGS.err_connect_fail_T, $public.STRINGS.err_connect_fail);
break;
case Ext.form.Action.SERVER_INVALID:
Ext.Msg.alert($public.STRINGS.err_server_invalid_T, $public.STRINGS.err_server_invalid);
break;
}
} else {
Ext.Msg.alert($public.STRINGS.err_auth_fail_T, $public.STRINGS.err_auth_fail);
}
};
var onSuccess = function(f,a) { };
var url = '';

/**
* @private member Ext.ux.LoginDialog
**
* failure ACTION handler
*/
function failure(f,a) { onFailure(f,a); }

/**
* @private member Ext.ux.LoginDialog
**
* success ACTION handler
*/
function success(f,a) {
if(autoClose) { $public.close(); }
onSuccess(f,a);
}

/**
* @private member Ext.ux.LoginDialog
**
* this function will submit the form to the URL we provided (default: '')
* and hookup our success/failure handlers to the supplied functions.
*/
function auth() {
var frm = getForm();

if(url) {
frm.submit({
url : url,
failure : failure,
success : success,
waitMsg : $public.STRINGS.waitMsg,
waitTitle : $public.STRINGS.waitTitle
});
} else {
Ext.Msg.alert($public.STRINGS.err_no_url_T, $public.STRINGS.err_no_url);
}
}

/**
* @private member Ext.ux.LoginDialog
**
* get the underlying Ext.Window.
* create one if one doesn't exist.
*/
var getWindow = function(args) {
args = args || { };
args.form = args.form || { };

if(!hWnd) {
autoClose = args.autoClose || autoClose;
onFailure = args.onFailure || onFailure;
onSuccess = args.onSuccess || onSuccess;
url = args.url || url;

hWnd = new Ext.Window({
animateTarget : args.animateTarget || Ext.getBody(),
bodyStyle : args.bodyStyle || 'padding: 5px',
closable : false,
collapsible : false,
draggable : false,
height : args.height || 175,

items : new Ext.FormPanel({
baseParams : args.form.baseParams || { async : true },
bodyStyle : args.form.bodyStyle || 'background-color: transparent; padding: 20px 0px;',
border : false,
defaults : {
allowBlank : false,
msgTarget : 'side',
vtype : 'alpha',
xtype : 'textfield'
},
labelAlign : 'right',
method : 'POST',

items : [{
fieldLabel : args.form.usernameLabel || 'Username',
maxLength : args.form.usernameMax || 10,
minLength : args.form.usernameMin || 3,
name : args.form.usernameName || 'username'
},{
fieldLabel : args.form.passwordLabel || 'Password',
inputType : 'password',
maxLength : args.form.passwordMax || 16,
minLength : args.form.passwordMin || 8,
name : args.form.passwordName || 'password',
vtype : 'alphanum'
}],
resizable : false
}),
title : args.title || ':: login',
width : args.width || 350,

buttons : [{ handler : auth, text : args.buttonText || 'Login' }]
});
}
return(hWnd);
};

/**
* @private member Ext.ux.LoginDialog
**
* get the form component
*/
function getForm() {
if(hWnd) { return(hWnd.findByType('form')[0].getForm()); }
return(null);
}

/**
* @public member Ext.ux.LoginDialog
**
* display the dialog
*/
$public.show = function(args) {
if(!hWnd) { getWindow(args); }
if(!hWnd.isVisible()) { hWnd.show(); }

return(this);
};

/**
* @public member Ext.ux.LoginDialog
**
* close the dialog
*/
$public.close = function() { if(hWnd) { hWnd.close(); } };

return($public);
}();

Animal
26 Oct 2007, 10:25 PM
Use Ext.apply:



Ext.ux.LoginDialog = function(config) {
config = Ext.apply(config || {}, {
overrideConfig: "MyValue"
}, {
defaultConfig: "DefaultValue"
});
};


That applies your own overrides where they must be set to the values, and provides defaults where the use does not pass a value in.

Actually, I think that exposes a flaw in Ext.apply.



Ext.apply = function(o, c, defaults){
if(defaults){
// no "this" reference for friendly out of scope calls
Ext.apply(o, defaults);
}
if(o && c && typeof c == 'object'){
for(var p in c){
o[p] = c[p];
}
}
return o;
};


The "defaults" are applied even if the value already exists in "o". That's not really defaulting.

It should surely be



Ext.apply = function(o, c, defaults){
if(defaults){
// no "this" reference for friendly out of scope calls
Ext.applyIf(o, defaults);
}
if(o && c && typeof c == 'object'){
for(var p in c){
o[p] = c[p];
}
}
return o;
};


Jack, is that right?

Animal
26 Oct 2007, 10:27 PM
You can provide default values in your prototype:



Ext.Extend(MyClass, BaseClass, {
height: 600,
width: 400,
etc...

brian.moeskau
27 Oct 2007, 2:22 PM
BTW, you should use Ext.ux -- lower case -- and that namespace already exists, so you should not need to create a new one. ;)

brian.moeskau
27 Oct 2007, 2:23 PM
Speaking of case, I believe you might have a bug here:


bodyStyle : cfg.form.BodyStyle || def.form.bodyStyle,

Glen
28 Oct 2007, 9:50 PM
I download ext-2.0-beta1 package ,and deploy it to IIS 6.0.
But i find some problem.
when i open http://localhost:808/examples/form/xml-form.html and click load button,ext can't fetch data from xml and submit button is disabled, I find ext will alert Connect Error when click load button.
Pls ,Tell me how to solve it .

jhorvath
28 Oct 2007, 10:23 PM
I download ext-2.0-beta1 package ,and deploy it to IIS 6.0.
But i find some problem.
when i open http://localhost:808/examples/form/xml-form.html and click load button,ext can't fetch data from xml and submit button is disabled, I find ext will alert Connect Error when click load button.
Pls ,Tell me how to solve it .
ahhHhhhH! hijack! kidding, forgive me. (you may wish to start a new topic though bro)

anyways, >:)

i fixed the case issues. ;)

i guess what i was really looking for was a way to say 'we have been passed this list of parameters in the config, there may be 20 properties/methods, where as i only need/want 5 specific ones', but without being so explicit.

in my example i have basically said in two separate spots, that i only want X and Y out of a possible A-Z. once in `def` and again in `ini` (could move all the '||s' to def but that is still room for typo's as we saw [read: save me from myself]) :)

i'm just looking at the config as a list of parameters to the constructor, but it adds the convenience of allowing the parameters in whichever order, but it doesn't allow you to easily ignore any of them. and, perhaps that amount of control isn't really necessary with most solutions, i don't know, i'm just a nub trying to figure it all out. :))

i was playing with something like the example below, it's the anti-applyIf B)



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Area51 : Testing</title>

<!-- change Ext path -->
<script type="text/javascript" src="/lib/js/ext/2.0b1/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="/lib/js/ext/2.0b1/ext-all-debug.js"></script>
</head>
<body>
<script type="text/javascript">
// **********************************************************************

/**
* recursive solution inspired by andrei.neculau's `deep/recursive Ext.apply()`
*/

/**
* opposite of Ext.applyIf(), with optional recursion
*/
Ext.ux.applyIf = function(o, c, r) {
r = (r) ? ((typeof r === "number" && r<11) ? r : 10) : false;

if(o && c) {
for(var p in c) {
if(typeof o[p] != "undefined") {
if(typeof o[p] === "object" && r) { Ext.ux.applyIf(o[p],c[p],r--); }
else { o[p] = c[p]; }
}
}
}
return o;
};

/**
* the old Ext.applyIf(), with optional recursion
*/
Ext.ux.applyIfNot = function(o, c, r) {
r = (r) ? ((typeof r === "number" && r<11) ? r : 10) : false;

if(o && c) {
for(var p in c) {
if(typeof o[p] == "undefined") { o[p] = c[p]; }
else if(typeof o[p] === "object" && r) { Ext.ux.applyIfNot(o[p],c[p],r--); }
}
}
return o;
};

Ext.ux.objectToString = function(o,r){
r = (r) ? ((typeof r === "number" && r<11) ? r : 10) : false;
var s = "";
var t = "";

if(r) for(var i=r; i<10;i++) { t+="\t"; }

if(o) {
for(var p in o) {
if(typeof o[p] === "object" && r) { s += t+p+" : \n"+Ext.ux.objectToString(o[p],--r)+"\n"; }
else { s += t+p+" : "+o[p]+"\n"; }
}
}

return s;
};
// **********************************************************************

var o1 = {
p1 : 'o1.p1',
p3 : 'o1.p3',
p5 : 'o1.p5',
p7 : {
n1 : {
p1 : 'o1.p7.n1.p1'
}
}
};

var o2 = {
p2 : 'o2.p2',
p3 : 'o2.p3',
p4 : 'o2.p4',
p6 : 'o2.p6',
p7 : {
n1 : {
p1 : 'o2.p7.n1.p1',
p2 : 'o2.p7.n1.p2'
}
}
};

alert("o1 -- Before `Ext.ux.applyIf(o1,o2,true)`\n\n"+Ext.ux.objectToString(o1,true)+"\n\no1 -- After\n\n"+Ext.ux.objectToString(Ext.ux.applyIf(o1,o2,true),true));

// reset o1
o1 = {
p1 : 'o1.p1',
p3 : 'o1.p3',
p5 : 'o1.p5',
p7 : {
n1 : {
p1 : 'o1.p7.n1.p1'
}
}
};

alert("o1 -- Before `Ext.ux.applyIfNot(o1,o2,true)`\n\n"+Ext.ux.objectToString(o1,true)+"\n\no1 -- After\n\n"+Ext.ux.objectToString(Ext.ux.applyIfNot(o1,o2,true),true));
</script>
</body>
</html>


the example above hasn't been thoroughly tested, only within the scope of the example really ^^

..son of a, i think i'm addicted to emoticons..