/** * DefaultHint * @version $Id$; * @author Nazar Lazorko * @uses jQuery * * Example of using DefaultHint in QSF2: * @see jQuery.fn.defaultHint#defaults * * $('#form_contact-form').defaultHint({ * useWrapper: true * }); * $().defaultHint({ * elements: [ * {selector: '#name', hint: '', trquired: true}, * {selector: '#emailOrPhone'} * ] * }); * $params = array( * 'elements' => array( * array('selector' => '#name', 'hint' => 'Name:', 'required' => true), * array('selector' => '#emailOrPhone') * ) * ); * $this->doc->addScript('js/defaultHint.js', array(), 'defaultHint'); * $this->doc->addInitFunction ('$().defaultHint', array($params)); * * */ (function ($, undefined) { var elementSelector = 'input[type="text"], input[type="password"], select, textarea'; var defaults = { elements: undefined, // [{selector: '', hint: '', required: false}, ...] requiredClass: 'required', // class of labels marked as required autoIndent: true, asteriskTag: 'span', asteriskClass: 'asterisk', // class of asterisk element asteriskText: '*', useWrapper: false, // wrap element with relative hints into div wrapperTag: 'div', wrapperIdSuffix: '-wrapper', wrapperClass: 'defaultHint_wrapper', hintTag: 'div', hintIdSuffix: '-hint', hintClass: 'defaultHint_element', activeElementClass: undefined, // class that applied to active element activeParentClass: undefined // class that applied to active element parent }; var isUndefined = function (mixed) { return ('undefined' === typeof mixed); }; var checkCallContext = function () { if (!(this instanceof $)) { throw new Error('DefaultHint: Helper must be called from jQuery context'); } }; var isSupported = function ($element) { return ($element instanceof $) ? $element.is(elementSelector) : false; }; var getElementLabel = function ($element) { return $($element.attr('form')).find('label[for="' + $element.attr('id') + '"]'); }; var helper = function (helperName, helperArgs) { checkCallContext.call(this); if ($.isFunction(helpers[helperName])) { return helpers[helperName].apply(this, helperArgs); } throw new Error('DefaultHint: Helper "' + helperName + '" not found'); }; var listeners = { hintClick: function (e) { $(this).data('$element.defaultHint').focus(); }, hintMouseDown: function (e) { var $element = $(this).data('$element.defaultHint'); setTimeout(function () { $element.focus(); }, 1); }, elementFocus: function (e) { var $this = $(this), $hint = $this.data('$hint.defaultHint'), _o = $this.data('options.defaultHint'); if ($hint) { $hint.css('display', 'none'); } if (_o.activeElementClass) { $this.addClass(_o.activeElementClass); } if (_o.activeParentClass) { $this.parent().addClass(_o.activeParentClass); } }, elementBlur: function (e) { var $this = $(this), $hint = $this.data('$hint.defaultHint'), _o = $this.data('options.defaultHint'); if ('' === $this.val() && $hint) { $hint.css('display', 'block'); } if (_o.activeElementClass) { $this.removeClass(_o.activeElementClass); } if (_o.activeParentClass && this.parentNode) { $this.parent().removeClass(_o.activeParentClass); } } }; var helpers = { hint: function ($element, hint, required) { checkCallContext.call(this); var label, $hint, $wrapper, tag = $element.prop('tagName'), _o = this.options; if (isUndefined(hint)) { label = getElementLabel($element); hint = label.text(); } if (isUndefined(required)) { label = label || getElementLabel($element); required = label.hasClass(_o.requiredClass); } if (($hint = helper.call(this, ''+tag.toLowerCase() + 'Hint', [$element, hint, required]))) { $element.data('options.defaultHint', _o) .bind('focus', listeners.elementFocus) .bind('blur', listeners.elementBlur); if (_o.useWrapper) { $wrapper = helper.call(this, 'wrapper', [$element, $hint]); } } return $hint; }, wrapper: function ($element, $hint) { checkCallContext.call(this); var _o = this.options, $wrapper, id; // do not wrap special hints (like 'option') if (_o.hintTag === $hint.prop('tagName').toLowerCase()) { $wrapper = $(document.createElement(_o.wrapperTag)).addClass(_o.wrapperClass); if ((id = $element.attr('id'))) { $wrapper.attr('id', $element.attr('id') + _o.wrapperIdSuffix); } $element.after($wrapper); $wrapper.append($element).append($hint); if (_o.autoIndent) { $wrapper.css({ position: 'relative' }); $hint.css({ position: 'absolute', top: 0, left: 0, paddingTop: $element.css('paddingTop'), paddingLeft: $element.css('paddingLeft'), maxWidth: $element.css('width'), maxHeight: $element.css('height'), overflow: 'hidden' // top: ('input' == $element.attr('tagName').toLowerCase()) ? // ($element.innerHeight() - $hint.outerHeight()) / 2 : // $element.css('padding-top') }); } return $wrapper; } return undefined; }, inputHint: function ($element, hint, required) { checkCallContext.call(this); var _o = this.options, $hint = $(document.createElement(_o.hintTag)).addClass(this.options.hintClass), elementId = $element.attr('id'); if (elementId) { $hint.attr('id', elementId + _o.hintIdSuffix); } if (required) { $(document.createElement(_o.asteriskTag)).addClass(_o.asteriskClass) .text(_o.asteriskText).appendTo($hint); } $hint.append(hint).data('options.defaultHint', _o).data('$element.defaultHint', $element) .bind('click', listeners.hintClick).bind('mousedown', listeners.hintMouseDown); if (document.activeElement == $element.get(0) || $element.val() !== '') { $hint.css('display', 'none'); } $element.data('$hint.defaultHint', $hint).after($hint); return $hint; }, textareaHint: function ($element, hint, required) { return helper.call(this, 'inputHint', [$element, hint, required]); }, selectHint: function ($element, hint, required) { checkCallContext.call(this); var $first = $element.children(':first'), $hint; $hint = $(document.createElement('option')).attr('text', hint).attr('label', hint).val(''); if ($first.size()) { if ('' === $first.val()) { $first.attr('label', hint).attr('text', hint); } else { $first.before($hint); if ($first.val() == $element.val()) { $element.attr('selectedIndex', 0); } } } else { $element.append($hint); } return $hint; } }; var setHint = function ($element, hint, required) { checkCallContext.call(this); if (isSupported($element) && isUndefined($element.data('$hint.defaultHint'))) { helper.call(this, 'hint', [$element, hint, required]); } }; $.fn.defaultHint = function (options) { var _o = $.extend({}, defaults, options); this.options = _o; if ($.isArray(_o.elements)) { for (var i = 0, len = _o.elements.length; i < len; i++) { setHint.call(this, $(_o.elements[i].selector), _o.elements[i].hint, _o.elements[i].required); } } else { var that = this; this.find(elementSelector).each(function (index, element) { setHint.call(that, $(element)); }); } return this; }; })(jQuery);