1: <?php
2: /**
3: * CWidgetFactory class file.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11:
12: /**
13: * CWidgetFactory creates new widgets to be used in views.
14: *
15: * CWidgetFactory is used as the default "widgetFactory" application component.
16: *
17: * When calling {@link CBaseController::createWidget}, {@link CBaseController::widget}
18: * or {@link CBaseController::beginWidget}, if the "widgetFactory" component is installed,
19: * it will be used to create the requested widget. To install the "widgetFactory" component,
20: * we should have the following application configuration:
21: * <pre>
22: * return array(
23: * 'components'=>array(
24: * 'widgetFactory'=>array(
25: * 'class'=>'CWidgetFactory',
26: * ),
27: * ),
28: * )
29: * </pre>
30: *
31: * CWidgetFactory implements the "skin" feature, which allows a new widget to be created
32: * and initialized with a set of predefined property values (called skin).
33: *
34: * When CWidgetFactory is used to create a new widget, it will first instantiate the
35: * widget instance. It then checks if there is a skin available for this widget
36: * according to the widget class name and the widget {@link CWidget::skin} property.
37: * If a skin is found, it will be merged with the initial properties passed via
38: * {@link createWidget}. Then the merged initial properties will be used to initialize
39: * the newly created widget instance.
40: *
41: * As aforementioned, a skin is a set of initial property values for a widget.
42: * It is thus represented as an associative array of name-value pairs.
43: * Skins are stored in PHP scripts like other configurations. Each script file stores the skins
44: * for a particular widget type and is named as the widget class name (e.g. CLinkPager.php).
45: * Each widget type may have one or several skins, identified by the skin name set via
46: * {@link CWidget::skin} property. If the {@link CWidget::skin} property is not set for a given
47: * widget, it means the default skin would be used. The following shows the possible skins for
48: * the {@link CLinkPager} widget:
49: * <pre>
50: * return array(
51: * 'default'=>array(
52: * 'nextPageLabel'=>'>>',
53: * 'prevPageLabel'=>'<<',
54: * ),
55: * 'short'=>array(
56: * 'header'=>'',
57: * 'maxButtonCount'=>5,
58: * ),
59: * );
60: * </pre>
61: * In the above, there are two skins. The first one is the default skin which is indexed by the string "default".
62: * Note that {@link CWidget::skin} defaults to "default". Therefore, this is the skin that will be applied
63: * if we do not explicitly specify the {@link CWidget::skin} property.
64: * The second one is named as the "short" skin which will be used only when we set {@link CWidget::skin}
65: * to be "short".
66: *
67: * By default, CWidgetFactory looks for the skin of a widget under the "skins" directory
68: * of the current application's {@link CWebApplication::viewPath} (e.g. protected/views/skins).
69: * If a theme is being used, it will look for the skin under the "skins" directory of
70: * the theme's {@link CTheme::viewPath} (as well as the aforementioned skin directory).
71: * In case the specified skin is not found, a widget will still be created
72: * normally without causing any error.
73: *
74: * @author Qiang Xue <qiang.xue@gmail.com>
75: * @package system.web
76: * @since 1.1
77: */
78: class CWidgetFactory extends CApplicationComponent implements IWidgetFactory
79: {
80: /**
81: * @var boolean whether to enable widget skinning. Defaults to false.
82: * @see skinnableWidgets
83: * @since 1.1.3
84: */
85: public $enableSkin=false;
86: /**
87: * @var array widget initial property values. Each array key-value pair
88: * represents the initial property values for a single widget class, with
89: * the array key being the widget class name, and array value being the initial
90: * property value array. For example,
91: * <pre>
92: * array(
93: * 'CLinkPager'=>array(
94: * 'maxButtonCount'=>5,
95: * 'cssFile'=>false,
96: * ),
97: * 'CJuiDatePicker'=>array(
98: * 'language'=>'ru',
99: * ),
100: * )
101: * </pre>
102: *
103: * Note that the initial values specified here may be overridden by
104: * the values given in {@link CBaseController::createWidget} calls.
105: * They may also be overridden by widget skins, if {@link enableSkin} is true.
106: * @since 1.1.3
107: */
108: public $widgets=array();
109: /**
110: * @var array list of widget class names that can be skinned.
111: * Because skinning widgets has performance impact, you may want to specify this property
112: * to limit skinning only to specific widgets. Any widgets that are not in this list
113: * will not be skinned. Defaults to null, meaning all widgets can be skinned.
114: * @since 1.1.3
115: */
116: public $skinnableWidgets;
117: /**
118: * @var string the directory containing all the skin files. Defaults to null,
119: * meaning using the "skins" directory under the current application's {@link CWebApplication::viewPath}.
120: */
121: public $skinPath;
122:
123: private $_skins=array(); // class name, skin name, property name => value
124:
125: /**
126: * Initializes the application component.
127: * This method overrides the parent implementation by resolving the skin path.
128: */
129: public function init()
130: {
131: parent::init();
132:
133: if($this->enableSkin && $this->skinPath===null)
134: $this->skinPath=Yii::app()->getViewPath().DIRECTORY_SEPARATOR.'skins';
135: }
136:
137: /**
138: * Creates a new widget based on the given class name and initial properties.
139: * @param CBaseController $owner the owner of the new widget
140: * @param string $className the class name of the widget. This can also be a path alias (e.g. system.web.widgets.COutputCache)
141: * @param array $properties the initial property values (name=>value) of the widget.
142: * @return CWidget the newly created widget whose properties have been initialized with the given values.
143: */
144: public function createWidget($owner,$className,$properties=array())
145: {
146: $className=Yii::import($className,true);
147: $widget=new $className($owner);
148:
149: if(isset($this->widgets[$className]))
150: $properties=$properties===array() ? $this->widgets[$className] : CMap::mergeArray($this->widgets[$className],$properties);
151: if($this->enableSkin)
152: {
153: if($this->skinnableWidgets===null || in_array($className,$this->skinnableWidgets))
154: {
155: $skinName=isset($properties['skin']) ? $properties['skin'] : 'default';
156: if($skinName!==false && ($skin=$this->getSkin($className,$skinName))!==array())
157: $properties=$properties===array() ? $skin : CMap::mergeArray($skin,$properties);
158: }
159: }
160: foreach($properties as $name=>$value)
161: $widget->$name=$value;
162: return $widget;
163: }
164:
165: /**
166: * Returns the skin for the specified widget class and skin name.
167: * @param string $className the widget class name
168: * @param string $skinName the widget skin name
169: * @return array the skin (name=>value) for the widget
170: */
171: protected function getSkin($className,$skinName)
172: {
173: if(!isset($this->_skins[$className][$skinName]))
174: {
175: $skinFile=$this->skinPath.DIRECTORY_SEPARATOR.$className.'.php';
176: if(is_file($skinFile))
177: $this->_skins[$className]=require($skinFile);
178: else
179: $this->_skins[$className]=array();
180:
181: if(($theme=Yii::app()->getTheme())!==null)
182: {
183: $skinFile=$theme->getSkinPath().DIRECTORY_SEPARATOR.$className.'.php';
184: if(is_file($skinFile))
185: {
186: $skins=require($skinFile);
187: foreach($skins as $name=>$skin)
188: $this->_skins[$className][$name]=$skin;
189: }
190: }
191:
192: if(!isset($this->_skins[$className][$skinName]))
193: $this->_skins[$className][$skinName]=array();
194: }
195: return $this->_skins[$className][$skinName];
196: }
197: }