blob: da61b193262fd0ff9f0974b9a6ad0108466af605 [file] [log] [blame]
//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
//>>description: Widget class extending functionality of jQueryMobile widget class
//>>label: Widget extension
//>>group: Tizen:Widgets
define( [
'jquery',
'../jquery.mobile.tizen.core'
], function ( jQuery ) {
//>>excludeEnd("jqmBuildExclude");
/*
*
* This software is licensed under the MIT licence (as defined by the OSI at
* http://www.opensource.org/licenses/mit-license.php)
*
* ***************************************************************************
* Copyright (C) 2011 by Intel Corporation Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software" ),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* ***************************************************************************
*/
// Base class for widgets that need the following features:
//
// I. HTML prototype loading
//
// This class provides HTML prototype loading for widgets. That is, the widget implementation specifies its HTML portions
// in one continuous HTML snippet, and it optionally provides an object containing selectors into the various parts of the
// HTML snippet. This widget loads the HTML snippet into a jQuery object, and optionally assigns jQuery objects to each of
// the selectors in the optionally provided object.
//
// To use this functionality you can either derive from this class, or you can call its prototype's gtype method.
//
// 1. Widgets deriving from this class should define _htmlProto as part of their prototype declaration. _htmlProto looks like
// this:
//
// _htmlProto: {
// source: string|jQuery object (optional) default: string - The name of the widget
// ui: {
// uiElement1: "#ui-element-1-selector",
// uiElement2: "#ui-element-2-selector",
// ...
// subElement: {
// subElement1: "#sub-element-1-selector",
// subElement2: "#sub-element-2-selector",
// ...
// }
// ...
// }
// }
//
// If neither 'source' nor 'ui' are defined, you must still include an empty _htmlProto key (_htmlProto: {}) to indicate
// that you wish to make use of this feature. This will cause a prototype HTML file named after your widget to be loaded.
// The loaded prototype will be placed into your widget's prototype's _protoHtml.source key.
//
// If 'source' is defined as a string, it is the name of the widget (including namespace). This is the default. If your
// widget's HTML prototype is loaded via AJAX and the name of the AJAX file is different from the name of your widget
// (that is, it is not "<widgetName>.prototype.html", then you should explicitly define 'source' as:
//
// If you wish to load HTML prototypes via AJAX, modify the getProtoPath() function defined below to reflect the directory
// structure holding your widget HTML prototypes.
//
// source: "alternateWidgetName"
//
// If AJAX loading fails, source is set to a jQuery object containing a div with an error message. You can check whether
// loading failed via the jQuery object's jqmData( "tizen.widgetex.ajax.fail" ) data item. If false, then the jQuery object
// is the actual prototype loaded via AJAX or present inline. Otherwise, the jQuery object is the error message div.
//
// If 'source' is defined as a jQuery object, it is considered already loaded.
//
// if 'ui' is defined inside _htmlProto, It is assumed to be an object such that every one of its keys is either a string,
// or another object with the same properties as itself.
//
// When a widget is instantiated, the HTML prototype is loaded if not already present in the prototype. If 'ui' is present
// inside _htmlProto, the prototype is cloned. Then, a new structure is created based on 'ui' with each selector replaced
// by a jQuery object containing the results of performing .find() on the prototype's clone with the filter set to the
// value of the string. In the special case where the selector starts with a '#', the ID is removed from the element after
// it is assigned into the structure being created. This structure is then made accessible from the widget instance via
// the '_ui' key (i.e., this._ui).
//
// 2. Use the loadPrototype method when your widget does not derive from $.tizen.widgetex:
// Add _htmlProto to your widget's prototype as described above. Then, in your widget's _create() method, call
// loadPrototype in the following manner:
//
// $.tizen.widgetex.loadPrototype.call(this, "namespace.widgetName" );
//
// Thereafter, you may use the HTML prototype from your widget's prototype or, if you have specified a 'ui' key in your
// _htmlProto key, you may use this._ui from your widget instance.
//
// II. realize method
//
// When a widget is created, some of its properties cannot be set immediately, because they depend on the widths/heights
// of its constituent elements. They can only be calculated when the page containing the widget is made visible via the
// "pageshow" event, because widths/heights always evaluate to 0 when retrieved from a widget that is not visible. When
// you inherit from widgetex, you can add a "_realize" function to your prototype. This function will be called once right
// after _create() if the element that anchors your widget is on a visible page. Otherwise, it will be called when the
// page to which the widget belongs emits the "pageshow" event.
//
// NB: If your widget is inside a container which is itself not visible, such as an expandable or a collapsible, your
// widget will remain hidden even though "pageshow" is fired and therefore _realize is called. In this case, widths and
// heights will be unreliable even during _realize.
//
// III. systematic option handling
//
// If a widget has lots of options, the _setOption function can become a long switch for setting each recognized option.
// It is also tempting to allow options to determine the way a widget is created, by basing decisions on various options
// during _create(). Often, the actions based on option values in _create() are the same as those in _setOption. To avoid
// such code duplication, this class calls _setOption once for each option after _create() has completed.
//
// Furthermore, to avoid writing long switches in a widget's _setOption method, this class implements _setOption in such
// a way that, for any given option (e.g. "myOption" ), _setOption looks for a method _setMyOption in the widget's
// implementation, and if found, calls the method with the value of the option.
//
// If your widget does not inherit from widgetex, you can still use widgetex' systematic option handling:
// 1. define the _setOption method for your widget as follows:
// _setOption: $.tizen.widgetex.prototype._setOption
// 2. Call this._setOptions(this.options) from your widget's _create() function.
// 3. As with widgetex-derived widgets, implement a corresponding _setMyOptionName function for each option myOptionName
// you wish to handle.
//
// IV. systematic value handling for input elements
//
// If your widget happens to be constructed from an <input> element, you have to handle the "value" attribute specially,
// and you have to emit the "change" signal whenever it changes, in addition to your widget's normal signals and option
// changes. With widgetex, you can assign one of your widget's "data-*" properties to be synchronized to the "value"
// property whenever your widget is constructed onto an <input> element. To do this, define, in your prototype:
//
// _value: {
// attr: "data-my-attribute",
// signal: "signal-to-emit"
// }
//
// Then, call this._setValue(newValue) whenever you wish to set the value for your widget. This will set the data-*
// attribute, emit the custom signal (if set) with the new value as its parameter, and, if the widget is based on an
// <input> element, it will also set the "value" attribute and emit the "change" signal.
//
// "attr" is required if you choose to define "_value", and identifies the data-attribute to set in addition to "value",
// if your widget's element is an input.
// "signal" is optional, and will be emitted when setting the data-attribute via this._setValue(newValue).
//
// If your widget does not derive from widgetex, you can still define "_value" as described above and call
// $.tizen.widgetex.setValue(widget, newValue).
//
// V. Systematic enabled/disabled handling for input elements
//
// widgetex implements _setDisabled which will disable the input associated with this widget, if any. Thus, if you derive
// from widgetex and you plan on implementing the disabled state, you should chain up to
// $.tizen.widgetex.prototype._setDisabled(value), rather than $.Widget.prototype._setOption( "disabled", value).
(function ($, undefined) {
// Framework-specific HTML prototype path for AJAX loads
function getProtoPath() {
var theScriptTag = $( "script[data-framework-version][data-framework-root][data-framework-theme]" );
return (theScriptTag.attr( "data-framework-root" ) + "/" +
theScriptTag.attr( "data-framework-version" ) + "/themes/" +
theScriptTag.attr( "data-framework-theme" ) + "/proto-html" );
}
$.widget( "tizen.widgetex", $.mobile.widget, {
_createWidget: function () {
$.tizen.widgetex.loadPrototype.call( this, this.namespace + "." + this.widgetName );
$.mobile.widget.prototype._createWidget.apply( this, arguments );
},
_init: function () {
// TODO THIS IS TEMPORARY PATCH TO AVOID CTXPOPUP PAGE CRASH
if ( this.element === undefined ) {
return;
}
var page = this.element.closest( ".ui-page" ),
self = this,
myOptions = {};
if ( page.is( ":visible" ) ) {
this._realize();
} else {
page.bind( "pageshow", function () { self._realize(); } );
}
$.extend( myOptions, this.options );
this.options = {};
this._setOptions( myOptions );
},
_getCreateOptions: function () {
// if we're dealing with an <input> element, value takes precedence over corresponding data-* attribute, if a
// mapping has been established via this._value. So, assign the value to the data-* attribute, so that it may
// then be assigned to this.options in the superclass' _getCreateOptions
if (this.element.is( "input" ) && this._value !== undefined) {
var theValue =
( ( this.element.attr( "type" ) === "checkbox" || this.element.attr( "type" ) === "radio" )
? this.element.is( ":checked" )
: this.element.is( "[value]" )
? this.element.attr( "value" )
: undefined);
if ( theValue != undefined ) {
this.element.attr( this._value.attr, theValue );
}
}
return $.mobile.widget.prototype._getCreateOptions.apply( this, arguments );
},
_setOption: function ( key, value ) {
var setter = "_set" + key.replace(/^[a-z]/, function (c) { return c.toUpperCase(); } );
if ( this[setter] !== undefined ) {
this[setter]( value );
} else {
$.mobile.widget.prototype._setOption.apply( this, arguments );
}
},
_setDisabled: function ( value ) {
$.Widget.prototype._setOption.call( this, "disabled", value );
if ( this.element.is( "input" ) ) {
this.element.attr( "disabled", value );
}
},
_setValue: function ( newValue ) {
$.tizen.widgetex.setValue( this, newValue );
},
_realize: function () {}
} );
$.tizen.widgetex.setValue = function ( widget, newValue ) {
if ( widget._value !== undefined ) {
var valueString = ( widget._value.makeString ? widget._value.makeString(newValue) : newValue ),
inputType;
widget.element.attr( widget._value.attr, valueString );
if ( widget._value.signal !== undefined ) {
widget.element.triggerHandler( widget._value.signal, newValue );
}
if ( widget.element.is( "input" ) ) {
inputType = widget.element.attr( "type" );
// Special handling for checkboxes and radio buttons, where the presence of the "checked" attribute is really
// the value
if ( inputType === "checkbox" || inputType === "radio" ) {
if ( newValue ) {
widget.element.attr( "checked", true );
} else {
widget.element.removeAttr( "checked" );
}
} else {
widget.element.attr( "value", valueString );
}
widget.element.trigger( "change" );
}
}
};
$.tizen.widgetex.assignElements = function (proto, obj) {
var ret = {},
key;
for ( key in obj ) {
if ( ( typeof obj[key] ) === "string" ) {
ret[key] = proto.find( obj[key] );
if ( obj[key].match(/^#/) ) {
ret[key].removeAttr( "id" );
}
} else {
if ( (typeof obj[key]) === "object" ) {
ret[key] = $.tizen.widgetex.assignElements( proto, obj[key] );
}
}
}
return ret;
};
$.tizen.widgetex.loadPrototype = function ( widget, ui ) {
var ar = widget.split( "." ),
namespace,
widgetName,
source,
noSource = false,
htmlProto,
protoPath;
if ( ar.length == 2 ) {
namespace = ar[0];
widgetName = ar[1];
// If htmlProto is defined
if ( $[namespace][widgetName].prototype._htmlProto !== undefined ) {
// If no source is defined, use the widget name
source = $[namespace][widgetName].prototype._htmlProto.source;
if ( source === undefined ) {
source = widgetName;
noSource = true;
}
// Load the HTML prototype via AJAX if not defined inline
if ( typeof source === "string" ) {
if ( noSource ) { // use external htmlproto file
// Establish the path for the proto file
widget = source;
protoPath = getProtoPath();
// Make the AJAX call
$.ajax( {
url: protoPath + "/" + widget + ".prototype.html",
async: false,
dataType: "html"
}).success( function (data, textStatus, jqXHR ) {
source = $( "<div></div>" ).html(data).jqmData( "tizen.widgetex.ajax.fail", false );
} );
// Assign the HTML proto to the widget prototype
source = $( "<div></div>" )
.text( "Failed to load proto for widget " + namespace + "." + widgetName + "!" )
.css( {background: "red", color: "blue", border: "1px solid black"} )
.jqmData( "tizen.widgetex.ajax.fail", true );
} else {
// inline definition (string)
source = $( source ).jqmData( "tizen.widgetex.ajax.fail", false );
}
} else {
// inline definition (object)
// AJAX loading has trivially succeeded, since there was no AJAX loading at all
source.jqmData( "tizen.widgetex.ajax.fail", false );
}
htmlProto = source;
$[namespace][widgetName].prototype._htmlProto.source = source;
// If there's a "ui" portion in the HTML proto, copy it over to this instance, and
// replace the selectors with the selected elements from a copy of the HTML prototype
if ( $[namespace][widgetName].prototype._htmlProto.ui !== undefined ) {
// Assign the relevant parts of the proto
$.extend( this, {
_ui: $.tizen.widgetex.assignElements( htmlProto.clone(), $[namespace][widgetName].prototype._htmlProto.ui )
});
}
}
}
};
}( jQuery ) );
//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
} );
//>>excludeEnd("jqmBuildExclude");