Skip to content

Instantly share code, notes, and snippets.

@steida
Created March 26, 2009 02:59
Show Gist options
  • Save steida/85837 to your computer and use it in GitHub Desktop.
Save steida/85837 to your computer and use it in GitHub Desktop.
/*
* MUI.Widget - is base class for all widgets, designed to be extended
*
* - allows you to add functionality to elements unobtrusively
* - the lifecycle methods: initialize, render and destroy
* - abstract rendering methods (render calls: createUI, updateUI) to support a consistent structure
* - provides a common set of base widget attributes
* - consistent class-name generation
* - for easy widget developement and maintanance use external stylesheets and HTML templates as much you can
* - basic widget HTML template consists of container (outermost element) and content (direct descendant)
*
* <div class="mui-widget mui-widget-hidden">
* <div class='content'></div>
* </div>
*
* Notes:
*
* - rewamped AUI with tests
* - some functionality inspired by YUI3
*
* TODO: automaticke nestovani? bude se hodit pro formularove elementy, dale buttony podle A, container generovat automatickz
*
* License:
* MIT-style license.
*
* Copyright:
*
* Copyright (c) 2009 [Daniel Steigerwald](http://daniel.steigerwald.cz/).
*
*/
window.MUI = window.MUI || {};
MUI.Widget = new Class({
Implements: [Events, Options],
// for class-name generation e.g. mui-widget
NS: 'mui',
NAME: 'widget',
// for class-name generation e.g. mui-widget-hidden
states: {
visible: true,
focused: false,
disabled: false
},
classes: {
hidden: 'hidden',
focused: 'focused',
disabled: 'disabled'
},
options: {/*
onShow: fn,
onHide: fn,
onFocus: fn,
onBlur: fn,
onEnable: fn,
onDisable: fn, */
container: null, // outermost node for the Widget, used for sizing and positioning of a Widget, this element can also serve as a containing element for any decorator elements used for skinning, is not expected to have any visual properties (e.g. borders, padding etc.) applied to it., however it will have CSS defining how the widget impacts the document flow. For example, the bounding box type ("display:inline", "display:inline-block", "display:block") and the positioning scheme ("position:absolute", "position:relative"), todo: The bounding box will be added dynamically when the widget is instantiated. This maintains semantic value (the content box ends up containing existing content), and avoids unnecessary markup on the page up front.
content: null, // a node that is a direct descendent of a container. Hold the look/feel for the widget.
placeholder: null, // if element is not in DOM, then it will be injected in placeholder. If placeholder doesn't exists, then element will be injected in document.body
skin: null // todo
},
/*
* initialize methods
*/
initialize: function(options) {
this.setOptions(options);
this.container = $(this.options.container);
this.content = $(this.options.content);
this.prepare();
this.render();
},
prepare: function() {
this.injectContainer();
this.setContainerClassNames();
this.setStatesByClassNames();
this.registerClassEvents();
},
injectContainer: function() {
var inDoc = this.container.isInDocument();
if (!inDoc) this.container.inject(this.options.placeholder || this.container.ownerDocument.body);
},
setContainerClassNames: function() {
this.container.addClass(this.classNameManager(this.NAME));
},
setStatesByClassNames: function() {
this.states.visible = !this.getClassNameState(this.classes.hidden);
this.states.focused = this.getClassNameState(this.classes.focused);
this.states.disabled = this.getClassNameState(this.classes.disabled);
},
registerClassEvents: function() {
this.addEvents({ stateChange: this.onStateChange.bind(this) });
'show,hide,focus,blur,enable,disable'.split(',').each(function(name) {
this.addEvent(name, this['on' + name.capitalize()].bind(this));
}, this);
},
render: function() {
this.createUI();
this.updateUI();
},
// e.g. create elements, attach listeners.. (point at which the DOM is first modified)
createUI: $empty,
// update markup from model by the current state of the widget
updateUI: $empty,
/*
* main methods
*/
show: function() {
this.set('visible', true);
return this;
},
hide: function() {
this.set('visible', false);
return this;
},
focus: function() {
this.set('focused', true);
return this;
},
blur: function() {
this.set('focused', false);
return this;
},
enable: function() {
this.set('disabled', false);
return this;
},
disable: function() {
this.set('disabled', true);
return this;
},
destroy: function() {
this.container.destroy();
},
/*
* states methods
*/
set: function(state, value) {
this.states[state] = value;
this.fireEvent('stateChange', [state, value]);
return this;
},
get: function(flag) {
return this.states[flag];
},
isVisible: function() {
return this.get('visible');
},
hasFocus: function() {
return this.get('focused');
},
isDisabled: function() {
return this.get('disabled');
},
/*
* listeners methods
*/
onStateChange: function(state, value) {
switch (state) {
case 'visible':
this.setClassNameState(this.classes.hidden, !value);
this.fireEvent(value ? 'show' : 'hide');
break;
case 'focused': this.fireEvent(value ? 'focus' : 'blur');
this.setClassNameState(this.classes.focused, value);
break;
case 'disabled': this.fireEvent(value ? 'disable' : 'enable');
this.setClassNameState(this.classes.disabled, value);
break;
}
},
onShow: $empty,
onHide: $empty,
onFocus: $empty,
onBlur: $empty,
onDisable: $empty,
onEnable: $empty,
/*
* util methods
*/
classNameManager: function() {
return [this.NS].extend(arguments).join('-');
},
setClassNameState: function(state, value) {
this.container.toggleClass(this.classNameManager(this.NAME, state), !!value);
},
getClassNameState: function(state) {
return this.container.hasClass(this.classNameManager(this.NAME, state));
},
toElement: function() {
return this.container;
}
});
var container, widget;
describe('MUI.Widget constructor', {
before_each: function() {
container = new Element('div');
widget = new MUI.Widget({ container: container });
},
after_each: function() {
container = widget = null;
},
"should create widget": function() {
value_of(widget).should_not_be_undefined();
},
"should set container and should returns it with $": function() {
value_of(widget.container).should_be(container);
value_of($(widget)).should_be(container);
},
"should inject widget into placeHolder option": function() {
var placeholder = new Element('div');
var container = new Element('div');
var widget = new MUI.Widget({
container: container,
placeholder: placeholder
});
value_of($(widget).parentNode).should_be(placeholder);
},
"should inject widget into containerDocument body in case of widget container is out of DOM and placeholder is not defined": function() {
container = new Element('div');
value_of(container.isInDocument()).should_be_false();
widget = new MUI.Widget({
container: container,
placeholder: null
});
value_of($(widget).isInDocument()).should_be_true();
},
"should set widget container element classNames": function() {
// precondition
value_of(widget.NS).should_be('mui');
value_of(widget.NAME).should_be('widget');
value_of($(widget).get('class')).should_be('mui-widget');
},
"should have default states": function() {
value_of(widget.isVisible()).should_be_true();
value_of(widget.hasFocus()).should_be_false();
value_of(widget.isDisabled()).should_be_false();
},
"should retrieve widget states from its classNames": function() {
var placeholder = new Element('div');
var container = new Element('div', {
'class': 'mui-widget-hidden mui-widget-focused mui-widget-disabled'
});
var widget = new MUI.Widget({
container: container,
placeholder: placeholder
});
value_of(widget.isVisible()).should_be_false();
value_of(widget.hasFocus()).should_be_true();
value_of(widget.isDisabled()).should_be_true();
}
});
describe('MUI.Widget states', {
before_each: function() {
widget = new MUI.Widget({
container: new Element('div')
});
// precondition
value_of(widget.NS).should_be('mui');
value_of(widget.NAME).should_be('widget');
},
after_each: function() {
container = widget = null;
},
"should add/remove 'hidden' className state (also check isVisible)": function() {
widget.hide();
value_of($(widget).hasClass('mui-widget-hidden')).should_be(true);
value_of(widget.isVisible()).should_be(false);
widget.show();
value_of($(widget).hasClass('mui-widget-hidden')).should_be(false);
value_of(widget.isVisible()).should_be(true);
},
"should add/remove 'focused' className state (also check hasFocus)": function() {
widget.focus();
value_of($(widget).hasClass('mui-widget-focused')).should_be(true);
value_of(widget.hasFocus()).should_be(true);
widget.blur();
value_of($(widget).hasClass('mui-widget-focused')).should_be(false);
value_of(widget.hasFocus()).should_be(false);
},
"should add/remove 'disabled' className state (also check isDisabled)": function() {
widget.disable();
value_of($(widget).hasClass('mui-widget-disabled')).should_be(true);
value_of(widget.isDisabled()).should_be(true);
widget.enable();
value_of($(widget).hasClass('mui-widget-disabled')).should_be(false);
value_of(widget.isDisabled()).should_be(false);
}
});
describe('MUI.Widget events ', {
"check events: show, hide, focus, blur, enable, disable": function() {
var calls = 0;
var increment = function() {
calls++;
};
var widget = new MUI.Widget({
container: new Element('div'),
onShow: increment,
onHide: increment,
onFocus: increment,
onBlur: increment,
onEnable: increment,
onDisable: increment
});
widget.show().hide().focus().blur().enable().disable();
value_of(calls).should_be(6);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment