001 
002 /*
003  *  JScripter Standard 1.0 - To Script In Java
004  *  Copyright (C) 2008-2011  J.J.Liu<jianjunliu@126.com> <http://www.jscripter.org>
005  *  
006  *  This program is free software: you can redistribute it and/or modify
007  *  it under the terms of the GNU Affero General Public License as published by
008  *  the Free Software Foundation, either version 3 of the License, or
009  *  (at your option) any later version.
010  *  
011  *  This program is distributed in the hope that it will be useful,
012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014  *  GNU Affero General Public License for more details.
015  *  
016  *  You should have received a copy of the GNU Affero General Public License
017  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
018  */
019 
020 package jsx.ui;
021 
022 import js.ArrayLike;
023 import js.Id;
024 import js.Initializer;
025 import js.Js;
026 import js.ObjectLike;
027 import js.Vars;
028 import jsx.Configurable;
029 import jsx.core.ArrayLikes;
030 import jsx.ui.event.OnRender;
031 import jsx.ui.event.OnStyle;
032 import jsx.ui.event.Render;
033 import jsx.ui.event.Style;
034 
035 /**
036  * <p>A base class for box widgets.</p>
037  * <p>A container widget is a {@link Widget} that not only wraps a component that wraps 
038  * an HTML containing element, but also contains other widgets whose underlying HTML 
039  * elements are graphically contained by the HTML containing element wrapped by the 
040  * component of the container widget.</p>
041  * <p>A {@link Container} widget is {@link Configurable} and is also an event source which 
042  * fires {@link Widget.Event} events. It is meanwhile an event listener that handles 
043  * events fired from its underlying component.</p>
044  * 
045  * @author <a href="mailto:jianjunliu@126.com">J.J.Liu (Jianjun Liu)</a> at <a href="http://www.jscripter.org" target="_blank">http://www.jscripter.org</a>
046  */
047 public class Box<T extends Widget> extends Widget implements OnRender, OnStyle
048 {
049     /**
050      * <p>A global identifier for a configurable property of a {@link Box} widget.</p>
051      * <p>The identified configurable property of a {@link Box} widget is a reference 
052      * to an array of children widgets that are contained by the box widget.</p>
053      * @since 1.0
054      */
055     public final static Id<ArrayLike<Widget>> CHILDREN = new Id<ArrayLike<Widget>>();
056 
057     /**
058      * <p>Typically constructs a box widget.</p>
059      * <p>This constructor invokes {@link #Box(ObjectLike)}.</p>
060      * @param e The underlying component for the box.
061      * @since 1.0
062      */
063     public Box(Component e) {
064         this(new Initializer().set(COMPONENT, e).set(CHILDREN, new Vars<Widget>().var()).var());
065     }
066 
067     /**
068      * <p>Typically constructs a box widget.</p>
069      * @param ini An object for configuration.
070      * @since 1.0
071      */
072     protected Box(ObjectLike ini) {
073         super(ini);
074         addClasses();
075         Component.addClass(unwrap(), ROUND_CORNER_ALL);
076         if (Js.undefined(ini(this).var(CHILDREN))) {
077             ini(this).var(CHILDREN, new Vars<Widget>().var());
078         }
079         unwrap().addListener(Style.class, this);
080         if (isRendered()) {
081             layout();
082         } else {
083             unwrap().addListener(Render.class, this);
084         }
085     }
086 
087 
088     /**
089      * <p>Gets an array of child widgets for the current box.</p>
090      * <p>This method copies the children to a new array so that changing 
091      * the array does not change the box.</p>
092      * @return An array of child widgets for the current box.
093      * @since 1.0
094      */
095     @SuppressWarnings("unchecked")
096     public ArrayLike<T> children() {
097         ArrayLike<T> children = (ArrayLike<T>)ini(this).var(CHILDREN);
098         return children;
099     }
100 
101     /**
102      * <p>Adds an array of child widgets for the current box.</p>
103      * <p>This method will force a layout for the current box if the children list has been changed.</p>
104      * @param awt An array of widgets to be the children of this box.
105      * @since 1.0
106      */
107     public final void add(ArrayLike<T> awt) {
108         boolean dirty = false;
109         for (int i = 0, len = ArrayLikes.length(awt); i < len; i++) {
110             dirty = append(awt.get(i)) || dirty;
111         }
112         if (dirty) {
113             layout();
114         }
115     }
116 
117     /**
118      * <p>Adds a widget to the children list of the current box.</p>
119      * <p>This method will force a layout for the current box if the children list has been changed.</p>
120      * @param w A widget to be added to the children list of this box.
121      * @return <tt>true</tt> if the specified widget becomes a new child of the 
122      * box; <tt>false</tt>, otherwise.
123      * @since 1.0
124      */
125     public boolean add(T w) {
126         if (append(w)) {
127             layout();
128             return true;
129         }
130         return false;
131     }
132 
133     /**
134      * <p>Inserts a given widget to this box immediately before an existing child widget.</p>
135      * <p>This method will force a layout for the current box if the children list has been changed.</p>
136      * @param w A widget to insert.
137      * @param r An existing child widget for reference.
138      * @return <tt>true</tt> if it is successfully inserted; <tt>false</tt>, otherwise.
139      * @since 1.0
140      */
141     public  boolean insert(T w, T r) {
142         return false;
143     }
144 
145     /**
146      * <p>Appends a given widget to this box.</p>
147      * @param w A widget to append.
148      * @return <tt>true</tt> if it is successfully appended; <tt>false</tt>, otherwise.
149      * @since 1.0
150      */
151     protected boolean append(T w) {
152         if (isParentOf(w)) {
153             return false;
154         }
155         boolean ret = Js.be(ini(this).var(CHILDREN).push(w));
156         if (ret) {
157             Component.addClasses(w.unwrap(), subs("item"));
158         }
159         return ret;
160     }
161 
162     /**
163      * <p>Removes a widget from the children list of the current box.</p>
164      * <p>This method will force a layout for the current box if the children list has been changed.</p>
165      * @param w A widget to be removed from the children list of this box.
166      * @return <tt>true</tt> if the specified widget is successfully removed; <tt>false</tt>, 
167      * otherwise.
168      * @since 1.0
169      */
170     public boolean remove(T w) {
171         if (isParentOf(w)) {
172             ArrayLikes.remove(ini(this).var(CHILDREN), w);
173             Component.removeClasses(w.unwrap(), subs("item"));
174             layout();
175             return true;
176         }
177         return false;
178     }
179 
180     /**
181      * <p>Lays out the box widget.</p>
182      * <p>This method detaches all the child components from their containing elements
183      * and then attaches them to this box.</p>
184      * @since 1.0
185      */
186     public void layout() {
187         detach();
188         Component c = unwrap();
189         ArrayLike<Widget> children = ini(this).var(CHILDREN);
190         for (int i = 0, len = ArrayLikes.length(children); i < len; i++) {
191             Widget w = children.get(i);
192             if (Js.be(w)) {
193                 Component.appendChild(c, w.unwrap());
194             }
195         }
196     }
197 
198     /**
199      * <p>Detaches all the children.</p>
200      * <p>This method detaches all the child components from their containing elements.</p>
201      * @since 1.0
202      */
203     public final void detach() {
204         ArrayLike<Widget> children = ini(this).var(CHILDREN);
205         for (int i = 0, len = ArrayLikes.length(children); i < len; i++) {
206             Widget w = children.get(i);
207             if (Js.be(w)) {
208                 Component.detach(w.unwrap());
209             }
210         }
211     }
212 
213     /**
214      * <p>Performs an action on the dispatched event.</p>
215      * <p>This method simply forces a layout with a call to the {@link #layout()} method 
216      * and then stops listening to {@link Render} events.</p>
217      * @param evt The event dispatched to this listener.
218      * @since 1.0
219      */
220     public void onEvent(Render evt) {
221         layout();
222         unwrap().removeListener(Render.class, this);
223     }
224 
225     /**
226      * <p>Performs an action on the dispatched event.</p>
227      * <p>This method recursively fires the event from all contained widgets.</p>
228      * @param evt The event dispatched to this listener.
229      * @since 1.0
230      */
231     public void onEvent(Style evt) {
232         ArrayLike<Widget> children = ini(this).var(CHILDREN);
233         for (int i = 0, len = ArrayLikes.length(children); i < len; i++) {
234             Widget w = children.get(i);
235             if (Js.be(w)) {
236                 w.fire(evt);
237             }
238         }
239     }
240 }