001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup.html.form;
018
019import java.util.ArrayList;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Set;
023
024import org.apache.wicket.Component;
025import org.apache.wicket.behavior.Behavior;
026import org.apache.wicket.markup.html.WebMarkupContainer;
027
028/**
029 * A Javascript-based "Select All" checkbox component that works with a loose collection of
030 * {@link CheckBox} components. By default, clicking on any of the controlled checkboxes
031 * automatically updates the state of the "select all" checkbox. Override
032 * {@link AbstractCheckSelector#wantAutomaticUpdate()} to change this.
033 * 
034 * @author Carl-Eric Menzel
035 */
036public class CheckBoxSelector extends AbstractCheckSelector
037{
038        private static final long serialVersionUID = 1L;
039
040        private final Set<CheckBox> connectedCheckBoxes = new HashSet<>();
041        
042        private final Behavior cleanup = new Behavior()
043        {
044                private static final long serialVersionUID = 1L;
045
046                @Override
047                public void onRemove(Component component)
048                {
049                        connectedCheckBoxes.remove(component);
050                        component.remove(this);
051                }
052        };
053
054        /**
055         * @param id
056         *            The component ID
057         *            
058         * @see CheckBoxSelector#getCheckBoxes()
059         */
060        public CheckBoxSelector(String id)
061        {
062                super(id);
063        }
064
065        /**
066         * @param id
067         *            The component ID
068         * @param boxes
069         *            checkBoxes this selector will control
070         */
071        public CheckBoxSelector(String id, CheckBox... boxes)
072        {
073                super(id);
074
075                for (CheckBox box : boxes)
076                {
077                        connectedCheckBoxes.add(box);
078                        box.add(cleanup);
079                }
080        }
081
082        @Override
083        protected CharSequence getFindCheckboxesFunction()
084        {
085                return String.format("Wicket.CheckboxSelector.getCheckboxesFunction(%s)",
086                        buildMarkupIdJSArrayLiteral(getCheckBoxes()));
087        }
088
089        /**
090         * Override this method to control a dynamic collection of {@link CheckBox}es.
091         * 
092         * @return by default returns the checkBoxes passed to the constructor
093         */
094        protected Iterable<? extends CheckBox> getCheckBoxes()
095        {
096                return connectedCheckBoxes;
097        }
098
099        /**
100         * Builds a JavaScript array literal containing the markup IDs of the given components. Example:
101         * "['foo', 'bar', 'baz']".
102         * 
103         * @param components
104         *            The components whose IDs we need
105         * @return a properly formatted JS array literal
106         */
107        private String buildMarkupIdJSArrayLiteral(final Iterable<? extends CheckBox> components)
108        {
109                StringBuilder buf = new StringBuilder();
110                buf.append('[');
111                if (components.iterator().hasNext())
112                {
113                        for (Component component : components)
114                        {
115                                buf.append('\'').append(component.getMarkupId()).append("', ");
116                        }
117                        buf.delete(buf.length() - 2, buf.length());
118                }
119                buf.append(']');
120                return buf.toString();
121        }
122
123        /**
124         * Utility method to collect all {@link CheckBox}es inside a container.
125         * 
126         * @param container
127         *            container with checkBoxes
128         * @return all contained checkBoxes
129         */
130        public static final Iterable<CheckBox> collectCheckBoxes(WebMarkupContainer container)
131        {
132                List<CheckBox> checkBoxes = new ArrayList<>();
133
134                container.<CheckBox, Void> visitChildren(CheckBox.class, (child, visit) -> {
135                        checkBoxes.add(child);
136                });
137
138                return checkBoxes;
139        }
140}