Update gentle-wake-up.groovy
[smartapps.git] / official / every-element.groovy
1 /**
2  *  Every Element
3  *
4  *  Copyright 2015 SmartThings
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7  *  in compliance with the License. You may obtain a copy of the License at:
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
12  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
13  *  for the specific language governing permissions and limitations under the License.
14  *
15  */
16 definition(
17         name: "Every Element",
18         namespace: "smartthings/examples",
19         author: "SmartThings",
20         description: "Every element demonstration app",
21         category: "SmartThings Internal",
22         iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
23         iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
24 )
25
26 preferences {
27     // landing page
28     page(name: "firstPage")
29
30     // PageKit
31     page(name: "buttonsPage")
32     page(name: "imagePage")
33     page(name: "inputPage")
34     page(name: "inputBooleanPage")
35     page(name: "inputIconPage")
36     page(name: "inputImagePage")
37     page(name: "inputDevicePage")
38     page(name: "inputCapabilityPage")
39     page(name: "inputRoomPage")
40     page(name: "inputModePage")
41     page(name: "inputSelectionPage")
42     page(name: "inputHubPage")
43     page(name: "inputContactBookPage")
44     page(name: "inputTextPage")
45     page(name: "inputTimePage")
46     page(name: "appPage")
47     page(name: "hrefPage")
48     page(name: "paragraphPage")
49     page(name: "videoPage")
50     page(name: "labelPage")
51     page(name: "modePage")
52
53     // Every element helper pages
54     page(name: "deadEnd", title: "Nothing to see here, move along.", content: "foo")
55     page(name: "flattenedPage")
56 }
57
58 def firstPage() {
59     dynamicPage(name: "firstPage", title: "Where to first?", install: true, uninstall: true) {
60         section {
61             href(page: "appPage", title: "Element: 'app'")
62             href(page: "buttonsPage", title: "Element: 'buttons'")
63             href(page: "hrefPage", title: "Element: 'href'")
64             href(page: "imagePage", title: "Element: 'image'")
65             href(page: "inputPage", title: "Element: 'input'")
66             href(page: "labelPage", title: "Element: 'label'")
67             href(page: "modePage", title: "Element: 'mode'")
68             href(page: "paragraphPage", title: "Element: 'paragraph'")
69             href(page: "videoPage", title: "Element: 'video'")
70         }
71         section {
72             href(page: "flattenedPage", title: "All of the above elements on a single page")
73         }
74     }
75 }
76
77 def inputPage() {
78     dynamicPage(name: "inputPage", title: "Links to every 'input' element") {
79         section {
80             href(page: "inputBooleanPage", title: "to boolean page")
81             href(page: "inputIconPage", title: "to icon page")
82             href(page: "inputImagePage", title: "to image page")
83             href(page: "inputSelectionPage", title: "to selection page")
84             href(page: "inputTextPage", title: "to text page")
85             href(page: "inputTimePage", title: "to time page")
86         }
87         section("subsets of selection input") {
88             href(page: "inputDevicePage", title: "to device selection page")
89             href(page: "inputCapabilityPage", title: "to capability selection page")
90             href(page: "inputRoomPage", title: "to room selection page")
91             href(page: "inputModePage", title: "to mode selection page")
92             href(page: "inputHubPage", title: "to hub selection page")
93             href(page: "inputContactBookPage", title: "to contact-book selection page")
94         }
95     }
96 }
97
98 def inputBooleanPage() {
99     dynamicPage(name: "inputBooleanPage") {
100         section {
101             paragraph "The `required` and `multiple` attributes have no effect because the value will always be either `true` or `false`"
102         }
103         section {
104             input(type: "boolean", name: "booleanWithoutDescription", title: "without description", description: null)
105             input(type: "boolean", name: "booleanWithDescription", title: "with description", description: "This has a description")
106         }
107         section("defaultValue: 'true'") {
108             input(type: "boolean", name: "booleanWithDefaultValue", title: "", description: "", defaultValue: "true")
109         }
110         section("with image") {
111             input(type: "boolean", name: "booleanWithoutDescriptionWithImage", title: "without description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", description: null)
112             input(type: "boolean", name: "booleanWithDescriptionWithImage", title: "with description", description: "This has a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
113         }
114     }
115 }
116 def inputIconPage() {
117     dynamicPage(name: "inputIconPage") {
118         section {
119             paragraph "`description` is not displayed for icon elements"
120             paragraph "`multiple` has no effect because you can only choose a single icon"
121         }
122         section("required: true") {
123             input(type: "icon", name: "iconRequired", title: "without description", required: true)
124             input(type: "icon", name: "iconRequiredWithDescription", title: "with description", description: "this is a description", required: true)
125         }
126         section("with image") {
127             paragraph "The image specified will be replaced after an icon is selected"
128             input(type: "icon", name: "iconwithImage", title: "without description", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
129         }
130     }
131 }
132 def inputImagePage() {
133     dynamicPage(name: "inputImagePage") {
134         section {
135             paragraph "This only exists in DeviceTypes. Someone should do something about that. (glares at MikeDave)"
136             paragraph "Go to the device preferences of a Mobile Presence device to see it in action"
137             paragraph "If you try to set the value of this, it will not behave as it would in Device Preferences"
138             input(type: "image", title: "This is kind of what it looks like", required: false)
139         }
140     }
141 }
142
143
144 def optionsGroup(List groups, String title) {
145     def group = [values:[], order: groups.size()]
146     group.title = title ?: ""
147     groups << group
148     return groups
149 }
150 def addValues(List groups, String key, String value) {
151     def lastGroup = groups[-1]
152     lastGroup["values"] << [
153             key: key,
154             value: value,
155             order: lastGroup["values"].size()
156     ]
157     return groups
158 }
159 def listToMap(List original) {
160     original.inject([:]) { result, v ->
161         result[v] = v
162         return result
163     }
164 }
165 def addGroup(List groups, String title, values) {
166     if (values instanceof List) {
167         values = listToMap(values)
168     }
169
170     values.inject(optionsGroup(groups, title)) { result, k, v ->
171         return addValues(result, k, v)
172     }
173     return groups
174 }
175 def addGroup(values) {
176     addGroup([], null, values)
177 }
178 /* Example usage of options builder
179
180 // Creating grouped options
181     def newGroups = []
182     addGroup(newGroups, "first group", ["foo", "bar", "baz"])
183     addGroup(newGroups, "second group", [zero: "zero", one: "uno", two: "dos", three: "tres"])
184
185 // simple list
186     addGroup(["a", "b", "c"])
187
188 // simple map
189     addGroup(["a": "yes", "b": "no", "c": "maybe"])​​​​
190 */
191
192
193 def inputSelectionPage() {
194
195     def englishOptions = ["One", "Two", "Three"]
196     def spanishOptions = ["Uno", "Dos", "Tres"]
197     def groupedOptions = []
198     addGroup(groupedOptions, "English", englishOptions)
199     addGroup(groupedOptions, "Spanish", spanishOptions)
200
201     dynamicPage(name: "inputSelectionPage") {
202
203         section("options variations") {
204             paragraph "tap these elements and look at the differences when selecting an option"
205             input(type: "enum", name: "selectionSimple", title: "Simple options", description: "no separators in the selectable options", groupedOptions: addGroup(englishOptions + spanishOptions))
206             input(type: "enum", name: "selectionGrouped", title: "Grouped options", description: "separate groups of options with headers", groupedOptions: groupedOptions)
207         }
208
209         section("list vs map") {
210             paragraph "These should be identical in UI, but are different in code and will produce different settings"
211             input(type: "enum", name: "selectionList", title: "Choose a device", description: "settings will be something like ['Device1 Label']", groupedOptions: addGroup(["Device1 Label", "Device2 Label"]))
212             input(type: "enum", name: "selectionMap", title: "Choose a device", description: "settings will be something like ['device1-id']", groupedOptions: addGroup(["device1-id": "Device1 Label", "device2-id": "Device2 Label"]))
213         }
214
215         section("segmented") {
216             paragraph "segmented should only work if there are either 2 or 3 options to choose from"
217             input(type: "enum", name: "selectionSegmented1", style: "segmented", title: "1 option", groupedOptions: addGroup(["One"]))
218             input(type: "enum", name: "selectionSegmented4", style: "segmented", title: "4 options", groupedOptions: addGroup(["One", "Two", "Three", "Four"]))
219
220             paragraph "multiple and required will have no effect on segmented selection elements. There will always be exactly 1 option selected"
221             input(type: "enum", name: "selectionSegmented2", style: "segmented", title: "2 options", options: ["One", "Two"])
222             input(type: "enum", name: "selectionSegmented3", style: "segmented", title: "3 options", options: ["One", "Two", "Three"])
223
224             paragraph "specifying defaultValue still works with segmented selection elements"
225             input(type: "enum", name: "selectionSegmentedWithDefault", title: "defaulted to 'two'", groupedOptions: addGroup(["One", "Two", "Three"]), defaultValue: "Two")
226         }
227
228         section("required: true") {
229             input(type: "enum", name: "selectionRequired", title: "This is required", description: "It should look different when nothing is selected", groupedOptions: addGroup(["only option"]), required: true)
230         }
231
232         section("multiple: true") {
233             input(type: "enum", name: "selectionMultiple", title: "This allows multiple selections", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true)
234         }
235
236         section("with image") {
237             input(type: "enum", name: "selectionWithImage", title: "This has an image", description: "and a description", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
238         }
239     }
240 }
241 def inputTextPage() {
242     dynamicPage(name: "inputTextPage", title: "Every 'text' variation") {
243         section("style and functional differences") {
244             input(type: "text", name: "textRequired", title: "required: true", description: "This should look different when nothing has been entered", required: true)
245             input(type: "text", name: "textWithImage", title: "with image", description: "This should look different when nothing has been entered", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", required: false)
246         }
247         section("text") {
248             input(type: "text", name: "text", title: "This has an alpha-numeric keyboard", description: "no special formatting", required: false)
249         }
250         section("password") {
251             input(type: "password", name: "password", title: "This has an alpha-numeric keyboard", description: "masks value", required: false)
252         }
253         section("email") {
254             input(type: "email", name: "email", title: "This has an email-specific keyboard", description: "no special formatting", required: false)
255         }
256         section("phone") {
257             input(type: "phone", name: "phone", title: "This has a numeric keyboard", description: "formatted for phone numbers", required: false)
258         }
259         section("decimal") {
260             input(type: "decimal", name: "decimal", title: "This has an numeric keyboard with decimal point", description: "no special formatting", required: false)
261         }
262         section("number") {
263             input(type: "number", name: "number", title: "This has an numeric keyboard without decimal point", description: "no special formatting", required: false)
264         }
265
266         section("specified ranges") {
267             paragraph "You can limit number and decimal inputs to a specific range."
268             input(range: "50..150", type: "decimal", name: "decimalRange50..150", title: "only values between 50 and 150 will pass validation", description: "no special formatting", required: false)
269             paragraph "Negative limits will add a negative symbol to the keyboard."
270             input(range: "-50..50", type: "number", name: "numberRange-50..50", title: "only values between -50 and 50 will pass validation", description: "no special formatting", required: false)
271             paragraph "Specify * to not limit one side or the other."
272             input(range: "*..0", type: "decimal", name: "decimalRange*..0", title: "only negative values will pass validation", description: "no special formatting", required: false)
273             input(range: "*..*", type: "number", name: "numberRange*..*", title: "only positive values will pass validation", description: "no special formatting", required: false)
274             paragraph "If you don't specify a range, it defaults to 0..*"
275         }
276     }
277 }
278 def inputTimePage() {
279     dynamicPage(name: "inputTimePage") {
280         section {
281             input(type: "time", name: "timeWithDescription", title: "a time picker", description: "with a description", required: false)
282             input(type: "time", name: "timeWithoutDescription", title: "without a description", description: null, required: false)
283             input(type: "time", name: "timeRequired", title: "required: true", required: true)
284         }
285     }
286 }
287
288 /// selection subsets
289 def inputDevicePage() {
290
291     dynamicPage(name: "inputDevicePage") {
292
293         section("required: true") {
294             input(type: "device.switch", name: "deviceRequired", title: "This is required", description: "It should look different when nothing is selected")
295         }
296
297         section("multiple: true") {
298             input(type: "device.switch", name: "deviceMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
299         }
300
301         section("with image") {
302             input(type: "device.switch", name: "deviceRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
303         }
304     }
305 }
306 def inputCapabilityPage() {
307
308     dynamicPage(name: "inputCapabilityPage") {
309
310         section("required: true") {
311             input(type: "capability.switch", name: "capabilityRequired", title: "This is required", description: "It should look different when nothing is selected")
312         }
313
314         section("multiple: true") {
315             input(type: "capability.switch", name: "capabilityMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
316         }
317
318         section("with image") {
319             input(type: "capability.switch", name: "capabilityRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
320         }
321     }
322 }
323 def inputRoomPage() {
324
325     dynamicPage(name: "inputRoomPage") {
326
327         section("required: true") {
328             input(type: "room", name: "roomRequired", title: "This is required", description: "It should look different when nothing is selected")
329         }
330
331         section("multiple: true") {
332             input(type: "room", name: "roomMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
333         }
334
335         section("with image") {
336             input(type: "room", name: "roomRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
337         }
338     }
339 }
340 def inputModePage() {
341
342     dynamicPage(name: "inputModePage") {
343
344         section("required: true") {
345             input(type: "mode", name: "modeRequired", title: "This is required", description: "It should look different when nothing is selected")
346         }
347
348         section("multiple: true") {
349             input(type: "mode", name: "modeMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
350         }
351
352         section("with image") {
353             input(type: "mode", name: "modeRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
354         }
355     }
356 }
357 def inputHubPage() {
358
359     dynamicPage(name: "inputHubPage") {
360
361         section("required: true") {
362             input(type: "hub", name: "hubRequired", title: "This is required", description: "It should look different when nothing is selected")
363         }
364
365         section("multiple: true") {
366             input(type: "hub", name: "hubMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
367         }
368
369         section("with image") {
370             input(type: "hub", name: "hubRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
371         }
372     }
373 }
374 def inputContactBookPage() {
375
376     dynamicPage(name: "inputContactBookPage") {
377
378         section("required: true") {
379             input(type: "contact", name: "contactRequired", title: "This is required", description: "It should look different when nothing is selected")
380         }
381
382         section("multiple: true") {
383             input(type: "contact", name: "contactMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
384         }
385
386         section("with image") {
387             input(type: "contact", name: "contactRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
388         }
389     }
390 }
391
392 def appPage() {
393     dynamicPage(name: "appPage", title: "Every 'app' type") {
394         section {
395             paragraph "These won't work unless you create a child SmartApp to link to... Sorry."
396         }
397         section("app") {
398             app(
399                     name: "app",
400                     title: "required:false, multiple:false",
401                     required: false,
402                     multiple: false,
403                     namespace: "Steve",
404                     appName: "Child SmartApp"
405             )
406             app(name: "appRequired", title: "required:true", required: true, multiple: false, namespace: "Steve", appName: "Child SmartApp")
407             app(name: "appComplete", title: "state:complete", required: false, multiple: false, namespace: "Steve", appName: "Child SmartApp", state: "complete")
408             app(name: "appWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp")
409         }
410         section("multiple:true") {
411             app(name: "appMultiple", title: "multiple:true", required: false, multiple: true, namespace: "Steve", appName: "Child SmartApp")
412         }
413         section("multiple:true with image") {
414             app(name: "appMultipleWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp")
415         }
416     }
417 }
418
419 def labelPage() {
420     dynamicPage(name: "labelPage", title: "Every 'Label' type") {
421         section("label") {
422             paragraph "The difference between a label element and a text input element is that the label element will effect the SmartApp directly by setting the label. An input element will place the set value in the SmartApp's settings."
423             paragraph "There are 3 here as an example. Never use more than 1 label element on a page."
424             label(name: "label", title: "required:false, multiple:false", required: false, multiple: false)
425             label(name: "labelRequired", title: "required:true", required: true, multiple: false)
426             label(name: "labelWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
427         }
428     }
429 }
430
431 def modePage() {
432     dynamicPage(name: "modePage", title: "Every 'mode' type") { // TODO: finish this
433         section("mode") {
434             paragraph "The difference between a mode element and a mode input element is that the mode element will effect the SmartApp directly by setting the modes it executes in. A mode input element will place the set value in the SmartApp's settings."
435             paragraph "Another difference is that you can select 'All Modes' when choosing which mode the SmartApp should execute in. This is the same as selecting no modes. When a SmartApp does not have modes specified, it will execute in all modes."
436             paragraph "There are 4 here as an example. Never use more than 1 mode element on a page."
437             mode(name: "mode", title: "required:false, multiple:false", required: false, multiple: false)
438             mode(name: "modeRequired", title: "required:true", required: true, multiple: false)
439             mode(name: "modeMultiple", title: "multiple:true", required: false, multiple: true)
440             mode(name: "modeWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
441         }
442     }
443 }
444
445 def paragraphPage() {
446     dynamicPage(name: "paragraphPage", title: "Every 'paragraph' type") {
447         section("paragraph") {
448             paragraph "This is how you should make a paragraph element"
449             paragraph image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", "This is a long description, blah, blah, blah."
450         }
451     }
452 }
453
454 def hrefPage() {
455     dynamicPage(name: "hrefPage", title: "Every 'href' variation") {
456         section("stylistic differences") {
457             href(page: "deadEnd", title: "state: 'complete'", description: "gives the appearance of an input that has been filled out", state: "complete")
458             href(page: "deadEnd", title: "with image", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
459             href(page: "deadEnd", title: "with image and description", description: "and state: 'complete'", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", state: "complete")
460         }
461         section("functional differences") {
462             href(page: "deadEnd", title: "to a page within the app")
463             href(url: "http://www.google.com", title: "to a url using all defaults")
464             href(url: "http://www.google.com", title: "external: true", description: "takes you outside the app", external: true)
465         }
466     }
467 }
468
469 def buttonsPage() {
470     dynamicPage(name: "buttonsPage", title: "Every 'button' type") {
471         section("Simple Buttons") {
472             paragraph "If there are an odd number of buttons, the last button will span the entire view area."
473             buttons(name: "buttons1", title: "1 button", buttons: [
474                     [label: "foo", action: "foo"]
475             ])
476             buttons(name: "buttons2", title: "2 buttons", buttons: [
477                     [label: "foo", action: "foo"],
478                     [label: "bar", action: "bar"]
479             ])
480             buttons(name: "buttons3", title: "3 buttons", buttons: [
481                     [label: "foo", action: "foo"],
482                     [label: "bar", action: "bar"],
483                     [label: "baz", action: "baz"]
484             ])
485             buttons(name: "buttonsWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", buttons: [
486                     [label: "foo", action: "foo"],
487                     [label: "bar", action: "bar"]
488             ])
489         }
490         section("Colored Buttons") {
491             buttons(name: "buttonsColoredSpecial", title: "special strings", description: "SmartThings highly recommends using these colors", buttons: [
492                     [label: "complete", action: "bar", backgroundColor: "complete"],
493                     [label: "required", action: "bar", backgroundColor: "required"]
494             ])
495             buttons(name: "buttonsColoredHex", title: "hex values work", buttons: [
496                     [label: "bg: #000dff", action: "foo", backgroundColor: "#000dff"],
497                     [label: "fg: #ffac00", action: "foo", color: "#ffac00"],
498                     [label: "both fg and bg", action: "foo", color: "#ffac00", backgroundColor: "#000dff"]
499             ])
500             buttons(name: "buttonsColoredString", title: "strings work too", buttons: [
501                     [label: "green", action: "foo", backgroundColor: "green"],
502                     [label: "red", action: "foo", backgroundColor: "red"],
503                     [label: "both fg and bg", action: "foo", color: "red", backgroundColor: "green"]
504             ])
505         }
506     }
507
508 }
509
510 def imagePage() {
511     dynamicPage(name: "imagePage", title: "Every 'image' type") { // TODO: finish thise
512         section("image") {
513             image "http://f.cl.ly/items/1k1S0A0m3805402o3O12/20130915-191127.jpg"
514             image(name: "imageWithMultipleImages", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, images: ["https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", "http://f.cl.ly/items/1k1S0A0m3805402o3O12/20130915-191127.jpg"])
515         }
516     }
517 }
518
519 def videoPage() {
520     dynamicPage(name: "videoPage", title: "Every 'video' type") { // TODO: finish this
521         section("video") {
522             // TODO: update this when there is a videoElement method
523             element(name: "videoElement", element: "video", type: "video", title: "this is a video!", description: "I am setting long title and descriptions to test the offset", required: false, image: "http://f.cl.ly/items/0w0D1p0K2D0d190F3H3N/Image%202015-12-14%20at%207.57.27%20AM.jpg", video: "http://f.cl.ly/items/3O2L03471l2K3E3l3K1r/Zombie%20Kid%20Likes%20Turtles.mp4")
524         }
525     }
526 }
527
528 def flattenedPage() {
529     def allSections = []
530     firstPage().sections[0].body.each { hrefElement ->
531         if (hrefElement.name != "inputPage") {
532             // inputPage is a bunch of hrefs
533             allSections += "${hrefElement.page}"().sections
534         }
535     }
536     // collect the input elements
537     inputPage().sections.each { section ->
538         section.body.each { hrefElement ->
539             allSections += "${hrefElement.page}"().sections
540         }
541     }
542     def flattenedPage = dynamicPage(name: "flattenedPage", title: "All elements in one page!") {}
543     flattenedPage.sections = allSections
544     return flattenedPage
545 }
546
547 def foo() {
548     dynamicPage(name: "deadEnd") {
549         section {  }
550     }
551 }
552
553 def installed() {
554     log.debug "Installed with settings: ${settings}"
555
556     initialize()
557 }
558
559 def updated() {
560     log.debug "Updated with settings: ${settings}"
561
562     unsubscribe()
563     initialize()
564 }
565
566 def initialize() {
567     // TODO: subscribe to attributes, devices, locations, etc.
568 }